Merge branch 'upstream-master'

Initial downstream of testng code (tag 6.9.10)
from https://github.com/cbeust/testng

Bug: 27552463
Change-Id: Ia72763ed396d2f3ac3bc2bc66fb3e383818e1c82
diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..a47380d
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="src" path="src/generated/java"/>
+	<classpathentry kind="src" path="src/test/java"/>
+	<classpathentry kind="src" path="src/main/resources"/>
+	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry exported="true" kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>
diff --git a/.classpath-linux b/.classpath-linux
new file mode 100644
index 0000000..d05023c
--- /dev/null
+++ b/.classpath-linux
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+  <classpathentry kind="src" path="src/main/java"/>
+  <classpathentry kind="src" path="src/test/java"/>
+  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.launching.macosx.MacOSXType/JVM 1.6"/>
+  <classpathentry kind="lib" path="lib/ant-1.6.5.jar"/>
+  <classpathentry kind="lib" path="lib/bsh-2.0b4.jar"/>
+  <classpathentry kind="lib" path="lib/guice-2.0.jar"/>
+  <classpathentry kind="lib" path="lib/junit-3.8.1.jar"/>
+  <classpathentry kind="output" path="z_build"/>
+</classpath>
diff --git a/.classpath-mac b/.classpath-mac
new file mode 100644
index 0000000..d05023c
--- /dev/null
+++ b/.classpath-mac
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+  <classpathentry kind="src" path="src/main/java"/>
+  <classpathentry kind="src" path="src/test/java"/>
+  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.launching.macosx.MacOSXType/JVM 1.6"/>
+  <classpathentry kind="lib" path="lib/ant-1.6.5.jar"/>
+  <classpathentry kind="lib" path="lib/bsh-2.0b4.jar"/>
+  <classpathentry kind="lib" path="lib/guice-2.0.jar"/>
+  <classpathentry kind="lib" path="lib/junit-3.8.1.jar"/>
+  <classpathentry kind="output" path="z_build"/>
+</classpath>
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..12ddf02
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,27 @@
+.ant-targets*
+.classpath
+.gitignore
+.gradle
+.idea
+.project
+.settings
+/_eclipse/
+/local.properties
+TESTNG-*
+build
+eclipse-build
+gradle.properties
+ivy*
+javadocs
+kobaltBuild
+lib
+maven-testng-plugin-1.2.jar
+nb-configuration.xml
+out
+src/generated
+src/test/java/test/ignore
+target
+test-output
+test-output-tests
+testng.iml
+z_build
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 0000000..58e8da7
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,13 @@
+Abraham Lin <github.atomicknight@xoxy.net> <abraham.lin@post.harvard.edu>
+Cédric Beust <cedric@beust.com> <cbeust@1a8b0fc8-9519-0410-aeec-afd8fd7729cf>
+Cédric Beust <cedric@beust.com> <beust@yahoo-inc.com>
+Cédric Beust <cedric@beust.com> <cbeust@linkedin.com>
+Cédric Beust <cedric@beust.com> <cedric@beust.com>
+Cédric Beust <cedric@beust.com> <cedric@color.com>
+Cédric Beust <cedric@beust.com> <cedric@color.com>
+Cédric Beust <cedric@beust.com> <cedric@refresh.io>
+Jose Dillet <jose.dillet@gmail.com> <jdillet@ada>
+Julien Herr <julien@herr.fr> <julien.herr@alcatel-lucent.com>
+Konstantin Savin <konstantin.a.savin@gmail.com>
+Tim wu <wutingbupt@gmail.com>
+Tomás Pollak <tpollak.ar@gmail.com>
diff --git a/.project b/.project
new file mode 100644
index 0000000..33d1108
--- /dev/null
+++ b/.project
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>testng</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.m2e.core.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.m2e.core.maven2Nature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..8d858a5
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,12 @@
+language: java
+jdk:
+  - oraclejdk8
+  - oraclejdk7
+  - openjdk7
+
+#before_install: bash "gradle/uploadSnapshot.sh"
+script:
+  - ./gradlew clean test
+  - ./travis.sh
+after_success: test "${TRAVIS_PULL_REQUEST}" == "false" && test "${TRAVIS_JDK_VERSION}" == "oraclejdk7" && ./gradlew uploadArchives
+
diff --git a/ANNOUNCEMENT.txt b/ANNOUNCEMENT.txt
new file mode 100644
index 0000000..abb2222
--- /dev/null
+++ b/ANNOUNCEMENT.txt
@@ -0,0 +1,110 @@
+javalobby.org
+testdriven.com
+opensourcetesting.org
+http://saloon.javaranch.com/cgi-bin/ubb/ultimatebb.cgi?ubb=forum&f=68
+java.net
+comp.lang.java.programmer
+
+==========
+
+The TestNG team is happy to announce the immediate availability of TestNG 4.5. 
+
+This release contains a lot of bug fixes and a few new features.  It also 
+includes the first phase of Distributed TestNG, a work in progress designed 
+to transparently distribute tests on many machines and collect their results.
+
+Announcement:
+
+http://beust.com/weblog/archives/000361.html
+
+Details on Distributed TestNG:  
+
+http://beust.com/weblog/archives/000362.html
+
+
+==========
+
+The TestNG team is happy to announce the immediate availability of TestNG 4.0, with a lot of improvements and new features.
+
+The announcement, along with a few examples and summary of the new features, can be found here:
+
+    http://tinyurl.com/dxlbh
+
+-- 
+Cédric
+http://testng.org
+
+================
+
+Announcing TestNG 2.3
+
+The TestNG team is happy to announce the availability of TestNG 2.3.
+
+The version is available at http://beust.com/testng as well as the new documentation, which has been considerably improved (highlighted code snippets, detailed DTD, ant task and description of all the new features).
+
+What's new:
+
+    * beforeSuite, afterSuite, beforeTest, afterTest
+    * Revamped ant task with haltonfailure and other helpful flags
+    * Better stack traces and improved level control for verbosity
+    * Better syntax for including and excluding methods in testng.xml
+    * Test classes can be invoked on the command line
+    * ... and many bug fixes.
+
+For Eclipse users, a new version (1.1.1) of the Eclipse plug-in that includes this new TestNG version is available on the remote update site or for direct download.
+
+Also, TestNG has joined OpenSymphony (big thanks to Patrick and Hani for setting this up).  As a consequence of this move, there is now a TestNG users forum as well as a Wiki and JIRA for issue tracking.
+
+The users mailing-list has been moved to Google Groups and is connected to the forum, so you only need to subscribe to one.
+
+Try it and let us know what you think!
+
+===
+
+I am happy to announce the availability of TestNG 2.1 (http://beust.com/testng).  
+
+TestNG is a testing framework inspired from JUnit and NUnit but introducing some new functionalities that make it more powerful and easier to use, such as:
+
+    * JSR 175 Annotations (JDK 1.4 is also supported with JavaDoc annotations).
+    * Flexible test configuration.
+    * Default JDK functions for runtime and logging (no dependencies).
+    * Powerful execution model (no more TestSuite).
+    * Supports dependent methods.
+
+Some of the new features in this version include:
+
+    * invocationCount and successPercentage, which I described in a previous entry (http://beust.com/weblog/archives/000236.html), and which allow you to invoke a test method a certain number of times while allowing some of these invocations to fail.  If the number of failures is under a certain threshold, the test is still considered a success.
+       
+    * timeOut is now applicable to all test methods.  Whether you are running your tests in parallel or not, you can specify a time-out for your test method and if it fails to complete within the given amount of time, TestNG will mark it as a failure.
+       
+    * dependsOnMethods was the most requested feature.  You can now specify dependencies on a method-based basis (no need to specify a group if your dependency graph is simple).  You can even mix dependsOnMethods and dependsOnGroups.
+       
+    * ... and of course, numerous bug fixes and other additions.
+
+A special thanks to Alexandru Popescu who has pulled all-nighters to make this release happen!
+
+We have an exciting list of new features lined up for our next version, among which a plug-in API, but in the meantime, enjoy TestNG 2.1.
+
+======
+
+Announcing TestNG for Eclipse
+
+The TestNG team is happy to announce the first release of the TestNG plug-in for Eclipse.
+
+http://beust.com/testng
+
+This first release covers the basic functionalities of TestNG, among which:
+
+    * Multiple ways to launch tests (from a method, from a class, groups or an entire suite).
+    * Convenient report view that lets you directly jump to failed tests.
+
+A more detailed overview can be found here:
+
+http://beust.com/weblog/archives/000261.html
+
+and the documentation and snapshots here:
+
+http://beust.com/testng/main.html#eclipse
+
+-- 
+Cedric
diff --git a/CHANGES.txt b/CHANGES.txt
new file mode 100644
index 0000000..afc58b0
--- /dev/null
+++ b/CHANGES.txt
@@ -0,0 +1,1259 @@
+Current
+Fixed: GITHUB-841: testName from @Test is now used and available from ITestResult#getName() and ITestResult#getTestName() (Julien Herr)
+New: GITHUB-776: Add BeforeClass/AfterClass like on ITestListener (@vguna & Julien Herr)
+Fixed: GITHUB-872: Enable end-users of TestNG to alter XmlSuite and XmlTest (Krishnan Mahadevan)
+New: GITHUB-900: Support @Listeners in annotation transformer (Julien Herr)
+New: GITHUB-898: Activate XML validation when possible (Julien Herr)
+Fixed: GITHUB-889: XmlSuite in nested directories results in FIleNotFoundException (Virender Singh)
+Fixed: GITHUB-811: Timeout is not working with parallel=tests (@michael-yxf & Julien Herr)
+Fixed: GITHUB-839: Missing encoding meta data for report file (@banbq & Julien Herr)
+Fixed: GITHUB-876: NullPointerException creating tests with parameters by a factory (@vixgeo & Julien Herr)
+New: GITHUB-886: Add some checks on factory methods (Julien Herr)
+New: GITHUB-874 / GITHUB-875 / GITHUB-882 / GITHUB-850 : Some code cleanup (Testo Nakada)
+Fixed: GITHUB-866 / GITHUB-869 : Some attributes were not cloned when XmlSuite#clone was used (Virender Singh)
+Fixed: GITHUB-842: Add TestResult#getTestName() support for @Test(testName) (Julien Herr)
+
+6.9.9:
+2015/10/27
+
+Fixed: GITHUB-829: Allowing suites to have duplicate names. You can now configure the same suite-file to run multiple times. (Eduardo Born)
+Fixed: GITHUB-834: nested suites not supported by 'testnames' (Tibor Digana & Julien Herr)
+
+6.9.8:
+2015/10/12
+
+Replace 6.9.7 that was build with Java8 by error.
+
+6.9.7:
+No official release
+
+Fixed: GITHUB-798: Set suitethreadpoolsize for Maven Surefire (Jan Dundáček)
+Fixed: GITHUB-171: ISuiteListener methods called multiple times if multiple test elements (Daniel Qian & Julien Herr)
+Fixed: GITHUB-169: IInvokedMethodListener methods executed several times before/after each test method (Mario Duarte & Julien Herr)
+Fixed: GITHUB-154: MethodInterceptor will be called twice (Tim wu & Julien Herr)
+
+6.9.6:
+2015/07/15
+
+New: GITHUB-717: Add assertThrows and expectThrows (Ryan Schmitt)
+Fixed: GITHUB-755: Fixed reporting of retried tests (Ryan Schmitt)
+Fixed: GITHUB-773: Test should not be skipped when the exception is expected (@CandyLiuM & Julien Herr)
+
+6.9.5:
+2015/07/12
+
+Fixed: The ServiceLoaderTest on Windows (Mathieu Sebire)
+Fixed: GITHUB-691: Fix classloading issue when using TestNG 6.9.4 and JMockit. (Mathieu Sebire)
+Fixed: GITHUB-686: IAnnotationTransformer.transform is called for methods with testClass populated. (Łukasz Rekucki & Julien Herr)
+Fixed: GITHUB-420: Before/AfterSuite methods may not run, when use inheritance, and enabled=false (Jakub Tokaj & Julien Herr)
+Fixed: GITHUB-697: Make addFailedInvocationNumber thread-safe (Ryan Schmitt)
+Fixed: GITHUB-698: Fix exit code reporting when IRetryAnalyzer is used (Ryan Schmitt)
+Fixed: GITHUB-465: assertEquals(Collection, Collection) prints "null" when collections are different sizes (Michael Diamond)
+New: GITHUB-710: AppVeyor is used for continuous integration on Windows (Julien Herr)
+Fixed: GITHUB-599: IHookable ignored when a timeout is set (@ryanlevell & Julien Herr)
+New: GITHUB-723: Allow users to add their own suite parser (Julien Herr)
+Fixed: Allow '-testnames' option to work with '-xmlpathinjar' (@earthling)
+Fixed: GITHUB-739: TestNG skips all test classes from suite when a @BeforeClass fails (Priyanshu Shekhar & Julien Herr)
+Fixed: GITHUB-471: If @beforeMethod or @afterMethod fails then all children of the same base class will be skipped (Anton Panferov & Julien Herr)
+Fixed: GITHUB-595: testng hang at switching test cases when running test cases with high thread count (vit0rg)
+
+Eclipse:
+
+Fixed: The 57% freeze bug (Patrick Hensley and @denyska)
+
+6.9.4:
+2015/05/09
+
+Added: GITHUB-631: Avoid the static limitation of external DataProvider. (Julien Herr)
+Added: GITHUB-631: Allow to use Guice injection in DataProvider. (Julien Herr)
+Added: Drop support of Java6 and previous.
+Added: GITHUB-617: Allow injection of org.testng.ITestContext into the guice parent module. (Julien Herr)
+Fixed: GITHUB-606: RetryAnalyzer loops endlessly. (Krishnan Mahadevan)
+Fixed: GITHUB-618: Start TestNG from jar cause recursive run of tests from packages in Suite XML without ".*" on the end (Stas Gromov)
+Fixed: GITHUB-639: Typo on preserveOrder (tabei-k & Julien Herr)
+Fixed: GITHUB-632: Typo in doc (Pétur Ingi Egilsson & Julien Herr)
+Fixed: GITHUB-629: InvokedMethod doesn't recognize configuration method (Jan Mewes & Julien Herr)
+Fixed: GITHUB-615: XmlSuite, XmlTest: Time-out tag not preserved (jphollingworth & Julien Herr)
+New: GITHUB-638: Travis CI is used for continuous integration (Julien Herr)
+New: GITHUB-647: SonarQube is used to follow technical debt (Julien Herr)
+Added: GITHUB-616: org.testng.internal.Version will be always up-to-date (Julien Herr)
+Fixed: GITHUB-634: Review of the collections package (Julien Herr)
+Fixed: GITHUB-624: Fixed failure/error inversion in JUnitReportReporter (Jerome Jacob)
+Fixed: GITHUB-545: TestNG running JUnit tests but not reporting all results for parameterized tests (Jonathan Leitschuh & jdillet)
+Fixed: GITHUB-610: CustomizedSuites must be saved using utf-8 encoding (Juha Heljoranta)
+Fixed: GITHUB-602: NoClassDefFoundError in TestNGClassFinder.<init> (aanno)
+Fixed: GITHUB-529: Close InputStream and OuputStream after use (Andrew Gaul)
+Fixed: GITHUB-532: Create the parent directory if it's missing (Ion Savin)
+Fixed: GITHUB-541: Some OSGi manifest fixes (Evgeny Zhuravlev)
+Fixed: GITHUB-657: Fix OSGI Import-Package to make jUnit4 dependency optional (Xavier Fournet)
+Fixed: GITHUB-523: externally synchronize our use of the static SimpleDateFormat (mcosby)
+Fixed: GITHUB-477: Typo in DTD attribute comment (Kamil Szymański)
+Fixed: GITHUB-353: Typo in documentation (Jan Święcki)
+Fixed: GITHUB-656: Upgrade to JCommander 1.48 (Ryan Schmitt)
+Fixed: GITHUB-582: TestNG tests don't pass reliably on JDK8 (Ryan Schmitt)
+New: GITHUB-645: TestNG project on Google Code redirect to GitHub
+Fixed: GITHUB-310: Upgrade Guice (kronar & Julien Herr)
+Fixed: GITHUB-87: @BeforeSuite/@BeforeTest methods happens to be disabled by mistake (romlom & Julien Herr)
+Fixed: GITHUB-425: Wrong invocation order with lastTimeOnly (Rafael Winterhalter & Julien Herr)
+Fixed: GITHUB-417: Expected Exceptions Message fails to match multi-line messages (Michael Diamond)
+New: GITHUB-663: Add Guice Stage configuration for a suite (Clément Guillaume)
+
+6.8.21:
+2015/02/02
+
+6.8.15:
+2015/01/14
+
+Fixed: OutOfMemoryException while generating reports.
+Fixed: GITHUB-566: Build does not fail when successPercentage for @Test is not met
+Fixed: XmlTest#setGroupInstances was not being shown in toXml().
+Fixed: GITHUB-376: Some results can be lost (Konstantin Savin).
+Fixed: Handle relative paths of Suite XML files properly (Nalin Makar)
+
+6.8.5:
+2013/05/13
+
+Fixed: the OutOfMemoryException in reports
+Fixed: Surefire + listeners "Can't load class" problem
+
+===========================================================================
+6.8.1
+2013/03/30
+
+Added: Descriptions in the HTML reports
+Added: Various improvements to EmailableReporter (Abraham Lin)
+Added: Allow injection of java.lang.reflect.Constructor and org.testng.ITestNGMethod into DataProvide (Vladislav Rassokhin)
+Fixed: Assertions in the Assertions class were not failing properly.
+Fixed: GITHUB-337: ConfigurationMethod#m_instance set to Boolean.FALSE due to incorrect constructor call in clone() + auto-boxing (davidely)
+Fixed: Fix NPE for dependency methods/groups (Krishnan Mahadevan)
+Fixed: preserve-order bug (found by VladSarrokhin). 
+Fixed: GITHUB-300: OutOfMemoryException from reporters when there are a lot of tests
+Fixed: GITHUB-137: Main parameters with a default value should be overridden if a main parameter is specified 
+Fixed: GITHUB-107: Allow enum values without converting them to uppercase.
+Fixed: @Guice with no modules specified is now supported
+Fixed: Reporter.log() invoked from listeners were being discarded
+
+Eclipse:
+Added: Predefined listeners (Tim Wu)
+Fixed: Compare dialog
+
+===========================================================================
+6.7
+2012/07/15
+
+Added: Big performance improvement when generating the reports (Frank Pavageau)
+Added: <dependencies> allows you to specify group dependencies in testng.xml
+Added: Blow up early if trying to include/exclude an unknown method
+Added: <parameters> can now be specified under <include> (Storm Qi)
+Added: GITHUB-243: Add Reporter Output per Test in XMLReporter (dunse) 
+Fixed: Better HTML escaping of the stack traces
+Fixed: The failed assertions now use [] as delimiters instead of <> (better for the HTML reports)
+Fixed: GITHUB-237: Wrong time format in XML reporter
+Fixed: Threads were started sequentially instead of being interleaved
+Fixed: dataProvider(parallel = true) was not killing its threads properly
+Fixed: XmlSuite#toXml wasn't outputting the <groups> tag correctly
+Fixed: testng-failed.xml was not carrying over the parameters from the original testng.xml
+Fixed: BeforeClass failing in parent failed to skip methods in sub classes
+Fixed: Better error message if <suite name=""> is missing
+Fixed: GITHUB-221: Honor excludeGroups on testng tests when run in mixed mode (criccio)
+Fixed: dependsOnGroups = {regexp} wasn't working properly (Alistair Ward)
+Fixed: GITHUB-205: white-space was spelled whitespace in testng.css (carlin-scott)
+
+Eclipse:
+Fixed: Environment is not transferred when rerunning failed tests.
+Fixed: Rerunning failed tests will preserve the environment of the original launch
+
+===========================================================================
+
+6.5.1
+04/10/2012
+
+Added: <suite allow-return-values="true"> (and in <test> as well)
+Added: data-provider attribute to testng-results.xml
+Added: Reporter display the results in the same order as test methods (Libor Zoubek)
+Added: Support for running JUnit 4 tests (Lukas Jungmann)
+Added: Ability to auto-detect JUnit tests ('-mixed' mode) (Lukas Jungmann)
+Added: Support for ResourceCollections in an Ant tasks (requires Ant >= 1.7.0) (Lukas Jungmann)
+Fixed: GITHUB-198: JUnitReportsReporter use commas in certain locales, which JUnitReports doesn't like
+Fixed: GITHUB-173: Dependent methods executed out-of-order if method names match across classes (jjedMoriAnktah)
+Fixed: ThreadLocal<ITestResult> leak (aslakknutsen)
+Fixed: In the HTML reports, only show the first 100 characters of the parameters
+Fixed: SkippedException are considered as real exception with @Test(expectedExceptions)
+
+Eclipse:
+Fixed: Java constants are properly resolved if they are used as group names (susanin)
+Fixed: @Test(groups = Foo.CONSTANT) (susanin)
+Fixed: Failed tests with allow-return-values="true" were not rerun
+Added: <suite allow-return-values="true"> (and in <test> as well)
+
+===========================================================================
+
+6.4:
+02/15/2012
+
+Added: @DataProvider(indices) to return specific indices of a data provider
+Added: New HTML reports
+Added: configfailurepolicy=continue with DataProviders (toddq)
+Added: ITestResult#getTestContext (bpedman)
+Fixed: invocationCount > 1 + timeOut wasn't timing out properly
+Fixed: When running TestNG programmatically, child xml suites are not run (when added using setSuiteFIles()) (Gaurav Gupta)
+Fixed: GITHUB-145: Excessive test method execution (githubCast)
+Fixed: GITHUB-149: reversed arguments in failAssertEqualsNoOrder().
+Fixed: EmailableReporter: methods are now *really* sorted chronologically.
+
+Eclipse:
+
+Added: You can now add the testng.jar sources as a library (Nick Tan)
+Added: Upgraded the plug-in to 3.4+ (Nick Tan)
+Added: dependsOnGroups now fully supported
+Fixed: @Parameters now works with both ("foo") and ({"foo"}) (davekerber)
+
+===========================================================================
+6.3.1
+10/22/2011
+
+Added: New system property: dataproviderthreadcount (Bill Ross)
+Fixed: Configuration methods were reported incorrectly in listeners.
+Fixed: Was creating too many listeners (Jacek Pulut)
+Fixed: IAnnotationTransformer2 beforeTest/afterTest booleans were not being set
+Fixed: GITHUB-92: @BeforeTest method in a super class will be called multiple time when alwayRun = true (Bubuntux)
+Fixed: GITHUB-111: @AfterClass on base classes run once too many (lrivera)
+Fixed: GITHUB-107: Displaying 0 tests run if a listener modifies the parameters of the suite
+
+===========================================================================
+6.3
+10/17/2011
+
+Added: "description" attribute on <include>, made available on ITestNGMethod#getDescription
+Added: RemoteTestNG waits infinitely for a connection (Aleksey Kabanov)
+Fixed: A method that's both a test and a factory would not invoke its data provider
+Fixed: @AfterClass was not called if one of the methods was not enabled (Aleksey Kabanov)
+Fixed: Groovy access bug
+Fixed: The XML parser doesn't recognize parallel="instances"
+Fixed: NPE when using inner classes
+Fixed: GITHUB-90: @AfterClass not being run when the class contains included and not included methods
+Fixed: @AfterClass not being run in some subclassing situations
+
+Eclipse:
+
+Fixed: Verbose levels specified in suites not respected
+Fixed: Variable substitution in VM arguments is not working properly (svenhoff)
+
+===========================================================================
+
+6.2
+08/18/2011
+
+Added: xmlpathinjar to the TestNG ant task
+Added: TestNG can now invoke package protected constructors
+Added: Injectors created by the @Guice annotation are now shared at the <test> level
+Added: IConfigurationListener is now a public listener, along with a new one: IConfigurationListener2
+Added: When a method fails, only dependents of the same instance will be skipped
+Added: parallel=instances for factory instance parallel runs
+Added: @Factory(enabled)
+Fixed: JUnitReports reports now report the cumulated time @{Before,After}Method+@Test for each test method
+Fixed: JUnitReports reports have the name of the <test> instead of that of the first class
+Fixed: Using preserve-order with a factory that creates instances of a different class causes NPE
+Fixed: GITHUB-74: Bad ordering of test methods when using a @Factory constructor with dataProvider
+Fixed: Changing the test result from success to failure in a listener would still count the test as a success
+Fixed: ServiceLoader wasn't resolving correctly if no service loader classloader was specified
+Fixed: Better ordering with mixed priorities and dependencies
+Fixed: Improved detection of graph cycles in parallel runs
+Fixed: @BeforeTest was invoked multiple times if a factory is used
+Fixed: GITHUB-57: Allow usage of package protected constructor of test classes
+Fixed: Injecting both Object[] and Method in @BeforeMethod didn't always work
+Fixed: testng-results.xml now lists the results chronologically
+Fixed: @Listeners specified on a base class will only be run once per listener class (dbriones)
+Fixed: -groups and -excludegroups were no longer overriding testng.xml
+
+Eclipse:
+
+Added: Each data provider method now has a separate node entry in the TestNG view
+Fixed: Nodes in error would sometimes remain green
+Fixed: The TestNG context menu no longer appears where it shouldn't
+
+===========================================================================
+
+6.1.1
+7/5/2011
+
+Fixed: https://github.com/cbeust/testng/issues/56 testng-results.xml was reporting the instance name instead of the method name
+Fixed: NPE when using preserve-order and factories.
+Fixed: Depending on a skipped method would not cause a method to be skipped
+
+===========================================================================
+
+6.1
+6/30/2011
+
+Possible backward incompatible changes:
+
+- Don't mutate the value returned by XmlTest#getIncludedGroups and XmlTest#getExcludedGroups.
+Instead, use addIncludedGroup/addExcludedGroup.
+- Failing methods that have dependees will only cause skips in the same instance. Different
+test instances will not be affected
+
+Added: Support for ServiceLoader for ITestNGListener
+Added: @Factory(dataProvider / dataProviderClass) on constructors
+Added: assertNotEquals() to Assert
+Added: assertArrayEquals() to AssertJUnit
+Added: Nested classes are now automatically added for consideration for inclusion
+Added: <suite preserve-order="true"> will cause this attribute to be propagated to all <test> tags
+Added: <groups> can now be specified under a <suite>
+Added: Tycho compatibility (Aleksander Pohl)
+Added: New <test> and <suite> flag: group-by-instances
+Added: -xmlpathinjar to specify the path of testng.xml inside a test jar file
+Added: ISuite#getAllMethods, to retrieve all the methods at the start of a suite.
+Added: Output ITestResult attributes in xml report (nguillaumin)
+Fixed: Thread safety problem in MethodInvocationHelper (Baron Roberts)
+Fixed: Group dependencies were not being skipped properly.
+Fixed: Dependency failures only impact the same instance
+Fixed: Static classes could cause a StackOverFlowError
+Fixed: IConfigurationListener was not extending ITestNGListener
+Fixed: IConfigurationListener#onConfigurationFailure was never called
+Fixed: TESTNG-476: <test> tags are now run in the order found in testng.xml
+Fixed: Now showing failed/skipped error messages on the console for verbose >= 2
+Fixed: ITestResult#getEndMillis() return 0
+Fixed: TESTNG-410: Clearer error message
+Fixed: TESTNG-475: @DataProvider doesn't support varargs
+Fixed: Performance problems in EmailableReporter
+Fixed: TESTNG-472: Better output for assertNull()
+Fixed: ConcurrentModificationException when using parallel data providers.
+Fixed: TESTNG-282: Problem when including+excluding packages (addicted)
+Fixed: TESTNG-471: assertEquals(Map, Map) fails if a map is a subset of the other
+Fixed: JUnitReporter generates an <error> tag for successful expectedExceptions tests
+Fixed: ISSUE-47: Don't allow two <test>s with same name within same suite (Nalin Makar)
+Fixed: If a listener implements both ISuiteListener and IInvokedMethodListener, only one of them gets invoked
+
+Eclipse:
+
+Added: New quick fix "Add static import org.testng.AssertJUnit.assertXXX"
+Added: New workspace wide setting: excluded stack traces, to provide shorter stack traces in the view
+Added: New "Clear results" icon in the tool bar
+Added: When the search filter is modified, don't update the tree live if it is too big 
+Added: Two new @Test refactorings (pull to class level, push to method level)
+Added: JUnit conversion: @Ignore
+Added: JUnit conversion: assertArrayEquals()
+Added: JUnit conversion: @RunWith(Parameterized.class)
+Added: Support for Hamcrest failed assertions in the compare dialog
+Added: JUnit conversion: suite() methods can now either be removed, commented out or left untouched
+Fixed: JUnit conversion: super.setUp()/tearDown() were being removed when extending a class other than TestCase
+Fixed: "Run as" menu not appearing for methods that take a generic parameter.
+Fixed: The tree was incorrect if the same class is used in different <test> tags
+Fixed: When creating a new Run/Debug configuration, "Launch.label" was displayed
+Fixed: TESTNG-459: TestNG menu should not always be present in context menu (Mykola Nikishov)
+Fixed: Performance problems in the plug-in
+Fixed: Workspace-wide XML template files are not being honored.
+Fixed: @BeforeClass/@AfterClass from JUnit4 are not being properly converted
+Fixed: Conversions generate @Test() instead of @Test
+
+===========================================================================
+
+6.0
+2011/03/16
+
+Added: @Guice(moduleFactory) and IModuleFactory
+Added: @Guice(module)
+Added: timeOut for configuration methods
+Added: -randomizesuites (Nalin Makar)
+Added: IConfigurable
+Fixed: @Test(priority) was not being honored in parallel mode
+Fixed: @Test(timeOut) was causing threadPoolSize to be ignored
+Fixed: TESTNG-468: Listeners defined in suite XML file are ignored (Michael Benz)
+Fixed: TESTNG-465: Guice modules are bound individually to an injector meaning that multiple modules can't be effectively used (Danny Thomas)
+Fixed: Method selectors from suites were not properly initialized (toddq)
+Fixed: Throw an error when two data providers have the same name
+Fixed: Better handling of classes that don't have any TestNG annotations
+Fixed: XmlTest#toXml wasn't displaying the thread-count attribute
+Fixed: TESTNG-438: Regression in 5.14.1: JUnit Test Execution no longer working
+Fixed: TESTNG-436: Deep Map comparison for assertEquals() (Nikolay Metchev)
+Fixed: Skipped tests were not always counted.
+Fixed: test listeners that throw were not reporting correctly (ansgarkonermann)
+Fixed: <suite junit="true"> wasn't working.
+Fixed: In parallel "methods" mode, method interceptors that remove methods would cause a lock up
+Fixed: EmailableReporter now sorts methods chronologically
+Fixed: TESTNG-411: Throw exception on mismatch of parameter values (via DP and/or Inject) and test parameters
+Fixed: IDEA-59073: exceptions that don't match don't have stack trace printed in console (Anna Kozlova)
+Fixed: IDEA's plug-in was not honoring ITest (fixed in TestResultMessage)
+Fixed: Methods depending on a group they belong were skipped instead of throwing a cycle exception
+Fixed: TESTNG-401: ClassCastException when using a listener from Maven
+Fixed: TESTNG-186: Rename IWorkerApadter to IWorkerAdapter (Tomas Pollak)
+Fixed: TESTNG-415: Assert.assertEquals() for sets and maps fails with 'null' as arguments
+Fixed: typo -testRunFactory
+Fixed: NPE while printing results for an empty suite (Nalin Makar)
+Fixed: Invoke IInvokedMethodListener.afterInvocation after fixing results for tests expecting exceptions (Nalin Makar)
+Fixed: TESTNG-441: NPE in SuiteHTMLReporter#generateMethodsChronologically caused by a race condition (Slawomir Ginter)
+
+Eclipse:
+Added: Convert to YAML
+Added: New global preference: JVM args
+Added: Eclipse can now monitor a test-output/ directory and update the view when a new result is created
+Added: Right clicking on a class/package/project now offers a menu "TestNG/Convert to TestNG"
+Added: Excluded methods are now listed in the Summary tab
+Added: "Description" column in the excluded methods table
+Added: Dialog box when the plug-in can't contact RemoteTestNG
+Added: Double clicking on an excluded method in the Summary tab will take you to its definition
+Added: If you select a package before invoking the "New TestNG class" wizard, the source and package text boxes will be auto-filled
+Added: When an item is selected in a tab, the same item will be selected when switching tabs
+Added: A new "Summary" tab that allows the user to see a summary of the tests, sort them by time, name, etc...
+Added: It's now possible "Run/Debug As" with a right click from pretty much any element that makes sense in the tree.
+Added: JUnit conversion: correctly replaces assertNull and assertNotNull
+Added: JUnit conversion: removes super.setUp() and super.tearDown()
+Added: JUnit conversion: removes @Override
+Added: JUnit conversion: replaces @Test(timeout) with @Test(timeOut) (5.14.2.4)
+Added: JUnit conversion: replaces @Test(expected) with @Test(expectedExceptions) (5.14.2.4)
+Added: JUnit conversion: replaces fail() with AssertJUnit.fail() (5.14.2.2)
+Added: JUnit conversion: replaces Assert with AssertJUnit (5.14.2.1)
+Added: The progress bar is now orange if the suite contained skipped tests and no failures
+Added: Skipped test and suite icons are now orange (previously: blue)
+Added: New method shortcuts: "Alt+Shift+X N", "Alt+Shift+D N" (Sven Johansson)
+Added: "Create TestNG class" context menu
+Added: When generating a new class, handle overridden methods by generating mangled test method names
+Fixed: Green nodes could override red parent nodes back to green
+Fixed: Was trying to load the classes found in the XML template file
+Fixed: Stack traces of skipped tests were not showing in the Exception view
+Fixed: XML files should be run in place and not copied.
+Fixed: NPE when you select a passed test and click on the Compare Result icon (Mohamed Mansour)
+Fixed: When the run is over, the plug-in will no longer force the focus back to the Console view
+Fixed: The counter in the progress bar sometimes went over the total number of test methods (5.14.2.9)
+Fixed: org.eclipse.ui.internal.ErrorViewPart cannot be cast to org.testng.eclipse.ui.TestRunnerViewPart (5.14.2.9)
+Fixed: Workspace preferences now offer the "XML template" option as well as the project specific preferences (Asiel Brumfield)
+Fixed: TESTNG-418: Only last suite-file in testng.xml run by Eclipse plugin
+
+Documentation:
+Added: Section on Selenium (Felipe Knorr Kuhn)
+Added: Link to an article on TestNG, Mockito and Emma in the Misc section
+
+===========================================================================
+
+5.14.7
+2011/01/27
+
+Release for IDEA
+
+===========================================================================
+
+5.14.1
+2010/10/2
+
+Fixed: TESTNG-401: ClassCastException when using a listener from Maven
+
+===========================================================================
+
+5.14
+2010/08/28
+
+Added: test suites can now be run in parallel with -suitethreadpoolsize
+Fixed: @Listeners now aggregate through base classes
+Fixed: ISuite was no longer serializable
+Fixed: Injection was sometimes not working properly when used with @Parameters
+Fixed: TESTNG-400: afterMethod was called after onTestFailure()
+Fixed: "excludedgroups" was not working on the ant task because of a typo
+Fixed: ant task error if <classfileset> is used with no classes (welex91)
+Fixed: TESTNG-404: threaded tests fail due to use of non-threadsafe collections (Marcus Better)
+Fixed: preserve-order was not preserving class order with dependent methods
+Fixed: RetryAnalyzer wasn't working properly with factories
+Fixed: The ant task was no longer supporting ',' for testclass
+
+Eclipse:
+
+Fixed: The plug-in wasn't running Groovy tests correctly (Andrew Eisenberg)
+Fixed: TESTNG-402 [Eclipse Plug-In] NPE occurred when I run twice a custom "Run configuration" on a group
+
+===========================================================================
+
+5.13.1
+2010/08/05
+
+Added: -methods
+Added: -configfailurepolicy (Todd Quessenberry)
+Added: -methodselectors (Todd Quessenberry)
+Added: @NoInjection
+Added: <test preserve-order="true">
+Added: -testnames (command line) and testnames (ant)
+Added: New ant task tag:  propertyset (Todd Wells)
+Added: ITestNGListenerFactory
+Added: Passing command line properties via the ant task and doc update (Todd Wells)
+Added: Hierarchical XmlSuites (Nalin Makar) 
+Added: Reporter#clear()
+Fixed: NullPointerException when a suite produces no results (Cefn Hoile)
+Fixed: Identical configuration methods were not always invoked in the correct order in superclasses (Nalin Makar) 
+Fixed: @DataProvider(parallel = true) was passing incorrect parameters with injection
+Fixed: Replaced @Test(sequential) with @Test(singleThreaded)
+Fixed: If inherited configuration methods had defined deps, they could be invoked in incorrect order (Nalin Makar)
+Fixed: Initialize all Suite/Test runners at beginning to catch configuration issues right at start (Nalin Makar)
+Fixed: Issue7: Issue86 Incorrect dates reported for configuration methods
+Fixed: Issue24: OOM errors in SuiteHTMLReporter (Nalin Makar)
+Fixed: Time outs specified in XML were not honored for <suite parallel="tests">
+Fixed: <suite> and <test> time outs were hardcoded, they now honor their time-out attribute
+Fixed: TestNG was hanging if no test methods were found
+Fixed: onTestSuccess() was called after @AfterMethod instead of after the test method (test: test.listener.ListenerTest)
+Fixed: XML test results contained skipfailedinvocationCounts instead of skipfailedinvocationcounts
+Fixed: Issue4 assertEquals for primitive arrays, Issue34 assertNull javadoc updated
+Fixed: Issue78 NPE with non-public class. Now throws TestNG exception
+Fixed: NPE with @Optional null parameters (Yves Dessertine)
+Fixed: TESTNG-387 TestNG not rerunning test method with the right data set from Data Provider (Francois Reynaud)
+Fixed: Show correct number of pass/failed numbers for tests using @DataProvider
+Fixed: Return correct method status and exception (if any) in InvokedMethodListener.afterInvocation() 
+Fixed: Trivial fixes: TESTNG-241 (log message at Info), Issue2 (throw SAXException and not NPE for invalid testng xml) 
+Fixed: Configuration methods couldn't depend on an abstract method (Nalin Makar) 
+Fixed: TestNG#setTestClasses was not resetting m_suites
+Fixed: Exceptions thrown by IInvokedMethodListeners were not caught (Nalin Makar)
+Fixed: @Listeners now works on base classes as well
+Fixed: Test priorities were not working properly in non-parallel mode
+Fixed: @Listeners wasn't working properly with ITestListener
+
+Eclipse
+
+Fixed: TESTNG-395 New wizard was creating classes called "NewTest"
+Fixed: TESTNG-397 Class level @Test was preventing groups from showing up in the launch configuration
+
+Doc
+Updated Maven documentation (Brett Porter)
+
+===========================================================================
+
+5.12.1
+2010/03/29
+
+Maven update
+
+===========================================================================
+5.12
+
+Removed: Javadoc annotation support
+
+Added: @Listeners
+Added: IAttributes#getAttributeNames and IAttributes#removeAttribute
+Added: testng-results.xml now includes test duration in the <suite> tag (Cosmin Marginean)
+Added: Injection now works for data providers
+Added: TestNG#setObjectFactory(IObjectFactory)
+Added: Priorities: @Test(priority = -1)
+Added: New attribute invocation-numbers in <include>
+Added: testng-failed.xml only contains the data provider invocations that failed
+Added: IInvokedMethodListener2 to have access to ITestContext in listeners (Karthik Krishnan)
+Fixed: @Before methods run from factories were not properly interleaved
+Fixed: The TextReporter reports skipped tests as PASSED (Ankur Agrawal)
+
+Eclipse:
+
+Added: New file wizard: can now create a class with annotations, including @DataProvider
+Added: You can now select multiple XML suites to be run in the launch dialog
+Fixed: @Test(groups = <constant>) was taking name of the constant instead of its value.
+Fixed: http://jira.codehaus.org/browse/GRECLIPSE-476 NPE with Groovy Tests (Andrew Eisenberg)
+Fixed: The custom XML file is now created in the temp directory instead of inside the project
+Fixed: In the launch dialog, now display an error if trying to pick groups when no project is selected
+Fixed: Was not setting the parallel attribute correctly on the temporary XML file
+
+===========================================================================
+5.11
+2009/12/08
+
+Added: Dependent methods can now run in their own thread
+Added: dataProviderThreadCount can be set from the command line and from ant (Adrian Grealish)
+Added: ITestAnnotation#setDataProvider
+Added: Assert#assertEquals() methods for Sets and Maps
+Fixed: The text reporter was no longer reporting stack traces for verbose >= 2
+Fixed: dataProviderClass was not respecting inheritance (like most attributes still)
+Fixed: @BeforeSuite/@AfterSuite would run multiple times when used in a @Factory
+Fixed: packages=".*" wasn't working properly (sandopolus)
+Fixed: TestResult#getName now returns the description instead of the method
+Fixed: @DataProvider and dependent methods were not skipping correctly (Francois Reynaud)
+Fixed: TESTNG-347 suite with parallel="tests" and test with parallel="classes" doesn't work correctly (Rob Allen)
+Fixed: TESTNG-67: @Configuration/@Factory methods in base class being ignored
+Fixed: Inner test classes were not excluded properly (Carsten Gubernator)
+Fixed: threadPoolSize without invocationCount was causing reporters not to be invoked
+Fixed: A @Factory throwing an exception did not cause any error
+Fixed: <classfilesetref> was not working properly in the ant task (Ed Randall)
+Fixed: @BeforeClass methods were not running in parallel (Aidan Short)
+Fixed: Test class with @ObjectFactory doesn't get instantiated via the factory
+Fixed: Allow IObjectFactory to load from non-standard classloader (for PowerMock support)
+
+Eclipse 5.11.0.19:
+Added: New "parallel" preference setting (Windows / Preferences / TestNG)
+Fixed: IIinvokedMethodListeners were not invoked
+
+===========================================================================
+5.10
+
+Added: The output in the testng-results.xml is now sorted by the starting timestamp (Daniel Rudman)
+Added: Better display of the test name and method description in the default and Emailable report
+Added: If both -testjar and an XML file are provided on the command line, the latter will be used
+Added: @Before and @After methods can be injected with the current XmlTest
+Added: Methods that time out now display the stack trace showing where the time out occurred
+Added: ITestResult#getAttribute and ITestResult#setAttribute
+Added: @After methods can now be injected with an ITestResult
+Added: @BeforeMethod and @AfterMethod methods can now be injected an ITestResult
+Added: ISuite#getAttribute and ISuite#setAttribute to share data within a suite
+Added: @Test(expectedExceptionsMessageRegExp = ".*foo.*")
+Added: @DataProvider(parallel=true)
+Fixed: @Test(dataProvider) was not working at the class level
+Fixed: Display a better error message if the wrong exception is thrown with an expectedExceptions
+Fixed: Classes created by factories were not run in the order they were created
+Fixed: Dependent methods are now run closer to methods within their class
+Fixed: xmlFileSet in ant was not working correctly (Sean Shou)
+Fixed: Various oversights in the DTD (Will McQueen)
+Fixed: XMLUtils was not escaping XML attribute values
+Fixed: TESTNG-317: Sequence order mis-calculation: testing using suite in sequence for classes and same method names creates non-sequential order
+Fixed: Test names (classes that implement org.testng.ITest) now appear more prominently in the HTML reports
+Fixed: expectedExceptions=RuntimeException.class was not failing when no exception was throw
+Fixed: TESTNG-291: Exceptions thrown by Iterable DataProviders are not caught, no failed test reported (Roberto Tyley)
+Fixed: TESTNG-301: Need to include parameters in testNG report for test created by @Factory
+Fixed: testng-failed.xml now includes skipped tests
+Fixed: TestNG couldn't find Groovy files (Haw-Bin)
+
+Eclipse
+
+Fixed: TESTNG-313: Provide extension point to contribute test and report listeners (Erik Putrycz)
+Fixed: Quick fixes no longer introduce deprecated annotations (Greg Turnquist)
+
+===========================================================================
+5.9
+2009/04/09
+
+Added: New ant task boolean flag: delegateCommandSystemProperties (Justin) 
+Added: skipfailedinvocations under <suite> in testng-1.0.dtd (Gael Marziou / Stevo Slavic) 
+Added: -testrunfactory on the command line and in the ant task (Vitalyi Pamajonkov)
+Added: TESTNG-298: parallel="classes", which allows entire classes to be run in the same thread
+Added: @BeforeMethod can now declare Object[] as a parameter, which will be filled by the parameters of the test method
+Added: IAnnotationTransformer2
+Added: @Test(invocationTimeOut), which lets you set a time out for the total time taken by invocationCount
+Added: IInvokedMethodListener
+Added: -testjar supports jar file with no testng.xml file
+Fixed: IInvokedMethodListener wasn't properly recognized from the command line (Leonardo Rafaeli)
+Fixed: TESTNG-309 Illegal default value for attribute in DTD file
+Fixed: TESTNG-192: JUnit XML output includes wrong tests (Aleksandar Borojevic)
+Fixed: Set a generated suite to default to non-parallel (Mark Derricutt)
+Fixed: -testJar command line parsing bug
+Fixed: testng-failed.xml didn't include the listeners
+Fixed: annotation transformers were not run when specified in testng.xml
+Fixed: TESTNG-192: JUnit XML output includes wrong tests (Borojevic)
+Fixed: @Parameters was not working correctly on @BeforeMethods with @DataProvider used on @Test methods
+Fixed: testng-failed.xml was sometimes incorrectly generated (Borojevic)
+Fixed: TestNG-228: Assert.assertEqualsNoOrder
+Fixed: TestNG-229: Assert.assertEquals does not behave properly when arguments are sets
+Fixed: TESTNG-36: assertEquals(Collection actual, Collection expected, String message) may have bug
+Fixed: TESTNG-296: Malformed jar URLs breaking -testJar
+Fixed: TESTNG-297: TestNG seemingly never stops running while building failed test suite (Gregg Yost)
+Fixed: TESTNG-285: @Test(sequential=true) works incorrectly for classes with inheritance
+Fixed: TESTNG-254: XMLSuite toXML() ignores listeners
+Fixed: TESTNG-276: Thread safety problem in Reporter class
+Fixed: TESTNG-277: Make Reporter.getCurrentTestResult() public
+Fixed: Potential NPE in XmlTest#getVerbose (Ryan Morgan)
+Fixed: EmailableReporter only displayed the first group for each test method
+Fixed: time-outs were not working in <test> and <suite>
+Fixed: @BeforeTest failing in a base class would not cause subsequent test methods to be skipped
+Fixed: TESTNG-195: @AfterMethod has no way of knowing if the current test failed
+Fixed: TESTNG-249: Overridden test methods were shadowing each other if specified with <include>
+Fixed: DataProviders from @Factory-created tests were all invoked from the same instance
+Fixed: enabled was not working on configuration methods
+Fixed: IIinvokedMethodListener was not correctly added in TestNG
+Fixed: NPE in XmlSuite#toXml
+Fixed: TESTNG-231: NullPointerException thrown converting a suite to XML (Mark)
+
+Doc:
+Added: 5.20: IInvokedMethodListener
+Added: -testjar
+
+===========================================================================
+5.8
+
+Fixed: TestNG-220: Ignore class definition/loader issues when scanning classpath for implicit classes
+Fixed: TestNG-224: Fix for relative suite filenames in XML file
+Added: TestNG-213: @Optional on a method parameter to allow optional @Parameters
+Fixed: TestNG-214: SkipException and TimeBombSkipException should accept nested exceptions
+Fixed: TestNG-211: new Parser(inputStream) doesn't work
+Added: Methods that form a cycle are now shown when the cycle is detected
+Added: Support for <listeners> in testng.xml
+Added: IMethodInterceptor
+Added: @TestInstance on a data provider method parameter
+Fixed: @AfterMethod(lastTimeOnly) didn't work properly with data providers
+Added: antlib.xml to allow autodiscovery of Ant task definition
+Fixed: name attribute on <test> is required
+
+Doc:
+Added: Method Interceptor
+Added: @Optional
+Added: Doc for IMethodInterceptor (5.16) and TestNG listeners (5.18)
+Added: 5.19: Dependency injection
+
+===========================================================================
+5.7
+
+Added: @BeforeMethod(firstTimeOnly) and @AfterMethod(lastTimeOnly)
+Added: @BeforeMethods can now take a Method and ITestContext parameters (like @DataProvider)
+Fixed: logging about abstract classes moved to level 5
+Added: if @Parameter is missing from testng.xml then it is read from the System properties
+Fixed: Don't run a @DataProvider method as a test when a class-level @Test is present
+Added: Attribute @Test#skipFailedInvocations
+Fixed: TESTNG-169 Error message: <method> is depending on nonexistent method null ("null" is uninformative)
+Fixed: -listener takes comma-separated classes
+Added: RetryAnalyzer (experimental) (Jeremie)
+
+===========================================================================
+5.6
+2007/06/14
+
+Added: SkipException/TimeBombedSkipException for manual skipping
+Added: <tests> can now be disabled at xml level using <test enabled="false">
+Added: Suite files that only contain other suites do not get reported
+Fixed: @BeforeClass methods would incorrectly report cyclic graphs
+Added: get/setAttribute to ITestContext
+Added: plugging in factory objects to handle the actual instantiation of tests
+Added: dataProvider to @Factory
+Added: ISuite now gives access to the current XmlSuite
+Fixed: TESTNG-139 dependsOnMethods gets confused when dependency is "protected"
+Fixed: TESTNG-141 junit attribute set to false in testng-failed.xml when it should be true
+Fixed: TESTNG-142 Exceptions in DataProvider are not reported as failed test
+Added: Improved behavior for @Before/@AfterClass when using @Factory 
+(http://forums.opensymphony.com/thread.jspa?threadID=6594&messageID=122294#122294)
+Added: Support for concurrent execution for invocationCount=1 threadPoolSize>1 and @DataProvider
+(http://forums.opensymphony.com/thread.jspa?threadID=64738&tstart=0)
+Added: New TestNG specific XML report, generated by default in 'xml' subdirectory of test-output
+Added: support in strprotocol for passing the ITest.getTestName() information
+Fixed: TESTNG-152 If DataProvider is not found, the exception message should tell exactly what happened
+
+Eclipse plug-in
+
+Fixed: Bug that made group launch configurations unusable
+Fixed: The plugin doesn't create the correct launch configuration for @Factory
+Fixed: Method based launchers cannot be editted
+Fixed: Plugin hangs while executing test with dataprovider that sends \n, \r messages
+Added: display ITest.getTestName()
+
+IDEA plug-in
+
+Fixed: IDEA 7.0 compatibility
+Fixed: occasional 'illegal arguments exception'
+Fixed: TESTNG-151 Final passing test result is not properly hidden
+Added: Auto-completion for dependsOnMethods
+Added: Highlighting of invalid groups/methods in dependsOn*
+
+===========================================================================
+5.5
+2007/01/25
+
+Fixed: @BeforeGroup methods were run twice when in a base class
+Fixed: @BeforeGroup methods were run twice with a @Test at class level
+Fixed: parallel="tests" didn't work as advertised
+Added: Support for thread-count at test level
+Added: Method selectors receive a Context and can stop the chain with setStopped()
+Fixed: XmlMethodSelector was always run first regardless of its priority
+Added: @BeforeGroups/@AfterGroups can live in classes without @Test methods
+Added: DataProvider can now take an ITestContext parameter
+Fixed: Wasn't parsing <selector-class-name> correctly 
+Fixed: Annotation Transformers now work on class-level annotations
+Fixed: Some class-level @Test attributes were not always honored
+Added: Clean separation between @Test invocation events and @Configuration invocation events
+       (see also TESTNG-111)
+Added: Test instances created by @Factory now run in multiple threads in parallel mode
+Fixed: @Before/@AfterGroups invocation order
+Fixed: TESTNG-27: Parameters are not used on <test> level anymore
+Fixed: TESTNG-107 don't create an output directory if "outputDirectory" is null
+Fixed: TESTNG-127 UseDefaultListeners in Ant Task does not work
+Fixed: TESTNG-119 Running TestNG runner with invalid '-sourcedir' on JDK14 JavaDoc annotated test classes won't fail.
+Fixed: TESTNG-113 Dependent methods within the same static inner class are not found
+Fixed: TESTNG-125 TestNG failed for test classes under *.java*.* pakages
+
+Eclipse plug-in
+Fixed: issue with launch configuration
+Fixed: TESTNG-124: setting location of testng reports output
+
+===========================================================================
+5.4
+
+Fixed: Ant task issue with paths containing spaces
+Added: for @BeforeGroups and @AfterGroups specifying the groups() attribute will auto-include the method
+			 into those groups by default (previously you had to also provide the value() attribute).
+Added: the load @Tests (invocationCount + threadPoolSize) are triggered simultaneous
+Fixed: reports are correctly displaying the thread info
+Added: @DataProvider name defaults to method name
+Added: support for remote protocol to pass parameter information
+Fixed: TextReporter logs information about the parameters of the test methods
+Fixed: concurrency issue in JUnitXMLReporter
+Fixed: output of JUnitXMLReporter must be CDATA
+Fixed: XML unsupported annotations/parallel attribute values are reported
+
+Eclipse plug-in
+Fixed: groups with multi-attribute javadoc annotations
+Fixed: consistent behavior for dependsOnMethods
+Fixed: consistent behavior for tests with dependsOnGroups (a warning is emitted)
+Fixed: consistent merge of configuration arguments when an existing launch configuration exists 
+===========================================================================
+5.3
+2006/10/30
+
+Fixed: use a single instance of bsh.Interpreter
+Added: @Before/@AfterMethod can declare a java.lang.reflect.Method parameter to be informed about the @Test method
+Fixed: super classes must not be listed in testng-failures.xml
+Fixed: parallel attribute must not appear if empty or null in testng-failures.xml
+Fixed: parsing for javadoc annotations is done on request only
+Added: improved multiple suite summary page report
+Added: -target option deprecated in favor of -annotations javadoc|jdk
+Fixed: filesets in the ant task didn't work if the paths have spaces in them
+Fixed: Before/After Suite were behaving wrong in parallel execution
+Added: A generic/extensible RemoteTestNG was added to the core
+Fixed: Before/AfterGroup-s were behaving wrong when using invocationCount, dataProvider and threadPoolSize
+Fixed: improved support for running different annotation type tests in the same suite
+Fixed: testng-failed.xml was generated even if there were no failures/skipps
+Fixed: -usedefaultlisteners was wrongly passed to JVM instead of TestNG options
+Added: Attribute dataProviderClass for @Test and @testng.test
+Fixed: Forgot to account for cases where both invocationCount and DataProviders are present
+Fixed: AfterGroups were invoked out of order with invocationCount and DataProviders
+Fixed: Reporter.getOutput() returned an empty array if a timeOut was specified
+Added: testng.xml now supports <suite-files>
+Added: ant task can receive several listeners
+Fixed: TESTNG-109 Skipped tests with expected exceptions are reported as failures
+Added: ant task can now select the parallel mode for running tests
+Fixed: ant task correctly deals with empty groups and excludedgroups parameters
+Added: ant task can override default suite and test names
+Added: comand line support for setting parallel mode, suite and test names
+
+Eclipse plug-in
+Added: Support for configuring per project usedefaultlisteners
+Added: Contextual drop-down menu on failures tab of the TestNG view to enable running/debugging method failure only
+Added: Suppport for configuring per project TestNG jar usage (project provided one or plugin provided one)
+
+===========================================================================
+5.2
+
+Added: "-usedefaultlisteners true/false" to command line and ant
+Added: EmailableReporter (from Paul Mendelson)
+Added: parallel can now be "methods" or "tests". Boolean version deprecated
+Added: TestNGAntTask now uses the @ syntax to invoke TestNG
+Added: Command line understands @ syntax
+Added: JUnitConverter uses the new syntax
+Added: -groups to JUnitConverter
+Fixed: Throw proper exception when a DataProvider declares parameters
+Added: completely revamped JUnit support (should run all kind of JUnit tests)
+Fixed: TESTNG-40 (Bug in testng-failed.xml generation)
+Fixed: TESTNG-106 (Failed "@BeforeSuite" method just skipps the last test in xml-file)
+Fixed: Success on 0 tests (http://forums.opensymphony.com/thread.jspa?threadID=41213)
+
+Eclipse plug-in
+Added: TESTNG-105 Automaticaly define TESTNG_HOME classpath variable
+
+===========================================================================
+5.1
+2006/08/18
+
+Added: @Test(sequential = true)
+Fixed: TESTNG-102 (Incorrect ordering of @BeforeMethod calls when a dependency is specified)
+Fixed: TESTNG-101 (HTML output contains nested <P> tags and a missing <tr> tag)
+Added: support for specifying test-only classpath (http://forums.opensymphony.com/thread.jspa?messageID=78048&tstart=0)
+Fixed: TESTNG-93 (method selectors filtering @BeforeMethod)
+Fixed: TESTNG-81 (Assert.assertFalse() displays wrong expected, actual value)
+Fixed: TESTNG-59 (multiple method selectors usage results in no tests run)
+Fixed: TESTNG-56 (invocation of @Before/AfterClass methods in parallel/sequential scenarios)
+Fixed: TESTNG-40 (failures suite does not contain @Before/After Suite/Test methods)
+Fixed: TESTNG-37 (allow passing null parameter value from testng.xml)
+Fixed: TESTNG-7 (display classname when hovering method)
+
+
+Eclipse plug-in
+
+Added: run contextual test classes with parameters from suite definition files
+Added: TESTNG-100 (Show HTML reports after running tests)
+Added: TESTNG-97 (Double click top stack to raise comparison)
+Added: TESTNG-84 (plug-in UI for suite option should support absolute path)
+Added: TESTNG-20 (copy stack trace)
+
+Fixed: TESTNG-72 (display groups with non-array values)
+Fixed: TESTNG-64 (Eclipse plug-in applies added groups to all launch configurations)
+Fixed: TESTNG-28 (Cannot select groups from dependent eclipse projects)
+Fixed: TESTNG-25 (do not display fully qualified method name when running contextual test class)
+
+Improved behavior:
+	TESTNG-98 (temporary files have guaranteed fixed names)
+	TESTNG-95 (Assertion failed comparison trims trailing ">")
+	TESTNG-70 (TestNG prevents eclipse from opening an older CVS version of a java class)
+	display of test hierarchy information (TESTNG-29)
+
+===========================================================================
+
+5.0.1
+
+Eclipse plug-in
+
+Added: Output directory for the tests
+Added: Can now specify listener classes
+
+===========================================================================
+5.0.1
+
+Fixed: reports generated by SuiteHTMLReporter do not work with JDK1.4
+			 
+===========================================================================
+
+5.0
+2009/04/01
+
+Added: Ant task: support for JVM, workingDir, timeout
+Added: Stack traces can be interactively shown in the HTML reports
+Added: Link to testng.xml in the reports
+Added: New structure for reports, suites go in their individual directory
+Added: @Test(suiteName) and @Test(testName)
+Added: The stack traces in reports do not include TestNG frames (system property testng.exception)
+			 (see: http://groups.google.com/group/testng-dev/browse_thread/thread/9f4d46ade10b0fda)
+Fixed: Exit with error when no methods are run
+			 (see: http://groups.google.com/group/testng-dev/browse_thread/thread/3c26e8a5658f22ac)
+Added: List of methods in alphabetical order
+Fixed: Class-scoped annotations were not recognized when inherited
+Added: Deprecated @Configuration and introduced @BeforeSuite/Test/Class/TestMethod
+Added: Deprecated @ExpectedExceptions and moved it into @Test
+Added: expectedExceptions to @Test, deprecated @ExpectedExceptions
+Added: New annotations:  @BeforeSuite, @BeforeTest, etc...
+Fixed: Was returning an exit code of 0 if a cyclic graph was detected
+Added: Interface org.testng.ITest so that tests can declare a name
+Fixed: The Text reporter was reporting the square of the actual number of methods
+Fixed: Bug reported by Eran about dependencies with an afterClass method
+Added: IHookCallBack now receives the ITestResult in its run() method
+Added: Name of suite for command line can be set with -Dtestng.suite.name=xxx
+Fixed: TestNGAntTask was hardcoding m_haltOnFSP to true
+Fixed: Passing a null parameter caused an NPE in the reports
+Added: "listener" to the ant task (and documentation)
+Added: if patch-testng-sourcedir.properties is found in the classpath
+       with a property "sourcedir" containing a ; separated list of
+       directories, this list will override -sourcedir.
+
+===========================================================================
+
+
+4.7
+
+Added: Maven 2 plug-in
+Fixed: Message formattings in TestNG assertion utility class
+Fixed: @Factory methods were counted as @Test as well
+       http://jira.opensymphony.com/browse/TESTNG-51
+Fixed: All DataProvider parameters were shown in the HTML report
+Fixed: Bug in testng-failed.xml generation
+Fixed: <packages> bug when using a jar file to load the test classes
+Added: alwaysRun for before @Configuration methods
+       http://jira.opensymphony.com/browse/TESTNG-35
+Fixed: groupless @Configurations were not invoked if a method depends on a group
+       http://jira.opensymphony.com/browse/TESTNG-45
+Added: beforeGroups/afterGroups to @Configuration
+
+Eclipse plugin:
+
+Added: last contextual launch is available in Eclipse launcher lists
+Fixed: 3.2M5 integration (removed dependency on non-existing class)
+Fixed: testng-failures.xml generation
+
+===========================================================================
+
+4.6
+2006/27/02
+
+Added: Documentation contains the new reports
+Added: TestNG.setUseDefaultListeners(boolean)
+Added: Descriptions now appear in TextReporter (verbose>=2) and the HTML reports
+Added: description attribute to @Test and @Configuration
+Added: combined Reporter output in the reports
+Added: methods not run in the reports
+Added: org.testng.IReporter
+Added: threadPoolSize to @Test
+Added: Reports now show relative timings (start at 0)
+Added: Reports now show different colors depending on the methods' classes
+Added: Reports now show all parameters used to invoke the test method
+Added: org.testng.Reporter
+Added: DataProviders can accept a Method as first parameter
+Fixed: Extraneous implicit inclusion of a method
+
+Eclipse plugin:
+
+Added: Run/Debug as TestNG test from the editor contextual menu
+Fixed: TESTNG-24: 'Run as testng test' does not appear of the Test annotation does not have a group
+Fixed: TESTNG-18: Eclipse plugin ignores Factory annotation
+Fixed: TESTNG-21: Show differences when double clicking assertion exceptions
+Added: UI allows setting orientation (even more space)
+	   http://forums.opensymphony.com/thread.jspa?threadID=17225&messageID=33805#33805
+	   
+===========================================================================
+
+4.5
+2007/07/02
+
+Core:
+
+Fixed: Methods were not implicitly included, only groups
+Fixed: Bug with failed parent @Configuration don't skip child @Configuration/@Test invocations
+Fixed: Bug with overridding @Configuration methods (both parent and child were run)
+Fixed: Bug when overriding beforeClass methods in base class (cyclic graph)
+Added: Support for JAAS (see org.testng.IHookable)
+Fixed: Problem with nested classes inside <package name="foo.*"
+Fixed: If a group is not found, mark the method as a skip instead of aborting
+Fixed: testng-failed.xml was not respecting dependencies
+Fixed: class/include method in testng.xml didn't work on default package
+Fixed: DTD only allowed one <define>
+Fixed: ArrayIndexOutOfBoundsException for jMock
+Added: dependsOnMethods can contain methods from another class
+Fixed: JUnitConverter required -restore, not any more (option is now a no-op)
+Fixed: JUnit mode wasn't invoking setName() on test classes
+Added: Regular expressions for classes in <package>
+Added: Distributed TestNG
+Fixed: Command line parameters and testng.xml are now cumulative
+Fixed: Reports now work for multiple suites
+Fixed: Was ignoring abstract classes even if they have non-abstract instances
+Fixed: If setUp() failed, methods were not skipped
+Fixed: Was not clearly indicating when beforeSuite fails
+Added: @Configuration.inheritGroups
+Fixed: inconsistency between testng.xml and objects regarding method selectors
+
+Eclipse plug-in:
+
+New look for the progress view.
+
+===========================================================================
+
+4.4
+
+Core:
+
+Fixed: testng-failures.xml was not excluding methods from base classes
+Fixed: Bug in suites of suites for JUnit mode
+
+===========================================================================
+
+4.3
+
+Core:
+
+Fixed: testng-failures.xml was not excluding methods from base classes
+Fixed: Bug in suites of suites for JUnit mode
+Added: Excluded groups on command line and ant task
+Fixed: When including a group, implicitly include groups depended upon
+Fixed: When depending on several groups, wasn't skipped if one of them failed
+Fixed: Failures weren't reported accurately in the JUnitReports report
+Fixed: Wasn't throwing an exception if depending on a non-existing group
+
+===========================================================================
+
+4.2
+
+Core:
+
+Fixed: wasn't excluding methods in base classes
+Added: alwaysRun for tests (soft dependencies)
+Fixed: Class-level enabled=false were not honored
+Fixed: Bug with multiple dataproviders on same class
+Fixed: Bug with dataprovider defined in the parent class
+Fixed: Bug with dataprovider defined in a subclass
+Fixed: Bug with dataprovider defined in an abstract class
+Fixed: testng-failures generation was excluding the methods even if a failed test depended on it
+
+===========================================================================
+
+4.1
+
+Core:
+
+Added: @DataProviders can return Iterable<Object[]>
+Fixed: Superclass test methods were not called in the presence of a class @Test
+Added: Reporter class to log messages in the HTML reports
+
+===========================================================================
+
+4.0
+2005/11/10
+
+Core:
+
+Fixed: suite methods now invoked only once in a hierarchy
+Added: @DataProvider and @testng.data-provider
+Fixed: Interleave order now respected for before/afterClass methods
+Added: Can now invoke java -jar testng-2.6.jar <...>
+Added: Support for BeanShell
+Added: Method Selectors (IMethodSelector)
+Fixed: In the absence of dependencies, @Configuration methods respect inheritance
+Fixed: Bug in multithreaded dependencies on methods
+Fixed: dependsOnGroups wasn't working on regular expressions
+Fixed: Bug in <package> when directories contain spaces in their names
+Fixed: Introduced a JDK5 dependency in the JDK1.4 build (getEnclosingClass())
+Fixed: Output directory in ant task was not honored if it didn't exist
+Fixed: Problem with timeout according to 
+       http://forums.opensymphony.com/thread.jspa?threadID=6707
+
+Eclipse plug-in:
+
+Fixed: Wasn't handling linked directories correctly
+Fixed: Bug in QuickFix implementation
+Added: Quick Fix for JUnit conversion (Annotations and JavaDoc)
+Fixed: Methods Run as TestNG test
+Added: Package level Run as TestNG test
+Fixed: Resources from the linked directories are using a wrong path when 
+       passed to command line TestNG
+
+IDEA plug-in:
+
+Added: Support for JDK 1.4 (both projects and IDEA itself)
+Fixed: Classes that contained only configuration were ignored
+
+===========================================================================
+
+2.5
+2005/08/08
+
+Added: ITestListener.onTestStart(ITestResult)
+Added: Support for <packages>
+Added: Resource files for easier ant taskdefs
+Fixed: @Configuration methods were not invoked with individual test methods
+Fixed: Bug with ExpectedExceptions
+Fixed: Didn't support nested factory classes
+Fixed: NPE if -target is omitted with JDK 1.4
+Fixed: @Configuration failures in a class would cause other classes to fail
+Added: alwaysRun
+Fixed: beforeTestClass/afterTestClass were broken for a pathological case
+Added: @Configuration(alwaysRun)
+Added: JUnitConverter task
+Fixed: < and > characters in reports were not escaped
+
+Eclipse plug-in:
+
+Fixed: Class dialog wasn't showing @Factory classes
+
+IDEA plug-in:
+
+First release!
+
+Documentation:
+
+Added: Brand new look!!!
+Added: Section on testng.xml
+Fixed: Numbering of sections
+
+===========================================================================
+
+2.4
+2005/07/05
+
+Changed: New package:  testng.org
+Fixed: Bug with @ExpectedException occuring the parallel mode
+Fixed: Bug with parameters and beforeTest
+Added: IInstanceInfo support
+Fixed: methods were not excluded when included by groups
+Fixed: testng-failures.xml is now including also the beforeSuite/afterSuite methods
+Fixed: generating the testng-failures.xml is now working as expected
+Fixed: Factories call all the tests even if some of them fail along the way
+Fixed: Better JUnit support (wasn't creating individual instances)
+Fixed: dependsOnGroups didn't work across different classes
+Added: command line (and Ant) -groups option
+Added: @Parameters (and made parameters attribute deprecated)
+Added: Parameters for constructors
+Fixed: Better interleaving of before/afterTestMethods
+Fixed: Ant task
+Fixed: TestNGException thrown when TestNG conditions are not fulfilled
+
+Documentation:
+- New assert classes
+- New ways to launch 
+- JUnitConverter documentation
+- new beforeSuite/afterSuite
+
+===========================================================================
+
+2.3
+2005/04/12
+
+Fixed: Spaces are now legal in JavaDoc comments
+Added: documentation for @Factory
+Fixed: factories were called multiple times
+Added: beforeSuite and afterSuite
+Fixed: inheritance and scope now working properly for annotations
+Fixed: dependsOnMethods wasn't working for 1.4
+Added: Better stack traces
+Added: Better syntax for included/excluded methods
+Fixed: Better verbose support
+Fixed: Various fixes for the Eclipse plug-in
+Added: Can specify a class name on the command line
+Fixed: Default package bug in JUnitConverter
+Added: Regression tests for JUnitConverter
+Added: -quiet option to JUnitConverter
+
+===========================================================================
+
+2.2
+
+Fixed: Wasn't handling several testng.xml files correctly
+Fixed: Renamed -src to -sourcedir
+Fixed: Complains if no sourcedir is specified in 1.4
+Added: In 1.4, don't require annotations="javadoc"
+Fixed: If setUp fails, complain and mark test methods as skips
+Fixed: Dependent methods weren't working for 1.4
+
+===========================================================================
+
+2.1
+2005/02/12
+
+Added: Parser can accept an InputStream for testng.xml
+Fixed: expected-exceptions now fails if test passes
+Fixed: reports now use the suite name in HTML
+Added: invocationCount and successPercentage
+Added: dependsOnMethods
+Added: timeOut works in non-parallel mode
+
+===========================================================================
+
+2.0
+2004/12/06
+
+Added: port on JDK 1.4
+
+===========================================================================
+
+1.3
+
+Added: new view:  classes (still experimental)
+Added: timeout on methods
+Added: thread-count
+Added: TestNG is now multithread, see "parallel" in <suite>
+
+===========================================================================
+
+1.2
+
+Added: JUnitConverter
+Fixed: Bug with afterClasses (test: AfterClassCalledAtTheEnd)
+
+===========================================================================
+
+1.1
+
+Added: new links for methods and groups in the HTML report
+Added: <methods>
+Added: <fileset> to <testng>
+
+===========================================================================
+
+1.0
+2004/04/28
+http://beust.com/weblog/2004/04/28/
+
+Fixed: Updated to the new DTD
+Fixed: Suite table of contents displays failures first
+Fixed: Bug in afterTestClass
+Added: Validating testng.xml
+Added: Scoped parameters
+Added:  testng.xml
+Removed: Property quiet
+Changed: Verbose is now an integer
+Added:  Dependent methods
+
+===========================================================================
+
+0.9
+
+Added:  Groups of groups
+Added:  Groups for Configuration methods
+Added:  Parameters
+
+===========================================================================
+
+0.2
+
+Fixed:  Merged TestMethod and TestClass into Test
+Added: HTML report
+Added: Regexps for groups
+Fixed:  Inheritance of methods
+Fixed:  ExpectedException is now called ExpectedExceptions
diff --git a/CHECKLIST b/CHECKLIST
new file mode 100644
index 0000000..e9488f1
--- /dev/null
+++ b/CHECKLIST
@@ -0,0 +1,10 @@
+Check list for releases:
+
+pom.xml
+pom-test.xml
+Version.java
+build.properties
+
+doc/index.html
+doc/documentation-main.html
+doc/maven.html
diff --git a/FILES b/FILES
new file mode 100644
index 0000000..482af74
--- /dev/null
+++ b/FILES
@@ -0,0 +1,11 @@
+build.properties

+build.xml

+lib/DTDDoc.jar

+examples/build.xml

+examples/testng.properties

+examples/testng.xml

+examples/src/

+maven-testng-plugin-1.1.jar

+CHANGES.txt

+LICENSE.txt

+README

diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/NOTES b/NOTES
new file mode 100644
index 0000000..07c2aa1
--- /dev/null
+++ b/NOTES
@@ -0,0 +1,38 @@
+=====
+Maven
+=====
+
+If you ran the full dist build, it should have generated a file on your hard drive called
+"testng-5.12-bundle.jar".
+
+Testing:
+
+-> Update <version>5.12</version> in bundle-pom.xml to the correct version
+  mvn install:install-file -DpomFile=bundle-pom.xml -Dfile=testng-5.12.1.jar
+  cd /tmp
+  svn co http://svn.apache.org/repos/asf/maven/surefire/trunk/surefire-integration-tests/src/test/resources/testng-simple/
+  cd testng-simple
+-> Update <testNgVersion> in pom.xml to the correct version
+-> Remove <classifier> in pom.xml if it's still there
+  mvn -DtestNgVersion=5.12.1 -Dsurefire.version=2.5 clean test
+-> Send bundle to Brett Porter <brett.porter@gmail.com>
+
+=======
+Eclipse
+=======
+
+I ended up doing the launch configuration, and just for information, here are the parameters:
+
+Run Configuration / Eclipse application.
+
+Check "Run an application" in "Program to run", and select org.eclipse.equinox.p2.metadata.generator.EclipseGenerator
+
+The program arguments are (replace the update site directory appropriately):
+
+Linux:
+-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl}    -application org.eclipse.equinox.p2.metadata.generator.EclipseGenerator -updateSite /usr/local/cbeust/java/testng-eclipse-update-site -site file:/usr/local/cbeust/java/testng-eclipse-update-site/site.xml  -metadataRepository file:/usr/local/cbeust/java/testng-eclipse-update-site/  -artifactRepository file:/usr/local/cbeust/java/testng-eclipse-update-site -artifactRepositoryName "Ganymede Artifacts" -compress  -append  -reusePack200Files  -noDefaultIUs  -vmargs -Xmx256m
+
+Mac
+-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl}    -application org.eclipse.equinox.p2.metadata.generator.EclipseGenerator -updateSite /Users/cbeust/java/testng-eclipse-update-site -site file:/Users/cbeust/java/testng-eclipse-update-site/site.xml  -metadataRepository file:/Users/cbeust/java/testng-eclipse-update-site/  -artifactRepository file:/Users/cbeust/java/testng-eclipse-update-site -artifactRepositoryName "Ganymede Artifacts" -compress  -append  -reusePack200Files  -noDefaultIUs  -vmargs -Xmx256m
+
+This will generate the site in ~/java/testng-eclipse-update-site
diff --git a/README b/README
new file mode 100644
index 0000000..ae34ca2
--- /dev/null
+++ b/README
@@ -0,0 +1,12 @@
+[![Build Status](https://travis-ci.org/cbeust/testng.svg)](https://travis-ci.org/cbeust/testng)
+
+Welcome to TestNG 6.8beta
+
+Please note that even though the .zip distribution contains the TestNG sources,
+you will not be able to build the software with them because we decided
+not to include the external jar files in order to keep the size down.
+
+If you want to build TestNG, please sync to the GitHub repository at https://github.com/cbeust/testng.
+
+--
+The TestNG team
diff --git a/README-publish b/README-publish
new file mode 100644
index 0000000..621ed43
--- /dev/null
+++ b/README-publish
@@ -0,0 +1,9 @@
+To publish:
+
+- ./gradlew bintrayUpload will upload the release to JCenter. It will fail if the version is a SNAPSHOT
+- ./gradlew uploadArchives will upload
+  - the snapshot to https://oss.sonatype.org/content/repositories/snapshots
+  - the release to https://oss.sonatype.org/service/local/staging/deploy/maven2
+For a release, you then need to go to https://oss.sonatype.org/index.html#stagingRepositories to manually close and release the distribution.
+
+Note that `./gradlew uploadArchives` is run by Travis after each new push to deploy the latest HEAD to the snapshot directory. As such, the version number of `build.gradle` in the git repo should always be -SNAPSHOT.
diff --git a/README.build b/README.build
new file mode 100644
index 0000000..8b10e0d
--- /dev/null
+++ b/README.build
@@ -0,0 +1,7 @@
+You need to install ivy in order to build.
+
+You can either download it from the ivy site or more simply,
+copy ivy-2.1.0.jar included in the root directory to ~/.ant/lib.
+
+After this, "ant" will build the distribution and run the tests.
+
diff --git a/README.dev b/README.dev
new file mode 100644
index 0000000..c74f7e5
--- /dev/null
+++ b/README.dev
@@ -0,0 +1,36 @@
+New installation:
+
+Install GPG at http://www.gpgtools.org/gpgmail/index.html
+Generate new key with gpg --gen-key
+build-with-maven
+gpg --list-keys
+Send the public key:
+  gpg --keyserver hkp://pool.sks-keyservers.net --send-keys <public_key>
+  or wwwkeys.at.pgp.net
+
+
+  instructions at https://docs.sonatype.org/display/Repository/How+To+Generate+PGP+Signatures+With+Maven
+
+
+Configure ~/.m2/settings.xml with Nexus user/password:
+
+  <settings>
+    <servers>
+      <server>
+        <id>sonatype-nexus-snapshots</id>
+        <username>***</username>
+        <password>***</password>
+      </server>
+      <server>
+        <id>sonatype-nexus-staging</id>
+        <username>***</username>
+        <password>***</password>
+      </server>
+    </servers>
+  </settings>
+
+Snaphot deploy:
+mvn -Dgpg.passphrase= -Dgpg.keyname=<public_key> deploy
+
+Staging deploy:
+mvn -Dgpg.passphrase= -Dgpg.keyname=<public_key> release:clean release:prepare release:perform
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..53879e4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,11 @@
+[![Build Status](http://img.shields.io/travis/cbeust/testng.svg)](https://travis-ci.org/cbeust/testng)
+[![Java9 EA Build Status](https://img.shields.io/jenkins/s/https/adopt-openjdk.ci.cloudbees.com/TestNG.svg?label="Java9 EA")](https://adopt-openjdk.ci.cloudbees.com/job/TestNG)
+[![Java9 Jigsaw EA Build Status](https://img.shields.io/jenkins/s/https/adopt-openjdk.ci.cloudbees.com/TestNG-Jigsaw.svg?label="Java9 Jigsaw")](https://adopt-openjdk.ci.cloudbees.com/job/TestNG-Jigsaw)
+[![AppVeyor](https://ci.appveyor.com/api/projects/status/github/cbeust/testng?svg=true)](https://ci.appveyor.com/project/cbeust/testng)
+[![Dependency Status](https://www.versioneye.com/user/projects/553a031c4e5d2e9408000059/badge.svg)](https://www.versioneye.com/user/projects/553a031c4e5d2e9408000059)
+[![Reference Status](https://www.versioneye.com/java/org.testng:testng/reference_badge.svg)](https://www.versioneye.com/java/org.testng:testng/references)
+[![Maven Central](https://img.shields.io/maven-central/v/org.testng/testng.svg)](https://maven-badges.herokuapp.com/maven-central/org.testng/testng)
+[![License](https://img.shields.io/github/license/cbeust/testng.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
+[![Sonarqube tech debt](https://img.shields.io/sonar/http/nemo.sonarqube.org/org.testng:testng/tech_debt.svg?label=Sonarqube%20tech%20debt)](http://nemo.sonarqube.org/dashboard/index?id=org.testng:testng)
+
+Documentation available at [TestNG's main web site](http://testng.org).
diff --git a/TESTNG-5.11beta b/TESTNG-5.11beta
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/TESTNG-5.11beta
diff --git a/TODO.txt b/TODO.txt
new file mode 100644
index 0000000..1013ce5
--- /dev/null
+++ b/TODO.txt
@@ -0,0 +1,160 @@
+                                       TODO for TestNG
+
+* Pass the XmlTest in @Before/@After methods
+* Allow a testng.xml file to be passed when -testjar is used
+* Add onStart to IConfigurationListener (create a new interface, actually)
+* Add timeout to @Before/@After
+* Pass parameters from ant
+* Make it possible to specify groups on command line and classes in testng.xml
+(and any combinations thereof:  command line, ant, testng.xml)
+* DataProvider index in testng.xml
+* Create a servlet for remote driving
+* Add time-outs at the testng.xml.  Also:  test and suite time-outs? http://tinyurl.com/kbwxq
+* Add working dir to the ant task
+* Add a servlet so TestNG can be invoked from a web browser
+* Make it possible to add listeners from the Eclipse plug-in
+
+Doc:
+
+* Document the fact that @Test methods with return values are ignored.
+
+===========================================================================
+Older TODO's:
+
+* Show enabled=false methods in the reports, as well as methods
+in groups that were not run
+* Multi-threading for invocationCount and maybe for <test> too
+* Annotation to specify that a method should be called concurrently by n threads 
+(on second thought, we should do that for an entire group)
+* more thread ideas: http://www.theserverside.com/news/thread.tss?thread_id=38922
+* package support for command line and ant
+* Parameters for classes (to be passed as parameters to constructors)
+* testng-dist.zip should contain a top-level directory
+* For dependent methods, if the user is trying to run an incomplete graph
+(A depends on B but B is being excluded from the run), what to do?  Ignore
+the exclusion on B and run it anyway, or abort with an exception explaining
+what's happening?)
+* Make timeOut() work with milliseconds (but keep seconds for backward
+compatibility)
+* Improve the plug-in API so people can add listeners without having to
+modify TestRunner
+* Use factories for the programmatic API.
+* Add dynamic generation of tests
+* Make Javadoc comments over methods appear in the final report
+
+Documentation:
+* IHookable
+* List<IReporter>
+
+
+DONE
+
+* Retry patch
+* If a method with invocationCount fails, don't run the others
+* Allow multiple listeners in ant task
+* Add working dir to the ant task
+* Introduce "test" and "suite" parameters to @Test at the class level to
+avoid having to use testng.xml
+* Remove TestNG stack traces from the report
+* When 0 tests were run, exit with an error http://tinyurl.com/ftng6
+* Show all the extra output for all methods in a single 
+dedicated page
+* report API
+* Show parameters used to invoke a specific test
+* show skipped groups/methods in HTML report
+* beforeTestGroups
+* <testng classfileset> doesn't add to classpath
+* threadPoolSize
+* Parameter logging
+* JavaDoc for org.testng.TestNG
+* org.testng.Reporter in the HTML report (screenshot ready)
+* Document @Parameters only works for @Test (should mention @Factory and @Configuration)
+* Document: <testng classfileset> doesn't add to classpath
+* Document org.testng.Reporter in the HTML report (screenshot ready)
+* Document parameter logging
+* JavaDoc for org.testng.TestNG
+* Document threadPoolSize
+* Make sure it can run several times in the same JVM
+* Implement invocationCount and successPercentage
+* Support multiple testng.xml (TestNG allows it but not the reporters
+yet)
+* The HTML reporter collapses all the suites into one report, we should
+create one HTML report per suite
+* testng-failed.xml should contain the parameters of testng.xml (if any)
+* Create a testng-failed.xml that includes dependent methods
+* Generic reported with compare(ITestResult, ITestResult) for
+easier reporter for "slowest method first" or generate testng-failed.xml
+* Iterator factories
+* configuration methods don't respect inheritance
+- build.xml should issue a clear error message if trying to build with JDK1.4
+- Implement <tasdkdef resource="testnganttasks"> so we can define
+ant tasks for TestNG and JUnitConverter automatically
+- Write documentation to declare ant task in section 3.2.8
+- Documentation for alwaysRun
+- Allow to specify packages or prefix in the <classes> tag:
+   <classes prefix="com.beust.testng"><class name="A"><class name="B"> />
+- New assert classes
+- New ways to launch 
+- JUnitConverter documentation
+- new beforeSuite/afterSuite
+
+* in testng-failures.xml include the beforeSuite/afterSuite methods (very tricky)
+* Provide log.properties configuration (not using log any more)
+* Make @ExpectedExceptions fail if no exception is thrown
+* Make timeOut() work in non-parallel mode (the default mode needs to become
+parallel=true thread-count=1
+* The exception thrown when a test passes with a @ExpectedExceptions is not 
+available via the TestNG API: ITestResult.getThrowable().
+* Add assert API for arrays and collections (undecided yet:  partial asserting)
+* dependsOnMethods
+Allow to specify <groups> at the <suite> level
+Make TestNG run on class files and not just on testng.xml
+Make TestNG run on a jar file that has a testng.xml file in its root or just on all
+  the classes inside that jar file.
+
+Implement parameter passing of tests:  define a property in the properties
+file and pass it to the test method:
+
+@Test(params = { "${fn}", "${ln}" }
+public void testNames(String firstName, String lastName) {
+}
+
+Run groups of groups
+List all tests that will be run, or show methods per group
+HTML generation
+Make test and class methods discoverable
+  JUnit adapter
+Multiple ExpectedException
+Inheritance
+Test listeners
+Group regexps for launching
+
+====
+
+A new comment has been posted on your blog Otaku, Cedric's weblog, on entry
+#149 (The poor shape of Unit Testing).
+http://beust.com/weblog/archives/000149.html
+
+IP Address: 68.72.49.189
+Name: Curt Cox
+Email Address: curtcox@gmail.com
+URL: 
+
+Comments:
+
+For whatever its worth, here are my problems with JUnit.  I've largely developed work-arounds.
+
+1. Interface-based testing is awkward.
+2. Only fast-fail tests are supported.
+3. The design is brittle, poorly documented, and thus hard to extend.
+
+How should interface-based testing be handled in TestNG?  A nice feature would be bundled tests for interfaces in the java* namespace that are automatically applied.  In other words:
+- all classes will fail a (supressible) test if they violate the contract for equals() and hashCode()
+- classes that implement Comparable will fail a test if they fail to implement Comparable
+- and likewise for java.io.Serializable, java.util.Map, java.util.List, etc...
+
+When I say all tests are fast-fail in JUnit, what I mean is that the first failed assertion in a method causes the test to exit.  While this is usually desirable, a more conversational style of tests can sometimes be much easier to read and write.  Such a conversational test doesn't generate a simple failure, but rather a score.  The score could be either x of y passed or x percent passed.  The important part is that the first failure doesn't terminate the test.
+
+As I said, I've largely developed work-arounds for doing these in JUnit, but developing tools for conversational tests that play nice with the various JUnit runners was a real challenge.  The exact contract that Eclipse expects of JUnit tests turns out to be different than what either of the bundled runners or the Ant task expect.  Anyone who considers either the JUnit code or interfaces well-documented has a much different concept of well-documented than I do.
+
+
diff --git a/ant/3rdparty/DTDDoc.jar b/ant/3rdparty/DTDDoc.jar
new file mode 100644
index 0000000..1ac999a
--- /dev/null
+++ b/ant/3rdparty/DTDDoc.jar
Binary files differ
diff --git a/ant/3rdparty/cobertura.jar b/ant/3rdparty/cobertura.jar
new file mode 100644
index 0000000..438fe55
--- /dev/null
+++ b/ant/3rdparty/cobertura.jar
Binary files differ
diff --git a/ant/3rdparty/doclava-1.0.3.jar b/ant/3rdparty/doclava-1.0.3.jar
new file mode 100644
index 0000000..c261657
--- /dev/null
+++ b/ant/3rdparty/doclava-1.0.3.jar
Binary files differ
diff --git a/ant/README.template b/ant/README.template
new file mode 100644
index 0000000..fe25c22
--- /dev/null
+++ b/ant/README.template
@@ -0,0 +1,12 @@
+[![Build Status](https://travis-ci.org/cbeust/testng.svg)](https://travis-ci.org/cbeust/testng)
+
+Welcome to TestNG ${version}
+
+Please note that even though the .zip distribution contains the TestNG sources,
+you will not be able to build the software with them because we decided
+not to include the external jar files in order to keep the size down.
+
+If you want to build TestNG, please sync to the GitHub repository at https://github.com/cbeust/testng.
+
+--
+The TestNG team
diff --git a/ant/build-ant.xml b/ant/build-ant.xml
new file mode 100644
index 0000000..4502117
--- /dev/null
+++ b/ant/build-ant.xml
@@ -0,0 +1,74 @@
+<project name="testng" default="all" basedir=".">
+
+  <property file="build.properties"/>
+  <property name="this.directory" value="${test.dir}" />
+  <property name="test-output" value="${this.directory}/test-output" />
+
+  <taskdef name="testng" classname="org.testng.TestNGAntTask" classpath="${testng.jar}" />
+
+  <target name="all">
+    <echo>This dir: ${this.directory}</echo>
+    <delete failonerror="false" dir="${this.directory}/build" />
+  	<mkdir dir="${this.directory}/build" />
+  	
+  	<javac classpath="${testng.jar}"
+  		  destdir="${this.directory}/build" srcdir="${this.directory}" includes="test/ant/*.java" />
+
+<!--
+    <testng classpath="build;../../testng-5.2beta-jdk15.jar"
+            outputdir="test-output"
+    	      dumpcommand="true">
+        <xmlfileset dir="." includes="testng.xml"/>
+    </testng>
+-->
+	<delete failonerror="false">
+	<fileset dir="${test-output}" includes="**"/></delete>
+  	
+    <testng classpath="${this.directory}/build;${testng.jar}"
+            outputdir="${test-output}"
+  	    parallel="methods"
+  	    threadcount="5"
+    	listeners = "org.testng.reporters.FailedReporter, org.testng.reporters.DotTestListener"
+    	haltonfailure="true"
+    	>
+        <classfileset dir="${this.directory}/build">
+        	<include name="test/ant/NoPackageTest.class" />
+        	<include name="test/ant/MultipleThreadTest.class" />
+        </classfileset>
+    </testng>
+  	
+  	<available file="${test-output}/Ant suite/Ant test.xml" 
+  		type="file" property="test.exists"/>
+  	<fail unless="test.exists" 
+  		message="The appropriately named output should have been created"/>
+  	
+    <testng classpath="${this.directory}/build;${testng.jar}"
+            outputdir="${test-output}"
+	   	listeners = "org.testng.reporters.FailedReporter, org.testng.reporters.DotTestListener"
+    	haltonfailure="true"
+    	suitename="Test Ant Suite"
+    	testname="Test Ant Test"
+    	>
+        <classfileset dir="${this.directory}/build">
+        	<include name="test/ant/DontOverrideSuiteNameTest.class" />
+         </classfileset>
+    </testng>
+  	
+  	<available file="${test-output}/Test Ant Suite/Test Ant Test.xml" 
+  		type="file" property="test2.exists"/>
+  	<fail unless="test2.exists" message="The appropriately named output should have been created"/>
+  	
+  	<!-- Ensure standard tests get run -->
+    <testng classpath="${this.directory}/build;${testng.jar}"
+            outputdir="${test-output}"
+    	listeners = "org.testng.reporters.FailedReporter, org.testng.reporters.DotTestListener"
+    	haltonfailure="true"
+    	>
+        <xmlfileset file="${test.resources.dir}/testng-ant.xml"/>
+    </testng>
+  	<available file="${test-output}/Suitename from xml/TestName.xml" 
+  		type="file" property="test3.exists"/>
+  	<fail unless="test3.exists" message="The appropriately named output should have been created"/>
+
+  </target>
+</project>
diff --git a/ant/build-tests.xml b/ant/build-tests.xml
new file mode 100644
index 0000000..4f51c47
--- /dev/null
+++ b/ant/build-tests.xml
@@ -0,0 +1,187 @@
+<project name="testng" default="all" basedir=".">
+
+  <property file="build.properties"/>
+
+  <property name="report.dir" value="${test.output.dir}"/>
+  <property name="junit.report.dir" value="${report.dir}/test-tmp"/>
+  <property name="testng.report.dir" value="${report.dir}"/>
+
+  <target name="all" depends="prepare,compile,run,reports,done"/>
+
+  <!-- ==================================================================== -->
+  <!-- Compile                                                              -->
+  <!-- ==================================================================== -->
+  <path id="compile.cp">
+    <pathelement location="${testng.jar}" />
+    <fileset dir="${lib.dir}" includes="${guice2.jar}" />
+    <fileset dir="${lib.dir}" includes="${junit.jar}" />
+    <fileset dir="${lib.dir}" includes="aopalliance-1.0.jar" />
+  </path>
+
+  <target name="env:info">
+    <echo>
+BASEDIR          =${basedir}
+TEST.DIR         =${test.dir}
+TEST.BUILD.DIR   =${test.build.dir}
+REPORT.DIR       =${report.dir}
+JUNIT.REPORT.DIR =${junit.report.dir}
+TESTNG.REPORT.DIR=${testng.report.dir}
+    </echo>
+  </target>
+
+  <target name="compile" depends="prepare">
+    <echo message="                                 -- Compiling tests --"/>
+
+    <property name="build.compiler" value="modern"/>
+    <javac debug="true"
+           source="1.7"
+           classpathref="compile.cp"
+           srcdir="${test.dir}"
+           destdir="${test.build.dir}"
+    />
+
+  </target>
+
+  <target name="prepare">
+    <tstamp/>
+    <mkdir dir="${test.build.dir}"/>
+    <mkdir dir="${junit.report.dir}"/>
+    <mkdir dir="${testng.report.dir}"/>
+
+  <taskdef name="testng"
+             classname="org.testng.TestNGAntTask"
+             classpath="${build.dir}"/>
+  </target>
+
+<!--
+  <property name="cobertura.dir" value="../cobertura-1.9.4.1" />
+
+  <path id="cobertura.classpath">
+      <fileset dir="${cobertura.dir}">
+          <include name="cobertura.jar" />
+          <include name="lib/**/*.jar" />
+      </fileset>
+  </path>
+-->
+
+  <!-- ==================================================================== -->
+  <!-- Run                                                             -->
+  <!-- ==================================================================== -->
+
+  <path id="run.cp">
+<!--
+  	<path location="target/instrumented-classes" />
+  	<path refid="cobertura.classpath" />
+-->
+    <path refid="compile.cp"/>
+    <pathelement location="${test.build.dir}"/>
+  </path>
+
+  <target name="run" description="Run tests" depends="compile,copy-resources">
+    <echo message="                                 -- Running tests --"/>
+    <echo message="                                 -- ${testng.jar} --" />
+    <testng classpathref="run.cp"
+            outputdir="${testng.report.dir}">
+      <xmlfileset dir="${test.resources.dir}" includes="testng.xml"/>
+      <jvmarg value="-Dtest.resources.dir=${test.resources.dir}" />
+      <jvmarg value="-Dsun.io.serialization.extendedDebugInfo=true" />
+    </testng>
+  </target>
+
+  <target name="copy-resources" description="Copies resources.">
+    <copy verbose="false"
+          file="${src.resources.dir}/testngtasks"
+          todir="${build.dir}" />
+    <copy todir="${build.dir}">
+      <fileset dir="${src.resources.dir}">
+        <exclude name="**/.*" />
+        <exclude name="**/CVS/*" />
+      </fileset>
+    </copy>
+  </target>
+
+  <target name="run:single" description="Run 1 property file named with the ant property test" depends="compile">
+      <echo message="                                 -- testng-tests-run1 --"/>
+
+    <testng outputdir="${testng.report.dir}"
+        classpathref="run.cp"
+        useDefaultListeners="true"
+        outputDir="${testng.report.dir}">
+      <xmlfileset dir="${test.resources.dir}" includes="testng-single.xml"/>
+     </testng>
+
+      <echo>Report created in    open ${testng.report.dir}/index.html</echo>
+  </target>
+
+  <target name="run:antprop" description="Run a test to see if ant system propertes are passed correctly" depends="compile">
+      <echo message="                                 -- testng-tests-run-antprop --"/>
+      <property name="syspropset1" value="value 1"/>
+      <property name="syspropset2" value="value 2"/>
+      <propertyset id="propset1">
+          <propertyref name="syspropset1"/>
+          <propertyref name="syspropset2"/>
+      </propertyset>
+
+      <testng outputdir="${testng.report.dir}"
+              classpathref="run.cp">
+          <xmlfileset dir="${test.resources.dir}" includes="testng-single3.xml"/>
+          <propertyset refid="propset1"/>
+          <sysproperty key="sysprop1" value="value 3"/>
+      </testng>
+  </target>
+
+  <!-- ==================================================================== -->
+  <!-- Run specific configuration                                           -->
+  <!-- ==================================================================== -->
+  <target name="run:conf"
+          if="testng.conf"
+          depends="clean:reports,compile"
+          description="Run specified tests">
+    <echo message="                                 -- testng-tests-run --"/>
+    <echo message="using: ${testng.conf}.xml"/>
+
+    <testng classpathref="run.cp"
+            outputDir="${testng.report.dir}">
+        <xmlfileset dir="${test.resources.dir}" includes="${testng.conf}.xml"/>
+    </testng>
+
+    <antcall target="clean.tmp"/>
+  </target>
+
+
+  <!-- ==================================================================== -->
+  <!-- Reports                                                              -->
+  <!-- ==================================================================== -->
+
+  <target name="reports">
+    <junitreport todir="${junit.report.dir}">
+      <fileset dir="${testng.report.dir}">
+        <include name="*.xml"/>
+        <exclude name="testng-failed.xml"/>
+        <exclude name="testng-results.xml" />
+      </fileset>
+      <report format="noframes" todir="${junit.report.dir}"/>
+    </junitreport>
+  </target>
+
+
+  <target name="clean.tmp">
+      <delete dir="${test.output.dir}"/>
+  </target>
+
+  <target name="clean:reports">
+      <delete dir="${report.dir}"/>
+      <delete dir="${junit.report.dir}"/>
+  </target>
+
+  <target name="clean" depends="clean.tmp,clean:reports">
+    <echo message="                                 -- test clean --"/>
+
+    <delete dir="${test.build.dir}"/>
+  </target>
+
+  <target name="done">
+    <echo>Reports can be found in:    open ${testng.report.dir}/index.html</echo>
+  </target>
+
+</project>
diff --git a/ant/build.properties b/ant/build.properties
new file mode 100644
index 0000000..f4309d8
--- /dev/null
+++ b/ant/build.properties
@@ -0,0 +1,76 @@
+#
+# TestNG distribution
+#
+testng.basename=testng
+testng.version=6.8.14-SNAPSHOT
+testng.fullname=${testng.basename}-${testng.version}
+
+#
+# Global directories
+#
+root.dir=${basedir}
+build.3rdparty.dir=${root.dir}/build3rdparty
+build.maven-bundle.dir=${build.dir}/maven-bundle
+target=${root.dir}/target
+lib.dir=${basedir}/lib
+lib-supplied.dir=${basedir}/lib-supplied
+src.dir=${basedir}/src/main/java
+src.resources.dir=${basedir}/src/main/resources
+build.dir=${target}/classes
+test.dir=${basedir}/src/test/java
+test.build.dir=${target}/test-classes
+test.output.dir=${target}/test-output
+test.resources.dir=${basedir}/src/test/resources
+example.dir=${basedir}/examples
+
+#
+# Jar files
+#
+beanshell.jar=bsh-2.0b4.jar
+guice2.jar=guice-2.0.jar
+junit.jar=junit-4.10.jar
+ant.jar=ant-1.7.0.jar
+jcommander.jar=jcommander-1.27.jar
+yaml.jar=snakeyaml-1.12.jar
+
+all.jar.files=${beanshell.jar},${junit.jar},${ant.jar},${jcommander.jar},${yaml.jar},${guice2.jar}
+
+#
+# Names of distributions
+#
+testng.jar=${target}/${testng.fullname}.jar
+testng.dist.jar=${testng.fullname}-dist.jar
+sources.testng.jar=${testng.fullname}-sources.jar
+testng.junit.jar=${testng.fullname}-junit.jar
+testng.nobsh.noguice.jar=${testng.fullname}-nobsh-noguice.jar
+testng.bsh.noguice.jar=${testng.fullname}-bsh-noguice.jar
+testng.nobsh.guice.jar=${testng.fullname}-nobsh-guice.jar
+testng.ibiblio.jar=${testng.fullname}-bundle.jar
+testng.zip=${target}/${testng.fullname}.zip
+testng.maven-bundle=${target}/${testng.fullname}-bundle.jar
+testng.javadoc.zip=${testng.fullname}-javadoc.zip
+
+other.jars.dir=${target}/other-jars
+
+#
+# Test locations
+#
+test.location.msg=Test results can be found in\n\
+test/test-output/index.html
+
+#
+# Eclipse plug-in
+#
+testng-eclipse.dir=../testng-eclipse
+
+#
+# Maven plug-in version
+# Note, this version number is appended to the resulting plug-in jar
+#
+testng-maven.version=1.2
+
+#
+# JDK 1.4 for sanity checking before a distribution
+#
+java14.home=jdk-1.5.0_04
+#java14.home=c:/java/jdk-1.4.2_08
diff --git a/ant/build.xml b/ant/build.xml
new file mode 100644
index 0000000..a1e4bba
--- /dev/null
+++ b/ant/build.xml
@@ -0,0 +1,467 @@
+<project name="testng" default="dev" basedir="." xmlns:ivy="antlib:org.apache.ivy.ant">
+  
+  <!-- ====================================================================== -->
+  <!-- TestNG build file                                                      -->
+  <!-- Created cbeust, April 26th, 2004                                       -->
+  <!-- ====================================================================== -->
+
+  <property file="build.properties" />
+  <property name="optimize" value="false" />
+
+  <!-- ====================================================================== -->
+  <!-- PREPARE                                                                -->
+  <!-- ====================================================================== -->
+  <target name="prepare" depends="retrieve-dependencies"
+          description="Performs all preparations required to build.">
+    <tstamp />
+    <mkdir dir="${build.dir}" />
+    <mkdir dir="${other.jars.dir}" />
+  </target>
+
+  <!-- ====================================================================== -->
+  <!-- DUMP                                                                   -->
+  <!-- ====================================================================== -->
+  <target name="dump" description="Dumps all properties." depends="prepare">
+    <echoproperties />
+  </target>
+
+	<!-- ====================================================================== -->
+  <!-- VALIDATE                                                               -->
+  <!-- ====================================================================== -->
+
+  <target name="validate" description="Validates the build environment.">
+    <!-- java greater than 1.6 required to build -->
+    <condition property="requiredJavaVersion">
+      <or>
+        <equals arg1="${ant.java.version}" arg2="1.9" />
+        <equals arg1="${ant.java.version}" arg2="1.8" />
+        <equals arg1="${ant.java.version}" arg2="1.7" />
+      </or>
+    </condition>
+    <fail unless="requiredJavaVersion"
+          message="Java version 1.7 required." />
+    <!-- validate ant version too ... -->
+  </target>
+
+  <!-- ==================================================================== -->
+  <!-- COPY-RESOURCES                                                       -->
+  <!-- ==================================================================== -->
+
+  <target name="copy-resources" description="Copies resources.">
+    <copy verbose="false"
+          file="${src.resources.dir}/testngtasks"
+          todir="${build.dir}" />
+    <copy todir="${build.dir}">
+      <fileset dir="${src.resources.dir}">
+        <exclude name="**/.*" />
+        <exclude name="**/CVS/*" />
+      </fileset>
+    </copy>
+  </target>
+
+
+  <target name="dev"
+          depends="prepare,validate,copy-resources,build,tests" />
+
+  <target name="all"
+          depends="prepare,validate,copy-resources,build,dist,tests,test-ant,javadocs,dist" />
+
+  <target name="build" depends="prepare,compile,testng-jar-all" />
+
+  <target name="testng-jar-all">
+    <antcall target="create-jar">
+      <param name="jar.file" value="${testng.jar}" />
+    </antcall>
+    <jar jarfile="${testng.jar}" update="true">
+      <zipfileset src="${lib.dir}/${beanshell.jar}" />
+      <zipfileset src="${lib.dir}/${jcommander.jar}" />
+      <zipfileset src="${lib.dir}/${yaml.jar}" />
+    </jar>
+
+  </target>
+
+  <target name="single">
+    <ant antfile="build-tests.xml" target="run:single"/>
+  </target>
+
+  <!-- ==================================================================== -->
+  <!-- Compile                                                              -->
+  <!-- ==================================================================== -->
+
+  <path id="compile2.cp">
+    <fileset dir="${lib.dir}" includes="${all.jar.files}" />
+  </path>
+
+  <target name="compile"
+          depends="prepare,copy-resources"
+          description="Compiles sources">
+    <echo message="                                 -- Compiling sources --" />
+
+    <echo>Src:${src.dir}</echo>
+    <javac classpathref="compile2.cp"
+           verbose="false"
+           target="1.7"
+           debug="true"
+           optimize="${optimize}"
+           destdir="${build.dir}">
+      <src path="${src.dir}" />
+    </javac>
+  </target>
+
+  <target name="compile:all" depends="compile" />
+
+  <target name="examples">
+    <ant inheritall="no" antfile="examples/build.xml" />
+  </target>
+
+  <target name="tests" depends="build" description="runs all JDK5 tests with JDK5 distro">
+    <ant inheritall="no" antfile="build-tests.xml" />
+  </target>
+  
+  <target name="test-ant" depends="build">
+    <echo message="                                 -- Testing ant testng task functionality --" />
+    <ant inheritall="no" antfile="build-ant.xml" />
+  </target>
+
+  <!-- ==================================================================== -->
+  <!-- Documentation                                                        -->
+  <!-- ==================================================================== -->
+
+  <target name="javadocs" depends="build,javadocs-current" />
+
+  <target name="doclava">
+    <javadoc
+      docletpath="3rdparty/doclava-1.0.3.jar"
+      bootclasspath="${javahome}/jre/lib/rt.jar"
+      classpath="${testng.jar}:lib/${ant.jar}:lib/${guice2.jar}:lib/aopalliance-1.0.jar"
+      maxmemory="2048M"
+      additionalparam="-quiet"
+      verbose="false"
+      destdir="javadocs"
+      >
+      <fileset dir="${src.dir}" defaultexcludes="yes">
+        <include name="org/testng/*.java" />
+      </fileset>
+      <fileset dir="${src.dir}" defaultexcludes="yes">
+        <include name="org/testng/xml/Xml*.java" />
+      </fileset>
+      <fileset dir="${src.dir}" defaultexcludes="yes">
+        <include name="org/testng/annotations/**" />
+      </fileset>
+      <doclet name="com.google.doclava.Doclava">
+        <param name="-stubs" value="build/stubs" />
+        <param name="-hdf"/>
+        <param name="project.name" value="TestNG" />
+        <!-- versioning -->
+        <param name="-since"/>
+        <param name="doclava/previous.xml"/>
+        <param name="v1" />
+        <param name="-apiversion" value="v2"/>
+        <!-- federation -->
+        <param name="-federate" />
+        <param name="JDK"/>
+        <param name="http://download.oracle.com/javase/6/docs/api/index.html?"/>
+        <param name="-federationxml"/><param name="JDK"/>
+        <param name="http://doclava.googlecode.com/svn/static/api/openjdk-6.xml"/>
+      </doclet>
+    </javadoc>
+  </target>
+
+  <target name="javadocs-current">
+    <javadoc additionalparam="-quiet" destdir="javadocs" source="1.7" windowtitle="TestNG"
+      classpath="${testng.jar}" classpathref="compile2.cp" verbose="false">
+      <fileset dir="${src.dir}" defaultexcludes="yes">
+        <include name="org/testng/*.java" />
+      </fileset>
+      <fileset dir="${src.dir}" defaultexcludes="yes">
+        <include name="org/testng/xml/Xml*.java" />
+      </fileset>
+      <fileset dir="${src.dir}" defaultexcludes="yes">
+        <include name="org/testng/annotations/**" />
+      </fileset>
+    </javadoc>
+  </target>
+
+  <!-- ==================================================================== -->
+  <!-- Distribution                                                         -->
+  <!-- ==================================================================== -->
+
+  <target name="dist" depends="build,all-jar-flavors,dist-all-zip,eclipse" />
+
+  <target name="all-jar-flavors" depends="dist-junit,dist-nobsh-guice,dist-bsh-noguice,dist-nobsh-noguice,dist-testng-dist,dist-testng-javadoc" />
+
+  <target name="dist-junit">
+    <antcall target="create-jar">
+      <param name="jar.file" value="${other.jars.dir}/${testng.junit.jar}" />
+    </antcall>
+    <jar jarfile="${other.jars.dir}/${testng.junit.jar}" update="true">
+      <zipfileset src="${lib.dir}/${beanshell.jar}" />
+      <zipfileset src="${lib.dir}/${jcommander.jar}" />
+      <zipfileset src="${lib.dir}/${junit.jar}" />
+      <zipfileset src="${lib.dir}/${yaml.jar}" />
+    </jar>
+  </target>
+
+  <target name="dist-nobsh-guice">
+    <antcall target="create-jar">
+      <param name="jar.file" value="${other.jars.dir}/${testng.nobsh.guice.jar}" />
+    </antcall>
+    <jar jarfile="${other.jars.dir}/${testng.nobsh.guice.jar}" update="true">
+      <zipfileset src="${lib.dir}/${guice2.jar}" />
+      <zipfileset src="${lib.dir}/${jcommander.jar}" />
+      <zipfileset src="${lib.dir}/${junit.jar}" />
+    </jar>
+  </target>
+
+  <target name="dist-bsh-noguice">
+    <antcall target="create-jar">
+      <param name="jar.file" value="${other.jars.dir}/${testng.bsh.noguice.jar}" />
+    </antcall>
+    <jar jarfile="${other.jars.dir}/${testng.bsh.noguice.jar}" update="true">
+      <zipfileset src="${lib.dir}/${beanshell.jar}" />
+      <zipfileset src="${lib.dir}/${jcommander.jar}" />
+      <zipfileset src="${lib.dir}/${junit.jar}" />
+    </jar>
+  </target>
+
+  <target name="dist-nobsh-noguice">
+    <antcall target="create-jar">
+      <param name="jar.file" value="${other.jars.dir}/${testng.nobsh.noguice.jar}" />
+    </antcall>
+    <jar jarfile="${other.jars.dir}/${testng.bsh.noguice.jar}" update="true">
+      <zipfileset src="${lib.dir}/${jcommander.jar}" />
+      <zipfileset src="${lib.dir}/${junit.jar}" />
+    </jar>
+  </target>
+
+  <target name="dist-testng-dist">
+    <antcall target="create-jar">
+      <param name="jar.file" value="${other.jars.dir}/${testng.dist.jar}" />
+    </antcall>
+    <jar jarfile="${other.jars.dir}/${testng.dist.jar}" update="true">
+      <zipfileset src="${lib.dir}/${jcommander.jar}" />
+    </jar>
+  </target>
+
+  <target name="dist-testng-javadoc" depends="javadocs">
+    <zip destfile="${other.jars.dir}/${testng.javadoc.zip}">
+        <fileset dir="javadocs"/>
+    </zip>
+  </target>
+
+  <target name="create-jar" description="Create a jar file with the Testng classes and nothing else" 
+          depends="compile">
+    <delete file="${jar.file}" />
+    <jar destfile="${jar.file}" >
+      <manifest>
+        <attribute name="Main-Class" value="org.testng.TestNG" />
+        <attribute name="Implementation-Version" value="${testng.version}-${DSTAMP}${TSTAMP}" />
+      </manifest>
+      <fileset dir="${build.dir}" />
+      <fileset file="TESTNG-${testng.version}" />
+    </jar>
+  </target>
+
+  <target name="dist-all-zip" depends="dist-prepare,javadocs">
+    <property name="zip.dir" value="testng-${testng.version}" />
+    <echo>Adding ${testng.fullname}</echo>
+    <zip zipfile="${testng.zip}">
+      <zipfileset prefix="${zip.dir}" dir="${target}">
+        <include name="${testng.fullname}.jar" />
+      </zipfileset>
+      <zipfileset prefix="${zip.dir}" dir="${basedir}" includesfile="FILES" />
+      <zipfileset dir="${other.jars.dir}" prefix="${zip.dir}/other-jars"/>
+      <zipfileset dir="javadocs" prefix="${zip.dir}/javadocs" />
+      <zipfileset dir="src" prefix="${zip.dir}/src" />
+      <zipfileset dir="spring" prefix="${zip.dir}/spring" />
+      <zipfileset dir="doc" prefix="${zip.dir}/doc" />
+      <zipfileset dir="${src.resources.dir}"
+               prefix="${zip.dir}/resources" 
+            includes="**/*.css"/>
+    </zip>
+  </target>
+  
+<!--
+  <target name="dist-maven-bundle" depends="dist-all-zip">
+    <mkdir dir="${build.maven-bundle.dir}" />
+    <copy file="bundle-pom.xml" tofile="${build.maven-bundle.dir}/pom.xml" overwrite="true">
+      <filterchain>
+        <expandproperties/>
+      </filterchain>
+    </copy>
+    <jar destfile="${build.maven-bundle.dir}/${sources.testng.jar}">
+      <fileset dir="${src.dir}" />
+    </jar>
+
+    <exec executable="gpg">
+      <arg value="- -yes" />
+      <arg value="-ab" />
+      <arg value="${testng.jar}" />
+    </exec>
+
+    <exec executable="gpg">
+      <arg value="- -yes" />
+      <arg value="-ab" />
+      <arg value="${build.maven-bundle.dir}/pom.xml" />
+    </exec>
+
+    <exec executable="gpg">
+      <arg value="- -yes" />
+      <arg value="-ab" />
+      <arg value="${build.maven-bundle.dir}/${sources.testng.jar}" />
+    </exec>
+
+    <jar destfile="${testng.maven-bundle}">
+      <fileset file="${build.maven-bundle.dir}/pom.xml" />
+      <fileset file="${build.maven-bundle.dir}/${sources.testng.jar}" />
+      <fileset file="${build.maven-bundle.dir}/*asc" />
+      <fileset file="${testng.jar}" />
+      <fileset file="${testng.jar}.asc" />
+    </jar>
+  </target>
+-->
+
+  <target name="dist-prepare" depends="update-readme">
+    <delete file="{other.jars.dir}/${testng.nobsh.jar}" />
+    <mkdir dir="${other.jars.dir}" />
+  </target>
+
+  <target name="update-readme">
+    <copy file="README.template" tofile="README.md" />
+    <replace file="README.md" token="${version}" value="${testng.version}" />
+    <touch file="TESTNG-${testng.version}" />
+  </target>
+      
+  <target name="eclipse" description="Used by Cedric to build distributions">
+    <property name="jdk15.jar"
+              value="${testng-eclipse.dir}/lib/testng.jar" />
+
+    <delete file="${jdk15.jar}" />
+    <copy file="${testng.jar}" tofile="${jdk15.jar}" />
+
+    <!-- sources -->
+    <property name="sources" value="${testng-eclipse.dir}/lib/testng-sources.jar"/>
+    <echo>Sources: ${sources}</echo>
+    <jar destfile="${sources}" basedir="src/main/java/" />
+  </target>
+
+  <target name="ftp">
+    <!--
+          <ftp action="put"
+             server="beust.com"
+             remotedir="w/testng/test-output"
+             userid="${userid}"
+             password="${password}">
+              <fileset dir="c:\weblogic\dev\sandbox\cbeust\testng\test\test-output">
+                <include name="index.html" />
+                <include name="main.html" />
+                <include name="toc.html" />
+                <include name="*egression*" />
+             </fileset>
+        </ftp>
+      -->
+    <ftp action="put"
+         server="beust.com"
+         remotedir="w/testng"
+         userid="${userid}"
+         password="${password}">
+      <fileset dir="${root.dir}">
+        <include name="${testng.zip}" />
+        <include name="javadocs/**" />
+        <include name="testng-1.0.dtd" />
+      </fileset>
+      <fileset dir="${root.dir}/doc">
+        <include name="*.html" />
+      </fileset>
+    </ftp>
+
+  </target>
+
+  <target name="ftp2">
+    <ftp action="put"
+         server="beust.com"
+         remotedir="w/testng"
+         userid="${userid}"
+         password="${password}">
+      <fileset dir="${root.dir}/test">
+        <include name="test-output/*" />
+      </fileset>
+      <fileset dir="${root.dir}/test">
+        <include name="test-report/*" />
+      </fileset>
+    </ftp>
+  </target>
+
+  <target name="clean">
+    <delete dir="${build.dir}" />
+    <delete dir="${target}" />
+    <delete dir="${build.maven-bundle.dir}" />
+    <delete failonerror="false">
+      <fileset dir="." includes="testng*.jar,*.zip" />
+    </delete>
+    <delete dir="test-output" failonerror="false" />
+    <delete dir="test-output-tests" failonerror="false" />
+    <ant antfile="build-tests.xml" target="clean" />
+    <ant dir="examples" target="clean" />
+  </target>
+
+  <!-- ==================================================================== -->
+  <!-- Maven1/2 Distribution                                                -->
+  <!-- ==================================================================== -->
+
+<!--
+  <target name="maven-plugin">
+    <jar destfile="maven-testng-plugin-${testng-maven.version}.jar"
+         basedir="./maven/" />
+  </target>
+-->
+
+  <!-- ==================================================================== -->
+  <!-- Ivy                                                                  -->
+  <!-- ==================================================================== -->
+
+  <target name="retrieve-dependencies" description="Retrieve dependencies with ivy">
+    <ivy:retrieve log="quiet" />
+  </target>
+
+  <target name="publish">
+    <ivy:resolve />
+    <mkdir dir="dist/jars" />
+    <copy file="${testng.jar}" todir="dist/jars" />
+    <ivy:publish organisation="org.testng" resolver="local" overwrite="true"/>
+  </target>
+
+  <target name="pom">
+    <ivy:makepom ivyfile="ivy.xml" pomfile="ivy-pom.xml" />
+  </target>
+
+  <!-- ==================================================================== -->
+  <!-- Code coverage                                                        -->
+  <!-- ==================================================================== -->
+
+<!--
+  <property name="cobertura.dir" value="../cobertura-1.9.4.1" />
+
+  <path id="cobertura.classpath">
+      <fileset dir="${cobertura.dir}">
+          <include name="cobertura.jar" />
+          <include name="lib/**/*.jar" />
+      </fileset>
+  </path>
+
+  <taskdef classpathref="cobertura.classpath" resource="tasks.properties" />
+
+  <target name="coverage:create">
+    <cobertura-instrument classpath="cobertura-1.9.4.1/lib/*" todir="target/instrumented-classes">
+      <fileset dir="${build.dir}">
+        <include name="**/*.class" />
+      </fileset>
+    </cobertura-instrument>
+  </target>
+
+  <target name="coverage">
+    <cobertura-report srcdir="${src.dir}" destdir="target/coverage-report"/>
+  </target>
+-->
+
+</project>
diff --git a/ant/ivy-2.1.0.jar b/ant/ivy-2.1.0.jar
new file mode 100644
index 0000000..3902b6f
--- /dev/null
+++ b/ant/ivy-2.1.0.jar
Binary files differ
diff --git a/ant/ivy.xml b/ant/ivy.xml
new file mode 100644
index 0000000..c6a5a97
--- /dev/null
+++ b/ant/ivy.xml
@@ -0,0 +1,13 @@
+<ivy-module version="2.0">
+  <info organisation="org.testng" module="testng" revision="6.5beta"/>
+
+  <dependencies>
+    <dependency org="org.apache.ant" name="ant" rev="1.7.0" />
+    <dependency org="junit" name="junit" rev="4.10" />
+    <dependency org="org.beanshell" name="bsh" rev="2.0b4" />
+    <dependency org="com.google.inject" name="guice" rev="2.0" />
+    <dependency org="org.yaml" name="snakeyaml" rev="1.15" />
+    <dependency org="com.beust" name="jcommander" rev="1.48" />
+  </dependencies>
+</ivy-module>
+
diff --git a/ant/old/build-dogfood.xml b/ant/old/build-dogfood.xml
new file mode 100644
index 0000000..0e39867
--- /dev/null
+++ b/ant/old/build-dogfood.xml
@@ -0,0 +1,30 @@
+<project name="testng" default="run" basedir=".">
+	<property name="testng.home" value="${basedir}/../" />
+    <property name="lib.dir" value="${testng.home}/3rdparty"/>
+ 	<property file="../build.properties"/>
+
+	<path id="run.cp">
+		<pathelement location="../z_build" />
+		<fileset dir="${lib.dir}">

+			<include name="junit.jar"/>

+			<include name="${beanshell.jar}"/>

+			<include name="${qdox.jar}"/>

+		</fileset>

+ 		<pathelement location="${java.home}/../lib/tools.jar"/>
+	</path>
+
+	<target name="run" description="Run tests" >
+		<echo>Defining task from ${basedir}/../${jdk15.testng.jar}</echo>
+		<taskdef name="testng"
+              classname="org.testng.TestNGAntTask"
+              classpath="${basedir}/../${jdk15.testng.jar}" />
+
+		<testng classpathref="run.cp"
+        dumpcommand="true" verbose="9" outputdir="test-outputs" haltonfailure="true">
+			<xmlfileset dir="${basedir}">
+				<include name="testng-single.xml" />
+			</xmlfileset>
+		</testng>
+	</target>
+
+</project>
diff --git a/ant/old/build-sample.xml b/ant/old/build-sample.xml
new file mode 100644
index 0000000..3ffea58
--- /dev/null
+++ b/ant/old/build-sample.xml
@@ -0,0 +1,30 @@
+<project name="testng" default="run" basedir=".">
+
+	<property name="testng.home" value="c:/java/testng" />
+    <property name="lib.dir" value="${testng.home}/lib"/>
+    <property name="testng.jar" value="${testng.home}/testng-2.5-jdk15.jar" />
+
+	<path id="run.cp">
+		<!--
+      <path refid="compile.cp"/>
+-->
+		<pathelement location="build" />
+		<pathelement location="${lib.dir}/qdox-1.5.jar"/>
+		<pathelement location="${java.home}/../lib/tools.jar"/>
+	</path>
+
+	<target name="run" description="Run tests" >
+		<echo>Defining task from ${testng.jar}</echo>
+		<taskdef name="testng"
+              classname="org.testng.TestNGAntTask"
+              classpath="${testng.jar}" />
+
+		<testng classpathref="run.cp"
+        dumpcommand="true" verbose="9" outputdir="test-outputs">
+			<classfileset dir="build">
+				<include name="**/justin/*Test*.class" />
+			</classfileset>
+		</testng>
+	</target>
+
+</project>
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..3212f2d
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,25 @@
+version: "{branch} {build}"
+skip_tags: true
+clone_depth: 10
+
+build:
+  verbosity: detailed
+
+build_script:
+  - gradlew.bat assemble --info --no-daemon
+
+test_script:
+  - gradlew.bat test --info --no-daemon
+
+environment:
+  matrix:
+  - JAVA_HOME: C:\Program Files\Java\jdk1.7.0
+  - JAVA_HOME: C:\Program Files\Java\jdk1.8.0
+  - JAVA_HOME: C:\Program Files (x86)\Java\jdk1.7.0
+  - JAVA_HOME: C:\Program Files (x86)\Java\jdk1.8.0
+
+matrix:
+  fast_finish: true
+
+cache:
+  - C:\Users\appveyor\.gradle
\ No newline at end of file
diff --git a/bin/junitconverter.bat b/bin/junitconverter.bat
new file mode 100644
index 0000000..0abcd0b
--- /dev/null
+++ b/bin/junitconverter.bat
@@ -0,0 +1,6 @@
+set ROOT=c:\java\TestNG

+set JAR=%ROOT%\testng-4.5-jdk15.jar;%ROOT%\test\build

+	rem set JAR=%ROOT%\testng-4.5-jdk14.jar;%ROOT%\test-14\build

+

+java -ea -classpath %ROOT%\3rdparty\junit.jar;%JAVA_HOME%\lib\tools.jar;%JAR%;%CLASSPATH% org.testng.JUnitConverter -restore -overwrite -annotation -srcdir src

+

diff --git a/bin/master.bat b/bin/master.bat
new file mode 100644
index 0000000..a4040d8
--- /dev/null
+++ b/bin/master.bat
@@ -0,0 +1 @@
+testng -hostfile test\hosts.properties -d test\test-output %1 %2 %3 %4 %5 test\testng.xml
\ No newline at end of file
diff --git a/bin/run-tests.sh b/bin/run-tests.sh
new file mode 100755
index 0000000..32792e1
--- /dev/null
+++ b/bin/run-tests.sh
@@ -0,0 +1,14 @@
+ROOT=~/java/testng
+VERSION=5.0
+JAR14=$ROOT/testng-$VERSION-jdk14.jar
+JAR15=$ROOT/testng-$VERSION-jdk15.jar
+
+java -ea -classpath test/build:$ROOT/3rdparty/junit.jar:$JAVA_HOME/lib/tools.jar:$JAR15:$CLASSPATH org.testng.TestNG test/testng.xml
+
+java -ea -classpath test/v4/build:$ROOT/3rdparty/junit.jar:$JAVA_HOME/lib/tools.jar:$JAR15:$CLASSPATH org.testng.TestNG test/v4/testng.xml
+
+java -ea -classpath test-14/build:$ROOT/3rdparty/junit.jar:$JAVA_HOME/lib/tools.jar:$JAR14:$CLASSPATH org.testng.TestNG -sourcedir test-14/src test-14/testng.xml
+
+java -ea -classpath test-14/v4/build:$ROOT/3rdparty/junit.jar:$JAVA_HOME/lib/tools.jar:$JAR14:$CLASSPATH org.testng.TestNG -sourcedir test-14/v4/src test-14/v4/testng.xml
+
+
diff --git a/bin/slave.bat b/bin/slave.bat
new file mode 100644
index 0000000..24d2fb1
--- /dev/null
+++ b/bin/slave.bat
@@ -0,0 +1 @@
+testng -d client-output -slave %1 %2 %3

diff --git a/bin/testng.bat b/bin/testng.bat
new file mode 100644
index 0000000..8ea1edf
--- /dev/null
+++ b/bin/testng.bat
@@ -0,0 +1,5 @@
+set ROOT=c:\java\TestNG

+set JAR=%ROOT%\testng-5.2beta-jdk15.jar;%ROOT%\test\build

+	rem set JAR=%ROOT%\testng-4.5-jdk14.jar;%ROOT%\test-14\build

+

+java -ea -classpath %ROOT%\3rdparty\junit.jar;%JAVA_HOME%\lib\tools.jar;%JAR%;%CLASSPATH% org.testng.TestNG %1 %2 %3 %4 %5 %6 %7 %8 %9

diff --git a/bin/testng.sh b/bin/testng.sh
new file mode 100755
index 0000000..af8bbb9
--- /dev/null
+++ b/bin/testng.sh
@@ -0,0 +1,6 @@
+ROOT=~/java/testng
+VERSION=5.0
+JAR14=$ROOT/testng-$VERSION-jdk14.jar
+JAR15=$ROOT/testng-$VERSION-jdk15.jar
+
+java -ea -classpath $ROOT/test/build:$ROOT/3rdparty/junit.jar:$JAVA_HOME/lib/tools.jar:$JAR15:$CLASSPATH org.testng.TestNG $*
diff --git a/build-with-gradle b/build-with-gradle
new file mode 100755
index 0000000..44e73f2
--- /dev/null
+++ b/build-with-gradle
@@ -0,0 +1 @@
+./gradlew --daemon --stacktrace clean build test
diff --git a/build-with-maven b/build-with-maven
new file mode 100755
index 0000000..a6c135a
--- /dev/null
+++ b/build-with-maven
@@ -0,0 +1,25 @@
+# Two different POMs are needed to build TestNG with Maven because it's not
+# allowed to have circular dependencies.
+# - pom.xml defines the project version "n-SNAPSHOT", builds, jars and deploys (but doesn't
+# run the tests).
+# - pom-test.xml declares a test dependency on "n-SNAPSHOT", which it will find
+# in the local repository (~/.m2/repository). All it does then is run the tests.
+
+
+mvn clean install -Dgpg.skip=true
+#or if you want to sign the jar, uncomment this:
+#mvn clean install
+
+mvn -f pom-test.xml test
+
+
+echo
+echo "To run the tests: mvn -f pom-test.xml test"
+echo "To deploy to the snapshot repository: mvn deploy"
+echo "To deploy to the release directory: mvn release:clean release:prepare release:perform"
+echo "Nexus UI:  https://oss.sonatype.org/index.html"
+echo "Wiki: https://docs.sonatype.org/display/Repository/Sonatype+OSS+Maven+Repository+Usage+Guide"
+
+# deploy without tagging: mvn deploy -DperformRelease
+
+
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..cf01873
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,177 @@
+buildscript {
+
+    def a_user = hasProperty('artifactory_user') ? artifactory_user : System.getenv('artifactory_user')
+    def a_password = hasProperty('artifactory_password') ? artifactory_password : System.getenv('artifactory_password')
+
+    repositories {
+        mavenCentral()
+        jcenter()
+        maven {
+            url 'http://dl.bintray.com/cbeust/maven'
+        }
+        maven {
+            url 'http://oss.jfrog.org/artifactory/plugins-release'
+            credentials {
+                username = "${a_user}"
+                password = "${a_password}"
+            }
+        }
+    }
+
+    dependencies {
+        //Check for the latest version here: http://plugins.gradle.org/plugin/com.jfrog.artifactory
+        classpath "org.jfrog.buildinfo:build-info-extractor-gradle:3.0.3"
+        classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.5.3"
+        classpath "com.netflix.nebula:gradle-extra-configurations-plugin:3.0.3"
+    }
+}
+
+plugins {
+    id "com.jfrog.bintray" version "1.2"
+    id "com.jfrog.artifactory" version "3.1.1"
+    id "org.sonarqube" version "1.0"
+}
+
+version = '6.9.10-SNAPSHOT'
+
+apply plugin: 'java'
+apply plugin: 'nebula.optional-base'
+apply plugin: 'nebula.provided-base'
+
+targetCompatibility = "1.7"
+sourceCompatibility = "1.7"
+
+apply plugin: 'osgi'
+
+repositories {
+    mavenCentral()
+    jcenter()
+    maven {
+        url 'http://dl.bintray.com/cbeust/maven'
+    }
+}
+
+dependencies {
+    compile 'org.beanshell:bsh:2.0b4'
+    compile 'com.beust:jcommander:1.48'
+
+    compile 'org.apache.ant:ant:1.7.0', optional
+    compile 'junit:junit:4.10', optional
+    compile 'org.yaml:snakeyaml:1.15', optional
+
+    provided 'com.google.inject:guice:4.0:no_aop'
+
+    testCompile 'org.assertj:assertj-core:2.0.0'
+    testCompile 'org.testng:testng:6.9.4'
+}
+
+task sourceJar(type: Jar) {
+    group 'Build'
+    description 'An archive of the source code'
+    classifier 'sources'
+    from sourceSets.main.allSource
+}
+
+artifacts {
+    sourceJar
+}
+
+import org.apache.tools.ant.filters.ReplaceTokens
+
+def generatedSourcesFolder = projectDir.toString() + '/src/generated/java'
+
+def dirFrom = projectDir.toString() + '/src/main/resources/org/testng/internal'
+def dirTo = generatedSourcesFolder + "/org/testng/internal"
+def fileFrom = 'VersionTemplateJava'
+def fileTo = 'Version.java'
+
+task removeVersion {
+    delete dirTo + fileTo
+}
+
+sourceSets {
+    generated {
+        java {
+            srcDir 'src/generated/java'
+        }
+        resources {
+            srcDir 'src/generated/resources'
+        }
+    }
+}
+
+sourceSets {
+    main {
+        compileClasspath += generated.output
+        runtimeClasspath += generated.output
+    }
+}
+
+gradle.projectsEvaluated {
+    compileJava.dependsOn(myDir)
+}
+
+task myDir {
+    delete dirTo + "/" + fileTo
+    mkdir(dirTo)
+}
+
+// Include the generated Version.class in the jar
+jar {
+    manifest {
+        instruction 'Bundle-License', 'http://apache.org/licenses/LICENSE-2.0'
+        instruction 'Bundle-Description', 'TestNG is a testing framework.'
+        instruction 'Import-Package',
+            'bsh.*;version="[2.0.0,3.0.0)";resolution:=optional',
+            'com.beust.jcommander.*;version="[1.7.0,3.0.0)";resolution:=optional',
+            'com.google.inject.*;version="[1.2,1.3)";resolution:=optional',
+            'junit.framework;version="[3.8.1, 5.0.0)";resolution:=optional',
+            'org.junit.*;resolution:=optional',
+            'org.apache.tools.ant.*;version="[1.7.0, 2.0.0)";resolution:=optional',
+            'org.yaml.*;version="[1.6,2.0)";resolution:=optional',
+            '!com.beust.testng',
+            '!org.testng.*',
+            '!com.sun.*',
+            '*'
+    }
+    from "$buildDir/classes/generated"
+}
+
+task createVersion(type: Copy, dependsOn: myDir) {
+    println("Creating Version file: ${version} in ${dirTo}")
+    from dirFrom
+    include fileFrom
+    into(dirTo)
+    rename(fileFrom, fileTo)
+    filter(ReplaceTokens, tokens: [version: version])
+}
+
+compileJava.dependsOn(createVersion)
+
+test {
+    useTestNG() {
+        suites 'src/test/resources/testng.xml'
+    }
+//    testLogging.showStandardStreams = true
+    systemProperties = System.getProperties()
+    systemProperties['test.resources.dir'] = 'build/resources/test/'
+}
+
+if (JavaVersion.current().isJava8Compatible()) {
+    allprojects {
+        tasks.withType(Javadoc) {
+            options.addStringOption('Xdoclint:none', '-quiet')
+        }
+    }
+}
+
+sonarqube {
+    properties {
+        property "sonar.host.url", "http://nemo.sonarqube.org"
+        property "sonar.analysis.mode", "preview"
+        property "sonar.github.repository", "cbeust/testng"
+        property "sonar.github.login", "testng-bot"
+    }
+}
+
+apply from: 'gradle/publishing.gradle'
diff --git a/bundle-pom.xml b/bundle-pom.xml
new file mode 100644
index 0000000..c969d80
--- /dev/null
+++ b/bundle-pom.xml
@@ -0,0 +1,59 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <!--
+    This POM cannot be used to build TestNG; it should only be used as part of a Maven
+    repository upload bundle.
+    
+    See the guide to creating a bundle here:
+    http://maven.apache.org/guides/mini/guide-central-repository-upload.html
+    -->
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.testng</groupId>
+    <artifactId>testng</artifactId>
+    <name>TestNG</name>
+    <version>5.12.1</version>
+    <description>TestNG is a testing framework inspired from JUnit and NUnit but introducing some new functionalities that make it more powerful and easier to use.</description>
+    <url>http://testng.org</url>
+    
+    <licenses>
+        <license>
+            <name>Apache License, Version 2.0</name>
+            <url>http://apache.org/licenses/LICENSE-2.0</url>
+        </license>
+    </licenses>
+
+    <scm>
+        <connection>scm:svn:http://testng.googlecode.com/svn/trunk/</connection>
+        <developerConnection>scm:svn:http://testng.googlecode.com/svn/trunk/</developerConnection>
+        <url>http://testng.googlecode.com/svn/trunk</url>
+    </scm>
+    
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.ant</groupId>
+            <artifactId>ant</artifactId>
+            <version>1.7.0</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.10</version>
+        </dependency>
+        <dependency>
+            <groupId>org.beanshell</groupId>
+            <artifactId>bsh</artifactId>
+            <version>2.0b4</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+          <groupId>com.google.inject</groupId>
+           <artifactId>guice</artifactId>
+           <version>2.0</version>
+           <scope>provided</scope>
+        </dependency>
+
+    </dependencies>    
+    
+</project>
diff --git a/deploy-to-maven b/deploy-to-maven
new file mode 100755
index 0000000..bff3a8f
--- /dev/null
+++ b/deploy-to-maven
@@ -0,0 +1,14 @@
+if [ -z "$SONATYPE_USER" ]
+then
+  echo "Please define SONATYPE_USER"
+  exit 1
+fi
+
+if [ -z "$SONATYPE_PASSWORD" ]
+then
+  echo "Please define SONATYPE_PASSWORD"
+  exit 1
+fi
+
+./gradlew uploadArchives && ./gradlew closeAndPromoteRepository
+
diff --git a/doc/ant.html b/doc/ant.html
new file mode 100644
index 0000000..a919ea2
--- /dev/null
+++ b/doc/ant.html
@@ -0,0 +1,397 @@
+<html>

+    <head>

+        <title>TestNG - Ant</title>

+

+        <link rel="stylesheet" href="testng.css" type="text/css" />

+        <link type="text/css" rel="stylesheet" href="http://beust.com/beust.css"  />

+        <script type="text/javascript" src="http://beust.com/prettify.js"></script>

+        <script type="text/javascript" src="banner.js"></script>

+

+        <script type="text/javascript" src="http://beust.com/scripts/shCore.js"></script>

+        <script type="text/javascript" src="http://beust.com/scripts/shBrushJava.js"></script>

+        <script type="text/javascript" src="http://beust.com/scripts/shBrushXml.js"></script>

+        <script type="text/javascript" src="http://beust.com/scripts/shBrushBash.js"></script>

+        <script type="text/javascript" src="http://beust.com/scripts/shBrushPlain.js"></script>

+        <link type="text/css" rel="stylesheet" href="http://beust.com/styles/shCore.css"/>

+        <link type="text/css" rel="stylesheet" href="http://beust.com/styles/shThemeCedric.css"/>

+        <script type="text/javascript">

+          SyntaxHighlighter.config.clipboardSwf = 'scripts/clipboard.swf';

+          SyntaxHighlighter.defaults['gutter'] = false;

+          SyntaxHighlighter.all();

+        </script>

+

+</head>

+

+<body onLoad="prettyPrint()">

+

+<script type="text/javascript">

+    displayMenu("ant.html")

+</script>

+

+<h2 align="center">TestNG Ant Task</h2>

+<p>You define the TestNG ant task as follows:</p>

+

+<pre class="brush: xml">

+&lt;taskdef resource="testngtasks" classpath="testng.jar"/&gt;

+</pre>

+

+This task runs TestNG tests and is always run in a forked JVM.&nbsp; It 

+accepts the following attributes: <br>

+&nbsp;<table border="2" id="table1">

+	<tr>

+		<th>Attribute </th>

+		<th>Description </th>

+		<th>Required </th>

+	</tr>

+	<tr>

+		<td><tt>classfilesetref</tt> </td>

+		<td>A reference to a

+		<a href="http://ant.apache.org/manual/Types/resources.html#collection">ResourceCollection</a>

+		containing the test classes to be run. Only File based

+                <a href="http://ant.apache.org/manual/Types/resources.html#collection">ResourceCollection</a>s

+                are supported (ie. <a href="http://ant.apache.org/manual/Types/fileset.html">FileSet</a>).</td>

+		<td>&nbsp; </td>

+	</tr>

+	<tr>

+		<td><tt>classpath</tt> </td>

+		<td>A PATH-like structure for the tests to be run. </td>

+		<td>&nbsp; </td>

+	</tr>

+	<tr>

+		<td><tt>classpathref</tt> </td>

+		<td>A reference to a PATH-like structure for the tests to be run. </td>

+		<td>&nbsp; </td>

+	</tr>

+	<tr>

+		<td><tt>configFailurePolicy</tt> </td>

+		<td>Whether TestNG should <tt>continue</tt> to execute the remaining tests in the suite or <tt>skip</tt> them if an @Before* method fails.</td>

+		<td>No. Defaults to <tt>skip</tt></td>

+	</tr>

+	<tr>

+		<td><tt>dataProviderThreadCount</tt> </td>

+		<td>The number of threads to use for data providers

+		for this run. Ignored unless the parallel mode is also specified</td>

+		<td>1</td>

+	</tr>

+	<tr>

+		<td><tt>delegateCommandSystemProperties</tt> </td>

+		<td>Pass the command line properties as system properties.</td>

+		<td>No. Defaults to false </td>

+	</tr>

+	<tr>

+		<td><tt>dumpCommand</tt> </td>

+		<td>Print the TestNG launcher command. </td>

+		<td>No. Defaults to false </td>

+	</tr>

+	<tr>

+		<td><tt>failureProperty</tt> </td>

+		<td>The name of a property to set in the event of a failure. It is used 

+		only if the <tt>haltonfailure</tt> is not set. </td>

+		<td>No.</td>

+	</tr>

+	<tr>

+		<td><tt>haltonfailure</tt> </td>

+		<td>Stop the build process if a failure has occurred during the test 

+		run. </td>

+		<td>No. Defaults to false 

+		</td>

+		<td>&nbsp;</td>

+	</tr>

+	<tr>

+		<td><tt>haltonskipped</tt> </td>

+		<td>Stop the build process if there is at least on skipped test. </td>

+		<td>No. Default to false </td>

+	</tr>

+	<tr>

+	  <td><tt>groups</tt></td>

+	  <td>

+   	The list of groups to run, separated by spaces or commas.</td>

+	  <td>

+   	</td>

+	</tr>

+	<tr>

+	  <td><tt>excludedgroups</tt></td>

+	  <td>

+   	The list of groups to exclude, separated by spaces or commas</td>

+	  <td>

+   	</td>

+	</tr>

+	<tr>

+		<td><tt>jvm</tt></td>

+		<td>The JVM to use, which will be run by <tt>Runtime.exec()</tt></td>

+		<td><tt>java</tt></td>

+	</tr>

+	<tr>

+		<td><tt>listeners</tt></td>

+		<td>A comma or space-separated list of fully qualified classes that are TestNG listeners (for example<tt>

+		<a href="http://testng.org/javadocs/org/testng/ITestListener.html">

+		org.testng.ITestListener</a></tt> or <tt>

+		<a href="http://testng.org/javadocs/org/testng/IReporter.html">

+		org.testng.IReporter</a>)</tt></td>

+		<td>No.</td>

+	</tr>

+

+        <tr>

+		<td><tt>methods</tt></td>

+		<td>A comma separated list of fully qualified class name and method. For example <tt>com.example.Foo.f1,com.example.Bar.f2</tt>.</td>

+		<td>No.</td>

+        </tr>

+

+        <tr>

+		<td><tt>mode</tt></td>

+                <td>Either <tt>"testng"</tt>, <tt>"junit"</tt> or <tt>"mixed"</tt>. Whether TestNG should run only TestNG tests, JUnit tests or both.</td>

+		<td>No. Defaults to "testng".</td>

+        </tr>

+

+	<tr>

+		<td><tt>outputdir</tt> </td>

+		<td>Directory for reports output. 

+		</td>

+		<td>No. Defaults to <tt>test-output</tt>. </td>

+	</tr>

+	<tr>

+		<td><tt>skippedProperty</tt> </td>

+		<td>The name of a property to set in the event of a skipped test. It is 

+		used only if the <tt>haltonskipped</tt> is not set. </td>

+		<td>No. </td>

+	</tr>

+	<tr>

+		<td><tt>suiteRunnerClass</tt> </td>

+		<td>A fully qualified name of a TestNG starter. </td>

+		<td>

+		<p align="left">No.&nbsp; Defaults to <tt>

+		<a href="http://testng.org/javadocs/org/testng/TestNG.html">org.testng.TestNG</a> </tt></td>

+	</tr>

+	

+	<tr>

+		<td><tt>suiteThreadPoolSize</tt> </td>

+		<td>The size of a thread pool to run suites.</td>

+		<td>

+		<p align="left">No.&nbsp; Defaults to 1.</td>

+	</tr>

+

+	<tr>

+		<td><tt>parallel</tt> </td>

+		<td>The parallel mode to use for running the tests - either methods or tests</td>

+		<td>No - if not present, parallel mode will not be selected</td>

+	</tr>

+

+	<tr>

+		<td><tt>suitename</tt> </td>

+		<td>Sets the default name of the test suite, if one is not specified in a suite xml file or in the source code</td>

+		<td>No. Defaults to "Ant suite"</td>

+	</tr>

+

+	<tr>

+		<td><tt>testJar</tt> </td>

+		<td>Path to a jar containing tests and a suite definition. </td>

+		<td>&nbsp;</td>

+	</tr>

+

+	<tr>

+		<td><tt>testname</tt> </td>

+		<td>Sets the default name of the test, if one is not specified in a suite xml file or in the source code</td>

+		<td>No. defaults to "Ant test"</td>

+	</tr>

+

+	<tr>

+		<td><tt>testnames</tt> </td>

+		<td>A comma separated list of test names, as defined

+		in the &lt;test&gt; tag. Only these tests will be run.</td>

+		<td>No. defaults to "Ant test"</td>

+	</tr>

+

+	<tr>

+		<td><tt>threadCount</tt> </td>

+		<td>The number of threads to use for this run. Ignored unless the parallel mode is also specified</td>

+		<td>1</td>

+	</tr>

+

+	<tr>

+		<td><tt>timeOut</tt></td>

+		<td>The maximum time out in milliseconds that all the tests should run 

+		under.</td>

+		<td>&nbsp;</td>

+	</tr>

+

+	<tr>

+		<td><tt>useDefaultListeners</tt></td>

+		<td>Whether the default listeners and reporters should be used.</td>

+		<td>Defaults to true.</td>

+	</tr>

+	<tr>

+		<td><tt>workingDir</tt></td>

+		<td>The directory where the ant task should change to before running 

+		TestNG.</td>

+		<td>&nbsp;</td>

+	</tr>

+	<tr>

+		<td><tt>xmlfilesetref</tt> </td>

+		<td>A reference to a

+		<a href="http://ant.apache.org/manual/Types/resources.html#collection">ResourceCollection</a>

+		containing the suite definitions to be run. Only File based

+                <a href="http://ant.apache.org/manual/Types/resources.html#collection">ResourceCollection</a>s

+                are supported (ie. <a href="http://ant.apache.org/manual/Types/fileset.html">FileSet</a>).</td>

+		<td>&nbsp; </td>

+	</tr>

+

+	<tr>

+		<td><tt>xmlPathInJar</tt></td>

+		<td>The path of the XML file inside the jar file, only applicable if <tt>testJar</tt> was specified</td>

+		<td>testng.xml</td>

+	</tr>

+</table>

+<br>

+One of attributes <tt>classpath</tt>, <tt>classpathref</tt> or nested <tt>

+&lt;classpath&gt;</tt> must be used for providing the tests classpath. 

+<p>One of the attributes <tt>xmlfilesetref</tt>, <tt>classfilesetref</tt> or 

+nested <tt>&lt;xmlfileset&gt;</tt>, respectively <tt>&lt;classfileset&gt;</tt> must be used 

+for providing the tests. </p>

+

+<h3>TestNG modes</h3>

+<p>The TestNG mode gets applied when tests are passed to TestNG using <tt>classfilesetref</tt>, <tt>methods</tt>

+or nested <tt>&lt;classfileset&gt;</tt> and tells TestNG what kind of

+tests it should look for and run:

+

+<ul>

+  <li><tt>"testng"</tt>: find and run TestNG tests.

+  <li><tt>"junit"</tt>: find and run JUnit tests.

+  <li><tt>"mixed"</tt>: run both TestNG and JUnit tests.

+</ul>

+

+<p><em>Note</em>: <tt>"junit"</tt> and <tt>"mixed"</tt> modes require the JUnit jar file on the classpath.</p>

+

+<h3><a name="nested">Nested Elements</a></h3>

+<h4>classpath</h4>

+<p>The <tt>&lt;testng&gt;</tt> task supports a nested <tt>&lt;classpath&gt;</tt> element 

+that represents a <em>PATH</em>-like structure. </p>

+<h4>bootclasspath</h4>

+<p>The location of bootstrap class files can be specified using this <em>

+PATH-like</em> structure - will be ignored if <tt>fork </tt>is not set. </p>

+<h4>xmlfileset</h4>

+<p>The suite definitions (<tt>testng.xml</tt>) can be passed to the task with a

+<tt><a href="http://ant.apache.org/manual/Types/fileset.html">FileSet</a></tt>

+structure. </p>

+<h4>classfileset</h4>

+<p>TestNG can also run directly on classes, also supplied with a <tt>

+<a href="http://ant.apache.org/manual/Types/fileset.html">FileSet</a></tt>

+structure.</p>

+<h4>jvmarg</h4>

+<p>Additional parameters may be passed to the new VM via nested <tt>&lt;jvmarg&gt;</tt> 

+elements. For example: </p>

+

+<pre class="brush: xml">

+&lt;testng&gt;

+   &lt;jvmarg value="-Djava.compiler=NONE" /&gt;

+   &lt;!-- ... --&gt;

+&lt;/testng&gt;

+</pre>

+

+<h4>sysproperty</h4>

+<p>Use nested <tt>&lt;sysproperty&gt;</tt> elements to specify system properties 

+required by the class. These properties will be made available to the virtual 

+machine during the execution of the test. The attributes for this element are 

+the same as for <em>environment variables</em>:</p>

+

+<pre class="brush: xml">

+&lt;testng&gt;

+   &lt;sysproperty key="basedir" value="${basedir}"/&gt;

+   &lt;!-- ... --&gt;

+&lt;/testng&gt;

+</pre>

+

+<p>will run the test and make the <code>basedir</code> property 

+available to the test.</p>

+

+<h4>propertyset</h4>

+<p>You may also use a nested <tt>&lt;propertyset&gt;</tt> element to specify a set of system properties that are defined 

+outside of the TestNG ant task. This allows for more flexible definitions of system properties, for instance selecting

+all properties with a specific prefix or matching a regex. See the 

+<a href="http://ant.apache.org/manual/Types/propertyset.html">PropertySet page</a> in the 

+<a href="http://ant.apache.org/manual/">Ant manual</a> for full details. Here's a simple example:</p>

+<pre class="brush: xml">

+      &lt;property name="myprop1" value="value 1"/&gt;

+      &lt;property name="myprop2" value="value 2"/>

+	

+      &lt;propertyset id="propset1"&gt;

+          &lt;propertyref name="myprop1"/&gt;

+          &lt;propertyref name="myprop2"/&gt;

+      &lt;/propertyset&gt;

+

+      &lt;testng outputdir="${testng.report.dir}" classpathref="run.cp"&gt;

+          &lt;xmlfileset dir="${test15.dir}" includes="testng-single3.xml"/&gt;

+          &lt;propertyset refid="propset1"/&gt;

+      &lt;/testng&gt;

+</pre>

+<p>In this case, the system properties named "myprop1" and "myprop2" are passed along to the TestNG process.</p>

+

+<h4>reporter</h4>

+<p>An inner <tt>&lt;reporter&gt;</tt> element is an alternative way to inject a

+custom report listener allowing the user to set custom properties in order to fine-tune

+the behavior of the reporter at run-time.

+<br>

+The element has one <tt>classname</tt> attribute which is mandatory, indicating

+the class of the custom listener. In order to set the properties of the reporter, the

+<tt>&lt;reporter&gt;</tt> element can contain several nested <tt>&lt;property&gt;</tt>

+elements which will provide the <tt>name</tt> and <tt>value</tt> attributes as seen below:

+</p>

+<pre class="brush: xml">

+&lt;testng ...&gt;

+   ...

+   &lt;reporter classname="com.test.MyReporter"&gt;

+      &lt;property name="methodFilter" value="*insert*"/&gt;

+      &lt;property name="enableFiltering" value="true"/&gt;

+   &lt;/reporter&gt;

+   ...

+&lt;/testng&gt;

+</pre>

+<pre class="brush: java">

+public class MyReporter {

+

+  public String getMethodFilter() {...}

+  public void setMethodFilter(String methodFilter) {...}

+  public boolean isEnableFiltering() {...}

+  public void setEnableFiltering(boolean enableFiltering) {...}

+  ...

+}

+</pre>

+You have to consider though that for the moment only a limited set of property types are supported:

+<tt>String, int, boolean, byte, char, double, float, long, short</tt>.

+

+<h4>env</h4>

+<p>It is possible to specify environment variables to pass to the TestNG forked 

+virtual machine via nested <tt>&lt;env&gt;</tt> elements. For a description of the <tt>

+&lt;env&gt;</tt> element's attributes, see the description in the <em>

+<a href="http://ant.apache.org/manual/CoreTasks/exec.html">exec</a></em> task.</p>

+<h3>Examples</h3>

+<h4>Suite xml</h4>

+

+<pre class="brush: text">

+&lt;testng classpathref="run.cp"

+        outputDir="${testng.report.dir}"

+        sourcedir="${test.src.dir}"

+        haltOnfailure="true"&gt;

+ 

+   &lt;xmlfileset dir="${test14.dir}" includes="testng.xml"/&gt;

+&lt;/testng&gt;

+</pre>

+

+<h4>Class FileSet</h4>

+<pre class="brush: xml">

+&lt;testng classpathref="run.cp"

+		outputDir="${testng.report.dir}"

+		haltOnFailure="true" verbose="2"&gt;

+	&lt;classfileset dir="${test.build.dir}" includes="**/*.class" /&gt;

+&lt;/testng&gt;

+</pre>

+

+<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">

+</script>

+<script type="text/javascript">

+_uacct = "UA-238215-2";

+urchinTracker();

+</script>

+

+</body>

+

+</html>

diff --git a/doc/banner.js b/doc/banner.js
new file mode 100644
index 0000000..525f767
--- /dev/null
+++ b/doc/banner.js
@@ -0,0 +1,52 @@
+//****************************************************************

+// Writes a table data element to the document. If pHRef equals

+// pCurrentPage then 'class="current"' is added to the td element.

+// For example:

+//    writeTD("a.html", "a.html", "b")

+// Would write

+//    <td class="current"><a href="a.html">b</a><td>

+//

+// @param pCurrentPage the current page. For example "index.html"

+// @param pHRef the string that should apear in the href

+// @param pValue the string that should apear as the value

+//****************************************************************

+function writeTD(pCurrentPage, pHRef, pValue)

+{

+   document.write('                <td')

+   document.write(pCurrentPage == pHRef ? ' class="current"' : '')

+   document.write('><a href="')

+   document.write(pHRef)

+   document.write('">')

+   document.write(pValue)

+   document.writeln('</a></td>')

+}

+

+//******************************************************************

+// Writes the main menu to the document.

+// @param pCurrentPage the current page. For example "index.html"

+//******************************************************************

+function displayMenu(pCurrentPage) {

+   document.writeln('<div id="topmenu">')

+   document.writeln('    <table width="100%">')

+   document.writeln('            <tr>')

+       writeTD(pCurrentPage,             "index.html", "Welcome")

+       writeTD(pCurrentPage,          "download.html", "Download")

+       writeTD(pCurrentPage,"documentation-main.html", "Documentation")

+       writeTD(pCurrentPage,         "migrating.html", "Migrating from JUnit")

+       writeTD(pCurrentPage, "../javadocs/index.html", "JavaDoc")

+       writeTD(pCurrentPage, "selenium.html", "Selenium")

+   document.writeln('            </tr>')

+   document.writeln('            <tr>')

+       writeTD(pCurrentPage,           "eclipse.html", "Eclipse")

+       writeTD(pCurrentPage,              "idea.html", "IDEA")

+       writeTD(pCurrentPage,             "maven.html", "Maven")

+       writeTD(pCurrentPage,               "ant.html", "Ant")

+       writeTD(pCurrentPage,              "misc.html", "Miscellaneous")

+       writeTD(pCurrentPage,              "book.html", "Book")

+       writeTD(pCurrentPage,              "http://beust.com/kobalt", "Kobalt")

+   document.writeln('            </tr>')

+   document.writeln('        </table>')

+   document.writeln('    </div>')

+

+}

+

diff --git a/doc/book-toc.html b/doc/book-toc.html
new file mode 100644
index 0000000..6894ce7
--- /dev/null
+++ b/doc/book-toc.html
@@ -0,0 +1,1609 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><META http-equiv="Content-Type" content="text/html; charset=utf-8"><style type="text/css" media="print">.hide{display:none}</style></head>
+
+
+<body style="margin:0;padding:0"><div class="hide">
+
+
+
+
+
+<div bgcolor="#ffffff" vlink="blue" link="blue">
+<table border="0" width="100%"><tr><td bgcolor="eeeeee" align="right"><font face="arial,sans-serif"><a name="0.1_1"><b>Page 1</b></a></font></td></tr></table><font size="3" face="Times"><span style="font-size:16px;font-family:Times">
+<div style="position:absolute;top:1148;left:738">v</div>
+</span></font>
+<font size="6" face="Times"><span style="font-size:48px;font-family:Times">
+
+<div style="position:absolute;top:355;left:162"><b>Contents</b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:512;left:270">Foreword</div>
+<div style="position:absolute;top:512;left:730">xiii</div>
+<div style="position:absolute;top:530;left:270">Preface</div>
+<div style="position:absolute;top:530;left:734">xv</div>
+<div style="position:absolute;top:548;left:270">Acknowledgments</div>
+<div style="position:absolute;top:548;left:730">xxi</div>
+<div style="position:absolute;top:566;left:270">About the Authors</div>
+
+<div style="position:absolute;top:566;left:723">xxiii</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:16px;font-family:Times">
+<div style="position:absolute;top:611;left:162">Chapter 1</div>
+<div style="position:absolute;top:611;left:270">Getting Started</div>
+<div style="position:absolute;top:611;left:736">1</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:640;left:270">Beyond JUnit 3</div>
+<div style="position:absolute;top:640;left:738">3</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:660;left:288">Stateful Classes</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:659;left:738">3</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:676;left:288">Parameters</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:675;left:738">4</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+
+<div style="position:absolute;top:693;left:288">Base Classes</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:692;left:738">4</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:709;left:288">Exceptions Are Not That Exceptional</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:708;left:738">4</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:726;left:288">Running Tests</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:725;left:738">5</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:743;left:288">Real-World Testing</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:741;left:738">6</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:759;left:288">Configuration Methods</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:758;left:738">6</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:775;left:288">Dependencies</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:774;left:738">6</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:792;left:288">Epiphanies</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+
+<div style="position:absolute;top:791;left:738">7</div>
+<div style="position:absolute;top:808;left:270">JUnit 4</div>
+<div style="position:absolute;top:808;left:738">7</div>
+<div style="position:absolute;top:828;left:270">Designing for Testability</div>
+<div style="position:absolute;top:828;left:738">8</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:847;left:288">Object-Oriented Programming and Encapsulation</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:846;left:738">8</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:864;left:288">The Design Patterns Revolution</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:863;left:738">9</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:880;left:288">Identifying the Enemy</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:879;left:729">10</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:897;left:288">Recommendations</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:896;left:729">16</div>
+<div style="position:absolute;top:913;left:270">TestNG</div>
+<div style="position:absolute;top:913;left:729">17</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:933;left:288">Annotations</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+
+<div style="position:absolute;top:932;left:729">17</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:950;left:288">Tests, Suites, and Configuration Annotations</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:948;left:729">18</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:966;left:288">Groups</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:965;left:729">20</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:983;left:288"><b>testng.xml</b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:981;left:729">21</div>
+<div style="position:absolute;top:999;left:270">Conclusion</div>
+<div style="position:absolute;top:999;left:729">21</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:16px;font-family:Times">
+<div style="position:absolute;top:1045;left:162">Chapter 2</div>
+<div style="position:absolute;top:1045;left:270">Testing Design Patterns</div>
+
+<div style="position:absolute;top:1045;left:725">23</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1075;left:270">Testing for Failures</div>
+<div style="position:absolute;top:1075;left:729">23</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1094;left:288">Reporting Errors</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1093;left:729">24</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1111;left:288">Runtime and Checked Exceptions</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1110;left:729">25</div>
+</span></font>
+<font size="2" face="Times"><span style="font-size:8px;font-family:Times">
+<div style="position:absolute;top:202;left:90">Beust.book  Page v  Thursday, August 16, 2007  10:22 AM</div>
+</span></font>
+
+<div style="position:absolute;top:1282;left:0"><hr><table border="0" width="100%"><tr><td bgcolor="eeeeee" align="right"><font face="arial,sans-serif"><a name="0.1_2"><b>Page 2</b></a></font></td></tr></table></div><font size="8" face="Times"><span style="font-size:16px;font-family:Times">
+<div style="position:absolute;top:1387;left:117">vi</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1390;left:189">Contents</div>
+<div style="position:absolute;top:1444;left:243">Testing Whether Your Code Handles Failures Gracefully</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1443;left:684">27</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1460;left:243">When Not to Use <b>expectedExceptions</b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+
+<div style="position:absolute;top:1459;left:684">31</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1477;left:243"><b>testng-failed.xml</b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1476;left:684">32</div>
+<div style="position:absolute;top:1493;left:225">Factories</div>
+<div style="position:absolute;top:1493;left:684">34</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1513;left:243"><b>@Factory</b></div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1512;left:684">35</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1530;left:243"><b>org.testng.ITest</b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1528;left:684">38</div>
+<div style="position:absolute;top:1546;left:225">Data-Driven Testing</div>
+<div style="position:absolute;top:1546;left:684">39</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1565;left:243">Parameters and Test Methods</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1564;left:684">42</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1582;left:243">Passing Parameters with <b>testng.xml</b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1581;left:684">44</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1598;left:243">Passing Parameters with <b>@DataProvider</b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1597;left:684">47</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1615;left:243">Parameters for Data Providers</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1614;left:684">50</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1631;left:243">The <b>Method </b>Parameter</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1630;left:684">50</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1648;left:243">The <b>ITestContext </b>Parameter</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+
+<div style="position:absolute;top:1647;left:684">52</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1664;left:243">Lazy Data Providers</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1663;left:684">54</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1681;left:243">Pros and Cons of Both Approaches</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1680;left:684">59</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1697;left:243">Supplying the Data</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1696;left:684">60</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1714;left:243">Data Provider or Factory?</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1713;left:684">62</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1730;left:243">Tying It All Together</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1729;left:684">63</div>
+<div style="position:absolute;top:1747;left:225">Asynchronous Testing</div>
+<div style="position:absolute;top:1747;left:684">67</div>
+<div style="position:absolute;top:1766;left:225">Testing Multithreaded Code</div>
+<div style="position:absolute;top:1766;left:684">71</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+
+<div style="position:absolute;top:1786;left:243">Concurrent Testing</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1785;left:684">72</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1803;left:243"><b>threadPoolSize</b>, <b>invocationCount</b>, and <b>timeOut</b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1801;left:684">75</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1819;left:243">Concurrent Running</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1818;left:684">79</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1835;left:243">Turning on the Parallel Bit</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1834;left:684">82</div>
+<div style="position:absolute;top:1852;left:225">Performance Testing</div>
+
+<div style="position:absolute;top:1852;left:684">83</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1871;left:243">Algorithm Complexity</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1870;left:684">84</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1888;left:243">Testing Complexity</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1887;left:684">87</div>
+
+<div style="position:absolute;top:1904;left:225">Mocks and Stubs</div>
+<div style="position:absolute;top:1904;left:684">90</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1924;left:243">Mocks versus Stubs</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1923;left:684">90</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1940;left:243">Designing for Mockability</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1939;left:684">95</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1957;left:243">Mock Libraries</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1956;left:684">96</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1973;left:243">Selecting the Right Strategy</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+
+<div style="position:absolute;top:1972;left:684">99</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:1990;left:243">Mock Pitfalls</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:1989;left:675">100</div>
+<div style="position:absolute;top:2006;left:225">Dependent Testing</div>
+<div style="position:absolute;top:2006;left:675">103</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2026;left:243">Dependent Code</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2025;left:675">104</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2042;left:243">Dependent Testing with TestNG</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2041;left:675">105</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2059;left:243">Deciding Whether to Depend on Groups or on Methods</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2058;left:675">106</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2075;left:243">Dependent Testing and Threads</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2074;left:675">110</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2092;left:243">Failures of Configuration Methods</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+
+<div style="position:absolute;top:2091;left:675">110</div>
+<div style="position:absolute;top:2108;left:225">Inheritance and Annotation Scopes</div>
+<div style="position:absolute;top:2108;left:675">113</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2128;left:243">The Problem</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2127;left:675">113</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2144;left:243">Pitfalls of Inheritance</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2143;left:675">116</div>
+<div style="position:absolute;top:2161;left:225">Test Groups</div>
+<div style="position:absolute;top:2161;left:675">119</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2180;left:243">Syntax</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2179;left:675">120</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2197;left:243">Groups and Runtime</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2196;left:675">122</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2213;left:243">Running Groups</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2212;left:675">125</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+
+<div style="position:absolute;top:2230;left:243">Using Groups Effectively</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2229;left:675">127</div>
+</span></font>
+<font size="2" face="Times"><span style="font-size:8px;font-family:Times">
+<div style="position:absolute;top:1309;left:90">Beust.book  Page vi  Thursday, August 16, 2007  10:22 AM</div>
+</span></font>
+
+<div style="position:absolute;top:2389;left:0"><hr><table border="0" width="100%"><tr><td bgcolor="eeeeee" align="right"><font face="arial,sans-serif"><a name="0.1_3"><b>Page 3</b></a></font></td></tr></table></div><font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2497;left:620">Contents</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:16px;font-family:Times">
+<div style="position:absolute;top:2494;left:729">vii</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2551;left:270">Code Coverage</div>
+<div style="position:absolute;top:2551;left:720">132</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2570;left:288">A Coverage Example</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2569;left:720">133</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2587;left:288">Coverage Metrics</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2586;left:720">134</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2603;left:288">Coverage Tools</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2602;left:720">136</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2620;left:288">Implementation</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2619;left:720">146</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2636;left:288">Beware!</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2635;left:720">147</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+
+<div style="position:absolute;top:2653;left:288">A Guide to Successful Coverage</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2652;left:720">147</div>
+<div style="position:absolute;top:2669;left:270">Conclusion</div>
+<div style="position:absolute;top:2669;left:720">150</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:16px;font-family:Times">
+<div style="position:absolute;top:2716;left:162">Chapter 3</div>
+<div style="position:absolute;top:2716;left:270">Enterprise Testing</div>
+<div style="position:absolute;top:2716;left:715">153</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2745;left:270">A Typical Enterprise Scenario</div>
+<div style="position:absolute;top:2745;left:720">154</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2764;left:288">Participants</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2763;left:720">155</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2781;left:288">Testing Methodology</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2780;left:720">155</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2797;left:288">Issues with the Current Approach</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2796;left:720">156</div>
+<div style="position:absolute;top:2814;left:270">A Concrete Example</div>
+<div style="position:absolute;top:2814;left:720">157</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2833;left:288">Goals</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2832;left:720">159</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2850;left:288">Nongoals</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2849;left:720">160</div>
+<div style="position:absolute;top:2866;left:270">Test Implementation</div>
+
+<div style="position:absolute;top:2866;left:720">160</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2886;left:288">Testing for Success</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2885;left:720">161</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2903;left:288">Building Test Data</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2901;left:720">163</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2919;left:288">Test Setup Issues</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2918;left:720">166</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2935;left:288">Error Handling</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2934;left:720">172</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2952;left:288">Emerging Unit Tests</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2951;left:720">175</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:2968;left:288">Coping with In-Container Components</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2967;left:720">177</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+
+<div style="position:absolute;top:2985;left:288">Putting It All Together</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:2984;left:720">178</div>
+<div style="position:absolute;top:3001;left:270">Exploring the Competing Consumers Pattern</div>
+<div style="position:absolute;top:3001;left:720">182</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3021;left:288">The Pattern</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3020;left:720">182</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3038;left:288">The Test</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3036;left:720">184</div>
+<div style="position:absolute;top:3054;left:270">The Role of Refactoring</div>
+<div style="position:absolute;top:3054;left:720">186</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3074;left:288">A Concrete Example</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3072;left:720">187</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3090;left:288">An In-Container Approach</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3089;left:720">193</div>
+<div style="position:absolute;top:3106;left:270">Conclusion</div>
+<div style="position:absolute;top:3106;left:720">194</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:16px;font-family:Times">
+
+<div style="position:absolute;top:3153;left:162">Chapter 4</div>
+<div style="position:absolute;top:3153;left:270">Java EE Testing</div>
+<div style="position:absolute;top:3153;left:715">197</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3182;left:270">In-Container versus Out-of-Container Testing</div>
+<div style="position:absolute;top:3182;left:720">198</div>
+<div style="position:absolute;top:3202;left:270">In-Container Testing</div>
+<div style="position:absolute;top:3202;left:720">200</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3221;left:288">Creating a Test Environment</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3220;left:720">200</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3238;left:288">Identifying Tests</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3237;left:720">201</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+
+<div style="position:absolute;top:3254;left:288">Registering Tests</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3253;left:720">203</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3271;left:288">Registering a Results Listener</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3270;left:720">204</div>
+<div style="position:absolute;top:3287;left:270">Java Naming and Directory Interface (JNDI)</div>
+<div style="position:absolute;top:3287;left:720">207</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3307;left:288">Understanding JNDI\u2019s Bootstrapping</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3306;left:720">207</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3323;left:288">Spring\u2019s <b>SimpleNamingContextBuilder</b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3322;left:720">209</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3340;left:288">Avoiding JNDI</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3339;left:720">210</div>
+</span></font>
+<font size="2" face="Times"><span style="font-size:8px;font-family:Times">
+<div style="position:absolute;top:2416;left:90">Beust.book  Page vii  Thursday, August 16, 2007  10:22 AM</div>
+</span></font>
+
+<div style="position:absolute;top:3496;left:0"><hr><table border="0" width="100%"><tr><td bgcolor="eeeeee" align="right"><font face="arial,sans-serif"><a name="0.1_4"><b>Page 4</b></a></font></td></tr></table></div><font size="8" face="Times"><span style="font-size:16px;font-family:Times">
+<div style="position:absolute;top:3601;left:117">viii</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3604;left:189">Contents</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3658;left:225">Java Database Connectivity (JDBC)</div>
+<div style="position:absolute;top:3658;left:675">210</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3677;left:243">c3p0</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3676;left:675">212</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3694;left:243">Commons DBCP</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3693;left:675">213</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3710;left:243">Spring</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3709;left:675">213</div>
+<div style="position:absolute;top:3727;left:225">Java Transaction API (JTA)</div>
+
+<div style="position:absolute;top:3727;left:675">215</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3746;left:243">Java Open Transaction Manager (JOTM)</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3745;left:675">217</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3763;left:243">Atomikos TransactionEssentials </div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3762;left:675">218</div>
+
+<div style="position:absolute;top:3779;left:225">Java Messaging Service (JMS)</div>
+<div style="position:absolute;top:3779;left:675">219</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3799;left:243">Creating a Sender/Receiver Test</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3798;left:675">219</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3815;left:243">Using ActiveMQ for Tests</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3814;left:675">221</div>
+<div style="position:absolute;top:3832;left:225">Java Persistence API (JPA)</div>
+<div style="position:absolute;top:3832;left:675">225</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3851;left:243">Configuring the Database</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3850;left:675">227</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+
+<div style="position:absolute;top:3868;left:243">Configuring the JPA Provider</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3867;left:675">227</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3884;left:243">Writing the Test</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3883;left:675">229</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3901;left:243">Simulating a Container</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3900;left:675">230</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3917;left:243">Using Spring as the Container</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3916;left:675">231</div>
+<div style="position:absolute;top:3934;left:225">Enterprise Java Beans 3.0 (EJB3)</div>
+<div style="position:absolute;top:3934;left:675">236</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3953;left:243">Message-Driven Beans</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3952;left:675">237</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:3970;left:243">Session Beans</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3969;left:675">240</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+
+<div style="position:absolute;top:3986;left:243">Another Spring Container</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:3985;left:675">243</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4003;left:243">Disadvantages of a Full Container</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4002;left:675">244</div>
+<div style="position:absolute;top:4019;left:225">Java API for XML Web Services (JAX-WS)</div>
+<div style="position:absolute;top:4019;left:675">246</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4039;left:243">Recording Requests</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4038;left:675">248</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4055;left:243">Setting Up the Test Environment</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4054;left:675">248</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4072;left:243">Creating the Service Test</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4071;left:675">251</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4088;left:243">XPath Testing</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4087;left:675">253</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+
+<div style="position:absolute;top:4105;left:243">Testing Remote Services</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4104;left:675">254</div>
+<div style="position:absolute;top:4121;left:225">Servlets</div>
+<div style="position:absolute;top:4121;left:675">255</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4141;left:243">In-Container Testing</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4140;left:675">255</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4157;left:243">Mock/Stub Objects</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4156;left:675">255</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4174;left:243">Refactoring</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4173;left:675">257</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4190;left:243">Embedded Container</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4189;left:675">257</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4207;left:243">In-Memory Invocation</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4206;left:675">260</div>
+<div style="position:absolute;top:4223;left:225">XML</div>
+
+<div style="position:absolute;top:4223;left:675">262</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4243;left:243">Using dom4j</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4242;left:675">263</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4259;left:243">Using XMLUnit</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4258;left:675">264</div>
+
+<div style="position:absolute;top:4276;left:225">Conclusion</div>
+<div style="position:absolute;top:4276;left:675">266</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:16px;font-family:Times">
+<div style="position:absolute;top:4322;left:117">Chapter 5</div>
+<div style="position:absolute;top:4322;left:225">Integration</div>
+<div style="position:absolute;top:4322;left:670">269</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4351;left:225">Spring</div>
+<div style="position:absolute;top:4351;left:675">270</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4371;left:243">Spring\u2019s Test Package Features</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4370;left:675">271</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4388;left:243">Test Class Hierarchy</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4386;left:675">272</div>
+<div style="position:absolute;top:4404;left:225">Guice</div>
+
+<div style="position:absolute;top:4404;left:675">280</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4424;left:243">The Issue with Spring</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4422;left:675">280</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4440;left:243">Enter Guice</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4439;left:675">281</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4456;left:243">A Typical Dependency Scenario</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4455;left:675">282</div>
+</span></font>
+<font size="2" face="Times"><span style="font-size:8px;font-family:Times">
+<div style="position:absolute;top:3523;left:90">Beust.book  Page viii  Thursday, August 16, 2007  10:22 AM</div>
+</span></font>
+
+<div style="position:absolute;top:4603;left:0"><hr><table border="0" width="100%"><tr><td bgcolor="eeeeee" align="right"><font face="arial,sans-serif"><a name="0.1_5"><b>Page 5</b></a></font></td></tr></table></div><font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4711;left:620">Contents</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:16px;font-family:Times">
+<div style="position:absolute;top:4708;left:733">ix</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4765;left:288">The Object Factory</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4764;left:720">284</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4781;left:288">Guice Configuration</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4780;left:720">286</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4798;left:288">Guice-Based Test</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4797;left:720">290</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4814;left:288">Grouping Test Dependencies</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+
+<div style="position:absolute;top:4813;left:720">291</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4831;left:288">Injecting Configuration</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4830;left:720">293</div>
+<div style="position:absolute;top:4847;left:270">DbUnit</div>
+<div style="position:absolute;top:4847;left:720">295</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4867;left:288">Configuration</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4866;left:720">295</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4883;left:288">Usage</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4882;left:720">297</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4900;left:288">Verifying Results</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4899;left:720">299</div>
+<div style="position:absolute;top:4916;left:270">HtmlUnit</div>
+<div style="position:absolute;top:4916;left:720">303</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:4936;left:288">Configuration</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4935;left:720">304</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+
+<div style="position:absolute;top:4952;left:288">Usage</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:4951;left:720">305</div>
+<div style="position:absolute;top:4969;left:270">Selenium</div>
+<div style="position:absolute;top:4969;left:720">310</div>
+<div style="position:absolute;top:4988;left:270">Swing UI Testing</div>
+<div style="position:absolute;top:4988;left:720">312</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5008;left:288">Testing Approach</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5007;left:720">312</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5024;left:288">Configuration</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5023;left:720">313</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5041;left:288">Usage</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5040;left:720">314</div>
+<div style="position:absolute;top:5057;left:270">Tests for Painting Code</div>
+<div style="position:absolute;top:5057;left:720">316</div>
+<div style="position:absolute;top:5077;left:270">Continuous Integration</div>
+<div style="position:absolute;top:5077;left:720">320</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5096;left:288">Why Bother?</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+
+<div style="position:absolute;top:5095;left:720">320</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5113;left:288">CI Server Features</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5112;left:720">320</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5129;left:288">TestNG Integration</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5128;left:720">321</div>
+
+<div style="position:absolute;top:5146;left:270">Conclusion</div>
+<div style="position:absolute;top:5146;left:720">322</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:16px;font-family:Times">
+<div style="position:absolute;top:5192;left:162">Chapter 6</div>
+<div style="position:absolute;top:5192;left:270">Extending TestNG</div>
+<div style="position:absolute;top:5192;left:715">325</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5221;left:270">The TestNG API</div>
+<div style="position:absolute;top:5221;left:720">325</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5242;left:288"><b>org.testng.TestNG</b>, <b>ITestResult</b>, <b>ITestListener</b>, </div>
+<div style="position:absolute;top:5258;left:306"><b>ITestNGMethod</b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5256;left:720">325</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+
+<div style="position:absolute;top:5274;left:288">A Concrete Example</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5273;left:720">328</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5290;left:288">The XML API</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5289;left:720">331</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5307;left:288">Synthetic XML Files</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5306;left:720">333</div>
+<div style="position:absolute;top:5323;left:270">BeanShell</div>
+<div style="position:absolute;top:5323;left:720">335</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5343;left:288">BeanShell Overview</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5342;left:720">335</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5360;left:288">TestNG and BeanShell</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5358;left:720">337</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5376;left:288">Interactive Execution</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5375;left:720">339</div>
+<div style="position:absolute;top:5392;left:270">Method Selectors</div>
+
+<div style="position:absolute;top:5392;left:720">341</div>
+<div style="position:absolute;top:5412;left:270">Annotation Transformers</div>
+<div style="position:absolute;top:5412;left:720">346</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5432;left:288">Annotation History</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5430;left:720">346</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5448;left:288">Pros and Cons</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5447;left:720">348</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5464;left:288">Using TestNG Annotation Transformers</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5463;left:720">348</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5481;left:288">Possible Uses of Annotation Transformers</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5480;left:720">353</div>
+<div style="position:absolute;top:5497;left:270">Reports</div>
+<div style="position:absolute;top:5497;left:720">355</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5517;left:288">Default Reports</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5516;left:720">355</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+
+<div style="position:absolute;top:5534;left:288">The Reporter API</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5533;left:720">360</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5550;left:288">The Report Plug-in API</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5549;left:720">360</div>
+</span></font>
+<font size="2" face="Times"><span style="font-size:8px;font-family:Times">
+<div style="position:absolute;top:4630;left:90">Beust.book  Page ix  Thursday, August 16, 2007  10:22 AM</div>
+
+</span></font>
+
+<div style="position:absolute;top:5710;left:0"><hr><table border="0" width="100%"><tr><td bgcolor="eeeeee" align="right"><font face="arial,sans-serif"><a name="0.1_6"><b>Page 6</b></a></font></td></tr></table></div><font size="8" face="Times"><span style="font-size:16px;font-family:Times">
+<div style="position:absolute;top:5815;left:117">x</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5818;left:189">Contents</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5872;left:225">Writing Custom Annotations</div>
+<div style="position:absolute;top:5872;left:675">366</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5891;left:243">Implementation</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5890;left:675">367</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:5908;left:243">Testing</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:5907;left:675">371</div>
+<div style="position:absolute;top:5924;left:225">Conclusion</div>
+
+<div style="position:absolute;top:5924;left:675">375</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:16px;font-family:Times">
+<div style="position:absolute;top:5971;left:117">Chapter 7</div>
+<div style="position:absolute;top:5971;left:225">Digressions</div>
+<div style="position:absolute;top:5971;left:670">377</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:6000;left:225">Motivation</div>
+<div style="position:absolute;top:6000;left:675">377</div>
+<div style="position:absolute;top:6019;left:225">The TestNG Philosophy</div>
+
+<div style="position:absolute;top:6019;left:675">378</div>
+<div style="position:absolute;top:6039;left:225">The Care and Feeding of Exceptions</div>
+<div style="position:absolute;top:6039;left:675">378</div>
+<div style="position:absolute;top:6058;left:225">Stateful Tests</div>
+<div style="position:absolute;top:6058;left:675">382</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:6078;left:243">Immutable State</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:6077;left:675">382</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:6094;left:243">Mutable State</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:6093;left:675">383</div>
+<div style="position:absolute;top:6111;left:225">The Pitfalls of Test-Driven Development</div>
+<div style="position:absolute;top:6111;left:675">385</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:6130;left:243">TDD Promotes Microdesign over Macrodesign</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:6129;left:675">385</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:6147;left:243">TDD Is Hard to Apply</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:6146;left:675">386</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:6163;left:243">Extracting the Good from Test-Driven Development</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+
+<div style="position:absolute;top:6162;left:675">388</div>
+<div style="position:absolute;top:6180;left:225">Testing Private Methods</div>
+<div style="position:absolute;top:6180;left:675">388</div>
+<div style="position:absolute;top:6199;left:225">Testing versus Encapsulation</div>
+<div style="position:absolute;top:6199;left:675">391</div>
+<div style="position:absolute;top:6219;left:225">The Power of Debuggers</div>
+<div style="position:absolute;top:6219;left:675">392</div>
+<div style="position:absolute;top:6238;left:225">Logging Best Practices</div>
+<div style="position:absolute;top:6238;left:675">394</div>
+
+<div style="position:absolute;top:6258;left:225">The Value of Time</div>
+<div style="position:absolute;top:6258;left:675">397</div>
+<div style="position:absolute;top:6277;left:225">Conclusion</div>
+<div style="position:absolute;top:6277;left:675">399</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:16px;font-family:Times">
+<div style="position:absolute;top:6324;left:117">Appendix A IDE Integration</div>
+<div style="position:absolute;top:6324;left:670">401</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:6353;left:225">Eclipse</div>
+
+<div style="position:absolute;top:6353;left:675">401</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:6373;left:243">Installing the Plug-in</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:6372;left:675">401</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:6389;left:243">Verifying the Installation</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:6388;left:675">404</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:6406;left:243">Creating a Launch Configuration</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:6405;left:675">404</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:6422;left:243">Configuring Preferences</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:6421;left:675">410</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:6439;left:243">Converting JUnit Tests</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:6438;left:675">410</div>
+<div style="position:absolute;top:6455;left:225">IntelliJ IDEA</div>
+<div style="position:absolute;top:6455;left:675">411</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:6475;left:243">Installing the Plug-in</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+
+<div style="position:absolute;top:6474;left:675">411</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:6491;left:243">Running Tests</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:6490;left:675">412</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:6508;left:243">Running Shortcuts</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:6507;left:675">417</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:6524;left:243">Viewing Test Results</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:6523;left:675">418</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:6541;left:243">Running Plug-in Refactorings</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:6540;left:675">419</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:16px;font-family:Times">
+<div style="position:absolute;top:6584;left:117">Appendix B TestNG Javadocs</div>
+<div style="position:absolute;top:6584;left:670">421</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:6613;left:225">JDK 1.4 and JDK 5</div>
+<div style="position:absolute;top:6613;left:675">421</div>
+<div style="position:absolute;top:6633;left:225">Shortcut Syntax for JDK 5 Annotations</div>
+<div style="position:absolute;top:6633;left:675">423</div>
+</span></font>
+<font size="2" face="Times"><span style="font-size:8px;font-family:Times">
+
+<div style="position:absolute;top:5737;left:90">Beust.book  Page x  Thursday, August 16, 2007  10:22 AM</div>
+</span></font>
+
+<div style="position:absolute;top:6817;left:0"><hr><table border="0" width="100%"><tr><td bgcolor="eeeeee" align="right"><font face="arial,sans-serif"><a name="0.1_7"><b>Page 7</b></a></font></td></tr></table></div><font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:6925;left:620">Contents</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:16px;font-family:Times">
+<div style="position:absolute;top:6922;left:733">xi</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:6979;left:270">Annotation Javadocs</div>
+<div style="position:absolute;top:6979;left:720">423</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:6999;left:288"><b>@<a href="mailto:DataProvider/@testng.data-provider" target="_blank">DataProvider/@testng.data<WBR>-provider</a></b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:6997;left:720">425</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7015;left:288"><b>@<a href="mailto:Factory/@testng.factory" target="_blank">Factory/@testng.factory</a></b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+
+<div style="position:absolute;top:7014;left:720">426</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7032;left:288"><b>@<a href="mailto:Parameters/@testng.parameters" target="_blank">Parameters/@testng.parameters</a></b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7030;left:720">426</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7048;left:288"><b>@<a href="mailto:Test/@testng.test" target="_blank">Test/@testng.test</a></b></div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7047;left:720">427</div>
+<div style="position:absolute;top:7064;left:270">The </div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7066;left:298"><b>org.testng.TestNG</b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7064;left:441">Class</div>
+<div style="position:absolute;top:7064;left:720">428</div>
+<div style="position:absolute;top:7084;left:270">The XML API</div>
+
+<div style="position:absolute;top:7084;left:720">432</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:16px;font-family:Times">
+<div style="position:absolute;top:7130;left:162">Appendix C <b>testng.xml</b></div>
+<div style="position:absolute;top:7131;left:715"><b>435</b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7159;left:270">Overview</div>
+<div style="position:absolute;top:7159;left:720">436</div>
+<div style="position:absolute;top:7179;left:270">Scopes</div>
+
+<div style="position:absolute;top:7179;left:720">437</div>
+<div style="position:absolute;top:7198;left:270">XML Tags</div>
+<div style="position:absolute;top:7198;left:720">437</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7219;left:288"><b>&lt;suite&gt;</b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7217;left:720">437</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7235;left:288"><b>&lt;packages&gt; </b>and <b>&lt;package&gt;</b></div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7233;left:720">440</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7252;left:288"><b>&lt;parameter&gt;</b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7250;left:720">441</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7268;left:288"><b>&lt;suite-files&gt; </b>and <b>&lt;suite-file&gt;</b></div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7266;left:720">442</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7285;left:288"><b>&lt;method-selectors&gt;</b>, <b>&lt;method-selector&gt;</b>, </div>
+<div style="position:absolute;top:7301;left:306"><b>&lt;selector-class&gt;</b>, and <b>&lt;script&gt;</b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+
+<div style="position:absolute;top:7299;left:720">443</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7318;left:288"><b>&lt;test&gt;</b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7316;left:720">444</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7334;left:288"><b>&lt;groups&gt;</b>, <b>&lt;define&gt;</b>, and <b>&lt;run&gt;</b></div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7332;left:720">446</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7351;left:288"><b>&lt;classes&gt; </b>and <b>&lt;class&gt;</b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7349;left:720">446</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+
+<div style="position:absolute;top:7367;left:288"><b>&lt;methods&gt;</b></div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7365;left:720">447</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:16px;font-family:Times">
+<div style="position:absolute;top:7410;left:162">Appendix D Migrating from JUnit</div>
+<div style="position:absolute;top:7410;left:715">449</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7441;left:270"><b>JUnitConverter</b></div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7439;left:720">449</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7459;left:288">From the Command Line</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7458;left:720">449</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7475;left:288">From <b>ant</b></div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7474;left:720">452</div>
+<div style="position:absolute;top:7492;left:270">Integrated Development Environments</div>
+<div style="position:absolute;top:7492;left:720">453</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7511;left:288">Eclipse</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7510;left:720">453</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+
+<div style="position:absolute;top:7528;left:288">IDEA</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7527;left:720">454</div>
+<div style="position:absolute;top:7544;left:270">Incremental Migration and JUnit Mode</div>
+<div style="position:absolute;top:7544;left:720">455</div>
+<div style="position:absolute;top:7564;left:270">Converting JUnit Code</div>
+<div style="position:absolute;top:7564;left:720">456</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7583;left:288">Assertions</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7582;left:720">457</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7600;left:288">Running a Single Test</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7599;left:720">458</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7616;left:288">Maintaining State between Invocations</div>
+</span></font>
+
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7615;left:720">461</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7633;left:288">Suite-wide Initialization</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7632;left:720">463</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7649;left:288">Class-wide Initialization</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+
+<div style="position:absolute;top:7648;left:720">463</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7666;left:288">The AllTests Pattern</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7665;left:720">463</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7682;left:288">Testing Exceptions</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7681;left:720">467</div>
+
+</span></font>
+<font size="8" face="Times"><span style="font-size:11px;font-family:Times">
+<div style="position:absolute;top:7699;left:288">The Parameterized Test Case Pattern</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:12px;font-family:Times">
+<div style="position:absolute;top:7698;left:720">469</div>
+</span></font>
+<font size="8" face="Times"><span style="font-size:16px;font-family:Times">
+<div style="position:absolute;top:7742;left:270">Index</div>
+<div style="position:absolute;top:7742;left:715">471</div>
+</span></font>
+<font size="2" face="Times"><span style="font-size:8px;font-family:Times">
+<div style="position:absolute;top:6844;left:90">Beust.book  Page xi  Thursday, August 16, 2007  10:22 AM</div>
+
+</span></font>
+
+<div style="position:absolute;top:7924;left:0"><hr><table border="0" width="100%"><tr><td bgcolor="eeeeee" align="right"><font face="arial,sans-serif"><a name="0.1_8"><b>Page 8</b></a></font></td></tr></table></div><font size="2" face="Times"><span style="font-size:8px;font-family:Times">
+<div style="position:absolute;top:7951;left:90">Beust.book  Page xii  Thursday, August 16, 2007  10:22 AM</div>
+</span></font>
+</div>
+
+</div></body></html>
diff --git a/doc/book.html b/doc/book.html
new file mode 100644
index 0000000..0d9b326
--- /dev/null
+++ b/doc/book.html
@@ -0,0 +1,52 @@
+<html>
+   <head>
+       <title>Next Generation Java Testing</title>
+
+       <link rel="stylesheet" href="testng.css" type="text/css" />
+       <link type="text/css" rel="stylesheet" href="http://beust.com/beust.css"  />
+       <script type="text/javascript" src="http://beust.com/prettify.js"></script>
+       <script type="text/javascript" src="http://testng.org/doc/banner.js"></script>
+     </head>
+
+<body onload="prettyPrint()">
+
+<script type="text/javascript">
+   displayMenu("book.html");
+</script>
+
+
+<p align="center">
+<a href="http://www.amazon.com/Next-Generation-Java-Testing-Advanced/dp/0321503104/ref=pd_bbs_sr_1/104-7105897-1187923?ie=UTF8&s=books&qid=1193417110&sr=8-1">
+<img border="0" src="http://beust.com/pics/book-cover.jpg" />
+</a>
+</p>
+
+Our book is now <a href="http://www.amazon.com/Next-Generation-Java-Testing-Advanced/dp/0321503104/ref=pd_bbs_sr_1/104-7105897-1187923?ie=UTF8&s=books&qid=1193417110&sr=8-1">available from Amazon</a> and various other retail stores (check out <a href="book-toc.html">the table of contents</a>).
+
+<P>
+
+<b>Book Description</b><br>
+ <p>Enterprise Java developers must achieve broader, deeper test coverage, going beyond unit testing to implement functional and integration testing with systematic acceptance. <b><i>Next Generation Java Testing</i></b> introduces breakthrough Java testing techniques and TestNG, a powerful open source Java testing platform.</p><p>Cédric Beust, TestNG's creator, and leading Java developer Hani Suleiman, present powerful, flexible testing patterns that will work with virtually any testing tool, framework, or language. They show how to leverage key Java platform improvements designed to facilitate effective testing, such as dependency injection and mock objects. They also thoroughly introduce TestNG, demonstrating how it overcomes the limitations of older frameworks and enables new techniques, making it far easier to test today's complex software systems.</p><p>Pragmatic and results-focused, <b><i>Next Generation Java Testing</i></b> will help Java developers build more robust!
+ code for today's mission-critical environments.</p><p>This book</p><ul><li>Illuminates the tradeoffs associated with testing, so you can make better decisions about what and how to test</li><li>Introduces TestNG, explains its goals and features, and shows how to apply them in real-world environments</li><li>Shows how to integrate TestNG with your existing code, development frameworks, and software libraries </li><li>Demonstrates how to test crucial code features, such as encapsulation, state sharing, scopes, and thread safety</li><li>Shows how to test application elements, including JavaEE APIs, databases, Web pages, and XML files </li><li>Presents advanced techniques: testing partial failures, factories, dependent testing, remote invocation, cluster-based test farms, and more</li><li>Walks through installing and using TestNG plug-ins for Eclipse, and IDEA</li><li>Contains extensive code examples</li></ul><p>Whether you use TestNG, JUnit, or another testing framework, the !
+ testing design patterns presented in this book will show you how to im
+prove your tests by giving you concrete advice on how to make your code and your design more testable.</p>
+
+
+<br><br>
+     <b>About the Author</b><br>
+ <p><b>Cédric Beust,</b> a senior software engineer at Google, is an active member of the Java Community Process who has been extensively involved in the development of the latest Java release. He is the creator and main contributor to the TestNG project.</p><p><b>Hani Suleiman</b> is CTO of Formicary, a consulting and portal company specializing in financial applications. He is one of two individual members who has been elected to the Executive Committee of the Java Community Process.</p>
+
+
+
+<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
+</script>
+<script type="text/javascript">
+_uacct = "UA-238215-2";
+urchinTracker();
+</script>
+
+
+</body>
+
+</html>
+ 
\ No newline at end of file
diff --git a/doc/documentation-main.html b/doc/documentation-main.html
new file mode 100644
index 0000000..1da7ea2
--- /dev/null
+++ b/doc/documentation-main.html
@@ -0,0 +1,2764 @@
+<html>
+    <head>
+        <title>TestNG</title>
+
+        <link rel="stylesheet" href="testng.css" type="text/css" />
+        <link type="text/css" rel="stylesheet" href="http://beust.com/beust.css"  />
+        <script type="text/javascript" src="banner.js"></script>
+
+      <script type="text/javascript" src="http://beust.com/scripts/shCore.js"></script>
+      <script type="text/javascript" src="http://beust.com/scripts/shBrushJava.js"></script>
+      <script type="text/javascript" src="http://beust.com/scripts/shBrushXml.js"></script>
+      <script type="text/javascript" src="http://beust.com/scripts/shBrushBash.js"></script>
+      <script type="text/javascript" src="http://beust.com/scripts/shBrushPlain.js"></script>
+      <link type="text/css" rel="stylesheet" href="http://beust.com/styles/shCore.css"/>
+      <link type="text/css" rel="stylesheet" href="http://beust.com/styles/shThemeCedric.css"/>
+      <script type="text/javascript">
+        SyntaxHighlighter.config.clipboardSwf = 'scripts/clipboard.swf';
+        SyntaxHighlighter.defaults['gutter'] = false;
+        SyntaxHighlighter.all();
+      </script>
+      <script type="text/javascript" src="http://beust.com/toc.js"></script>
+
+        <style type="text/css">
+            /* Set the command-line table option column width. */
+            #command-line colgroup.option {
+                 width: 7em;
+            }
+        </style>
+    </head>
+<body onLoad="generateToc();">
+
+<script type="text/javascript">
+    displayMenu("documentation-main.html")
+</script>
+
+<h2 align="center">TestNG</h2>
+
+<!-- --------------------------
+
+<table class="float-right">
+  <tr>
+    <td>
+<script type="text/javascript"><!--
+google_ad_client = "pub-1467757024002850";
+google_ad_width = 120;
+google_ad_height = 600;
+google_ad_format = "120x600_as";
+google_ad_channel ="5560744946";
+//-->
+<!-- 
+
+</script>
+
+<script type="text/javascript"
+  src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
+</script>      
+    </td>
+  </tr>
+</table>
+
+------------------------------- -->
+
+<!-------------------------------------
+  TOC
+  ------------------------------------>
+
+<h3>Table of Contents</h3>
+
+<div id="table-of-contents">
+</div>
+
+
+<!-------------------------------------
+  INTRODUCTION
+  ------------------------------------>
+<h3><a class="section" name="introduction">Introduction</a></h3>
+
+TestNG is a testing framework designed to simplify a broad range of testing needs, from unit testing (testing a class in isolation of the others) to integration testing (testing entire systems made of several classes, several packages and even several external frameworks, such as application servers).
+
+<p>
+
+Writing a test is typically a three-step process:
+
+<ul>
+<li>Write the business logic of your test and insert <a href="#annotations">TestNG annotations</a> in your code.
+</li>
+<li>Add the information about your test (e.g. the class name, the groups you wish to run, etc...) in a <tt><a href="#testng-xml">testng.xml</a></tt> file or in build.xml.
+</li>
+<li><a href="ant.html">Run TestNG</a>.
+</li>
+</ul>
+You can find a quick example on the <a href="index.html">Welcome page</a>.
+<p>
+The concepts used in this documentation are as follows:
+
+<ul>
+<li>
+A suite is represented by one XML file.  It can contain one or more tests and is defined by the <tt>&lt;suite&gt;</tt> tag.
+</li>
+<li>
+A test is represented by <tt>&lt;test&gt;</tt> and can contain one or more TestNG classes.
+</li>
+<li>
+A TestNG class is a Java class that contains at least one TestNG annotation.  It is represented by the <tt>&lt;class&gt;</tt> tag and can contain one or more test methods.
+</li>
+<li>
+A test method is a Java method annotated by <tt>@Test</tt> in your source.
+</li></ul>A TestNG test can be configured by <tt>@BeforeXXX and @AfterXXX </tt>annotations which allows to perform some Java logic before and after a certain point, these points being either of the items listed above.<p>
+The rest of this manual will explain the following:
+<p>
+<ul>
+<li>A list of all the annotations with a brief explanation.  This will give you an idea of the various functionalities offered by TestNG but you will probably want to consult the section dedicated to each of these annotations to learn the details.
+</li>
+<li>A description of the testng.xml file, its syntax and what you can specify in it.
+</li>
+<li>A detailed list of the various features and how to use them with a combination of annotations and testng.xml.
+</li>
+</ul>
+
+
+<!-------------------------------------
+  ANNOTATIONS
+  ------------------------------------>
+
+<h3><a class="section" name="annotations">Annotations</a></h3>
+
+Here is a quick overview of the annotations available in TestNG along with their attributes.
+
+<p>
+
+<table>
+
+<tr>
+<td colspan="2"><b><tt>@BeforeSuite<br>@AfterSuite<br>@BeforeTest<br>@AfterTest<br>@BeforeGroups<br>@AfterGroups<br>@BeforeClass<br>@AfterClass<br>@BeforeMethod<br>@AfterMethod</tt></b></td><td><b>Configuration information for a TestNG class:</b>
+
+<br>
+
+<br><b>@BeforeSuite: </b>The annotated method will be run before all tests in this suite have run.
+
+<br><b>@AfterSuite: </b> The annotated method will be run after all tests in this suite have run. 
+
+<br><b>@BeforeTest</b>: The annotated method will be run before any test method belonging to the classes inside the &lt;test&gt; tag is run.
+
+<br><b>@AfterTest</b>: The annotated method will be run after all the test methods belonging to the classes inside the &lt;test&gt; tag have run.
+
+<br><b>@BeforeGroups</b>:   The list of groups that this configuration method will run before. This method is guaranteed to run shortly before the first test method that belongs to any of these groups is invoked.
+
+<br><b>@AfterGroups</b>:   The list of groups that this configuration method will run after.  This method is guaranteed to run shortly after the last test method that belongs to any of these groups is invoked.
+
+<br><b>@BeforeClass</b>: The annotated method will be run before the first test method in the current class is invoked.
+
+<br><b>@AfterClass</b>: The annotated method will be run after all the test methods in the current class have been run. 
+
+<br><b>@BeforeMethod</b>: The annotated method will be run before each test method.
+
+<br><b>@AfterMethod</b>: The annotated method will be run after each test method.
+
+</td>
+</tr>
+
+<tr>
+<td>
+</td>
+<td>
+<tt>alwaysRun</tt>
+</td>
+<td>
+   For before methods (beforeSuite, beforeTest, beforeTestClass and
+   beforeTestMethod, but not beforeGroups):
+   If set to true, this configuration method will be run
+   regardless of what groups it belongs to. 
+   <br>
+   For after methods (afterSuite, afterClass, ...): 
+   If set to true, this configuration method will be run
+   even if one or more methods invoked previously failed or
+   was skipped.
+</td>
+</tr>
+
+<tr>
+<td>
+</td>
+<td>
+<tt>dependsOnGroups</tt>
+</td>
+<td>
+          The list of groups this method depends on.
+</td>
+</tr>
+
+<tr>
+<td>
+</td>
+<td>
+<tt>dependsOnMethods</tt>
+</td>
+<td>
+          The list of methods this method depends on.
+</td>
+</tr>
+
+<tr>
+<td>
+</td>
+<td>
+<tt>enabled</tt>
+</td>
+<td>
+          Whether methods on this class/method are enabled.
+</td>
+</tr>
+
+<tr>
+<td>
+</td>
+<td>
+<tt>groups</tt>
+</td>
+<td>
+          The list of groups this class/method belongs to.
+</td>
+</tr>
+
+<tr>
+<td>
+</td>
+<td>
+<tt>inheritGroups</tt>
+</td>
+<td>
+          If true, this method will belong to groups specified in the @Test annotation at the class level.
+</td>
+</tr>
+
+<tr class="separator">
+<td colspan="3">&nbsp;</td>
+</tr>
+
+<tr>
+<td colspan="2"><tt><b>@DataProvider</b></tt></td><td><b>Marks a method as supplying data for a test method. The annotated method must return an Object[][] where each Object[] can be assigned the parameter list of the test method. The @Test method that wants to receive data from this DataProvider needs to use a dataProvider name equals to the name of this annotation.</b></td></tr><tr>
+<td>
+</td>
+<td>
+<tt>name</tt>
+</td>
+<td>
+The name of this data provider. If it's not supplied, the name of this data provider will automatically be set to the name of the method.
+</td>
+</tr>
+<tr>
+<td>
+</td>
+<td>
+<tt>parallel</tt>
+</td>
+<td>
+If set to true, tests generated using this data provider are run in parallel. Default value is false.
+</td>
+</tr>
+<tr>
+<td colspan="3">&nbsp;</td>
+</tr>
+
+<tr>
+<td colspan="2"><b><tt>@Factory</tt></b></td><td><b> Marks a method as a factory that returns objects that will be used by TestNG as Test classes.  The method must return Object[].</b></td></tr><tr>
+<td colspan="3">&nbsp;</td>
+</tr>
+
+<tr>
+<td colspan="2"><b><tt>@Listeners</tt></b></td><td><b>Defines listeners on a test class.</b></td></tr>
+<tr>
+  <td></td>
+  <td>
+    <tt>value</tt>
+  </td>
+  <td>
+    An array of classes that extend <tt>org.testng.ITestNGListener</tt>.
+  </td>
+</tr>
+
+<tr>
+<td colspan="3">&nbsp;</td>
+</tr>
+
+<td colspan="2"><b><tt>@Parameters</tt></b></td><td><b>Describes how to pass parameters to a @Test method.</b></td></tr><tr>
+<td>
+</td>
+<td>
+<tt>value</tt>
+</td>
+<td>
+The list of variables used to fill the parameters of this method.
+</td>
+</tr>
+
+<tr>
+<td colspan="3">&nbsp;</td>
+</tr>
+
+<tr>
+<td colspan="2"><b>@Test</b></td><td><b>Marks a class or a method as part of the test.</b></td></tr><tr>
+<td>
+</td>
+<td>
+<tt>alwaysRun</tt>
+</td>
+<td>
+          If set to true, this test method will always be run even if it depends on a method that failed.
+</td>
+</tr>
+
+<tr>
+<td>
+</td>
+<td>
+<tt>dataProvider</tt>
+</td>
+<td>
+The name of the data provider for this test method.
+</td>
+</tr>
+
+
+<tr>
+<td>
+</td>
+<td>
+<tt>dataProviderClass</tt>
+</td>
+<td>
+The class where to look for the data provider.  If not specified, the data provider will be looked on the class of the current test method or one of its base classes. If this attribute is specified, the data provider method needs to be static on the specified class.  
+</td>
+</tr>
+
+
+
+
+
+
+<tr>
+<td>
+</td>
+<td>
+<tt>dependsOnGroups</tt>
+</td>
+<td>
+          The list of groups this method depends on.
+</td>
+</tr>
+
+<tr>
+<td>
+</td>
+<td>
+<tt>dependsOnMethods</tt>
+</td>
+<td>
+          The list of methods this method depends on.
+</td>
+</tr>
+
+<tr>
+<td>
+</td>
+<td>
+<tt>description</tt>
+</td>
+<td>
+          The description for this method.
+</td>
+</tr>
+
+<tr>
+<td>
+</td>
+<td>
+<tt>enabled</tt>
+</td>
+<td>
+          Whether methods on this class/method are enabled.
+</td>
+</tr>
+
+<tr>
+<td>
+</td>
+<td>
+<tt>expectedExceptions</tt>
+</td>
+<td>
+           The list of exceptions that a test method is expected to throw.  If no exception or a different than one on this list is thrown, this test will be marked a failure.
+</td>
+</tr>
+
+<tr>
+<td>
+</td>
+<td>
+<tt>groups</tt>
+</td>
+<td>
+          The list of groups this class/method belongs to.
+</td>
+</tr>
+
+<tr>
+<td>
+</td>
+<td>
+<tt>invocationCount</tt>
+</td>
+<td>
+          The number of times this method should be invoked.
+</td>
+</tr>
+
+<tr>
+<td>
+</td>
+<td>
+<tt>invocationTimeOut</tt>
+</td>
+<td>
+          The maximum number of milliseconds this test should take for the cumulated time of all the invocationcounts.  This attribute will be ignored if invocationCount is not specified.
+</td>
+</tr>
+
+<tr>
+<td>
+</td>
+<td>
+<tt>priority</tt>
+</td>
+<td>
+          The priority for this test method. Lower priorities will be scheduled first.
+</td>
+</tr>
+
+<tr>
+<td>
+</td>
+<td>
+
+<tt>successPercentage</tt>
+</td>
+<td>
+          The percentage of success expected from this method
+</td>
+</tr>
+
+<tr>
+<td>
+</td>
+<td>
+<tt>singleThreaded</tt>
+</td>
+<td>
+             If set to true, all the methods on this test class are guaranteed to run in the same thread, even if the tests are currently being run with parallel="methods". This attribute can only be used at the class level and it will be ignored if used at the method level. Note: this attribute used to be called <tt>sequential</tt> (now deprecated).
+
+</td>
+</tr>
+
+<tr>
+<td>
+</td>
+<td>
+<tt>timeOut</tt>
+</td>
+<td>
+          The maximum number of milliseconds this test should take.
+</td>
+</tr>
+
+<tr>
+<td>
+</td>
+<td>
+<tt>threadPoolSize</tt>
+</td>
+<td>
+             The size of the thread pool for this method.  
+The method will be invoked from multiple threads as specified by 
+invocationCount. <br>Note:  this attribute is ignored if invocationCount is not specified
+
+</td>
+</tr>
+
+</table>
+
+
+
+
+</pre>
+<!-------------------------------------
+  TESTNG.XML
+  ------------------------------------>
+<h3><a class="section" name="testng-xml">testng.xml</a></h3>
+
+<p>You can invoke TestNG in several different ways:</p><ul>
+	<li>With a <tt>testng.xml</tt> file</li><li><a href="http://testng.org/doc/ant.html">With ant</a></li><li>From the command line</li></ul><p>This section describes the format of <tt>testng.xml</tt> (you will find documentation 
+on ant and the command line below).</p><p>The current DTD for <tt>testng.xml</tt> can be found on the main Web site:&nbsp;
+<a href="http://testng.org/testng-1.0.dtd">http://testng.org/testng-1.0.dtd</a> 
+(for your convenience, you might prefer to browse the
+<a href="http://testng.org/dtd">HTML version</a>).</p>
+
+Here is an example <tt>testng.xml</tt> file:
+
+<p>
+<h3 class="sourcetitle">testng.xml</h3>
+<pre class="brush: xml">
+&lt;!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" &gt;
+ 
+&lt;suite name="Suite1" verbose="1" &gt;
+  &lt;test name="Nopackage" &gt;
+    &lt;classes&gt;
+       &lt;class name="NoPackageTest" /&gt;
+    &lt;/classes&gt;
+  &lt;/test&gt;
+
+  &lt;test name="Regression1"&gt;
+    &lt;classes&gt;
+      &lt;class name="test.sample.ParameterSample"/&gt;
+      &lt;class name="test.sample.ParameterTest"/&gt;
+    &lt;/classes&gt;
+  &lt;/test&gt;
+&lt;/suite&gt;
+</pre>
+
+You can specify package names instead of class names:
+
+<h3 class="sourcetitle">testng.xml</h3>
+<pre class="brush: xml; highlight: [5,6,7]">
+&lt;!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" &gt;
+
+&lt;suite name="Suite1" verbose="1" &gt;
+  &lt;test name="Regression1"   &gt;
+    &lt;packages&gt;
+      &lt;package name="test.sample" /&gt;
+   &lt;/packages&gt;
+ &lt;/test&gt;
+&lt;/suite&gt;
+</pre>
+
+
+<p>In this example, TestNG will look at all the classes in the package 
+	<tt>test.sample</tt> and will retain only classes that have TestNG annotations.</p>
+
+You can also specify groups and methods to be included and excluded:
+
+<h3 class="sourcetitle">testng.xml</h3>
+<pre class="brush: xml">
+&lt;test name="Regression1"&gt;
+  &lt;groups&gt;
+    &lt;run&gt;
+      &lt;exclude name="brokenTests"  /&gt;
+      &lt;include name="checkinTests"  /&gt;
+    &lt;/run&gt;
+  &lt;/groups&gt;
+ 
+  &lt;classes&gt;
+    &lt;class name="test.IndividualMethodsTest"&gt;
+      &lt;methods&gt;
+        &lt;include name="testMethod" /&gt;
+      &lt;/methods&gt;
+    &lt;/class&gt;
+  &lt;/classes&gt;
+&lt;/test&gt;
+</pre>
+
+<p>You can also define new groups inside <tt>testng.xml</tt> and specify additional details in attributes, such as whether to run the tests in parallel, how many threads to use, whether you are running JUnit tests, etc...&nbsp;
+<p>
+
+By default, TestNG will run your tests in the order they are found in the XML
+file. If you want the classes and methods listed in this file to be
+run in an unpredictible order, set the <tt>preserve-order</tt>
+attribute to <tt>false</tt>
+
+<h3 class="sourcetitle">testng.xml</h3>
+<pre class="brush: xml">
+&lt;test name="Regression1" preserve-order="false"&gt;
+  &lt;classes&gt;
+
+    &lt;class name="test.Test1"&gt;
+      &lt;methods&gt;
+        &lt;include name="m1" /&gt;
+        &lt;include name="m2" /&gt;
+      &lt;/methods&gt;
+    &lt;/class&gt;
+
+    &lt;class name="test.Test2" /&gt;
+
+  &lt;/classes&gt;
+&lt;/test&gt;
+</pre>
+
+<p>
+
+Please see the DTD for a complete list of the features, or read on.</p>
+
+<!-------------------------------------
+  RUNNING TESTNG
+  ------------------------------------>
+
+<h3><a class="section" name="running-testng">Running TestNG</a></h3>
+
+TestNG can be invoked in different ways:
+
+<ul>
+<li>Command line
+</li>
+<li><a href="ant.html">ant</a>
+</li>
+<li><a href="eclipse.html">Eclipse</a>
+</li>
+<li><a href="idea.html">IntelliJ's IDEA</a>
+</li>
+</ul>
+
+This section only explains how to invoke TestNG from the command line.  Please click on one of the links above if you are interested in one of the other ways.
+<p>
+Assuming that you have TestNG in your class path, the simplest way to invoke TestNG is as follows:
+
+<pre class="brush: text">
+java org.testng.TestNG testng1.xml [testng2.xml testng3.xml ...]
+</pre>
+
+You need to specify at least one XML file describing the TestNG suite you are trying to run.  Additionally, the following command-line switches are available:
+
+</p>
+
+<table id="command-line">
+    <caption>Command Line Parameters</caption><colgroup class="option"/>
+    <colgroup class="argument"/>
+    <colgroup class="documentation"/>
+    <thead>
+
+        <tr>
+            <th>Option</th>
+    	<th>Argument</th>
+    	<th>Documentation</th>
+        </tr>
+    </thead>
+
+    <tbody>
+        <tr>
+            <td>-configfailurepolicy</td>
+	    <td><tt>skip</tt>|<tt>continue</tt></td>
+	    <td>Whether TestNG should <tt>continue</tt> to execute the remaining tests in the suite or <tt>skip</tt> them if
+            an @Before* method fails.  Default behavior is <tt>skip</tt>.</td>
+        </tr>
+
+        <tr>
+            <td>-d</td>
+	    <td>A directory</td>
+	    <td>The directory where the reports will be generated (defaults to <tt>test-output</tt>).</td>
+        </tr>
+
+        <tr>
+            <td>-dataproviderthreadcount</td>
+	    <td>The default number of threads to use for data
+            providers when running tests in parallel.</td>
+	    <td>This sets the default maximum number of threads to use
+            for data providers when running tests in parallel. It will only take effect if the parallel mode has been selected (for example, with the -parallel option). This can be overridden in the suite definition.</td>
+        </tr>
+
+	<tr>
+            <td>-excludegroups</td>
+	    <td>A comma-separated list of groups.</td><td>The list of groups you want to be excluded from this run.</td>
+        </tr>
+	  
+        <tr>
+            <td>-groups</td>
+	    <td>A comma-separated list of groups.</td>
+	    <td>The list of groups you want to run (e.g. <tt>"windows,linux,regression"</tt>).</td>
+	</tr>
+
+        <tr>
+            <td>-listener</td>
+	    <td>A comma-separated list of Java classes that can be found on your classpath.</td>
+	    <td>Lets you specify your own test listeners.  The classes need to implement <a href="../javadocs/org/testng/ITestListener.html"> <tt>org.testng.ITestListener</tt></a></td>
+        </tr>
+
+	<tr>
+            <td>-methods</td>
+	    <td>A comma separated list of fully qualified class name and method. For example <tt>com.example.Foo.f1,com.example.Bar.f2</tt>.</td>
+	    <td>Lets you specify individual methods to run.</tt></a></td>
+        </tr>
+
+        <tr>
+            <td>-methodselectors</td>
+	    <td>A comma-separated list of Java classes and method
+            priorities that define method selectors.</td>
+	    <td>Lets you specify method selectors on the command
+            line. For example: <tt>com.example.Selector1:3,com.example.Selector2:2</tt></td>
+        </tr>
+
+	<tr>
+            <td>-parallel</td>
+	    <td>methods|tests|classes</td>
+	    <td>If specified, sets the default mechanism used to determine how to use parallel threads when running tests. If not set, default mechanism is not to use parallel threads at all. This can be overridden in the suite definition.</td>
+        </tr>
+
+        <tr>
+            <td>-reporter</td>
+	    <td>The extended configuration for a custom report listener.</td>
+	    <td>Similar to the <tt>-listener</tt> option, except that it allows the configuration of JavaBeans-style properties on the reporter instance.
+	      <br>
+            Example: <tt>-reporter com.test.MyReporter:methodFilter=*insert*,enableFiltering=true</tt>
+	      <br>
+            You can have as many occurences of this option, one for each reporter that needs to be added.</td>
+        </tr>
+
+        <tr>
+            <td>-sourcedir</td>
+    	    <td>A semi-colon separated list of directories.</td>
+            <td>The directories where your javadoc annotated test sources are. This option is only necessary if you are using javadoc type annotations. (e.g. <tt>"src/test"</tt> or <tt>"src/test/org/testng/eclipse-plugin;src/test/org/testng/testng"</tt>).</td>
+        </tr>
+
+       <tr>
+           <td>-suitename</td>
+	   <td>The default name to use for a test suite.</td>
+	   <td>This specifies the suite name for a test suite defined on the command line. This option is ignored if the suite.xml file or the source code specifies a different suite name.  It is possible to create a suite name with spaces in it if you surround it with double-quotes "like this".</td>
+        </tr>
+        
+        <tr>
+            <td>-testclass</td>
+	    <td>A comma-separated list of classes that can be found in your classpath.</td><td>A list of class files separated by commas (e.g. <tt>"org.foo.Test1,org.foo.test2"</tt>).</td>
+	</tr>
+
+         <tr>
+            <td>-testjar</td>
+	    <td>A jar file.</td>
+	    <td>Specifies a jar file that contains test classes.  If a <tt>testng.xml</tt> file is found at the root of that jar file, it will be used, otherwise, all the test classes found in this jar file will be considered test classes.</td>
+        </tr>
+
+        <tr>
+            <td>-testname</td>
+	    <td>The default name to use for a test.</td>
+	    <td>This specifies the name for a test defined on the command line. This option is ignored if the suite.xml file or the source code specifies a different test name. It is possible to create a test name with spaces in it if you surround it with double-quotes "like this".</td>
+        </tr>
+
+        <tr>
+            <td>-testnames</td>
+	    <td>A comma separated list of test names.</td>
+	    <td>Only tests defined in a &lt;test&gt; tag matching one of these names will be run.</td>
+        </tr>
+
+        <tr>
+            <td>-testrunfactory</td>
+	    <td>A Java classes that can be found on your classpath.</td>
+	    <td>Lets you specify your own test runners.  The class needs to implement <a href="../javadocs/org/testng/ITestRunnerFactory.html"> <tt>org.testng.ITestRunnerFactory</tt></a>.</td>
+        </tr>
+
+        <tr>
+            <td>-threadcount</td>
+	    <td>The default number of threads to use when running tests in parallel.</td>
+	    <td>This sets the default maximum number of threads to use for running tests in parallel. It will only take effect if the parallel mode has been selected (for example, with the -parallel option). This can be overridden in the suite definition.</td>
+        </tr>
+
+	<tr>
+            <td>-xmlpathinjar</td>
+	    <td>The path of the XML file inside the jar file.</td>
+	    <td>This attribute should contain the path to a valid XML file inside the test jar (e.g. <tt>"resources/testng.xml"</tt>). The default is <tt>"testng.xml"</tt>, which means a file called "<tt>testng.xml</tt>" at the root of the jar file. This option will be ignored unless <tt>-testjar</tt> is specified.</td>
+        </tr>
+
+      </tbody>
+</table>
+
+<p>
+
+This documentation can be obtained by invoking TestNG without any arguments.
+
+<p>
+
+You can also put the command line switches in a text file, say <tt>c:\command.txt</tt>, and tell TestNG to use that file to retrieve its parameters:
+
+<pre class="brush: text">
+  C:> more c:\command.txt
+  -d test-output testng.xml
+  C:> java org.testng.TestNG @c:\command.txt
+</pre>
+
+<p>
+
+Additionally, TestNG can be passed properties on the command line of the Java Virtual Machine, for example
+
+<pre class="brush: text">
+java -Dtestng.test.classpath="c:/build;c:/java/classes;" org.testng.TestNG testng.xml
+</pre>
+
+Here are the properties that TestNG understands:
+
+<table id="system=properties">
+    <caption>System properties</caption>
+    <colgroup class="option"/>
+    <colgroup class="type"/>
+    <colgroup class="documentation"/>
+    <thead>
+      <tr><th>Property</th>
+      <th>Type</th>
+      <th>Documentation</th></tr>
+    </thead>
+
+    <tr>
+    <td>testng.test.classpath</td>
+    <td>A semi-colon separated series of directories that contain your test classes.</td>
+    <td>If this property is set, TestNG will use it to look for your test classes instead of the class path.  This is convenient if you are using the <tt>package</tt> tag in your XML file and you have a lot of classes in your classpath, most of them not being test classes.
+    </tr>
+</table>
+
+<br>
+
+<b>Example:</b>
+
+<pre class="brush: text">
+java org.testng.TestNG -groups windows,linux -testclass org.test.MyTest
+</pre>
+
+The <a href="ant.html">ant task</a> and <a href="#testng-xml">testng.xml</a> allow you to launch TestNG with more parameters (methods to include, specifying parameters, etc...), so you should consider using the command line only when you are trying to learn about TestNG and you want to get up and running quickly.
+
+<p>
+
+<em>Important</em>: The command line flags that specify what tests should be run will be ignored if you also specify a <tt>testng.xml</tt> file, with the exception of <tt>-includedgroups</tt> and <tt>-excludedgroups</tt>, which will override all the group inclusions/exclusions found in <tt>testng.xml</tt>.
+
+<!-------------------------------------
+  METHODS
+  ------------------------------------>
+
+<h3><a class="section" name="methods">Test methods, Test classes and Test groups</a></h3>
+
+<h4><a class="section" indent=".." name="test-methods">Test methods</a></h4>
+
+Test methods are annotated with <tt>@Test</tt>. Methods annotated with <tt>@Test</tt> that happen to return a value will be ignored, unless you set <tt>allow-return-values</tt> to <tt>true</tt> in your <tt>testng.xml</tt>:
+
+<pre class="brush: xml">
+&lt;suite allow-return-values="true"&gt;
+
+or
+
+&lt;test allow-return-values="true"&gt;
+</pre>
+
+
+<h4><a class="section" indent=".." name="test-groups">Test groups</a></h4>
+
+<p>
+
+TestNG allows you to perform sophisticated groupings of test methods. Not 
+only can you declare that methods belong to groups, but you can also specify 
+groups that contain other groups. Then TestNG can be invoked and asked to 
+include a certain set of groups (or regular expressions) while excluding another 
+set.&nbsp; This gives you maximum flexibility in how you partition your tests 
+and doesn't require you to recompile anything if you want to run two different 
+sets of tests back to back.</p>
+
+<p>
+Groups are specified in your <tt>testng.xml</tt> file and can be found either under the <tt>&lt;test&gt;</tt> or <tt>&lt;suite&gt;</tt> tag. Groups specified in the <tt>&lt;suite&gt;</tt> tag apply to all the <tt>&lt;test&gt;</tt> tags underneath. Note that groups are accumulative in these tags: if you specify group "a" in <tt>&lt;suite&gt;</tt> and "b" in <tt>&lt;test&gt;</tt>, then both "a" and "b" will be included.
+
+<p>
+
+<p>For example, it is quite common to have at least two categories of tests</p><ul>
+  <li>Check-in tests.&nbsp; These tests should be run before you submit new 
+	code.&nbsp; They should typically be fast and just make sure no basic 
+	functionality was broken.<br>
+&nbsp;</li>
+  <li>Functional tests.&nbsp; These tests should cover all the functionalities 
+	of your software and be run at least once a day, although ideally you would 
+	want to run them continuously.</li></ul>
+
+
+	Typically, check-in tests are a subset of functional tests.&nbsp; TestNG 
+allows you to specify this in a very intuitive way with test groups.&nbsp; For 
+example, you could structure your test by saying that your entire test class 
+belongs to the &quot;functest&quot; group, and additionally that a couple of methods 
+belong to the group &quot;checkintest&quot;:
+
+<h3 class="sourcetitle">Test1.java</h3>
+<pre class="brush: java">
+public class Test1 {
+  @Test(groups = { "functest", "checkintest" })
+  public void testMethod1() {
+  }
+
+  @Test(groups = {"functest", "checkintest"} )
+  public void testMethod2() {
+  }
+
+  @Test(groups = { "functest" })
+  public void testMethod3() {
+  }
+}
+</pre>
+
+Invoking TestNG with
+	<br>
+
+<h3 class="sourcetitle">testng.xml</h3>
+<pre class="brush: xml">
+&lt;test name="Test1"&gt;
+  &lt;groups&gt;
+    &lt;run&gt;
+      &lt;include name="functest"/&gt;
+    &lt;/run&gt;
+  &lt;/groups&gt;
+  &lt;classes&gt;
+    &lt;class name="example1.Test1"/&gt;
+  &lt;/classes&gt;
+&lt;/test&gt;
+</pre>
+
+<p>will run all the test methods in that classes, while invoking it with <tt>checkintest</tt> will only run 
+<tt>testMethod1()</tt> and <tt>testMethod2()</tt>.</p>
+
+
+Here is another example, using regular expressions this time.&nbsp; Assume 
+that some of your test methods should not be run on Linux, your test would look 
+like:
+
+<h3 class="sourcetitle">Test1.java</h3>
+<pre class="brush: java">
+@Test
+public class Test1 {
+  @Test(groups = { "windows.checkintest" }) 
+  public void testWindowsOnly() {
+  }
+
+  @Test(groups = {"linux.checkintest"} )
+  public void testLinuxOnly() {
+  }
+
+  @Test(groups = { "windows.functest" )
+  public void testWindowsToo() {
+  }
+}
+</pre>
+
+
+You could use the following testng.xml to launch only the Windows methods:
+
+<h3 class="sourcetitle">testng.xml</h3>
+<pre class="brush: xml; highlight: [4,9]">
+&lt;test name="Test1"&gt;
+  &lt;groups&gt;
+    &lt;run&gt;
+      &lt;include name="windows.*"/&gt;
+    &lt;/run&gt;
+  &lt;/groups&gt;
+
+  &lt;classes&gt;
+    &lt;class name="example1.Test1"/&gt;
+  &lt;/classes&gt;
+&lt;/test&gt;
+</pre>
+
+<blockquote>
+<em>Note:  TestNG uses <a href="http://en.wikipedia.org/wiki/Regular_expression">regular expressions</a>, and not <a href="http://en.wikipedia.org/wiki/Wildmat">wildmats</a>.  Be aware of the difference (for example, "anything" is matched by ".*" -- dot star -- and not "*").</em>
+</blockquote>
+
+<h4><a name="method-groups">Method groups</a></h4>
+
+You can also exclude or include individual methods:
+
+<h3 class="sourcetitle">testng.xml</h3>
+<pre class="brush: xml">
+&lt;test name="Test1"&gt;
+  &lt;classes&gt;
+    &lt;class name="example1.Test1"&gt;
+      &lt;methods&gt;
+        &lt;include name=".*enabledTestMethod.*"/&gt;
+        &lt;exclude name=".*brokenTestMethod.*"/&gt;
+      &lt;/methods&gt;
+     &lt;/class&gt;
+  &lt;/classes&gt;
+&lt;/test&gt;
+</pre>
+
+This can come in handy to deactivate a single method without having to recompile 
+anything, but I don't recommend using this technique too much since it makes 
+your testing framework likely to break if you start refactoring your Java code 
+(the regular expressions used in the tags might not match your methods any 
+more).
+
+
+<h4><a class="section" indent=".." name="groups-of-groups">Groups of groups</a></h4>
+
+Groups can also include other groups.&nbsp;These groups are called &quot;MetaGroups&quot;.&nbsp; 
+For example, you might want to define a group &quot;all&quot; that includes &quot;checkintest&quot; 
+and &quot;functest&quot;.&nbsp; &quot;functest&quot; itself will contain the groups &quot;windows&quot; and 
+&quot;linux&quot; while &quot;checkintest will only contain &quot;windows&quot;.&nbsp; Here is how you 
+would define this in your property file:
+
+
+<h3 class="sourcetitle">testng.xml</h3>
+<pre class="brush: xml">
+&lt;test name="Regression1"&gt;
+  &lt;groups&gt;
+    &lt;define name="functest"&gt;
+      &lt;include name="windows"/&gt;
+      &lt;include name="linux"/&gt;
+    &lt;/define&gt;
+ 
+    &lt;define name="all"&gt;
+      &lt;include name="functest"/&gt;
+      &lt;include name="checkintest"/&gt;
+    &lt;/define&gt;
+ 
+    &lt;run&gt;
+      &lt;include name="all"/&gt;
+    &lt;/run&gt;
+  &lt;/groups&gt;
+ 
+  &lt;classes&gt;
+    &lt;class name="test.sample.Test1"/&gt;
+  &lt;/classes&gt;
+&lt;/test&gt;
+</pre>
+
+</p><!-------------------------------------
+  EXCLUSION
+  ------------------------------------>
+
+<h4><a class="section" indent=".." name="exclusions">Exclusion groups</a></h4>
+
+<p>TestNG allows you to include groups as well as exclude them.</p>
+
+
+For example, it is quite usual to have tests that temporarily break because 
+of a recent change, and you don't have time to fix the breakage yet.&nbsp; 4
+However, you do want to have clean runs of your functional tests, so you need to 
+deactivate these tests but keep in mind they will need to be reactivated.</p><p>A simple way to solve this problem is to create a group called &quot;broken&quot; and 
+make these test methods belong to it.&nbsp; For example, in the above example, I 
+know that testMethod2() is now broken so I want to disable it:
+
+
+<h3 class="sourcetitle">Java</h3>
+<pre class="brush: java">
+@Test(groups = {"checkintest", "broken"} )
+public void testMethod2() {
+}
+</pre>
+
+All I need to do now is to exclude this group from the run:
+
+<h3 class="sourcetitle">testng.xml</h3>
+<pre class="brush: xml; highlight: 5">
+&lt;test name="Simple example"&gt;
+  &lt;groups&gt;
+    &lt;run&gt;
+      &lt;include name="checkintest"/&gt;
+      &lt;exclude name="broken"/&gt;
+    &lt;/run&gt;
+  &lt;/groups&gt;
+ 
+  &lt;classes&gt;
+    &lt;class name="example1.Test1"/&gt;
+  &lt;/classes&gt;
+&lt;/test&gt;
+</pre>
+
+<p>This way, I will get a clean test run while keeping track of what tests are 
+broken and need to be fixed later.</p>
+
+<blockquote>
+	<p><i>Note:&nbsp; you can also disable tests on an individual basis by using the 
+&quot;enabled&quot; property available on both @Test and @Before/After
+	annotations.</i></p>
+</blockquote>
+
+
+<!-------------------------------------
+  PARTIAL GROUPS
+  ------------------------------------>
+
+<h4><a class="section" indent=".." name="partial-groups">Partial groups</a></h4>
+
+You can define  groups at the class level and then add groups at the method level:
+
+<h3 class="sourcetitle">All.java</h3>
+<pre class="brush: java">
+@Test(groups = { "checkin-test" })
+public class All {
+
+  @Test(groups = { "func-test" )
+  public void method1() { ... }
+
+  public void method2() { ... }
+}
+</pre>
+
+In this class, method2() is part of the group &quot;checkin-test&quot;, which is defined 
+at the class level, while method1() belongs to both &quot;checkin-test&quot; and 
+&quot;func-test&quot;.
+
+<!-------------------------------------
+  PARAMETERS
+  ------------------------------------>
+
+<h4><a class="section" indent=".." name="parameters">Parameters</a></h4>
+
+<p>
+
+
+Test methods don't have to be parameterless.&nbsp; You can use an arbitrary 
+number of parameters on each of your test method, and you instruct TestNG to 
+pass you the correct parameters with the <tt>@Parameters</tt> annotation.</p><p>
+
+
+There are two ways to set these parameters:&nbsp; with <tt>testng.xml</tt> or 
+programmatically.</p>
+
+
+<h5><a class="section" indent="..." name="parameters-testng-xml">Parameters from <tt>testng.xml</tt></a></h5>
+
+
+If you are using simple values for your parameters, you can specify them in your 
+<tt>testng.xml</tt>:
+
+
+<h3 class="sourcetitle">Java</h3>
+<pre class="brush: java">
+@Parameters({ "first-name" })
+@Test
+public void testSingleString(String firstName) { 
+  System.out.println("Invoked testString " + firstName);
+  assert "Cedric".equals(firstName);
+}
+</pre>
+
+In this code, we specify that the parameter <tt>firstName</tt> of your Java method 
+should receive the value of the XML parameter called <tt>first-name</tt><i>.</i>&nbsp; 
+This XML parameter is defined in <tt>testng.xml</tt>:<p>
+
+<h3 class="sourcetitle">testng.xml</h3>
+<pre class="brush: xml">
+&lt;suite name="My suite"&gt;
+  &lt;parameter name="first-name"  value="Cedric"/&gt;
+  &lt;test name="Simple example"&gt;
+  &lt;-- ... --&gt;
+</pre>
+
+<h4><span style="font-weight: 400">The same technique can be used for <tt>@Before/After </tt>and <tt>@Factory</tt> annotations:</span></h4>
+
+<pre class="brush: java">
+@Parameters({ "datasource", "jdbcDriver" })
+@BeforeMethod
+public void beforeTest(String ds, String driver) {
+  m_dataSource = ...;                              // look up the value of datasource
+  m_jdbcDriver = driver;
+}
+</pre>
+
+This time, the two Java parameter <i>ds</i> 
+and <i>driver</i> will receive the value given to the properties <tt>datasource</tt> 
+and <tt>jdbc-driver </tt>respectively.&nbsp;
+
+<p>
+
+Parameters can be declared optional with the <a href="../javadocs/org/testng/annotations/Optional.html"><tt>Optional</tt></a> annotation:
+
+<pre class="brush: java">
+@Parameters("db")
+@Test
+public void testNonExistentParameter(@Optional("mysql") String db) { ... }
+</pre>
+
+If no parameter named "db" is found in your <tt>testng.xml</tt> file, your test method will receive the default value specified inside the <tt>@Optional</tt> annotation: "mysql".
+
+<p>The <tt>@Parameters</tt> annotation can be placed at the following locations:</p><ul>
+	<li>On any method that already has a <tt>@Test</tt>, <tt>@Before/After</tt> 
+	or <tt>@Factory</tt> annotation.</li><li>On at most one constructor of your test class.&nbsp; In this case, 
+	TestNG will invoke this particular constructor with the parameters 
+	initialized to the values specified in <tt>testng.xml</tt> whenever it needs 
+	to instantiate your test class.&nbsp; This feature can be used to initialize fields 
+	inside your classes to values that will then be used by your
+	test methods.</li></ul>
+	<blockquote>
+	<p><i>Notes:
+
+</i>
+	<ul>
+		<li><i>The XML parameters are mapped to the Java parameters in the same order as 
+they are found in the annotation, and TestNG will issue an error if the numbers 
+don't match. </i>
+		<li><i>Parameters are scoped. In <tt>testng.xml</tt>, you can declare them either under a 
+		<tt>&lt;suite&gt;</tt> tag or under <tt>&lt;test&gt;</tt>. If two parameters have the same name, it's the one 
+defined in <tt>&lt;test&gt;</tt> that has precedence. This is convenient if you need to specify 
+a parameter applicable to all your tests and override its value only for certain 
+tests. </i>
+		</ul>
+	<p></p>
+</blockquote>
+
+
+<h5><a class="section" indent="..." name="parameters-dataproviders">Parameters with DataProviders</a></h5>
+
+
+<p>Specifying parameters in <tt>testng.xml</tt> might not be sufficient if you need to pass complex parameters, or parameters that need to be created  from Java (complex objects, objects read from a property file or a database, etc...). In this case, you can use a Data Provider to supply the values you need to test.&nbsp; A Data Provider is a method on your class that returns an array of array of objects.&nbsp; This method is annotated with <tt>@DataProvider</tt>:
+
+<h3 class="sourcetitle">Java</h3>
+<pre class="brush: java">
+//This method will provide data to any test method that declares that its Data Provider
+//is named "test1"
+@DataProvider(name = "test1")
+public Object[][] createData1() {
+ return new Object[][] {
+   { "Cedric", new Integer(36) },
+   { "Anne", new Integer(37)}, 
+ };
+}
+
+//This test method declares that its data should be supplied by the Data Provider
+//named "test1"
+@Test(dataProvider = "test1")
+public void verifyData1(String n1, Integer n2) {
+ System.out.println(n1 + " " + n2);
+} 
+</pre>
+will print
+
+<pre class="brush: text">
+Cedric 36
+Anne 37
+</pre>
+
+A <tt>@Test</tt> method specifies its Data Provider with the <tt>dataProvider</tt> attribute.&nbsp; 
+This name must correspond to a method on the same class annotated with <tt>@DataProvider(name=&quot;...&quot;)</tt> 
+with a matching name.
+
+<p>
+By default, the data provider will be looked for in the current test class or one of its base classes.  If you want to put your data provider in a different class, it needs to be a static method or a class with a non-arg constructor, and you specify the class where it can be found in the <tt>dataProviderClass</tt> attribute:
+
+<h3 class="sourcetitle">StaticProvider.java</h3>
+<pre class="brush: java">
+public class StaticProvider {
+  @DataProvider(name = "create")
+  public static Object[][] createData() {
+    return new Object[][] {
+      new Object[] { new Integer(42) }
+    };
+  }
+}
+
+public class MyTest {
+  @Test(dataProvider = "create", dataProviderClass = StaticProvider.class)
+  public void test(Integer n) {
+    // ...
+  }
+}
+</pre>
+
+The data provider supports injection too. TestNG will use the test context for the injection.
+
+The Data Provider method can return one of the following two types:
+
+<ul>
+<li>An array of array of objects (<tt>Object[][]</tt>) where the first dimension's size is the number of times the test method will be invoked and the second dimension size contains an array of objects that must be compatible with the parameter types of the test method. This is the cast illustrated by the example above.</li><li>An <tt>Iterator&lt;Object[]&gt;</tt>.  The only difference with <tt>Object[][]</tt> is that an <tt>Iterator</tt> lets you create your test data lazily.  TestNG will invoke the iterator and then the test method with the parameters returned by this iterator one by one.  This is particularly useful if you have a lot of parameter sets to pass to the method and you don't want to create all of them upfront.
+</ul>
+Here is an example of this feature:
+
+<pre class="brush: java">
+@DataProvider(name = "test1")
+public Iterator&lt;Object[]> createData() {
+  return new MyIterator(DATA);
+} 
+</pre>
+
+If you declare your <tt>@DataProvider</tt> as taking a <tt>java.lang.reflect.Method</tt>
+as first parameter, TestNG will pass the current test method for this
+first parameter.  This is particularly useful when several test methods
+use the same <tt>@DataProvider</tt> and you want it to return different
+values depending on which test method it is supplying data for.
+<p>
+For example, the following code prints the name of the test method inside its <tt>@DataProvider</tt>:
+
+<pre class="brush: java">
+@DataProvider(name = "dp")
+public Object[][] createData(Method m) {
+  System.out.println(m.getName());  // print test method name
+  return new Object[][] { new Object[] { "Cedric" }};
+}
+
+@Test(dataProvider = "dp")
+public void test1(String s) {
+}
+
+@Test(dataProvider = "dp")
+public void test2(String s) {
+}
+</pre>
+
+and will therefore display:
+
+<pre class="brush: text">
+test1
+test2
+</pre>
+
+Data providers can run in parallel with the attribute <tt>parallel</tt>:
+
+<pre class="brush: java">
+@DataProvider(parallel = true)
+// ...
+</pre>
+
+Parallel data providers running from an XML file share the same pool of threads, which has a size of 10 by default.  You can modify this value in the <tt>&lt;suite&gt;</tt> tag of your XML file:
+
+
+<pre class="brush: xml">
+&lt;suite name="Suite1" data-provider-thread-count="20" &gt;
+... 
+</pre>
+
+If you want to run a few specific data providers in a different thread pool, you need to run them from a different XML file.
+
+<p>
+
+<h5><a class="section" indent="..." name="parameters-reports">Parameters in reports</a></h5>
+
+<p>
+
+Parameters used to invoke your test methods are shown in the HTML reports generated by TestNG.  Here is an example:
+
+<p align="center">
+<img src="pics/parameters.png" />
+</p>
+
+
+</p>
+
+
+<!-------------------------------------
+  DEPENDENCIES
+  ------------------------------------>
+
+
+<h4><a class="section" indent=".." name="dependent-methods">Dependencies</a></h4>
+
+<p>Sometimes, you need
+your test methods to be invoked in a certain order.&nbsp; Here are a
+few examples:
+
+<ul>
+<li>To make sure a certain number of test methods have completed and succeeded 
+before running more test methods.
+<li>To initialize your tests while wanting this initialization methods to be 
+test methods as well (methods tagged with <tt>@Before/After</tt> will not be part of the 
+final report).
+</ul>
+
+TestNG allows you to specify dependencies either with annotations or
+in XML.
+
+<h5><a class="section" indent="..." name="dependencies-with-annotations">Dependencies with annotations</a></h5>
+
+<p>You can use the attributes <tt>dependsOnMethods</tt> or <tt>dependsOnGroups</tt>, found on the <tt>@Test</tt> annotation.</p>There are two kinds of dependencies:
+
+<ul>
+<li><b>Hard dependencies</b>.  All the methods you depend on must have run and succeeded for you to run.  If at least one failure occurred in your dependencies, you will not be invoked and marked as a SKIP in the report.
+</li>
+<li><b>Soft dependencies</b>.  You will always be run after the methods you depend on, even if some of them have failed.  This is useful when you just want to make sure that your test methods are run in a certain order but their success doesn't really depend on the success of others.  A soft dependency is obtained by adding <tt>"alwaysRun=true"</tt> in your <tt>@Test</tt> annotation.
+</ul>
+
+Here is an example of a hard dependency:
+
+<pre class="brush: java">
+@Test
+public void serverStartedOk() {}
+
+@Test(dependsOnMethods = { "serverStartedOk" })
+public void method1() {}
+</pre>
+
+<p>In this example, <tt>method1()</tt> is declared as depending on method 
+serverStartedOk(), which guarantees that serverStartedOk() 
+will always be invoked first.</p><p>You can also have methods that depend on entire groups:</p>
+
+<pre class="brush: java">
+@Test(groups = { "init" })
+public void serverStartedOk() {}
+
+@Test(groups = { "init" })
+public void initEnvironment() {}
+
+@Test(dependsOnGroups = { "init.*" })
+public void method1() {}
+</pre>
+
+<p>In this example, method1() is declared as depending on any group matching the 
+regular expression &quot;init.*&quot;, which guarantees that the methods <tt>serverStartedOk()</tt> 
+and <tt>initEnvironment()</tt> will always be invoked before <tt>method1()</tt>.&nbsp; </p>
+<blockquote>
+	<p><i>Note:&nbsp; as stated before, the order of invocation for methods that 
+	belong in the same group is not guaranteed to be the same across test runs.</i></p></blockquote><p>If a method depended upon fails and you have a hard dependency on it (<tt>alwaysRun=false</tt>, which is the default), the methods that depend on it are <b>not</b> 
+marked as <tt>FAIL</tt> but as <tt>SKIP</tt>.&nbsp; Skipped methods will be reported as such in 
+the final report (in a color that is neither red nor green in HTML), 
+which is important since skipped methods are not necessarily failures.</p><p>Both <tt>dependsOnGroups</tt> and <tt>dependsOnMethods</tt> accept regular 
+expressions as parameters.&nbsp; For <tt>dependsOnMethods</tt>, if you are 
+depending on a method which happens to have several overloaded versions, all the 
+overloaded methods will be invoked.&nbsp; If you only want to invoke one of the 
+overloaded methods, you should use <tt>dependsOnGroups</tt>.</p><p>For a more advanced example of dependent methods, please refer to 
+<a href="http://beust.com/weblog/archives/000171.html">this article</a>, which 
+uses inheritance to provide an elegant solution to the problem of multiple 
+dependencies.</p>
+
+By default, dependent methods are grouped by class. For example, if method <tt>b()</tt> depends on method <tt>a()</tt> and you have several instances of the class that contains these methods (because of a factory of a data provider), then the invocation order will be as follows:
+
+<pre class="brush: plain">
+a(1)
+a(2)
+b(2)
+b(2)
+</pre>
+
+TestNG will not run <tt>b()</tt> until all the instances have invoked their <tt>a()</tt> method.
+
+<p>
+
+This behavior might not be desirable in certain scenarios, such as for example testing a sign in and sign out of a web browser for various countries. In such a case, you would like the following ordering:
+
+<pre class="brush: plain">
+signIn("us")
+signOut("us")
+signIn("uk")
+signOut("uk")
+</pre>
+
+For this ordering, you can use the XML attribute <tt>group-by-instances</tt>. This attribute is valid either on &lt;suite&gt; or &lt;test&gt;:
+
+<pre class="brush: xml">
+  &lt;suite name="Factory" group-by-instances="true"&gt;
+or
+  &lt;test name="Factory" group-by-instances="true"&gt;
+</pre>
+
+
+<h5><a class="section" indent="..." name="dependencies-in-xml">Dependencies in XML</a></h5>
+
+Alternatively, you can specify your group dependencies in the <tt>testng.xml</tt> file. You use the <tt>&lt;dependencies&gt;</tt> tag to achieve this:
+
+<pre class="brush: xml">
+  &lt;test name="My suite"&gt;
+    &lt;groups&gt;
+      &lt;dependencies&gt;
+        &lt;group name="c" depends-on="a  b" /&gt;
+        &lt;group name="z" depends-on="c" /&gt;
+      &lt;/dependencies&gt;
+    &lt;/groups&gt;
+  &lt;/test&gt;
+</pre>
+
+The <tt>&lt;depends-on&gt;</tt> attribute contains a space-separated list of groups.
+
+
+<!-------------------------------------
+  FACTORIES
+  ------------------------------------>
+
+<h4><a class="section" indent=".." name="factories">Factories</a></h4>
+
+Factories allow you to create tests dynamically. For example, imagine you 
+want to create a test method that will access a page on a Web site several 
+times, and you want to invoke it with different values:
+
+<h3 class="sourcetitle">TestWebServer.java</h3>
+<pre class="brush: java">
+public class TestWebServer {
+  @Test(parameters = { "number-of-times" })
+  public void accessPage(int numberOfTimes) {
+    while (numberOfTimes-- > 0) {
+     // access the web page
+    }
+  }
+}
+</pre>
+
+<h3 class="sourcetitle">testng.xml</h3>
+<pre class="brush: java">
+&lt;test name="T1"&gt;
+&nbsp;&nbsp;&lt;parameter name="number-of-times" value="10"/&gt;
+&nbsp;&nbsp;&lt;class name= "TestWebServer" /&gt;
+&lt;/test&gt;
+
+&lt;test name="T2"&gt;
+&nbsp;&nbsp;&lt;parameter name="number-of-times" value="20"/&gt;
+&nbsp;&nbsp;&lt;class name= "TestWebServer"/&gt;
+&lt;/test&gt;
+
+&lt;test name="T3"&gt;
+&nbsp;&nbsp;&lt;parameter name="number-of-times" value="30"/&gt;
+&nbsp;&nbsp;&lt;class name= "TestWebServer"/&gt;
+&lt;/test&gt;
+</pre>
+
+This can become quickly impossible to manage, so instead, you should use a factory:
+
+<h3 class="sourcetitle">WebTestFactory.java</h3>
+<pre class="brush: java">
+public class WebTestFactory {
+  @Factory
+  public Object[] createInstances() {
+   Object[] result = new Object[10];  
+   for (int i = 0; i < 10; i++) {
+      result[i] = new WebTest(i * 10);
+    }
+    return result;
+  }
+}
+</pre>
+
+and the new test class is now:
+
+<h3 class="sourcetitle">WebTest.java</h3>
+<pre class="brush: java">
+public class WebTest {
+  private int m_numberOfTimes;
+  public WebTest(int numberOfTimes) {
+    m_numberOfTimes = numberOfTimes;
+  }
+
+  @Test
+  public void testServer() {
+   for (int i = 0; i < m_numberOfTimes; i++) {
+     // access the web page
+    }
+  }
+}
+</pre>
+
+<p>Your <tt>testng.xml</tt> only needs to reference the class that 
+contains the factory method, since the test instances themselves will be created 
+at runtime:</p>
+
+<pre class="brush: java">
+&lt;class name="WebTestFactory" /&gt;
+</pre>
+
+<p>The factory method can receive parameters just like <tt>@Test</tt> and <tt>@Before/After</tt> and it must return <tt>Object[]</tt>.&nbsp; 
+The objects returned can be of any class (not necessarily the same class as the 
+factory class) and they don't even need to contain TestNG annotations (in which 
+case they will be ignored by TestNG).</p>
+
+<p>
+
+Factories can also be used with data providers, and you can leverage this functionality by putting the <tt>@Factory</tt> annotation either on a regular method or on a constructor. Here is an example of a constructor factory:
+
+<pre class="brush:java">
+  @Factory(dataProvider = "dp")
+  public FactoryDataProviderSampleTest(int n) {
+    super(n);
+  }
+
+  @DataProvider
+  static public Object[][] dp() {
+    return new Object[][] {
+      new Object[] { 41 },
+      new Object[] { 42 },
+    };
+  }
+</pre>
+
+The example will make TestNG create two test classes, on with the constructor invoked with the value 41 and the other with 42.
+
+
+
+<!-------------------------------------
+  CLASS LEVEL ANNOTATIONS
+  ------------------------------------>
+
+<h4><a class="section" indent=".." name="class-level">Class level annotations</a></h4>
+
+The <tt>@Test</tt> annotation can be put on a class instead of a test method:
+
+<h3 class="sourcetitle">Test1.java</h3>
+<pre class="brush: java">
+@Test
+public class Test1 {
+  public void test1() {
+  }
+
+  public void test2() {
+  }
+}
+</pre>
+The effect of a class level <tt>@Test</tt> annotation is to make all the public methods of this class to become test methods even if they are not annotated.  You can still repeat the <tt>@Test</tt> annotation on a method if you want to add certain attributes.
+<p>
+
+For example:
+
+<h3 class="sourcetitle">Test1.java</h3>
+<pre class="brush: java">
+@Test
+public class Test1 {
+  public void test1() {
+  }
+
+  @Test(groups = "g1")
+  public void test2() {
+  }
+}
+</pre>
+will make both <tt>test1()</tt> and <tt>test2()</tt> test methods but on top of that, <tt>test2()</tt> now belongs to the group "g1".
+<p>
+
+
+
+<!-------------------------------------
+  PARALLEL RUNNING
+  ------------------------------------>
+
+<h4><a class="section" indent=".." name="parallel-running">Parallelism and time-outs</a></h4>
+
+You can  instruct TestNG to run your tests in separate threads in various ways.
+
+<h5><a class="section" indent="..." name="parallel-suites">Parallel suites</a></h5>
+
+This is useful if you are running several suite files (e.g. "<tt>java org.testng.TestNG testng1.xml testng2.xml"</tt>) and you want each of these suites to be run in a separate thread. You can use the following command line flag to specify the size of a thread pool:
+
+<pre class="brush: plain">
+java org.testng.TestNG -suitethreadpoolsize 3 testng1.xml testng2.xml testng3.xml
+</pre>
+
+The corresponding ant task name is <tt>suitethreadpoolsize</tt>.
+
+<h5><a class="section" indent="..." name="parallel-tests">Parallel tests, classes and methods</a></h5>
+The <i>parallel</i> attribute on the &lt;suite&gt; tag can take one of following values:
+
+<pre class="brush: xml">
+&lt;suite name="My suite" parallel="methods" thread-count="5"&gt;
+</pre>
+
+<pre class="brush: xml">
+&lt;suite name="My suite" parallel="tests" thread-count="5"&gt;
+</pre>
+
+<pre class="brush: xml">
+&lt;suite name="My suite" parallel="classes" thread-count="5"&gt;
+</pre>
+
+<pre class="brush: xml">
+&lt;suite name="My suite" parallel="instances" thread-count="5"&gt;
+</pre>
+
+<ul>
+<li>
+<b><tt>parallel="methods"</tt></b>:  TestNG will run all your test methods in separate threads. Dependent methods will also run in separate threads but they will respect the order that you specified.
+</li>
+
+<br>
+
+<li>
+<b><tt>parallel="tests"</tt></b>:  TestNG will run all the methods in the same &lt;test&gt; tag in the same thread, but each &lt;test&gt; tag will be in a separate thread.  This allows you to group all your classes that are not thread safe in the same &lt;test&gt; and guarantee they will all run in the same thread while taking advantage of TestNG using as many threads as possible to run your tests.
+</li>
+
+<br>
+
+<li>
+<b><tt>parallel="classes"</tt></b>:  TestNG will run all the methods in the same class in the same thread, but each class will be run in a separate thread.
+</li>
+
+<br/>
+
+<li>
+<b><tt>parallel="instances"</tt></b>:  TestNG will run all the methods in the same instance in the same thread, but two methods on two different instances will be running in different threads.
+</li>
+
+</ul>
+
+<p>
+
+
+Additionally, the attribute <i>
+thread-count</i> allows you to specify how many threads should be allocated for 
+this execution.<blockquote>
+	<p><i>Note: the <tt>@Test</tt> attribute <tt>timeOut</tt> works in both 
+	parallel and non-parallel mode.</i></p></blockquote>You can also specify that a <tt>@Test</tt> method should be invoked from different threads.  You can use the attribute <tt>threadPoolSize</tt> to achieve this result:
+
+<pre class="brush: java">
+@Test(threadPoolSize = 3, invocationCount = 10,  timeOut = 10000)
+public void testServer() {
+</pre>
+In this example, the function <tt>testServer</tt> will be invoked ten times from three different threads.  Additionally, a time-out of ten seconds guarantees that none of the threads will block on this thread forever. 
+
+<!-------------------------------------
+  RERUNNING
+  ------------------------------------>
+
+
+<h4><a class="section" indent=".." name="rerunning">Rerunning failed tests</a></h4>
+
+Every time tests fail in a suite, TestNG creates a file called <tt>testng-failed.xml</tt> in the output directory.
+This XML file contains the necessary information to rerun only these methods 
+that failed, allowing you to quickly reproduce the failures without having to 
+run the entirety of your tests.&nbsp; Therefore, a typical session would look 
+like this:
+
+<pre class="brush: text">
+java -classpath testng.jar;%CLASSPATH% org.testng.TestNG -d test-outputs testng.xml
+java -classpath testng.jar;%CLASSPATH% org.testng.TestNG -d test-outputs test-outputs\testng-failed.xml
+</pre>
+
+<p>Note that <tt>testng-failed.xml</tt> will contain all the necessary dependent 
+methods so that you are guaranteed to run the methods that failed without any 
+SKIP failures.</p>
+
+<h4><a class="section" indent=".." name="junit">JUnit tests</a></h4>
+
+TestNG can run JUnit 3 and JUnit 4 tests.&nbsp; All you need to do is
+put the JUnit jar file on the classpath, specify your JUnit test classes in the <tt>testng.classNames</tt>
+property and set the <tt>testng.junit</tt> property to true:
+
+<p></p>
+<h3 class="sourcetitle">testng.xml</h3>
+<pre class="brush: xml">
+&lt;test name="Test1" junit="true"&gt;
+  &lt;classes&gt;
+    &lt;!-- ... --&gt;
+</pre>
+
+<p>The behavior of TestNG in this case is similar to JUnit depending on the JUnit version found on the class path:<br>
+</p>
+<ul>
+    <li>JUnit 3:
+<ul>
+  <li>All methods starting with test* in your classes will be run</li><li>If there is a method setUp() on your test class, it will be invoked before 
+	every test method</li><li>If there is a method tearDown() on your test class, it will be invoked 
+	before after every test method</li><li>If your test class contains a method suite(), all the tests returned by 
+	this method will be invoked</li></ul>
+    </li>
+    <li>JUnit 4:
+        <ul>
+            <li>TestNG will use the <tt>org.junit.runner.JUnitCore</tt> runner to run your tests</li>
+        </ul>
+    </li>
+</ul>
+
+<!-------------------------------------
+  JUNIT
+--------------------------------------->
+
+
+<!-------------------------------------
+  RUNNING TESTNG
+ ------------------------------------>
+<h4><a class="section" indent=".." name="running-testng-programmatically">Running TestNG programmatically</a></h4>
+
+You can invoke TestNG from your own programs very easily:
+
+<h3 class="sourcetitle">Java</h3>
+<pre class="brush: java">
+TestListenerAdapter tla = new TestListenerAdapter();
+TestNG testng = new TestNG();
+testng.setTestClasses(new Class[] { Run2.class });
+testng.addListener(tla);
+testng.run(); 
+</pre>
+
+This example creates a <tt><a href="http://testng.org/javadocs/org/testng/TestNG.html">TestNG</a></tt> object and runs the test class <tt>Run2</tt>.  It also adds a <tt>TestListener</tt>.  You can either use the adapter class <tt><a href="http://testng.org/javadocs/org/testng/TestListenerAdapter.html">org.testng.TestListenerAdapter</a></tt> or implement <tt><a href="http://testng.org/javadocs/org/testng/ITestListener.html">org.testng.ITestListener</a></tt> yourself.  This interface contains various callback methods that let you keep track of when a test starts, succeeds, fails, etc... 
+<p>
+Similary, you can invoke TestNG on a <tt>testng.xml</tt> file or you can create a virtual <tt>testng.xml</tt> file yourself.  In order to do this, you can use the classes found the package <tt><a href="http://testng.org/javadocs/org/testng/xml/package-frame.html">org.testng.xml</a></tt>:  <tt><a href="http://testng.org/javadocs/org/testng/xml/XmlClass.html">XmlClass</a></tt>, <tt><a href="http://testng.org/javadocs/org/testng/xml/XmlTest.html">XmlTest</a></tt>, etc...  Each of these classes correspond to their XML tag counterpart.
+<p>
+For example, suppose you want to create the following virtual file:
+
+<pre class="brush: java">
+&lt;suite name="TmpSuite" &gt;
+  &lt;test name="TmpTest" &gt;
+    &lt;classes&gt;
+      &lt;class name="test.failures.Child"  /&gt;
+    &lt;classes&gt;
+    &lt;/test&gt;
+&lt;/suite&gt;
+</pre>
+
+You would use the following code:
+
+<pre class="brush: java">
+XmlSuite suite = new XmlSuite();
+suite.setName("TmpSuite");
+
+XmlTest test = new XmlTest(suite);
+test.setName("TmpTest");
+List&lt;XmlClass&gt; classes = new ArrayList&lt;XmlClass&gt;();
+classes.add(new XmlClass("test.failures.Child"));
+test.setXmlClasses(classes) ;
+</pre>
+And then you can pass this <tt>XmlSuite</tt> to TestNG:
+
+<pre class="brush: java">
+List&lt;XmlSuite&gt; suites = new ArrayList&lt;XmlSuite&gt;();
+suites.add(suite);
+TestNG tng = new TestNG();
+tng.setXmlSuites(suites);
+tng.run(); 
+</pre>
+
+<p>Please see the <a href="../doc/javadocs/org/testng/package-summary.html" target="mainFrame">JavaDocs</a> for the entire API.</p><p>
+
+
+<!-------------------------------------
+  BEANSHELL
+ ------------------------------------>
+	
+<h4><a class="section" indent=".." name="beanshell">BeanShell and advanced group selection</a></h4>
+
+
+	<p>If the <tt>&lt;include&gt;</tt> and <tt>&lt;exclude&gt;</tt> tags in <tt>testng.xml</tt> are not enough for your needs, you can use a <a href="http://beanshell.org">BeanShell</a> expression to decide whether a certain test method should be included in a test run or not. You specify this expression just under the <tt>&lt;test&gt;</tt> tag:</p>
+
+<h3 class="sourcetitle">testng.xml</h3>
+<pre class="brush: xml">
+&lt;test name="BeanShell test"&gt;
+   &lt;method-selectors&gt;
+     &lt;method-selector&gt;
+       &lt;script language="beanshell"&gt;&lt;![CDATA[
+         groups.containsKey("test1")
+       ]]&gt;&lt;/script&gt;
+     &lt;/method-selector&gt;
+   &lt;/method-selectors&gt;
+  &lt;!-- ... --&gt;
+</pre>
+
+When a <tt>&lt;script&gt;</tt> tag is found in <tt>testng.xml</tt>, TestNG will ignore subsequent <tt>&lt;include&gt;</tt> and <tt>&lt;exclude&gt;</tt> of groups and methods in the current <tt>&lt;test&gt;</tt> tag:&nbsp; your BeanShell expression will be the only way to decide whether a test method is included or not.</p><p>Here are additional information on the BeanShell script:</p><ul>
+<li>
+It must return a boolean value.&nbsp; Except for this constraint, any valid BeanShell code is allowed (for example, you might want to return <tt>true </tt>during week days and false during weekends, which would allow you to run tests differently depending on the date).<br>&nbsp;
+</li>
+<li>
+TestNG defines the following variables for your convenience:<br>&nbsp; <b><tt>java.lang.reflect.Method method</tt></b>:&nbsp; the current test method.<br>&nbsp; <b>org.testng.ITestNGMethod testngMethod</b>:&nbsp; the description of the current test method.<br>&nbsp; <b><tt>java.util.Map&lt;String, String&gt; groups</tt></b>:&nbsp; a map of the groups the current test method belongs to.<br>&nbsp;
+</li>
+<li>
+You might want to surround your expression with a <tt>CDATA</tt> declaration (as shown above) to avoid tedious quoting of reserved XML characters).<br>&nbsp;
+</li>
+</ul>
+
+<!-------------------------------------
+  ANNOTATION TRANSFORMERS
+ ------------------------------------>
+	
+<h4><a class="section" indent=".." name="annotationtransformers">Annotation Transformers</a></h4>
+
+TestNG allows you to modify the content of all the annotations at runtime.  This is especially useful if the annotations in the source code are right most of the time, but there are a few situations where you'd like to override their value.
+<p>
+
+In order to achieve this, you need to use an Annotation Transformer.
+
+<p>
+
+An Annotation Transformer is a class that implements the following interface:
+
+<pre class="brush: java">
+public interface IAnnotationTransformer {
+
+  /**
+   * This method will be invoked by TestNG to give you a chance
+   * to modify a TestNG annotation read from your test classes.
+   * You can change the values you need by calling any of the
+   * setters on the ITest interface.
+   * 
+   * Note that only one of the three parameters testClass,
+   * testConstructor and testMethod will be non-null.
+   * 
+   * @param annotation The annotation that was read from your
+   * test class.
+   * @param testClass If the annotation was found on a class, this
+   * parameter represents this class (null otherwise).
+   * @param testConstructor If the annotation was found on a constructor,
+   * this parameter represents this constructor (null otherwise).
+   * @param testMethod If the annotation was found on a method,
+   * this parameter represents this method (null otherwise).
+   */
+  public void transform(ITest annotation, Class testClass,
+      Constructor testConstructor, Method testMethod);
+}
+</pre>
+
+Like all the other TestNG listeners, you can specify this class either on the command line or with ant:
+
+<p>
+
+<pre class="brush: java">
+  java org.testng.TestNG -listener MyTransformer testng.xml
+</pre>
+
+or programmatically:
+
+<p>
+
+<pre class="brush: java">
+  TestNG tng = new TestNG();
+  tng.setAnnotationTransformer(new MyTransformer());
+  // ...
+</pre>
+
+When the method <tt>transform()</tt> is invoked, you can call any of the setters on the <tt>ITest test</tt> parameter to alter its value before TestNG proceeds further.
+<p>
+For example, here is how you would override the attribute <tt>invocationCount</tt> but only on the test method <tt>invoke()</tt> of one of your test classes:
+
+<pre class="brush: java">
+  public class MyTransformer implements IAnnotationTransformer {
+    public void transform(ITest annotation, Class testClass,
+        Constructor testConstructor, Method testMethod)
+    {
+      if ("invoke".equals(testMethod.getName())) {
+        annotation.setInvocationCount(5);
+      }
+    }
+  }
+</pre>
+
+<tt>IAnnotationTransformer</tt> only lets you modify a <tt>@Test</tt> annotation.  If you need to modify another TestNG annotation (a configuration annotation, <tt>@Factory</tt> or <tt>@DataProvider</tt>), use an <tt>IAnnotationTransformer2</tt>.
+
+<!-------------------------------------
+  METHOD INTERCEPTORS
+ ------------------------------------>
+	
+<h4><a class="section" indent=".." name="methodinterceptors">Method Interceptors</a></h4>
+
+Once TestNG has calculated in what order the test methods will be invoked, these methods are split in two groups:
+
+<ul>
+  <li><em>Methods run sequentially</em>.  These are all the test methods that have dependencies or dependents.  These methods will be run in a specific order.
+  <li><em>Methods run in no particular order</em>.  These are all the methods that don't belong in the first category.  The order in which these test methods are run is random and can vary from one run to the next (although by default, TestNG will try to group test methods by class).
+</ul>
+
+In order to give you more control on the methods that belong to the second category, TestNG defines the following interface:
+
+<pre class="brush: java">
+public interface IMethodInterceptor {
+  
+  List&lt;IMethodInstance&gt; intercept(List&lt;IMethodInstance&gt; methods, ITestContext context);
+
+}
+
+</pre>
+
+The list of methods passed in parameters are all the methods that can be run in any order.  Your <tt>intercept</tt> method is expected to return a similar list of <tt>IMethodInstance</tt>, which can be either of the following:
+
+<ul>
+  <li>The same list you received in parameter but in a different order.
+  <li>A smaller list of <tt>IMethodInstance</tt> objects.
+  <li>A bigger list of <tt>IMethodInstance</tt> objects.
+</ul>
+
+Once you have defined your interceptor, you pass it to TestNG as a listener.  For example:
+
+<p>
+
+<h3 class="sourcetitle">Shell</h3>
+<pre class="brush: text">
+java -classpath "testng-jdk15.jar:test/build" org.testng.TestNG -listener test.methodinterceptors.NullMethodInterceptor
+   -testclass test.methodinterceptors.FooTest
+</pre>
+
+For the equivalent <tt>ant</tt> syntax, see the <tt>listeners</tt> attribute in the <a href="ant.html">ant documentation</a>.
+<p>
+For example, here is a Method Interceptor that will reorder the methods so that test methods that belong to the group "fast" are always run first:
+
+<pre class="brush: java">
+public List&lt;IMethodInstance&gt; intercept(List&lt;IMethodInstance&gt; methods, ITestContext context) {
+  List&lt;IMethodInstance&gt; result = new ArrayList&lt;IMethodInstance&gt;();
+  for (IMethodInstance m : methods) {
+    Test test = m.getMethod().getConstructorOrMethod().getAnnotation(Test.class);
+    Set&lt;String&gt; groups = new HashSet&lt;String&gt;();
+    for (String group : test.groups()) {
+      groups.add(group);
+    }
+    if (groups.contains("fast")) {
+      result.add(0, m);
+    }
+    else {
+      result.add(m);
+    }
+  }
+  return result;
+}
+</pre>
+
+
+<!-------------------------------------
+  TESTNG LISTENERS
+ ------------------------------------>
+	
+<h4><a class="section" indent=".." name="testng-listeners">TestNG Listeners</a></h4>
+
+There are several interfaces that allow you to modify TestNG's behavior.  These interfaces are broadly called "TestNG Listeners".  Here are a few listeners:
+
+<ul>
+  <li><tt>IAnnotationTransformer</tt> (<a href="#annotationtransformers">doc</a>, <a href="../javadocs/org/testng/IAnnotationTransformer.html">javadoc</a>)
+  <li><tt>IAnnotationTransformer2</tt> (<a href="#annotationtransformers">doc</a>, <a href="../javadocs/org/testng/IAnnotationTransformer2.html">javadoc</a>)
+  <li><tt>IHookable</tt> (<a href="#ihookable">doc</a>, <a href="../javadocs/org/testng/IHookable.html">javadoc</a>)
+  <li><tt>IInvokedMethodListener</tt> (doc, <a href="../javadocs/org/testng/IInvokedMethodListener.html">javadoc</a>)
+  <li><tt>IMethodInterceptor</tt> (<a href="#methodinterceptors">doc</a>, <a href="../javadocs/org/testng/IMethodInterceptor.html">javadoc</a>)
+  <li><tt>IReporter</tt> (<a href="#logging-reporters">doc</a>, <a href="../javadocs/org/testng/IReporter.html">javadoc</a>)
+  <li><tt>ISuiteListener</tt> (doc, <a href="../javadocs/org/testng/ISuiteListener.html">javadoc</a>)
+  <li><tt>ITestListener</tt> (<a href="#logging-listeners">doc</a>, <a href="../javadocs/org/testng/ITestListener.html">javadoc</a>)
+</ul>
+
+When you implement one of these interfaces, you can let TestNG know about it with either of the following ways:
+
+<ul>
+  <li><a href="#running-testng">Using -listener on the command line.</a>
+  <li><a href="ant.html">Using &lt;listeners&gt; with ant.</a>
+  <li>Using &lt;listeners&gt; in your <tt>testng.xml</tt> file.
+  <li>Using the <tt>@Listeners</tt> annotation on any of your test classes.
+  <li>Using <tt>ServiceLoader</tt>.
+</ul>
+
+<h5><a class="section" indent="..." name="listeners-testng-xml">Specifying listeners with <tt>testng.xml</tt> or in Java</a></h5>
+
+Here is how you can define listeners in your <tt>testng.xml</tt> file:
+
+<pre class="brush: xml">
+&lt;suite&gt;
+
+  &lt;listeners&gt;
+    &lt;listener class-name="com.example.MyListener" /&gt;
+    &lt;listener class-name="com.example.MyMethodInterceptor" /&gt;
+  &lt;/listeners&gt;
+
+...
+
+</pre>
+
+Or if you prefer to define these listeners in Java:
+
+<pre class="brush: java">
+@Listeners({ com.example.MyListener.class, com.example.MyMethodInterceptor.class })
+public class MyTest {
+  // ...
+}
+</pre>
+
+The <tt>@Listeners</tt> annotation can contain any class that extends <tt>org.testng.ITestNGListener</tt> <b>except</b> <tt>IAnnotationTransformer</tt> and <tt>IAnnotationTransformer2</tt>.  The reason is that these listeners need to be known very early in the process so that TestNG can use them to rewrite your annotations, therefore you need to specify these listeners in your <tt>testng.xml</tt> file.
+
+<p>
+
+Note that the <tt>@Listeners</tt> annotation will apply to your entire suite file, just as if you had specified it in a <tt>testng.xml</tt> file. If you want to restrict its scope (for example, only running on the current class), the code in your listener could first check the test method that's about to run and decide what to do then.
+
+<h5><a class="section" indent="..." name="listeners-service-loader">Specifying listeners with <tt>ServiceLoader</tt></a></h5>
+
+Finally, the JDK offers a very elegant mechanism to specify implementations of interfaces on the class path via the <tt><a href="http://download.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html">ServiceLoader</a></tt> class.
+
+<p>
+With ServiceLoader, all you need to do is create a jar file that contains your listener(s) and a few configuration files, put that jar file on the classpath when you run TestNG and TestNG will automatically find them.
+<p>
+
+Here is a concrete example of how it works.
+
+<p>
+
+Let's start by creating a listener (any TestNG listener should work):
+
+<pre class="brush: java">
+package test.tmp;
+
+public class TmpSuiteListener implements ISuiteListener {
+  @Override
+  public void onFinish(ISuite suite) {
+    System.out.println("Finishing");
+  }
+
+  @Override
+  public void onStart(ISuite suite) {
+    System.out.println("Starting");
+  }
+}
+</pre>
+
+Compile this file, then create a file at the location <tt>META-INF/services/org.testng.ITestNGListener</tt>, which will name the implementation(s) you want for this interface.
+
+<p>
+
+You should end up with the following directory structure, with only two files:
+
+<pre class="brush: plain; highlight: [4, 7]">
+$ tree
+|____META-INF
+| |____services
+| | |____org.testng.ITestNGListener
+|____test
+| |____tmp
+| | |____TmpSuiteListener.class
+
+$ cat META-INF/services/org.testng.ITestNGListener
+test.tmp.TmpSuiteListener
+</pre>
+
+Create a jar of this directory:
+
+<pre class="brush: plain">
+$ jar cvf ../sl.jar .
+added manifest
+ignoring entry META-INF/
+adding: META-INF/services/(in = 0) (out= 0)(stored 0%)
+adding: META-INF/services/org.testng.ITestNGListener(in = 26) (out= 28)(deflated -7%)
+adding: test/(in = 0) (out= 0)(stored 0%)
+adding: test/tmp/(in = 0) (out= 0)(stored 0%)
+adding: test/tmp/TmpSuiteListener.class(in = 849) (out= 470)(deflated 44%)
+</pre>
+
+Next, put this jar file on your classpath when you invoke TestNG:
+
+<pre class="brush: plain">
+$ java -classpath sl.jar:testng.jar org.testng.TestNG testng-single.yaml
+Starting
+f2 11 2
+PASSED: f2("2")
+Finishing
+</pre>
+
+This mechanism allows you to apply the same set of listeners to an entire organization just by adding a jar file to the classpath, instead of asking every single developer to remember to specify these listeners in their testng.xml file. 
+
+<!-------------------------------------
+  DEPENDENCY INJECTION
+ ------------------------------------>
+	
+<h4><a class="section" indent=".." name="dependency-injection">Dependency injection</a></h4>
+
+TestNG supports two different kinds of dependency injection: native (performed by TestNG itself) and external (performed by a dependency injection framework such as Guice).
+
+<h5><a class="section" indent="..." name="native-dependency-injection">Native dependency injection</a></h5>
+
+TestNG lets you declare additional parameters in your methods.  When this happens, TestNG will automatically fill these parameters with the right value.  Dependency injection can be used in the following places:
+
+<ul>
+  
+<li>
+  Any @Before method or @Test method can declare a parameter of type <tt>ITestContext</tt>.
+<li>
+  Any @AfterMethod method can declare a parameter of type <tt>ITestResult</tt>, which will reflect the result of the test method that was just run.
+<li>
+  Any @Before and @After methods can declare a parameter of type <tt>XmlTest</tt>, which contain the current <tt>&lt;test&gt;</tt> tag.
+<li>
+  Any @BeforeMethod (and @AfterMethod) can declare a parameter of type
+  <tt>java.lang.reflect.Method</tt>.  This parameter will receive the
+  test method that will be called once this @BeforeMethod finishes (or
+  after the method as run for @AfterMethod).
+<li>
+  Any @BeforeMethod can declare a parameter of type <tt>Object[]</tt>.  This parameter will receive the list of parameters that are about to be fed to the upcoming test method, which could be either injected by TestNG, such as <tt>java.lang.reflect.Method</tt> or come from a <tt>@DataProvider</tt>.
+<li>
+  Any @DataProvider can declare a parameter of type
+  <tt>ITestContext</tt> or <tt>java.lang.reflect.Method</tt>.  The
+  latter parameter will receive the test method that is about to be invoked.
+</ul>
+
+You can turn off injection with the <tt>@NoInjection</tt> annotation:
+
+<pre class="brush: java; highlight: [9]">
+public class NoInjectionTest {
+
+  @DataProvider(name = "provider")
+  public Object[][] provide() throws Exception {
+      return new Object[][] { { CC.class.getMethod("f") } };
+  }
+
+  @Test(dataProvider = "provider")
+  public void withoutInjection(@NoInjection Method m) {
+      Assert.assertEquals(m.getName(), "f");
+  }
+
+  @Test(dataProvider = "provider")
+  public void withInjection(Method m) {
+      Assert.assertEquals(m.getName(), "withInjection");
+  }
+}
+</pre>
+
+<h5><a class="section" indent="..." name="guice-dependency-injection">Guice dependency injection</a></h5>
+
+If you use Guice, TestNG gives you an easy way to inject your test objects with a Guice module:
+
+<pre class="brush: java">
+@Guice(modules = GuiceExampleModule.class)
+public class GuiceTest extends SimpleBaseTest {
+
+  @Inject
+  ISingleton m_singleton;
+
+  @Test
+  public void singletonShouldWork() {
+    m_singleton.doSomething();
+  }
+
+}
+</pre>
+
+In this example, <tt>GuiceExampleModule</tt> is expected to bind the interface <tt>ISingleton</tt> to some concrete class:
+
+<pre class="brush: java">
+public class GuiceExampleModule implements Module {
+
+  @Override
+  public void configure(Binder binder) {
+    binder.bind(ISingleton.class).to(ExampleSingleton.class).in(Singleton.class);
+  }
+
+}
+</pre>
+
+If you need more flexibility in specifying which modules should be used to instantiate your test classes, you can specify a module factory:
+
+<pre class="brush: java">
+@Guice(moduleFactory = ModuleFactory.class)
+public class GuiceModuleFactoryTest {
+
+  @Inject
+  ISingleton m_singleton;
+
+  @Test
+  public void singletonShouldWork() {
+    m_singleton.doSomething();
+  }
+}
+</pre>
+
+The module factory needs to implement the interface <a href="../javadocs/org/testng/IModuleFactory.html">IModuleFactory</a>:
+
+<pre class="brush: java">
+public interface IModuleFactory {
+ /**
+   * @param context The current test context
+   * @param testClass The test class
+   *
+   * @return The Guice module that should be used to get an instance of this
+   * test class.
+   */
+  Module createModule(ITestContext context, Class&lt;?&gt; testClass);
+}
+</pre>
+
+Your factory will be passed an instance of the test context and the test class that TestNG needs to instantiate. Your <tt>createModule</tt> method should return a Guice Module that will know how to instantiate this test class. You can use the test context to find out more information about your environment, such as parameters specified in <tt>testng.xml</tt>, etc...
+
+You will get even more flexibility and Guice power with <tt>parent-module</tt> and <tt>guice-stage</tt> suite parameters.
+<tt>guice-stage</tt> allow you to chose the <a href="https://github.com/google/guice/wiki/Bootstrap"><tt>Stage</tt></a> used to create the parent injector.
+The default one is <tt>DEVELOPMENT</tt>. Other allowed values are <tt>PRODUCTION</tt> and <tt>TOOL</tt>.
+Here is how you can define parent-module in your test.xml file:
+
+<pre class="brush: xml">
+<suite parent-module="com.example.SuiteParenModule" guice-stage="PRODUCTION">
+</suite>
+</pre>
+
+TestNG will create this module only once for given suite. Will also use this module for obtaining instances of test specific Guice modules and module factories, then will create child injector for each test class.
+
+With such approach you can declare all common bindings in parent-module also you can inject binding declared in parent-module in module and module factory. Here is an example of this functionality:
+
+<pre class="brush: java">
+package com.example;
+
+public class ParentModule extends AbstractModule {
+  @Override
+  protected void conigure() {
+    bind(MyService.class).toProvider(MyServiceProvider.class);
+    bind(MyContext.class).to(MyContextImpl.class).in(Singleton.class);
+  }
+}
+</pre>
+
+<pre class="brush: java">
+package com.example;
+
+public class TestModule extends AbstractModule {
+  private final MyContext myContext;
+
+  @Inject
+  TestModule(MyContext myContext) {
+    this.myContext = myContext
+  }
+  
+  @Override
+  protected void configure() {
+    bind(MySession.class).toInstance(myContext.getSession());
+  }
+}
+</pre>
+
+<pre class="brush: xml">
+<suite parent-module="com.example.ParentModule">
+</suite>
+</pre>
+
+<pre class="brush: java">
+package com.example;
+
+@Test
+@Guice(modules = TestModule.class)
+public class TestClass {
+  @Inject
+  MyService myService;
+  @Inject
+  MySession mySession;
+  
+  public void testServiceWithSession() {
+    myService.serve(mySession);
+  }
+}
+</pre>
+
+As you see ParentModule declares binding for MyService and MyContext classes. Then MyContext is injected using constructor injection into TestModule class, which also declare binding for MySession. Then parent-module in test XML file is set to ParentModule class, this enables injection in TestModule. Later in TestClass you see two injections:
+ * MyService - binding taken from ParentModule
+ * MySession - binding taken from TestModule
+This configuration ensures you that all tests in this suite will be run with same session instance, the MyContextImpl object is only created once per suite, this give you possibility to configure common environment state for all tests in suite. 
+
+<!-------------------------------------
+  INVOKED METHOD LISTENERS
+ ------------------------------------>
+	
+<h4><a class="section" indent=".." name="invokedmethodlistener">Listening to method invocations</a></h4>
+
+The listener <tt><a href="../javadocs/org/testng/IInvokedMethodListener.html">IInvokedMethodListener</a></tt> allows you to be notified whenever TestNG is about to invoke a test (annotated with <tt>@Test</tt>) or configuration (annotated with any of the <tt>@Before</tt> or <tt>@After</tt> annotation) method.  You need to implement the following interface:
+
+<pre class="brush: java">
+public interface IInvokedMethodListener extends ITestNGListener {
+  void beforeInvocation(IInvokedMethod method, ITestResult testResult);
+  void afterInvocation(IInvokedMethod method, ITestResult testResult);
+}
+</pre>
+
+and declare it as a listener, as explained in <a href="#testng-listeners">the section about TestNG listeners</a>.
+
+<p>
+
+
+<!-------------------------------------
+  IHOOKABLE AND ICONFIGURABLE
+  ------------------------------------>
+
+<h4><a class="section" indent=".." name="ihookable">Overriding test methods</a></h4>
+
+TestNG allows you to override and possibly skip the invocation of test methods. One example of where this is useful is if you need to your test methods with a specific security manager. You achieve this by providing a listener that implements <a href="../javadocs/org/testng/IHookable.html"><tt>IHookable</tt></a>.
+<p>
+Here is an example with JAAS:
+
+<pre class="brush: java">
+public class MyHook implements IHookable {
+  public void run(final IHookCallBack icb, ITestResult testResult) {
+    // Preferably initialized in a @Configuration method
+    mySubject = authenticateWithJAAs();
+   
+    Subject.doAs(mySubject, new PrivilegedExceptionAction() {
+      public Object run() {
+        icb.callback(testResult);
+      }
+    };
+  }
+}
+</pre>
+
+<p>
+<!-------------------------------------
+  IALTERSUITELISTENER
+  ------------------------------------>
+
+<h4><a class="section" indent=".." name="ialtersuite">Altering suites (or) tests</a></h4>
+Sometimes you may need to just want to alter a suite (or) a test tag in a suite xml in runtime without having to
+change the contents of a suite file.
+
+<p>
+A classic example for this would be to try and leverage your existing suite file and try using it for simulating a load test
+on your "Application under test".
+At the minimum you would end up duplicating the contents of your &lt;test&gt; tag multiple
+times and create a new suite xml file and work with. But this doesn't seem to scale a lot.
+
+<p>
+TestNG allows you to alter a suite (or) a test tag in your suite xml file at runtime via listeners.
+You achieve this by providing a listener that implements <a href="../javadocs/org/testng/IAlterSuiteListener.html"><tt>IAlterSuiteListener</tt></a>.
+Please refer to <a href="#testng-listeners">Listeners section</a> to learn about listeners.
+<p>
+Here is an example that shows how the suite name is getting altered in runtime:
+
+<pre class="brush: java">
+public class AlterSuiteNameListener implements IAlterSuiteListener {
+
+    @Override
+    public void alter(List&lt;XmlSuite&gt; suites) {
+        XmlSuite suite = suites.get(0);
+        suite.setName(getClass().getSimpleName());
+    }
+}
+</pre>
+
+This listener can only be added with either of the following ways: 
+<ul>
+  <li>Through the <tt>&lt;listeners&gt;</tt> tag in the suite xml file.</li>
+  <li>Through a <a href="#listeners-service-loader">Service Loader</a></li>
+</ul>
+
+This listener cannot be added to execution using the <tt>@Listeners</tt> annotation.
+
+<!------------------------------------
+  TEST SUCCESS
+  ------------------------------------>
+
+<h3><a class="section" indent="." name="test-results">Test results</a></h3>
+
+
+<h4><a class="section" indent=".." name="success-failure">Success, failure and assert</a></h4>
+
+
+<p>A test is considered successful if it completed without throwing any 
+exception or if&nbsp; it threw an exception that was expected (see the 
+documentation for the <tt>expectedExceptions</tt> attribute found on the <tt>@Test</tt> annotation).
+</p>
+
+<p>Your test methods will typically be made of calls that can throw an 
+exception, or of various assertions (using the Java &quot;assert&quot; keyword).&nbsp; An 
+&quot;assert&quot; failing will trigger an AssertionErrorException, which in turn will 
+mark the method as failed (remember to use -ea on the JVM if you are not seeing 
+the assertion errors).</p><p>Here is an example test method:</p>
+
+<pre class="brush: java">
+@Test
+public void verifyLastName() {
+  assert "Beust".equals(m_lastName) : "Expected name Beust, for" + m_lastName;
+}
+</pre>
+
+TestNG also include JUnit's Assert class, which lets you perform 
+assertions on complex objects:
+
+<pre class="brush: java">
+import static org.testng.AssertJUnit.*;
+//...
+@Test
+public void verify() {
+  assertEquals("Beust", m_lastName);
+}
+</pre>
+<p>Note that the above code use a static import in order to be able to use the
+<tt>assertEquals</tt> method without having to prefix it by its class.
+
+<!-------------------------------------
+  LOGGING
+  ------------------------------------>
+</p>
+
+<h4><a class="section" indent=".." name="logging">Logging and results</a></h4>
+
+The results of the test run are created in a file called <tt>index.html</tt> in the 
+directory specified when launching SuiteRunner.&nbsp; This file points to 
+various other HTML and text files that contain the result of the entire test 
+run.&nbsp; You can see a typical example
+<a href="http://testng.org/test-output/index.html">here</a>.
+
+<p>
+It's very easy to generate your own reports with TestNG with Listeners and Reporters:
+
+<ul>
+<li><b>Listeners</b> implement the interface <a href="../javadocs/org/testng/ITestListener.html"><tt>org.testng.ITestListener</tt></a> and are notified in real time of when a test starts, passes, fails, etc...</li><li><b>Reporters</b> implement the interface <a href="../javadocs/org/testng/IReporter.html"><tt>org.testng.IReporter</tt></a> and are notified when all the suites have been run by TestNG.  The IReporter instance receives a list of objects that describe the entire test run.</li></ul>For example, if you want to generate a PDF report of your test run, you don't need to be notified in real time of the test run so you should probably use an <tt>IReporter</tt>.  If you'd like to write a real-time reporting of your tests, such as a GUI with a progress bar or a text reporter displaying dots (".") as each test is invoked (as is explained below), <tt>ITestListener</tt> is your best choice.
+
+<h5><a class="section" indent="..." name="logging-listeners">Logging Listeners</a></h5>
+
+Here is a listener that displays a "." for each passed test, a "F" for each failure and a "S" for each skip:
+
+<pre class="brush: java">
+public class DotTestListener extends TestListenerAdapter {
+  private int m_count = 0;
+
+  @Override
+  public void onTestFailure(ITestResult tr) {
+    log("F");
+  }
+
+  @Override
+  public void onTestSkipped(ITestResult tr) {
+    log("S");
+  }
+
+  @Override
+  public void onTestSuccess(ITestResult tr) {
+    log(".");
+  }
+
+  private void log(String string) {
+    System.out.print(string);
+    if (++m_count % 40 == 0) {
+      System.out.println("");
+    }
+  }
+} 
+</pre>
+
+In this example, I chose to extend <a href="../javadocs/org/testng/TestListenerAdapter.html"><tt>TestListenerAdapter</tt></a>, which implements <a href="../javadocs/org/testng/ITestListener.html"><tt>ITestListener</tt></a> with empty methods, so I don't have to override other methods from the interface that I have no interest in.  You can implement the interface directly if you prefer.
+
+<p>
+Here is how I invoke TestNG to use this new listener:
+
+<h3 class="sourcetitle">Shell</h3>
+<pre class="brush: text">
+java -classpath testng.jar;%CLASSPATH% org.testng.TestNG -listener org.testng.reporters.DotTestListener test\testng.xml
+</pre>
+
+and the output:
+
+<p>
+
+<h3 class="sourcetitle">Shell</h3>
+<pre class="brush: text">
+........................................
+........................................
+........................................
+........................................
+........................................
+.........................
+===============================================
+TestNG JDK 1.5
+Total tests run: 226, Failures: 0, Skips: 0
+===============================================
+</pre>
+
+Note that when you use <tt>-listener</tt>, TestNG will automatically determine the type of listener you want to use.
+
+<h5><a class="section" indent="..." name="logging-reporters">Logging Reporters</a></h5>
+
+The <a href="../javadocs/org/testng/IReporter.html"><tt>org.testng.IReporter</tt></a> interface only has one method:
+
+<pre class="brush: java">
+public void generateReport(List&lt;ISuite</a>&gt; suites, String outputDirectory)
+</pre>
+
+This method will be invoked by TestNG when all the suites have been run and you can inspect its parameters to access all the information on the run that was just completed.
+
+<p>
+
+<h5><a class="section" indent="..." name="logging-junitreports">JUnitReports</a></h5>
+
+<p>
+
+
+TestNG contains a listener that takes the TestNG results 
+and outputs an XML file that can then be fed to JUnitReport.  <a href="http://testng.org/test-report/junit-noframes.html">
+Here</a> is an example, and the ant task to create this report:
+
+<h3 class="sourcetitle">build.xml</h3>
+<pre class="brush: xml">
+&lt;target name="reports"&gt;
+  &lt;junitreport todir="test-report"&gt;
+    &lt;fileset dir="test-output"&gt;
+      &lt;include name="*/*.xml"/&gt;
+    &lt;/fileset&gt;
+ 
+    &lt;report format="noframes"  todir="test-report"/&gt;
+  &lt;/junitreport&gt;
+&lt;/target&gt;
+</pre>
+<blockquote>
+	<em>Note:&nbsp; a current incompatibility between the JDK 1.5 and JUnitReports 
+prevents the frame version from working, so you need to specify &quot;noframes&quot; to 
+get this to work for now.</em>
+	</blockquote>
+
+<h5><a class="section" indent="..." name="logging-reporter-api">Reporter API</a></h5>
+
+<p>
+If you need to log messages that should appear in the generated HTML reports, you can use the class <tt><a href="../javadocs/org/testng/Reporter.html">org.testng.Reporter</a></tt>:
+
+<blockquote class="brush: text">
+<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#000000">Reporter.log</font><font color="#000000">(</font><font color="#2a00ff">&#34;M3 WAS CALLED&#34;</font><font color="#000000">)</font><font color="#000000">;</font>
+
+</blockquote>
+
+<p align="center">
+
+<img src="pics/show-output1.png" />
+<img src="pics/show-output2.png" />
+
+</p>
+
+<h5><a class="section" indent="..." name="logging-xml-reports">XML Reports</a></h5>
+
+<p>
+TestNG offers an XML reporter capturing TestNG specific information that is not available in JUnit reports. This is particulary useful when the user's test environment needs to consume XML results with TestNG-specific data that the JUnit format can't provide.  Below is a sample of the output of such a reporter:
+</p>
+
+<pre class="brush: xml">
+&lt;testng-results&gt;
+  &lt;suite name=&quot;Suite1&quot;&gt;
+    &lt;groups&gt;
+      &lt;group name=&quot;group1&quot;&gt;
+        &lt;method signature=&quot;com.test.TestOne.test2()&quot; name=&quot;test2&quot; class=&quot;com.test.TestOne&quot;/&gt;
+        &lt;method signature=&quot;com.test.TestOne.test1()&quot; name=&quot;test1&quot; class=&quot;com.test.TestOne&quot;/&gt;
+      &lt;/group&gt;
+      &lt;group name=&quot;group2&quot;&gt;
+        &lt;method signature=&quot;com.test.TestOne.test2()&quot; name=&quot;test2&quot; class=&quot;com.test.TestOne&quot;/&gt;
+      &lt;/group&gt;
+    &lt;/groups&gt;
+    &lt;test name=&quot;test1&quot;&gt;
+      &lt;class name=&quot;com.test.TestOne&quot;&gt;
+        &lt;test-method status=&quot;FAIL&quot; signature=&quot;test1()&quot; name=&quot;test1&quot; duration-ms=&quot;0&quot;
+              started-at=&quot;2007-05-28T12:14:37Z&quot; description=&quot;someDescription2&quot;
+              finished-at=&quot;2007-05-28T12:14:37Z&quot;&gt;
+          &lt;exception class=&quot;java.lang.AssertionError&quot;&gt;
+            &lt;short-stacktrace&gt;
+              &lt;![CDATA[
+                java.lang.AssertionError
+                ... Removed 22 stack frames
+              ]]&gt;
+            &lt;/short-stacktrace&gt;
+          &lt;/exception&gt;
+        &lt;/test-method&gt;
+        &lt;test-method status=&quot;PASS&quot; signature=&quot;test2()&quot; name=&quot;test2&quot; duration-ms=&quot;0&quot;
+              started-at=&quot;2007-05-28T12:14:37Z&quot; description=&quot;someDescription1&quot;
+              finished-at=&quot;2007-05-28T12:14:37Z&quot;&gt;
+        &lt;/test-method&gt;
+        &lt;test-method status=&quot;PASS&quot; signature=&quot;setUp()&quot; name=&quot;setUp&quot; is-config=&quot;true&quot; duration-ms=&quot;15&quot;
+              started-at=&quot;2007-05-28T12:14:37Z&quot; finished-at=&quot;2007-05-28T12:14:37Z&quot;&gt;
+        &lt;/test-method&gt;
+      &lt;/class&gt;
+    &lt;/test&gt;
+  &lt;/suite&gt;
+&lt;/testng-results&gt;
+</pre>
+<p>This reporter is injected along with the other default listeners so you can get this type of output by default. The listener provides some properties that can tweak the reporter to fit your needs. The following table contains a list of these properties with a short explanation:
+</p>
+<table border="1" width="100%" id="table6">
+  <tr>
+    <th>Property</th>
+    <th>Comment</th>
+    <th>Default value</th>
+  </tr>
+  <tr>
+    <td>outputDirectory</td>
+    <td>A <tt>String</tt> indicating the directory where should the XML files be outputed.</td>
+    <td>The TestNG output directory</td>
+  </tr>
+  <tr>
+    <td>timestampFormat</td>
+    <td>Specifies the format of date fields that are generated by this reporter</td>
+    <td>yyyy-MM-dd'T'HH:mm:ss'Z'</td>
+  </tr>
+  <tr>
+    <td>fileFragmentationLevel</td>
+    <td>An integer having the values 1, 2 or 3, indicating the way that the XML files are generated:
+      <br>
+<pre>
+   1 - will generate all the results in one file.
+   2 - each suite is generated in a separate XML file that is linked to the main file.
+   3 - same as 2 plus separate files for test-cases that are referenced from the suite files.
+</pre>
+    </td>
+    <td>1</td>
+  </tr>
+  <tr>
+    <td>splitClassAndPackageNames</td>
+    <td>This boolean specifies the way that class names are generated for the <tt>&lt;class&gt;</tt> element.
+      For example, you will get <tt>&lt;class class="com.test.MyTest"&gt;</tt> for false and <tt>&lt;class class="MyTest" package="com.test"&gt;</tt> for true.
+    </td>
+    <td>false</td>
+  </tr>
+  <tr>
+    <td>generateGroupsAttribute</td>
+    <td>A boolean indicating if a <tt>groups</tt> attribute should be generated for the <tt>&lt;test-method&gt;</tt> element. This feature aims at providing a
+      straight-forward method of retrieving the groups that include a test method without having to surf through the <tt>&lt;group&gt;</tt> elements.
+    </td>
+    <td>false</td>
+  </tr>
+  <tr>
+    <td>generateTestResultAttributes</td>
+    <td>
+      A boolean indicating if an <tt>&lt;attributes&gt;</tt> tag should be generated for each <tt>&lt;test-method&gt;</tt> element, containing the test result
+      attributes (See <tt>ITestResult.setAttribute()</tt> about setting test result attributes). Each attribute <tt>toString()</tt> representation will be
+      written in a <tt>&lt;attribute name="[attribute name]"&gt;</tt> tag.
+    </td>
+    <td>false</td>
+  </tr>
+  <tr>
+    <td>stackTraceOutputMethod</td>
+    <td>Specifies the type of stack trace that is to be generated for exceptions and has the following values:
+         <br>
+<pre>
+   0 - no stacktrace (just Exception class and message).
+   1 - a short version of the stack trace keeping just a few lines from the top
+   2 - the complete stacktrace with all the inner exceptions
+   3 - both short and long stacktrace
+</pre>
+    </td>
+    <td>2</td>
+  </tr>
+  <tr>
+    <td>generateDependsOnMethods</td>
+    <td>Use this attribute to enable/disable the generation of a <tt>depends-on-methods</tt> attribute for the <tt>&lt;test-method&gt;</tt> element.
+    </td>
+    <td>true</td>
+  </tr>
+  <tr>
+    <td>generateDependsOnGroups</td>
+    <td>Enable/disable the generation of a <tt>depends-on-groups</tt> attribute for the <tt>&lt;test-method&gt;</tt> element.
+    </td>
+    <td>true</td>
+  </tr>
+</table>
+<p>
+  In order to configure this reporter you can use the <tt>-reporter</tt> option in the command line or the <a href="http://testng.org/doc/ant.html">Ant</a>
+  task with the nested <tt>&lt;reporter&gt;</tt> element. For each of these you must specify the class <tt>org.testng.reporters.XMLReporter</tt>.
+  Please note that you cannot configure the built-in reporter because this one will only use default settings. If you need just the XML report with custom settings
+  you will have to add it manually with one of the two methods and disable the default listeners.
+</p>
+
+<!------------------------------------
+  YAML
+  ------------------------------------>
+
+<h3><a class="section" name="yaml">YAML</a></h3>
+
+TestNG supports <a href="http://www.yaml.org/">YAML</a> as an alternate way of specifying your suite file. For example, the following XML file:
+
+<pre class="brush: xml">
+<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
+
+<suite name="SingleSuite" verbose="2" thread-count="4" >
+
+  &lt;parameter name="n" value="42" /&gt;
+
+  <test name="Regression2">
+    <groups>
+      <run>
+        &lt;exclude name="broken" /&gt;
+      </run>
+    </groups>
+
+    <classes>
+      &lt;class name="test.listeners.ResultEndMillisTest" /&gt;
+    </classes>
+  </test>
+</suite>
+</pre>
+<p>and here is its YAML version:</p>
+<pre class="brush: plain">
+name: SingleSuite
+threadCount: 4
+parameters: { n: 42 }
+
+tests:
+  - name: Regression2
+    parameters: { count: 10 }
+    excludedGroups: [ broken ]
+    classes:
+      - test.listeners.ResultEndMillisTest
+</pre>
+
+Here is <a href="https://github.com/cbeust/testng/blob/master/src/test/resources/testng.xml">TestNG's own suite file</a>, and its <a href="https://github.com/cbeust/testng/blob/master/src/test/resources/testng.yaml">YAML counterpart</a>.
+
+<p>
+
+You might find the YAML file format easier to read and to maintain. YAML files are also recognized by the TestNG Eclipse plug-in. You can find more information about YAML and TestNG in this <a href="http://beust.com/weblog/2010/08/15/yaml-the-forgotten-victim-of-the-format-wars/">blog post</a>.
+
+
+<!---------------------------------------------------------------------->
+
+<a name="testng-dtd">
+&nbsp;<hr width="100%">
+<p>Back to my <a href="http://beust.com/weblog">home page</a>.</p><p>Or check out some of my other projects:</p><ul>
+	<li><a href="http://beust.com/ejbgen">EJBGen</a>:&nbsp; an EJB tag 
+	generator.</li><li><a href="http://testng.org">TestNG</a>:&nbsp; A testing framework using annotations, test groups and method parameters. </li><li><a href="http://beust.com/doclipse">Doclipse</a>:&nbsp; a JavaDoc tag 
+	Eclipse plug-in.</li><li><a href="http://beust.com/j15">J15</a>:&nbsp; an Eclipse plug-in to help 
+	you migrate your code to the new JDK 1.5 constructs.</li><li><a href="http://beust.com/sgen">SGen</a>:&nbsp; a replacement for 
+	XDoclet with an easy plug-in architecture.</li><li><a href="http://beust.com/canvas">Canvas</a>:&nbsp; a template generator 
+	based on the Groovy language.</li></ul><p>
+</p>
+
+<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
+</script>
+<script type="text/javascript">
+_uacct = "UA-238215-2";
+urchinTracker();
+</script>
+	
diff --git a/doc/download.html b/doc/download.html
new file mode 100644
index 0000000..dab04fd
--- /dev/null
+++ b/doc/download.html
@@ -0,0 +1,123 @@
+<html>

+    <head>

+        <title>TestNG - Download Current Release and Beta Versions</title>

+

+        <link rel="stylesheet" href="testng.css" type="text/css" />

+        <link type="text/css" rel="stylesheet" href="http://beust.com/beust.css"  />

+        <script type="text/javascript" src="banner.js"></script>

+

+      <script type="text/javascript" src="http://beust.com/scripts/shCore.js"></script>

+      <script type="text/javascript" src="http://beust.com/scripts/shBrushJava.js"></script>

+      <script type="text/javascript" src="http://beust.com/scripts/shBrushXml.js"></script>

+      <script type="text/javascript" src="http://beust.com/scripts/shBrushBash.js"></script>

+      <script type="text/javascript" src="http://beust.com/scripts/shBrushPlain.js"></script>

+      <link type="text/css" rel="stylesheet" href="http://beust.com/styles/shCore.css"/>

+      <link type="text/css" rel="stylesheet" href="http://beust.com/styles/shThemeCedric.css"/>

+      <script type="text/javascript">

+        SyntaxHighlighter.config.clipboardSwf = 'scripts/clipboard.swf';

+        SyntaxHighlighter.defaults['gutter'] = false;

+        SyntaxHighlighter.all();

+      </script>

+      <script type="text/javascript" src="http://beust.com/toc.js"></script>

+

+        <style type="text/css">

+            /* Set the command-line table option column width. */

+            #command-line colgroup.option {

+                 width: 7em;

+            }

+        </style

+      </head>

+<body onload="prettyPrint()">

+

+<script type="text/javascript">

+    displayMenu("download.html")

+</script>

+

+

+<h2 align="center">Downloading TestNG</h2>

+

+<h3>Current Release Version</h3>

+

+<h4>Maven</h4>

+

+<pre class="brush: xml">

+    &lt;repositories&gt;

+      &lt;repository&gt;

+        &lt;id&gt;central&lt;/id&gt;

+        &lt;name&gt;bintray&lt;/name&gt;

+        &lt;url&gt;http://jcenter.bintray.com&lt;/url&gt;

+      &lt;/repository&gt;

+    &lt;/repositories&gt;

+

+    &lt;dependency&gt;

+      &lt;groupId&gt;org.testng&lt;/groupId&gt;

+      &lt;artifactId&gt;testng&lt;/artifactId&gt;

+      &lt;version&gt;6.9.4&lt;/version&gt;

+      &lt;scope&gt;test&lt;/scope&gt;

+    &lt;/dependency&gt;

+</pre>

+

+<h4>Gradle</h4>

+

+<pre class="brush: java">

+repositories {

+    jcenter()

+}

+

+dependencies {

+    testCompile 'org.testng:testng:6.9.4'

+}

+</pre>

+

+<h3>Snapshots</h3>

+

+TestNG <a href="https://oss.sonatype.org/content/repositories/snapshots/org/testng/testng/">automatically uploads snapshots to Sonatype</a> which you can access by adding the following repository:

+

+<pre class="brush: java">

+repositories {

+    maven {

+        url 'https://oss.sonatype.org/content/repositories/snapshots'

+    }

+}

+</pre>

+

+<h3>Eclipse plug-in</h3>

+<p>You can use either the Eclipse Market Place or the update site:</p>

+<ul>

+	<li>Select<i> Help / Install new software.</i> 

+	</li>

+	<li><i>Search for new features to install.</i> 

+	</li>

+	<li><i>New remote site.</i> 

+	</li>

+	<li>For Eclipse 3.4 and above, enter <a href="http://beust.com/eclipse">http://beust.com/eclipse</a>.

+	<li>For Eclipse 3.3 and below, enter <a href="http://beust.com/eclipse1">http://beust.com/eclipse1</a>.

+	</li>

+	<li>Make sure the check box next to URL is checked and click <i>Next</i>. 

+	</li>

+	<li>Eclipse will then guide you through the process. </li>

+</ul>

+

+<p>You can also install older versions of the plug-ins <a href="http://beust.com/eclipse-old">here</a>. Note that the URL's on this page are update sites as well, not direct download links.

+

+<p>TestNG is also <a href="http://github.com/cbeust/testng">hosted on GitHub</a>, where you can download the source and build the distribution yourself:

+

+<pre class="brush: plain">

+$ git clone git://github.com/cbeust/testng.git

+$ cd testng

+$ mvn package

+</pre>

+

+<p>You will then find the jar file in the <tt>target</tt> directory</p>

+

+<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">

+</script>

+<script type="text/javascript">

+_uacct = "UA-238215-2";

+urchinTracker();

+</script>

+

+

+</body>

+

+</html>

diff --git a/doc/eclipse.html b/doc/eclipse.html
new file mode 100644
index 0000000..7d81dc5
--- /dev/null
+++ b/doc/eclipse.html
@@ -0,0 +1,345 @@
+<html>
+    <head>
+        <title>TestNG - Eclipse</title>
+        <script type="text/javascript" src="banner.js"></script>
+        <link rel="stylesheet" href="testng.css" type="text/css" />
+      </head>
+<body>
+
+<script type="text/javascript">
+    displayMenu("eclipse.html")
+</script>
+
+
+<h2 align="center">TestNG Eclipse plug-in</h2>
+<p>The TestNG Eclipse plug-in allows you to run your TestNG tests from Eclipse 
+and easily monitor their execution and their output. It has its own <a href="http://github.com/cbeust/testng-eclipse">project repository called <tt>testng-eclipse</tt></a>.</p>
+
+
+<h3>Table of Contents</h3>
+
+&nbsp;&nbsp;<a class="summary" href="#eclipse-installation">1 - Installation</a>
+<br>
+&nbsp;&nbsp;<a class="summary" href="#eclipse-creating">2 - Creating a TestNG class</a>
+<br>
+&nbsp;&nbsp;<a class="summary" href="#eclipse-create-launch">3 - Launch configurations</a>
+<br>
+&nbsp;&nbsp;<a class="summary" href="#eclipse-create-class">3.1 - From a class file</a>
+<br>
+&nbsp;&nbsp;<a class="summary" href="#eclipse-create-groups">3.2 - From groups</a>
+<br>
+&nbsp;&nbsp;<a class="summary" href="#eclipse-create-xml">3.3 - From an XML file</a>
+<br>
+&nbsp;&nbsp;<a class="summary" href="#eclipse-create-method">3.4 - From a method</a>
+<br>
+&nbsp;&nbsp;<a class="summary" href="#eclipse-listeners">3.5 - Specifying listeners and other settings</a>
+<br>
+&nbsp;&nbsp;<a class="summary" href="#eclipse-viewing">4 - Viewing the results</a>
+<br>
+&nbsp;&nbsp;<a class="summary" href="#eclipse-search">5 - Search</a>
+<br>
+&nbsp;&nbsp;<a class="summary" href="#eclipse-summary">6 - The Summary tab</a>
+<br>
+&nbsp;&nbsp;<a class="summary" href="#eclipse-quickfix">7 - Converting JUnit tests</a>
+<br>
+&nbsp;&nbsp;<a class="summary" href="#eclipse-quickfixes">8 - Quick fixes</a>
+<br>
+&nbsp;&nbsp;<a class="summary" href="#eclipse-preferences">9 - Preferences and Properties</a>
+<br>
+&nbsp;&nbsp;<a class="summary" href="#eclipse-workbench-preferences">9.1 - Workbench Preferences</a>
+<br>
+&nbsp;&nbsp;<a class="summary" href="#eclipse-project-properties">9.2 - Project Properties</a>
+<br>
+&nbsp;&nbsp;<a class="summary" href="#testng-xml"></a>
+
+<!--
+  Installation
+-->
+<h3><a name="eclipse-installation">1 - Installation</a></h3><p>
+Once you have <a href="http://testng.org/doc/download.html">installed the plug-in</a>, restart Eclipse and select the menu <tt>Window / Show View 
+/ Other...</tt>&nbsp; and you should see the TestNG view listed in the Java category.</p><p align="center">
+<img border="0" src="http://testng.org/pictures/view.png" />
+</p>
+<p>
+NOTE: since TestNG Eclipse Plugin 6.9.8, the minimum required TestNG version is <b>6.5.1</b>
+</p>
+
+<!--
+  Creating
+-->
+<h3><a name="eclipse-creating">2 - Creating a TestNG class</a></h3><p>
+
+To create a new TestNG class, select the menu File / New / TestNG:
+
+<p align="center">
+<img border="0" src="http://testng.org/pictures/new-1.png" />
+</p>
+
+If you currently have a Java file open in the editor or if you have a Java file selected in the Navigator, the first page of the wizard will show you a list of all the public methods of that class and it will give you the option to select the ones you want to test. Each method you select on this page will be included in the new TestNG class with a default implementation that throws an exception, so you remember to implement it.
+
+<p align="center">
+<img border="0" src="http://testng.org/pictures/new-2.png" />
+</p>
+
+The next page lets you specify where that file will be created, whether it should contain default implementation for some configuration methods, if you'd like a data provider and finally, if a <tt>testng.xml</tt> file should be generated.
+
+<p align="center">
+<img border="0" src="http://testng.org/pictures/new-3.png" />
+</p>
+
+The plug-in will make a guess about the best location where this file should be created (for example, if you are using Maven, the default location will be under <tt>src/test/java</tt>).
+
+<h3><a name="eclipse-create-launch">3 - Launch configuration</a></h3><p>Once you have created classes that contain TestNG annotations and/or one or 
+more <tt>testng.xml</tt> files, you can create a TestNG Launch Configuration.&nbsp; 
+Select the <tt>Run / Run...</tt> (or <tt>Run / Debug...</tt>) menu and create a new TestNG 
+configuration:</p><p align="center">
+<img border="0" src="http://testng.org/pictures/launch.png" />
+</p>
+<p>You should change the name of this configuration and pick a project, which 
+can be selected by clicking on the <tt>Browse...</tt> button at the top of the window.</p>
+<p>
+Runtime options:
+<ul>
+<li><b>Log Level</b>: specify the value (0-10) for different verbose log levels</li>
+<li><b>Verbose</b>: enable the runtime TestNG verbose log</li>
+<li><b>Debug</b>: enable more runtime TestNG debug info</li>
+<li><b>Serialization Protocol</b>: the serialization protocol used for communicating between TestNG Eclipse Plugin and TestNG runtime.
+The default is "Object Serialization", the other option ("String Serialization") is deprecated.</li>
+<li><b>Add the POM's JVM Arguments when launch</b>: for maven based project, we normally define maven-surefire-plugin for test phase and maven-failsafe-plugin for integration test phase, there might be having the testing runtime JVM arguments defined in the "argLine" element of the maven plugin configuration. If you check this option, TestNG Eclipse Plugin will try to recognize the JVM arguments in "argLine", add them to TestNG runtime JVM arguments when launch.<br/>
+NOTE 1: these JVM arguments are put on front of user defined VM arguments on "Arguments" tab, which means you can overide the POM defined JVM argumetns with the ones on "Arguments" tab.<br/>
+NOTE 2: if there are multiple "argLine" elements in the pom.xml, the first presence will be used. If it's not the desired behaviour, you can simply uncheck this option, and type your JVM arguments on the "Arguments" tab next to this "Test" tab.</li>
+</ul>
+</p>
+<p>Then you choose to launch your TestNG tests in the following ways:</p><h4><a name="eclipse-launch-class">4.2.1 - From a class file</a></h4><p>Make sure the box near <tt>Class</tt> is checked and then pick a class from your 
+project.&nbsp; You can click on the <tt>Browse...</tt> button and pick it directly from a 
+list.&nbsp; This list only contains classes that contain TestNG annotations:</p><p align="center">
+<img border="0" src="http://testng.org/pictures/classes.png" />
+</p>
+
+
+<h4><a name="eclipse-create-groups">3.2 - From groups</a></h4>
+<p>If you only want to launch one or several groups, you can type them in the 
+text field or pick them from a list by clicking on the <tt>Browse...</tt> button&nbsp;
+
+<p align="center">
+<img border="0" src="http://testng.org/pictures/groups.png" />
+</p>
+
+
+<h4><a name="eclipse-create-xml">3.3 - From a definition file</a></h4>
+Finally, you can select a suite definition from your project.  It doesn't have to be named
+<tt>testng.xml</tt>, the plug-in will automatically identify all the applicable TestNG XML files 
+in your project:
+<p align="center">
+<img border="0" src="http://testng.org/pictures/suites.png" />
+</p>
+You can type the regex on the filter text field to narrow down to suite definition files matching your search from a long list of files.
+
+<h4>
+<a name="eclipse-create-method">3.4 - From a method </a>
+</h4>
+<p>This launch isn't accomplished from the Launch dialog but 
+directly from your Outline view:</p><p align="center">
+<img border="0" src="http://testng.org/pictures/outline.png" />
+</p>
+You can right-click on any test methods and select <tt>Run as... / TestNG test</tt> and only the selected
+method will be run (not shown on the above screenshot because I couldn't find a way to capture
+a contextual menu).<p>Method launching is also available from the Package 
+Explorer view and from the Java Browser perspective.</p><p>Once you have selected one of these launches, you can also choose the logging of level. Then you can launch the tests by pressing the <tt>Debug</tt> (or <tt>Run</tt>) button, which will switch you to the Debug perspective and will open the main TestNG view.</p>
+
+<h4>
+<a name="eclipse-listeners">3.5 -Specifying listeners and other settings </a>
+</h4>
+
+As you saw above, the plug-in will let you start tests in many different ways:  from an XML file, from a method, a class, etc...  When you are running an XML file, you can specify all the settings you want for this run in the XML file, but what if you want to run a package in parallel mode with specific listeners?  How can you configure the settings for all the launches that are not done from an XML file?
+
+<p>
+
+In order to give you access to the most flexibility, TestNG lets you specify an XML suite file for all these launches, which you can find in the Preferences menu:
+
+<p align="center">
+<img border="0" src="http://testng.org/pictures/template.png"/>
+</p>
+
+If you specify a valid suite file as "XML template file", TestNG will reuse all the settings found in this XML file, such as parallel, name, listeners, thread pool size, etc...  Only the <tt>&lt;test&gt;</tt> tags in this file will be ignored since the plug-in will replace these by a generated <tt>&lt;test&gt;</tt> tag that represents the launch you chose.
+
+<!--
+  Viewing
+-->
+<h3>
+<a name="eclipse-viewing">4 - Viewing the test results </a>
+</h3>
+
+<p align="center">
+<img border="0" src="http://testng.org/pictures/success.png" />
+</p>
+
+<p>The above view shows a successful run of the tests:&nbsp; the bar is green 
+and no failed tests are reported.&nbsp; The <tt>All tests</tt> tab shows you a list of 
+all the classes and methods that were run.</p><p>If your test run contains failures, the view will look like this:</p><p align="center">
+<img border="0" src="http://testng.org/pictures/failure.png" />
+</p>
+
+<p>You can use the <tt>Failed tests</tt> tab to display only these tests that failed, and 
+when you select such a test, the stack trace will be shown on the right-hand 
+pane.&nbsp; You can double click on the offending line to be taken directly to 
+the failure in your code.</p>
+
+<h3>
+<a name="eclipse-search">5 - Search </a>
+</h3>
+
+
+<p align="center">
+<img border="0" src="http://testng.org/pictures/search.png" />
+</p>
+
+<p>When you have hundreds of tests running, finding a specific one is not always easy, so you can type a few letters of the test method or its parameters in the Search box and the content of the tree will automatically narrow down to methods matching your search. Note in the screen shot above that the search also works on parameters provided by <tt>@DataProvider</tt>.</p>
+
+<h3>
+<a name="eclipse-summary">6 - Summary </a>
+</h3>
+
+
+<p align="center"> 
+<img border="0" src="http://testng.org/pictures/summary1.png" />
+</p> 
+<p>The Summary tab gives you statistics on your test run, such as the timings, the test names, the number of methods and classes, etc&#8230; Since the results are shown in a table, you can also sort on any criterion you like for easier parsing. This is especially handy when you are trying to determine what tests take the longest time.</p>
+
+<p>The search box works in this view as well, and note that in the screen shot below, the Time column is sorted in decreasing order:</p> 
+<p align="center"> 
+<img border="0" src="http://testng.org/pictures/summary2.png" />
+
+
+
+<h3><a name="eclipse-quickfix">7 - Converting JUnit tests</a></h3>
+
+You can easily convert JUnit 3 and JUnit 4 tests to TestNG.
+
+<p>
+
+Your first option is to use the Quick Fix function:
+
+<p>
+
+<p align="center"> 
+<img border="0" src="http://testng.org/pictures/convert1.png" />
+<br>
+<b>Convert from JUnit 3</b>
+</p>
+
+<p align="center"> 
+<img border="0" src="http://testng.org/pictures/convert2.png" />
+<br>
+<b>Convert from JUnit 4</b>
+</p>
+
+You can also convert packages or entire source folders with the conversion refactoring:
+
+<p align="center"> 
+<img border="0" src="http://testng.org/pictures/refactoring1.png" />
+</p>
+
+The refactoring wizard contains several pages:
+
+<p align="center"> 
+<img border="0" src="http://beust.com/pics/menu-convert2.png " />
+</p>
+ 
+This page lets you generate a testng.xml automatically. You can configure whether to include your test classes individually or by package, the suite and test name and also whether these tests should run in parallel.
+
+<p align="center"> 
+<img border="0" src="http://testng.org/pictures/refactoring2.png" />
+</p>
+
+This page gives you an overview of the changes that are about to be performed. You can also decide to exclude certain files from the refactoring.
+
+<p>
+
+When you are done, press the "Finish" button. Like all Eclipse refactorings, you can undo all these changes in one click:
+
+<p align="center"> 
+<img border="0" src="http://beust.com/pics/menu-convert4.png" />
+</p>
+
+<h3><a name="eclipse-quickfixes">8 - Quick fixes</a></h3>
+
+The TestNG Eclipse plug-in offers several quick fixes while you are editing a TestNG class (accessible with <tt>Ctrl-1</tt> on Windows/Linux and <tt>&#8984;-1</tt> on Mac OS):
+
+<h4>Convert to JUnit</h4>
+
+This was covered in the <a href="#eclipse-quickfix"> previous section</a>.
+
+<h4>Pushing and pulling <tt>@Test</tt> annotations</h4>
+
+If you have several test methods annotated with <tt>@Test</tt> and you'd like to replace them all with a single <tt>@Test</tt> annotation at the class level, choose the "Pull annotation" quick fix:
+
+<p align="center">
+<img border="0" src="http://testng.org/pictures/refactoring-pull-annotation.png" />
+</p>
+
+Reciprocally, you can move a class level <tt>@Test</tt> annotation onto all your public methods:
+
+<p align="center">
+<img border="0" src="http://testng.org/pictures/refactoring-push-annotation.png" />
+</p>
+
+<h4>Automatically importing <tt>asserts</tt></h4>
+
+Apply a quick fix on an assert method to automatically import it:
+
+<p align="center">
+<img border="0" src="http://testng.org/pictures/quickfix-import.png" />
+</p>
+
+<!--
+  Preferences and Properties
+-->
+<h3><a name="eclipse-preferences">9 - Preferences and Properties</a></h3>
+<h4><a name="eclipse-workbench-preferences">9.1 - Workbench Preferences</a></h4>
+TestNG workbench preferences:
+<p align="center">
+<img border="0" src="http://testng.org/pictures/preferences.png" />
+</p>
+<p>
+The preferences here are shared among projects and launch configurations.
+<ul>
+<li><b>Output directory</b>: the path where to store the output including temp files, report files, etc. By default, the path is relative to each project except you check the option <b>Absolute ouput path</b> below.</li>
+<li><b>Disalbe default listeners</b>: disable the default listeners when launch testng runtime</li>
+<li><b>Show view when test complete</b>: active the TestNG result view when the test complete</li>
+<li><b>Use Project TestNG jar</b>: It's default to be checked which means TestNG Eclipse Plugin will use the project TestNG jar (for example, the TestNG version managed with maven) when launch TestNG runtime. Otherwise, the TestNG eclipse plugin embedded TestNG version will be used.<br/>
+When to check this option?
+<ul>
+  <li>you want it behviour as close as running outside IDE, e.g. with maven surefire plugin, since the Plugin embedded version of TestNG most likely different with the version of your project.</li>
+  <li>you don't want the Plugin embedded plugin and it's dependent libs (e.g. snakeyaml, etc.) pollute your TestNG runtime classpath.</li>
+</ul></li>
+<li><b>Template XML file</b>: the absolute path of the template xml file used to genernate the custom test suite xml before launch</li>
+<li><b>Excluded stack traces</b>: </li>
+<li><b>Pre Defined Listeners</b>: </li>
+</ul>
+</p>
+
+<h4><a name="eclipse-project-properties">9.2 - Project Properties</a></h4>
+Project level properties:
+<p align="center">
+<img border="0" src="http://testng.org/pictures/project_properties.png" />
+</p>
+<p>
+Here are properties on each project level, it will override the same properties if defined in <a class="summary" href="#eclipse-workbench-preferences">TestNG workbench preferences</a>
+<ul>
+<li><b>Output directory</b>: for example, in the figure above, I prefer to put the output to maven 'target' directory rather than the default one under project root</li>
+<li><b>Watch testng-result.xml</b>:</li>
+<li><b>Template XML file</b>: see in <a class="summary" href="#eclipse-workbench-preferences">TestNG workbench preferences</a></li>
+<li><b>Use Project TestNG jar</b>: see in <a class="summary" href="#eclipse-workbench-preferences">TestNG workbench preferences</a></li>
+<li><b>Pre Defined Listeners</b>: see in <a class="summary" href="#eclipse-workbench-preferences">TestNG workbench preferences</a></li>
+</ul>
+</p>
+
+<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
+</script>
+<script type="text/javascript">
+_uacct = "UA-238215-2";
+urchinTracker();
+</script>
+
+</body>
\ No newline at end of file
diff --git a/doc/faq b/doc/faq
new file mode 100644
index 0000000..500bdd0
--- /dev/null
+++ b/doc/faq
@@ -0,0 +1,3 @@
+- I'm running groups and now, my @Before methods are no longer running.
+- How to return various numbers of parameters in a data provider based on the group
+being run
diff --git a/doc/idea.html b/doc/idea.html
new file mode 100644
index 0000000..6f5c555
--- /dev/null
+++ b/doc/idea.html
@@ -0,0 +1,73 @@
+<html>
+    <head>
+        <title>TestNG - IDEA Plugin</title>
+        <script type="text/javascript" src="banner.js"></script>
+        <link rel="stylesheet" href="testng.css" type="text/css" />
+      </head>
+<body>
+
+<script type="text/javascript">
+    displayMenu("idea.html")
+</script>
+
+<h2 align="center">TestNG IDEA Plug-in</h2>
+
+
+
+<p align="center">
+<img src="http://testng.org/pictures/idea-output.png" />
+</p>
+
+<h3>IDEA 7</h3>
+
+TestNG is bundled in IDEA 7 onwards, no extra plugins need to be installed.  The full documentation can be found <a href="http://www.jetbrains.com/idea/features/junit_testng.html#TestNG">here</a>.
+
+<h3>IDEA 6 and older </h3>
+
+
+TestNG has an IDEA plug-in that mirrors much of the behavior of the built-in JUnit support. You should be able to run tests within IDEA without defining any files externally, as well as quickly and easily migrate JUnit code. The plug-in also provides the same output test view as JUnit tests.
+
+Installation
+
+<ul>
+	<li>From IDEA, go to Preferences -> Plugins.</li>
+	<li>Click over to the Available Plugins tab.</li>
+</ul>
+<p>The latest version of the TestNG plugin will be listed there, ready to download and install.&nbsp;
+Also, you can go to the <a href="http://svn.jetbrains.org/idea/Trunk/bundled/testng">
+TestNG IDEA plug-in project page</a> and download its source from there.</p>
+<p>Please note that this plug-in only works on IDEA 5.0 and 6.0.</p>
+
+<p><b>Creating a TestNG Run/Debug configuration</b></p>
+<p>Once you have installed the plug-in and restarted IDEA, and have some TestNG classes you would like to run, simply
+open the Run/Debug window. You will see a TestNG tab, where you can add a configuration.
+
+</p>
+<p>There are a number of methods for determining the set of tests that will be run. These are:</p>
+<ul>
+	<li>Package: Specify a package to run. All tests in this package and below will be included.</li>
+	<li>Group: Specify a TestNG group to run.</li>
+	<li>Suite: Specify an external testng.xml file to run.</li>
+	<li>Class: Run all tests in a single class.</li>
+	<li>Method: Run a single test method.
+
+</li>
+</ul>
+<p>Once you create the run configuration, you can run it. Upon running, the plug-in will launch an external process to run your tests. The test results will be display in a tree view, with passed and failed tests highlighted. You can narrow down on the console output for a specify test by clicking on it, while double clicking a test will navigate to its source code.
+</p>
+
+<p align="center">
+<img src="http://testng.org/pictures/idea-rundialog.png" />
+</p>
+
+
+<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
+</script>
+<script type="text/javascript">
+_uacct = "UA-238215-2";
+urchinTracker();
+</script>
+
+</body>
+
+</html>
diff --git a/doc/index.html b/doc/index.html
new file mode 100644
index 0000000..0a03e34
--- /dev/null
+++ b/doc/index.html
@@ -0,0 +1,238 @@
+<html>
+  <head>
+    <title>TestNG - Welcome</title>
+      <link rel="stylesheet" href="testng.css" type="text/css" />
+      <link type="text/css" rel="stylesheet" href="http://beust.com/beust.css"  />
+      <script type="text/javascript" src="http://beust.com/prettify.js"></script>
+      <script type="text/javascript" src="banner.js"></script>
+
+      <script type="text/javascript" src="http://beust.com/scripts/shCore.js"></script>
+      <script type="text/javascript" src="http://beust.com/scripts/shBrushJava.js"></script>
+      <script type="text/javascript" src="http://beust.com/scripts/shBrushXml.js"></script>
+      <script type="text/javascript" src="http://beust.com/scripts/shBrushBash.js"></script>
+      <script type="text/javascript" src="http://beust.com/scripts/shBrushPlain.js"></script>
+      <link type="text/css" rel="stylesheet" href="http://beust.com/styles/shCore.css"/>
+      <link type="text/css" rel="stylesheet" href="http://beust.com/styles/shThemeCedric.css"/>
+      <script type="text/javascript">
+        SyntaxHighlighter.config.clipboardSwf = 'scripts/clipboard.swf';
+        SyntaxHighlighter.defaults['gutter'] = false;
+        SyntaxHighlighter.all();
+      </script>
+
+    </head>
+
+<body onload="prettyPrint()">
+
+<script type="text/javascript">
+   displayMenu("index.html");
+</script>
+
+
+<h2 >TestNG</h2>
+<h2>Now available</h2>
+<p align="center">
+<a href="book.html">
+<img border="0" src="http://beust.com/pics/book-cover.jpg" />
+</a>
+</p>
+<p align="center">
+<a href="book.html">Click for more details.</a>
+</p>
+
+
+
+<p align="right"><font size="-2"><em>C&eacute;dric Beust (cedric at beust.com)<br>
+Current version: 6.9.4<br>
+Created:&nbsp;April 27th, 2004<br>
+Last Modified:&nbsp; May 9th, 2015</em></font></p>
+
+
+<p>TestNG is a testing framework inspired from JUnit and NUnit but introducing
+some new functionalities that make it more powerful and easier to use, such as:</p>
+<ul>
+       <li>Annotations.
+       </li>
+       <li>Run your tests in arbitrarily big thread pools with various policies available
+       (all methods in their own thread, one thread per test class, etc...).
+       </li>
+       <li>Test that your code is multithread safe.
+       </li>
+       <li>Flexible test configuration.
+       </li>
+       <li>Support for data-driven testing (with <tt>@DataProvider</tt>).
+       </li>
+       <li>Support for parameters.
+       </li>
+       <li>Powerful execution model (no more <tt>TestSuite</tt>).
+       </li>
+       <li>Supported by a variety of tools and plug-ins (Eclipse, IDEA, Maven,
+       etc...).
+       </li>
+       <li>Embeds BeanShell for further flexibility.
+       </li>
+       <li>Default JDK functions for runtime and logging (no dependencies).
+       </li>
+       <li>Dependent methods for application server testing.</li>
+</ul>
+<p>TestNG is designed to cover all categories of tests:&nbsp; unit, functional,
+end-to-end, integration, etc...</p>
+<p>I started TestNG out of frustration for some JUnit deficiencies which I have
+documented on my weblog <a href="http://beust.com/weblog/2004/08/25/testsetup-and-evil-static-methods/">here</a> and <a href="http://beust.com/weblog/2004/02/08/junit-pain/">here</a>
+Reading these entries might give you a better idea of the goal I am trying to
+achieve with TestNG.&nbsp; You can also check out a quick
+<a href="http://www.beust.com/weblog/archives/000176.html">overview of the main
+features</a> and an <a href="http://beust.com/weblog/2004/08/18/using-annotation-inheritance-for-testing/">
+article</a> describing a very concrete example where the combined use of several
+TestNG's features provides for a very intuitive and maintainable testing design.</p>
+<p>Here is a very simple test:</p>
+
+<h3 class="sourcetitle">SimpleTest.java</h3>
+<pre class="brush: java" >
+package example1;
+
+import org.testng.annotations.*;
+
+public class SimpleTest {
+
+ @BeforeClass
+ public void setUp() {
+   // code that will be invoked when this test is instantiated
+ }
+
+ @Test(groups = { "fast" })
+ public void aFastTest() {
+   System.out.println("Fast test");
+ }
+
+ @Test(groups = { "slow" })
+ public void aSlowTest() {
+    System.out.println("Slow test");
+ }
+
+}
+</pre>
+
+The method <tt>setUp()</tt> will be invoked after the test class has been built and before
+any test method is run.&nbsp; In this example, we will be running the group
+fast, so <tt>aFastTest()</tt> will be invoked while <tt>aSlowTest()</tt> will be
+skipped.<p>
+<!-------------------------------------
+
+ WRITING A TEST
+
+ ------------------------------------>
+
+Things to note:</p><ul>
+       <li>No need to extend a class or implement an interface.</li><li>Even though the example above uses the JUnit conventions, our methods
+       can be called any name you like, it's the annotations that tell TestNG what
+       they are.</li><li>A test method can belong to one or several groups.</li></ul>
+
+       <p>
+
+Once you have compiled your test class into the <tt>build</tt> directory, you
+can invoke your test with the command line, an ant task (shown below) or an XML
+file:
+
+<h3 class="sourcetitle">build.xml</h3>
+<pre class="brush:java">
+&lt;project default="test"&gt;
+
+ &lt;path id="cp"&gt;
+   &lt;pathelement location="lib/testng-testng-5.13.1.jar"/&gt;
+   &lt;pathelement location="build"/&gt;
+ &lt;/path&gt;
+
+ &lt;taskdef name="testng" classpathref="cp"
+          classname="org.testng.TestNGAntTask" /&gt;
+
+ &lt;target name="test"&gt;
+   &lt;testng classpathref="cp" groups="fast"&gt;
+     &lt;classfileset dir="build" includes="example1/*.class"/&gt;
+   &lt;/testng&gt;
+ &lt;/target&gt;
+
+&lt;/project&gt;
+</pre>
+
+Use ant to invoke it:
+
+<pre class="brush: text">
+c:&gt; ant
+Buildfile: build.xml
+
+test:
+[testng] Fast test
+[testng] ===============================================
+[testng] Suite for Command line test
+[testng] Total tests run: 1, Failures: 0, Skips: 0
+[testng] ===============================================
+
+
+BUILD SUCCESSFUL
+Total time: 4 seconds
+</pre>
+
+Then you can browse the result of your tests:
+
+<pre class="brush: text">
+start test-output\index.html (on Windows)
+</pre>
+
+<h3><a name="requirements">Requirements</a></h3>
+<p>TestNG requires JDK 7 or higher.</p>
+
+<h3><a name="mailing-lists">Mailing-lists</a></h3>
+
+<ul>
+<li>The users mailing-list can be found on <a href="http://groups.google.com/group/testng-users">Google Groups</a>.
+<li>If you are interested in working on TestNG itself, join the <a href="http://groups.google.com/group/testng-users">developer mailing-list</a>.
+<li>If you are only interested in hearing about new versions of TestNG, you can join the low volume <a href="http://groups.google.com/group/testng-announcements">TestNG announcement mailing-list</a>.
+</ul>
+
+
+<h3><a name="locations-projects">Locations of the projects</a></h3>
+<p>If you are interested in contributing to TestNG or one of the IDE plug-ins,
+you will find them in the following locations:</p>
+<ul>
+       <li><a href="http://github.com/cbeust/testng/">TestNG</a></li>
+       <li><a href="http://github.com/cbeust/testng-eclipse/">Eclipse plug-in</a></li>
+<!--
+       <li><a href="http://code.google.com/p/testng/">TestNG</a></li>
+       <li><a href="http://code.google.com/p/testng-eclipse">Eclipse plug-in</a></li>
+-->
+      <li><a href="https://github.com/JetBrains/intellij-community/tree/master/plugins/testng">IDEA IntelliJ plug-in</a></li>
+       <li><a href="http://wiki.netbeans.org/TestNG">NetBeans plug-in</a></li>
+</ul>
+<h3><a id="bug-reports" name="bug-reports">Bug reports</a></h3>
+
+If you think you found a bug, here is how to report it:
+
+<ul>
+<li>Create a small project that will allow us to reproduce this bug. In most cases, one or two Java source files and a <tt>testng.xml</tt> file should be sufficient. Then you can either zip it and email it to the <a href="http://groups.google.com/group/testng-dev">testng-dev mailing-list</a> or make it available on an open source hosting site, such as <a href="http://github.com">github</a> or <a href="http://code.google.com/hosting/">Google code</a> and email <tt>testng-dev</tt> so we know about it. Please make sure that this project is self contained so that we can build it right away (remove the dependencies on external or proprietary frameworks, etc...).
+
+<li>If the bug you observed is on the Eclipse plug-in, make sure your sample project contains the <tt>.project</tt> and <tt>.classpath</tt> files.
+
+<li><a href="https://github.com/cbeust/testng/issues">File a bug</a>.
+
+</ul>
+
+</p>
+
+<p>For more information, you can either <a href="download.html">download TestNG</a>, read the <a href="documentation-main.html">manual</a> or browse the links at the<a href="#top">top</a>.</p>
+
+<h3>License</h3>
+
+<a href="http://testng.org/license">Apache 2.0</a>
+
+<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
+</script>
+<script type="text/javascript">
+_uacct = "UA-238215-2";
+urchinTracker();
+</script>
+
+
+</body>
+
+</html>
+
diff --git a/doc/maven.html b/doc/maven.html
new file mode 100644
index 0000000..6f9fd8d
--- /dev/null
+++ b/doc/maven.html
@@ -0,0 +1,218 @@
+<html>
+    <head>
+        <title>TestNG - Maven</title>
+
+        <link rel="stylesheet" href="testng.css" type="text/css" />
+        <link type="text/css" rel="stylesheet" href="http://beust.com/beust.css"  />
+        <script type="text/javascript" src="http://beust.com/prettify.js"></script>
+        <script type="text/javascript" src="banner.js"></script>
+
+        <script type="text/javascript" src="http://beust.com/scripts/shCore.js"></script>
+        <script type="text/javascript" src="http://beust.com/scripts/shBrushJava.js"></script>
+        <script type="text/javascript" src="http://beust.com/scripts/shBrushXml.js"></script>
+        <script type="text/javascript" src="http://beust.com/scripts/shBrushBash.js"></script>
+        <script type="text/javascript" src="http://beust.com/scripts/shBrushPlain.js"></script>
+        <link type="text/css" rel="stylesheet" href="http://beust.com/styles/shCore.css"/>
+        <link type="text/css" rel="stylesheet" href="http://beust.com/styles/shThemeCedric.css"/>
+        <script type="text/javascript">
+          SyntaxHighlighter.config.clipboardSwf = 'scripts/clipboard.swf';
+          SyntaxHighlighter.defaults['gutter'] = false;
+          SyntaxHighlighter.all();
+        </script>
+
+</head>
+
+<body onLoad="prettyPrint()">
+
+<script type="text/javascript">
+    displayMenu("maven.html")
+</script>
+
+
+<style type="text/css">
+    *.P1 { font-family:Sans; font-size:10pt; margin-left:0in; margin-right:0in; text-align:left ! important; text-indent:0inch; color:#a52a2a; }
+    *.P2 { font-family:Sans; font-size:10pt; margin-left:0in; margin-right:0in; text-align:left ! important; text-indent:0inch; }
+    *.P3 { font-family:'Nimbus Roman No9 L'; font-size:12pt; margin-left:0in; margin-right:0in; text-align:left ! important; text-indent:0inch; }
+    *.Standard { font-family:'Nimbus Roman No9 L'; font-size:12pt; }
+    *.Textbody { font-family:'Nimbus Roman No9 L'; font-size:12pt; margin-top:0in; margin-bottom:0.0835in; }
+    *.T1 { font-weight:bold; }
+    *.T2 { color:#353535; }
+    *.T3 { color:#a52a2a; font-weight:bold; }
+    *.T4 { color:#a52a2a; }
+    *.T5 { color:#353535; font-family:Sans; font-size:10pt; }
+    *.T6 { color:#a52a2a; font-family:Sans; font-size:10pt; font-weight:bold; }
+    *.T7 { color:#a52a2a; font-family:Sans; font-size:10pt; }
+  p,pre { width: 80%; }
+
+  ul.toc {
+    list-style: none;
+    margin:0 0.7em 0;
+    padding:0;
+    font-family: verdana, arial, sans-serif;
+  }
+  ul.toc li {
+    padding:0;
+    margin: 0.2em 0 0;
+  }
+  ul ul {
+    margin:0 2em 0;
+    padding: 0;
+    list-style-type: none;
+  }
+  li a {
+    display: block;
+    text-decoration: none;
+    padding: 2px 10px;
+    width: 140px;
+  }
+  ul.sub li a {
+    display: block;
+    border-top: none;
+    padding: 2px 10px;
+    background-color: transparent;
+  }
+</style>
+
+<h2>TestNG Maven plug-ins</h2>
+
+<h4>Table of Contents</h4>
+<ul class="toc">
+  <li class="first">
+    <a class="summary" href="#maven2">Maven2 Plugin</a>
+
+    <ul class="sub">
+      <li><a href="#archetype">Archetype</a></li>
+    </ul>
+  </li>
+  
+  <li class="last"><a class="summary" href="#maven1">Maven 1 Plugin</a></li>
+</ul>
+
+<!--  begin maven2  -->
+<h3 id="maven2">Maven 2</h3>
+
+<p>Maven 2 supports TestNG out of the box without the need to download any additional plugins <em>(other than TestNG itself)</em>. It is recommended that you use version 2.4 or above of the Surefire plugin (this is the case in all recent versions of Maven).</p>
+
+<p>
+You can find the full instructions on the <a href="http://maven.apache.org/plugins/maven-surefire-plugin/">Maven Surefire Plugin web site</a>. There are also <a href="http://maven.apache.org/plugins/maven-surefire-plugin/examples/testng.html">TestNG-specific instructions</a>.
+</p>
+
+<h4>Specifying your pom.xml</h4>
+
+The dependency in your project should look like the following:
+
+<pre class="brush: xml">
+ &lt;dependency&gt;
+   &lt;groupId&gt;org.testng&lt;/groupId&gt;
+   &lt;artifactId&gt;testng&lt;/artifactId&gt;
+   &lt;version&gt;6.8&lt;/version&gt;
+   &lt;scope&gt;test&lt;/scope&gt;
+ &lt;/dependency&gt;
+</pre>
+
+<h4>Sample Report</h4>
+<p>
+A sample surefire report with TestNG can be found <a href="samplereport/index.html">here</a>.
+</p>
+<br/>
+<!--  end maven2  -->
+
+<!-- maven2 archetype -->
+<h3 id="archetype">Maven TestNG Archetype <em>(Martin Gilday)</em></h3>
+<p>
+  Martin Gilday has added a new archetype for Maven2: to create a project using the archetype you simply have to specify my repository and the archetype ID.
+</p>
+  <pre class="brush: text">
+  mvn archetype:create -DgroupId=org.martingilday -DartifactId=test1 -DarchetypeGroupId=org.martingilday -DarchetypeArtifactId=testng-archetype
+    -DarchetypeVersion=1.0-SNAPSHOT -DremoteRepositories=http://www.martingilday.org/repository/</pre>
+
+<p>Of course substitute in your own groupId and artifactId.</p>
+<p>Don't forget to keep checking back at <a href="http://www.martingilday.org/updates/Maven+TestNG+Archetype">Martin's blog</a> for more updates. </p>
+<!-- end maven2 archetype -->
+
+<br/><br/>
+<h3 id="maven1">Maven 1 (by Andrew Glover)</h3>
+
+<p>The TestNG Maven plug-in is quite simple and consists of
+two goals and a series of optional properties.</p>
+
+<p>Currently the 1.1 version of the plug-in is bundled with
+official releases of TestNG. To utilize the plug-in, copy the
+<tt>maven-testng-plugin-<version>.jar</tt> to the <tt>$MAVEN_HOME/plugins</tt>
+directory.</p>
+
+<p>For the latest version of the plug-in (1.2 as of 12/12/05),
+update your <tt>maven.repo.remote</tt> to include <tt>http://www.vanwardtechnologies.com/repository/</tt>
+and then issue the following command: <tt>maven plugin:download</tt>. Maven will issue a series of questions,
+answer them as follows: <BR>
+<BR>
+<TABLE border="1" id="table1">
+    <TR>
+        <TD><tt>artifactId:</tt></TD>
+        <TD><tt>maven-testng-plugin</tt></TD>
+    </TR>
+    <TR>
+        <TD>groupId:</TD>
+        <TD><tt>testng</tt></TD>
+    </TR>
+    <TR>
+        <TD>version:</TD>
+        <TD><tt>1.2</tt></TD>
+    </TR>
+</TABLE>
+</p>
+
+
+<h4>Goals</h4>
+<table border="1" id="table2">
+    <tr>
+        <th>Goal</th>
+        <th>Description</th>
+    </tr>
+    <tr>
+        <td><tt>testng</tt></td>
+        <td>Runs TestNG</td>
+    </tr>
+    <tr>
+        <td><tt>testng:junit-report</tt></td>
+        <td>Creates a JUnit style report</td>
+    </tr>
+</table>
+<h4>Properties</h4>
+<table border="1" id="table3">
+    <tr>
+        <th>Property</th>
+        <th>Optional?</th>
+        <th>Description</th>
+    </tr>
+    <tr>
+        <td><tt>maven.testng.suitexml.name</tt></td>
+        <td>Yes</td>
+        <td>XML file name- defaults to <tt>testng.xml</tt></td>
+    </tr>
+    <tr>
+        <td><tt>maven.testng.suitexml.dir</tt></td>
+        <td>Yes</td>
+        <td>Directory where XML file lives. Defaults to <tt>${basedir}/test/conf</tt></td>
+    </tr>
+    <tr>
+        <td><tt>maven.testng.output.dir</tt></td>
+        <td>Yes</td>
+        <td>Default report directory. Defaults to <tt>${maven.build.dir}/testng-output</tt></td>
+    </tr>
+    <tr>
+        <td><tt>maven.testng.report.dir</tt></td>
+        <td>Yes</td>
+        <td>Directory for JUnit reports. Defaults to <tt>${maven.build.dir}/testngJunitReport</tt></td>
+    </tr>
+</table>
+
+<!-- end maven stuff -->
+
+<script src="http://www.google-analytics.com/urchin.js" type="text/javascript"></script>
+<script type="text/javascript">
+_uacct = "UA-238215-2";
+urchinTracker();
+</script>
+	
+</body>
diff --git a/doc/migrating.html b/doc/migrating.html
new file mode 100644
index 0000000..1767c8a
--- /dev/null
+++ b/doc/migrating.html
@@ -0,0 +1,114 @@
+<html>
+    <head>
+        <title>TestNG - Migrating from JUnit</title>
+
+        <link rel="stylesheet" href="testng.css" type="text/css" />
+        <link type="text/css" rel="stylesheet" href="http://beust.com/beust.css"  />
+        <script type="text/javascript" src="http://beust.com/prettify.js"></script>
+        <script type="text/javascript" src="banner.js"></script>
+
+        <script type="text/javascript" src="http://beust.com/scripts/shCore.js"></script>
+        <script type="text/javascript" src="http://beust.com/scripts/shBrushJava.js"></script>
+        <script type="text/javascript" src="http://beust.com/scripts/shBrushXml.js"></script>
+        <script type="text/javascript" src="http://beust.com/scripts/shBrushBash.js"></script>
+        <script type="text/javascript" src="http://beust.com/scripts/shBrushPlain.js"></script>
+        <link type="text/css" rel="stylesheet" href="http://beust.com/styles/shCore.css"/>
+        <link type="text/css" rel="stylesheet" href="http://beust.com/styles/shThemeCedric.css"/>
+        <script type="text/javascript">
+          SyntaxHighlighter.config.clipboardSwf = 'scripts/clipboard.swf';
+          SyntaxHighlighter.defaults['gutter'] = false;
+          SyntaxHighlighter.all();
+        </script>
+
+      </head>
+<body onLoad="prettyPrint()">
+
+<script type="text/javascript">
+    displayMenu("migrating.html")
+</script>
+
+
+<h2 align="center">Migrating from JUnit</h2>
+
+<h3>Using Eclipse</h3>
+
+The easiest way to convert your JUnit tests to TestNG is to use the Eclipse TestNG plug-in refactoring support. You will find a full description of its features in the <a href="eclipse.html#eclipse-quickfix">Eclipse section</a>.
+
+<h3>Asserts</h3>
+Note that the class <tt>org.testng.Assert</tt> uses a different argument ordering than the ones used by JUnit. If you are porting code that uses JUnit's asserts, you might want to us a static import of that class:
+
+<pre class="brush: java">
+import static org.testng.AssertJUnit.*;
+</pre>
+
+<h3>Running JUnit Tests</h3>
+
+<p>TestNG can automatically recognize and run JUnit tests, so you can use TestNG as a runner for all your existing tests and write new tests using TestNG.</p>
+
+<p>All you have to do is to put JUnit library on the TestNG classpath, so it can find and use JUnit classes,
+change your test runner from JUnit to TestNG in Ant and then run TestNG in <tt>"mixed"</tt> mode.
+This way you can have all your tests in the same project, even in the same package, and start using TestNG.
+This approach also allows you to convert your existing JUnit tests to TestNG incrementally.</p>
+
+<h4>Example - replacing JUnit Ant task with TestNG one</h4>
+
+JUnit version:
+<pre class="brush: xml">
+&lt;junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true"&gt;
+    &lt;batchtest todir="${build.test.results.dir}"&gt;
+        &lt;fileset dir="${test.src.dir}"&gt;
+            &lt;include name="**/*Test.*"/&gt;
+    &lt;/batchtest&gt;
+    &lt;classpath&gt;
+        &lt;path path="${run.test.classpath}"/&gt;
+    &lt;/classpath&gt;
+    &lt;syspropertyset&gt;
+        &lt;propertyref prefix="test-sys-prop."/&gt;
+        &lt;mapper from="test-sys-prop.*" to="*" type="glob"/&gt;
+    &lt;/syspropertyset&gt;
+    &lt;formatter type="xml"/&gt;
+    &lt;jvmarg value="-ea"/&gt;
+    &lt;jvmarg line="${run.jvmargs}"/&gt;
+&lt;/junit&gt;
+</pre>
+
+TestNG version:
+<pre class="brush: xml">
+&lt;taskdef name="testng" classname="org.testng.TestNGAntTask" classpath="${run.test.classpath}"/&gt;
+
+&lt;fileset id="mixed.tests" dir="${test.src.dir}"&gt;
+    &lt;include name="**/*Test.*"/&gt;
+&lt;/fileset&gt;
+
+&lt;testng mode="mixed" classfilesetref="mixed.tests" workingDir="${work.dir}" failureProperty="tests.failed" outputdir="${build.test.results.dir}"&gt;
+    &lt;classpath&gt;
+        &lt;pathelement path="${build.test.classes.dir}"/&gt;
+        &lt;pathelement path="${run.test.classpath}"/&gt;
+        &lt;pathelement path="${junit.lib}"/&gt;
+    &lt;/classpath&gt;
+    &lt;propertyset&gt;
+        &lt;propertyref prefix="test-sys-prop."/&gt;
+        &lt;mapper from="test-sys-prop.*" to="*" type="glob"/&gt;
+    &lt;/propertyset&gt;
+    &lt;jvmarg line="${run.jvmargs}"/&gt;
+&lt;/testng&gt;
+</pre>
+
+
+<h3>Related reading</h3>
+
+<ul>
+    <li><a href="http://www.opengamma.com/blog/2011/04/04/converting-opengamma-junit-testng">Here is the detailed report of a company that successfully converted a large codebase of JUnit 4 tests over to TestNG</a>.</li>
+    <li><a href="http://wiki.netbeans.org/TestNG_MixedMode">Mixed mode in TestNG</a>.</li>
+</ul>
+
+<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
+</script>
+<script type="text/javascript">
+_uacct = "UA-238215-2";
+urchinTracker();
+</script>
+
+
+</body>
+	
\ No newline at end of file
diff --git a/doc/misc.html b/doc/misc.html
new file mode 100644
index 0000000..9dcb57a
--- /dev/null
+++ b/doc/misc.html
@@ -0,0 +1,200 @@
+<html>
+    <head>
+        <title>TestNG - Miscellaneous</title>
+        <script type="text/javascript" src="banner.js"></script>
+        <link rel="stylesheet" href="testng.css" type="text/css" />
+      </head>
+<body>
+
+<script type="text/javascript">
+    displayMenu("misc.html")
+</script>
+
+
+
+<h2 align="center">MORE <a name="news">TESTNG READING</a></h2><p>Here are
+several articles and presentations about TestNG (<a href="#english">English</a>,
+<a href="#french">French</a>, <a href="#german">German</a>).</p>
+<h3><a name="english">English</a></h3><ul>
+
+
+<!--
+       <li><a href=""></a></li>
+-->
+
+       <li><a href="https://community.oracle.com/docs/DOC-916315">Use TestNG to create test logs that are structured like Javadocs (Raghunandan Seshadri, August 2015)</a></li>
+       <li><a href="http://blogs.atlassian.com/2013/02/testng-plus-junit-bamboo/">
+        How I Learned to Stop Worrying and Love TestNG (March 2013)</a></li>
+       <li><a href="http://zeroturnaround.com/labs/using-spock-to-test-groovy-and-java-applications/">Using Spock to test Groovy AND Java applications (March 2013)</a></li>
+       <li><a href="http://www.asjava.com/testng/testng-tutorials/">TestNG tutorials (Jammy Chen)</a></li>
+       <li><a
+       href="http://kaczanowscy.pl/tomek/2012-04/why-testng-and-not-junit">Why TestNG and not JUnit? (Tomek Kaczanowski)</a></li>
+       <li><a href="http://www.randomsync.net/2012/02/excel-testng-driving-testng-tests.html">TestNG and Excel</a></li>
+       <li><a href="https://github.com/rackspace/python-proboscis">TestNG in Python</a></li>
+       <li><a href="http://www.opengamma.com/blog/2011/04/04/converting-opengamma-junit-testng">Converting a large codebase from JUnit 4 to TestNG</a></li>
+       <li><a href="http://seleniumexamples.com/blog/guide/using-soft-assertions-in-testng/">Using soft assertions with TestNG</a></li>
+       <li><a href="http://kaczanowscy.pl/tomek/2011-02/visualization-dependent-test-methods">Visualizing live dependencies (Tomek Kaczanowski)</a></li>
+       <li><a href="http://www.lysergicjava.com/?p=165">Passing parameters to @DataProviders.</a></li>
+       <li><a href="http://barisergun.blogspot.com/2010/09/integrated-mockito-testng-and-emma-on.html">TestNG, Emma and Mockito </a></li>
+       <li><a href="http://www.dotneter.org/2010/09/data-driven-testing-using-selenium-testng-part-1-of-4.html">Data-driven testing using Selenium and TestNG (video)</a></li>
+       <li><a href="http://blog.meesqa.com/2010/09/11/combine-multiple-testng-resultsxml-files-into-a-single-xml-file/">Combining multiple testng-results.xml file into a single file</a></li>
+       <li><a href="http://techo-ecco.com/blog/testng-with-cobertura/">TestNG + Cobertura</a></li>
+       <li><a href="http://blog.jayway.com/2009/12/14/powermock-testng-true/">TestNG + PowerMock + Mockito</a></li>
+       <li><a href="http://maxheapsize.com/2009/09/23/test-jboss-rules-5-with-testng/">TestNG and Drools </a></li>
+       <li><a href="http://functionaltestautomation.blogspot.com/2009/10/dataprovider-data-driven-testing-with.html">Data Driven Testing with Selenium and TestNG</a></li>
+       <li><a href="http://margelatu.org/2009/06/25/java-code-coverage-reports-in-eclipse/">Code coverage with Emma and TestNG</a></li>
+       <li><a href="http://www.mkyong.com/unittest/junit-4-vs-testng-comparison/">Comparison between JUnit 4 and TestNG</a></li>
+       <li><a href="http://www.michaelminella.com/testing/unit-testing-with-testng-and-jmockit.html">Unit testing with TestNG and JMockit (part 1)</a></li>
+
+       <li>
+	<a href="http://www.michaelminella.com/testing/unit-testing-with-testng-and-jmockit-part-2.html">Unit testing with TestNG and JMockit (part 2)</a></li>
+
+       <li>
+	<a href="http://www.techbookreport.com/tbr0332.html">Review of the book</a></li>
+
+	<li>
+	<a href="http://thediscoblog.com/2006/10/05/testng-is-so-groovy/">TestNG and
+	Groovy (Andrew Glover, October 2006)</a></li>
+		<li>
+	<a href="http://www-128.ibm.com/developerworks/java/library/j-cq08296/">In
+	pursuit of code quality (Andrew Glover, August 2006)</a></li>
+		<li>
+	<a href="http://membres.lycos.fr/testng/">TestNG Tutorial (Claude Quezel,
+	March 2006)</a></li>
+		<li>
+	<a href="http://stripes.mc4j.org/confluence/display/stripes/Unit+Testing">
+	TestNG and Stripes (Tim Fennel, April 2006)</a></li>
+		<li>
+	<a href="http://testearly.com/2006/04/03/rerunning-of-failed-tests/">
+	Rerunning failed tests (Andrew Glover, April 2006)</a></li>
+		<li>
+	<a href="http://howardlewisship.com/tapestry-javaforge/tapestry-testng/">
+	Tapestry and TestNG (Howard Lewis-Ship, April 2006)</a></li>
+		<li>
+	<a href="http://thediscoblog.com/2006/03/27/using-junit-extensions-in-testng/">
+	Using JUnit extensions in TestNG (Andrew Glover, March 2006)</a></li>
+		<li>
+	<a href="http://www-128.ibm.com/developerworks/forums/dw_thread.jsp?forum=812&thread=110765&cat=10">
+	Code coverage of TestNG tests with Cobertura (Andrew Glover, March 2006)</a></li>
+		<li>
+	<a href="http://beust.com/weblog/archives/000369.html">Statistical Testing (C&eacute;dric
+	Beust, February 2006)</a></li>
+		<li>
+	<a href="http://bill.dudney.net/roller/page/bill?entry=testng_is_a_leap_beyond">
+	TestNG and JUnit (Bill Dudney, February 2006)</a></li>
+		<li>
+	<a href="http://beust.com/weblog/archives/000362.html">Distributed TestNG (C&eacute;dric
+	Beust, January 2006)</a></li>
+		<li>
+	<a href="http://erik.thauvin.net/blog/news.jsp?date=2006-01-29#418">Testing
+	private methods (Erik Thauvin, January 2006)</a></li>
+		<li>
+	<a href="http://blog.hibernate.org/cgi-bin/blosxom.cgi/2005/11/24#ejb3withtestng">
+	Testing EJB3 with TestNG (Christian Bauer, November 2005)</a></li>
+		<li>
+	<a href="http://www.vanwardtechnologies.com/cedricb01.php">Vanward
+	Technologies interview (October 2005)</a></li>
+	<li>
+	<a href="http://jroller.com/page/tfenne?entry=i_m_starting_to_really">TestNG
+	reaction (Tim Fennell, October 2005)</a></li>
+	<li>
+	<a href="http://www.realsolve.co.uk/site/tech/blog.php?name=philzoio&mydate=20050826">
+	Using JUnit libraries with TestNG (Phil Zoio, August 2005)</a></li>
+	<li><a href="http://jyperion.org/articles/testng/testng.htm">Introducing
+	TestNG (Thierry Janaudy, August 2005)</a></li>
+	<li><a href="http://www.theserverside.com/news/thread.tss?thread_id=35737">
+	Migrating to TestNG (TheServerSide, August 2005)</a></li>
+	<li><a href="http://forum.springframework.org/viewtopic.php?t=7903">TestNG
+	and Spring (August 2005)</a></li>
+	<li>
+	<a href="http://groups-beta.google.com/group/comp.lang.ruby/browse_thread/thread/3a0952199bb1bc1d/8a4a6d7931c039ce?lnk=st&q=testng+ruby&rnum=1&hl=en">
+	TestNG and Ruby (August 2005)</a></li>
+	<li><a href="http://www.thechanfam.net/wordpress/?p=36">TestNG and Maven
+	(April 2005)</a></li><li><a href="http://kevin.oneill.id.au/2005/04/06/testng">
+		TestNG review (April 2005)</a></li><li>
+	<a href="http://www.javaworld.com/javaworld/jw-04-2005/jw-0404-testng_p.html">
+	JavaWorld (April 2005)</a></li><li><a href="http://pcal.net/blog/archives/2005/03/a_great_new_ide.html">
+		Solving the JUnit dependency problem (March 2005)</a></li><li><a href="http://beust.com/weblog/archives/000259.html">
+		Are dependent test methods really evil?&nbsp; (March 2005)</a></li><li><a href="http://beust.com/weblog/archives/000252.html">
+		The &quot;call super&quot; anti-pattern (March 2005)</a></li><li>TestNG:&nbsp; A
+		different look at testing (Presentation at TheServerSide Symposium,
+		March 2005)</li><li><a href="http://beust.com/weblog/archives/000236.html">
+		Testing asynchronous code with TestNG (February 2005)</a></li><li><a href="http://www-106.ibm.com/developerworks/java/library/j-testng/">
+		Making Testing a Breeze with TestNG (DeveloperWorks, January 2005)</a>
+		(also
+	<a href="http://www.51testing.com/tech/20050122_1.htm">in Chinese</a>)</li><li><a href="http://www.theserverside.com/news/thread.tss?thread_id=31214">
+		TestNG:&nbsp; Catch the Testing Fever (TheServerSide, January 2005)</a></li><li><a href="http://www.beust.com/weblog/archives/000230.html">
+		Why use XML to configure tests?&nbsp; (January 2005)</a></li><li>
+	<a href="http://www.javapolis.com/confluence/display/JP04/Beyond+JUnit+,+introducing+TestNG,+the+next+generation+in+testing">
+	TestNG:&nbsp; Beyond JUnit (Presentation at JavaPolis, Belgium, December
+	2004)</a></li><li><a href="http://www.theserverside.com/news/thread.tss?thread_id=30394">
+		Announcing TestNG 2.0 (TheServerSide, December 2004)</a></li><li><a href="http://www.javalobby.org/articles/testng/">
+		TestNG:&nbsp; Catching the TestNG fever (JavaLobby, December 2004)</a></li><li><a href="http://www.beust.com/weblog/archives/000170.html">
+		Using inheritance of annotations for flexible testing (August 2004)</a></li><li><a href="http://www.beust.com/weblog/archives/000175.html">
+		Multiple instantations of tests (August 2004)</a></li><li><a href="http://theserverside.com/blogs/showblog.tss?id=Unitized">
+		Don't get unitized (July 2004)</a></li><li>
+	<a href="http://www.google.com/url?sa=X&start=3&q=http://www.theserverside.com/news/thread.tss?thread_id=25568">
+	TestNG:&nbsp; Testing, the Next Generation (TheServerSide, April 2004)</a></li></ul>
+<h3><a name="french">French</a></h3>
+<ul>
+	<li><a href="http://www.spalud.info/testng/">Le framework TestNG</a> (S&eacute;bastien
+	Palud, April 2006)<br>
+&nbsp;</li>
+</ul>
+
+<h3><a name="german">German</a></h3>
+<ul>
+	<li><a href="http://www.qaware.de/downloads/to1-adersberger.pdf">Comparison TestNG / JUnit 4</a> (Josef Adersberger,
+	May 2006, PDF)<br>
+&nbsp;</li>
+</ul>
+
+<h2 align="center">
+<a name="contributors">CONTRIBUTORS</a></h2><p align="left">TestNG has received a lot of help from various people throughout
+its existence.</p><p align="left">With code:</p><ul>
+	<li>
+	<p align="left"><a href="mailto:the_mindstorm@evolva.ro">Alexandru Popescu</a>,
+	who ported TestNG to JDK 1.4 and has been tirelessly contributing ever
+	since.</li>
+	<li>
+	<p align="left">Hani Suleiman and Mark Derricutt (IDEA plug-in).</li>
+	<li>
+	<p align="left">Andrew Glover and <a href="http://tacos.sourceforge.net">Jesse Kuhnert</a>/<a href="http://blogs.codehaus.org/people/brett//archives/maven.html">Brett Porter</a> (Maven 1 and Maven 2
+	plug-ins respectively).</li><li>
+	<p align="left">Jolly Chen (JUnitReport plug-in).</li><li>
+	<p align="left">Thierry Janaudy (PDF report plug-in).</li></ul><p align="left">And also with ideas, articles and bug reports:</p><ul>
+	<li>
+	<p align="left">Filippo Diotalevi, Matthew Schmidt Robert McIntosh, JR
+	Boyens, Robert Watkins, Aleksi Kallio, Doug Lea, Eron Wright, Guillaume
+	Laforge, Owen Griffiths, Jean-Louis Berliet,
+	Morten Christensen and many others... </li>
+</ul>
+
+<h2 align="center">&nbsp;</h2>
+
+<h2 align="center">
+SPECIAL THANKS
+</h2>
+To <a href="http://yourkit.com">YourKit</a> for helping us profiling TestNG.<p>&nbsp;</p>
+
+
+<h2 align="center"><a name="testimonies">TESTIMONIES</a></h2><p align="left">A great product is nothing without testimonies, so here is what
+people have to say about TestNG:</p><p align="left">&quot;It's a lot of fun.&nbsp; Not as fun as Seinfeld, but more fun than
+Chris Rock on a bad day&quot;<br>
+&nbsp;&nbsp;&nbsp; - <i>A buddy</i>&nbsp;<br>
+<br>
+&quot;Are you going to eat your dessert?&quot;<br>
+&nbsp;&nbsp;&nbsp; - <i>Someone I met at the cafeteria</i><br>
+&nbsp;<br>
+&quot;Rock on, dude&quot;<br>
+&nbsp;&nbsp;&nbsp; - <i>A skateboarder<br>
+</i>&nbsp;<br>
+&quot;Okay, I'll try it.&nbsp; Can I have my laptop back now&quot;?<br>
+&nbsp;&nbsp;&nbsp; - <i>A scared contractor<br>
+</i>&nbsp;<br>
+&quot;Cedric, stop talking to him, he's sleeping&quot;<br>
+&nbsp;&nbsp;&nbsp; - <i>Some random person who should mind their own business</i></p>
+
+</body>
+
+</html>
diff --git a/doc/pics/chronological-methods.png b/doc/pics/chronological-methods.png
new file mode 100644
index 0000000..84e23be
--- /dev/null
+++ b/doc/pics/chronological-methods.png
Binary files differ
diff --git a/doc/pics/parameters.png b/doc/pics/parameters.png
new file mode 100644
index 0000000..7fa9f71
--- /dev/null
+++ b/doc/pics/parameters.png
Binary files differ
diff --git a/doc/pics/show-output1.png b/doc/pics/show-output1.png
new file mode 100644
index 0000000..bac1f3b
--- /dev/null
+++ b/doc/pics/show-output1.png
Binary files differ
diff --git a/doc/pics/show-output2.png b/doc/pics/show-output2.png
new file mode 100644
index 0000000..1106f37
--- /dev/null
+++ b/doc/pics/show-output2.png
Binary files differ
diff --git a/doc/prettify.css b/doc/prettify.css
new file mode 100644
index 0000000..c3bab21
--- /dev/null
+++ b/doc/prettify.css
@@ -0,0 +1,34 @@
+.prettyprint .kwd, .tag {

+  font-weight: bold;

+  color: #7f0055;

+}

+

+.prettyprint .str,.atv {

+  color: #0000cc;

+}

+

+.prettyprint .com {

+  color: #008800;

+}

+

+.prettyprint .lit {

+  color: #cc2222;

+  font-weight: bold;

+}

+

+

+pre.prettyprint { padding: 2px; border: 1px solid #888; }

+

+@media print {

+  .str { color: #060; }

+  .kwd { color: #006; font-weight: bold; }

+  .com { color: #600; font-style: italic; }

+  .typ { color: #404; font-weight: bold; }

+  .lit { color: #044; }

+  .pun { color: #440; }

+  .pln { color: #000; }

+  .tag { color: #006; font-weight: bold; }

+  .atn { color: #404; }

+  .atv { color: #060; }

+}

+

diff --git a/doc/prettify.js b/doc/prettify.js
new file mode 100644
index 0000000..e788667
--- /dev/null
+++ b/doc/prettify.js
@@ -0,0 +1,1314 @@
+// Copyright 2005 Google Inc.

+// All Rights Reserved.

+//

+// msamuel@google.com

+

+// Usage:

+// 1) include this source file in an html page via

+// <script type=text/javascript src=prettify.js></script>

+// 2) define style rules.  See the example page for examples.

+// 3) mark the <pre> and <code> tags in your source with class=prettyprint.

+//    You can also use the (html deprecated) <xmp> tag, but the pretty printer

+//    needs to do more substantial DOM manipulations to support that, so some

+//    css styles may not be preserved.

+

+// Change log:

+// cbeust, 2006/08/22

+//   Java annotations (start with "@") are now captured as literals ("lit")

+// 

+

+var PR_keywords = new Object();

+/** initialize the keyword list for our target languages. */

+(function () {

+  var CPP_KEYWORDS = (

+    "bool break case catch char class const const_cast continue default " +

+    "delete deprecated dllexport dllimport do double dynamic_cast else enum " +

+    "explicit extern false float for friend goto if inline int long mutable " +

+    "naked namespace new noinline noreturn nothrow novtable operator private " +

+    "property protected public register reinterpret_cast return selectany " +

+    "short signed sizeof static static_cast struct switch template this " +

+    "thread throw true try typedef typeid typename union unsigned using " +

+    "declaration, using directive uuid virtual void volatile while typeof");

+  var JAVA_KEYWORDS = (

+    "abstract default goto package synchronized boolean do if private this " +

+    "break double implements protected throw byte else import public throws " +

+    "case enum instanceof return transient catch extends int short try char " +

+    "final interface static void class finally long strictfp volatile const " +

+    "float native super while continue for new switch");

+  var PYTHON_KEYWORDS = (

+    "and assert break class continue def del elif else except exec finally " +

+    "for from global if import in is lambda not or pass print raise return " +

+    "try while yield");

+  var JSCRIPT_KEYWORDS = (

+    "abstract boolean break byte case catch char class const continue " +

+    "debugger default delete do double else enum export extends false final " +

+    "finally float for function goto if implements import in instanceof int " +

+    "interface long native new null package private protected public return " +

+    "short static super switch synchronized this throw throws transient " +

+    "true try typeof var void volatile while with NaN Infinity");

+  var PERL_KEYWORDS = (

+    "foreach require sub unless until use elsif BEGIN END");

+  var SH_KEYWORDS = (

+    "if then do else fi end");

+  var KEYWORDS = [CPP_KEYWORDS, JAVA_KEYWORDS, PYTHON_KEYWORDS,

+                  JSCRIPT_KEYWORDS, PERL_KEYWORDS, SH_KEYWORDS];

+  for (var k = 0; k < KEYWORDS.length; k++) {

+    var kw = KEYWORDS[k].split(' ');

+    for (var i = 0; i < kw.length; i++) {

+      if (kw[i]) { PR_keywords[kw[i]] = true; }

+    }

+  }

+}).call(this);

+

+// token style names.  correspond to css classes

+/** token style for a string literal */

+var PR_STRING = 'str';

+/** token style for a keyword */

+var PR_KEYWORD = 'kwd';

+/** token style for a comment */

+var PR_COMMENT = 'com';

+/** token style for a type */

+var PR_TYPE = 'typ';

+/** token style for a literal value.  e.g. 1, null, true. */

+var PR_LITERAL = 'lit';

+/** token style for a punctuation string. */

+var PR_PUNCTUATION = 'pun';

+/** token style for a punctuation string. */

+var PR_PLAIN = 'pln';

+

+/** token style for an sgml tag. */

+var PR_TAG = 'tag';

+/** token style for a markup declaration such as a DOCTYPE. */

+var PR_DECLARATION = 'dec';

+/** token style for embedded source. */

+var PR_SOURCE = 'src';

+/** token style for an sgml attribute name. */

+var PR_ATTRIB_NAME = 'atn';

+/** token style for an sgml attribute value. */

+var PR_ATTRIB_VALUE = 'atv';

+

+/** the position of the end of a token during.  A division of a string into

+  * n tokens can be represented as a series n - 1 token ends, as long as

+  * runs of whitespace warrant their own token.

+  * @private

+  */

+function PR_TokenEnd(end, style) {

+  if (undefined === style) { throw new Error('BAD'); }

+  if ('number' != typeof(end)) { throw new Error('BAD'); }

+  this.end = end;

+  this.style = style;

+}

+PR_TokenEnd.prototype.toString = function () {

+  return '[PR_TokenEnd ' + this.end +

+    (this.style ? ':' + this.style : '') + ']';

+};

+

+

+/** a chunk of text with a style.  These are used to represent both the output

+  * from the lexing functions as well as intermediate results.

+  * @constructor

+  * @param token the token text

+  * @param style one of the token styles defined in designdoc-template, or null

+  *   for a styleless token, such as an embedded html tag.

+  * @private

+  */

+function PR_Token(token, style) {

+  if (undefined === style) { throw new Error('BAD'); }

+  this.token = token;

+  this.style = style;

+}

+

+PR_Token.prototype.toString = function () {

+  return '[PR_Token ' + this.token + (this.style ? ':' + this.style : '') + ']';

+};

+

+

+/** a helper class that decodes common html entities used to escape source and

+  * markup punctuation characters in html.

+  * @constructor

+  * @private

+  */

+function PR_DecodeHelper() {

+  this.next = 0;

+  this.ch = '\0';

+}

+

+PR_DecodeHelper.prototype.decode = function (s, i) {

+  var next = i + 1;

+  var ch = s.charAt(i);

+  if ('&' == ch) {

+    var semi = s.indexOf(';', next);

+    if (semi >= 0 && semi < next + 4) {

+      var entityName = s.substring(next, semi).toLowerCase();

+      next = semi + 1;

+      if ('lt' == entityName) {

+        ch = '<';

+      } else if ('gt' == entityName) {

+        ch = '>';

+      } else if ('quot' == entityName) {

+        ch = '"';

+      } else if ('apos' == entityName) {

+        ch = '\'';

+      } else if ('amp' == entityName) {

+        ch = '&';

+      } else {

+        next = i + 1;

+      }

+    }

+  }

+  this.next = next;

+  this.ch = ch;

+  return this.ch;

+}

+

+

+// some string utilities

+function PR_isWordChar(ch) {

+  return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');

+}

+

+function PR_isIdentifierStart(ch) {

+  return PR_isWordChar(ch) || ch == '_' || ch == '$' || ch == '@';

+}

+

+function PR_isIdentifierPart(ch) {

+  return PR_isIdentifierStart(ch) || PR_isDigitChar(ch);

+}

+

+function PR_isSpaceChar(ch) {

+  return "\t \r\n".indexOf(ch) >= 0;

+}

+

+function PR_isDigitChar(ch) {

+  return ch >= '0' && ch <= '9';

+}

+

+function PR_trim(s) {

+  var i = 0, j = s.length - 1;

+  while (i <= j && PR_isSpaceChar(s.charAt(i))) { ++i; }

+  while (j > i && PR_isSpaceChar(s.charAt(j))) { --j; }

+  return s.substring(i, j + 1);

+}

+

+function PR_startsWith(s, prefix) {

+  return s.length >= prefix.length && prefix == s.substring(0, prefix.length);

+}

+

+function PR_endsWith(s, suffix) {

+  return s.length >= suffix.length &&

+         suffix == s.substring(s.length - suffix.length, s.length);

+}

+

+/** true iff prefix matches the first prefix characters in chars[0:len].

+  * @private

+  */

+function PR_prefixMatch(chars, len, prefix) {

+  if (len < prefix.length) { return false; }

+  for (var i = 0, n = prefix.length; i < n; ++i) {

+    if (prefix.charAt(i) != chars[i]) { return false; }

+  }

+  return true;

+}

+

+/** used to convert html special characters embedded in XMP tags into html. */

+function PR_textToHtml(str) {

+  return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');

+}

+

+

+/** split markup into chunks of html tags (style null) and

+  * plain text (style {@link #PR_PLAIN}).

+  *

+  * @param s a String of html.

+  * @return an Array of PR_Tokens of style PR_PLAIN and null.

+  * @private

+  */

+function PR_chunkify(s) {

+  var chunks = new Array();

+  var state = 0;

+  var start = 0;

+  var pos = -1;

+  for (var i = 0, n = s.length; i < n; ++i) {

+    var ch = s.charAt(i);

+    switch (state) {

+      case 0:

+        if ('<' == ch) { state = 1; }

+        break;

+      case 1:

+        pos = i - 1;

+        if ('/' == ch) { state = 2; }

+        else if (PR_isWordChar(ch)) { state = 3; }

+        else if ('<' == ch) { state = 1; }

+        else { state = 0; }

+        break;

+      case 2:

+        if (PR_isWordChar(ch)) { state = 3; }

+        else if ('<' == ch) { state = 1; }

+        else { state = 0; }

+        break;

+      case 3:

+        if ('>' == ch) {

+          if (pos > start) {

+            chunks.push(new PR_Token(s.substring(start, pos), PR_PLAIN));

+          }

+          chunks.push(new PR_Token(s.substring(pos, i + 1), null));

+          start = i + 1;

+          pos = -1;

+          state = 0;

+        }

+        break;

+    }

+  }

+  if (s.length > start) {

+    chunks.push(new PR_Token(s.substring(start, s.length), PR_PLAIN));

+  }

+  return chunks;

+}

+

+/** splits chunks around entities.

+  * @private

+  */

+function PR_splitEntities(chunks) {

+  var chunksOut = new Array();

+  var state = 0;

+  for (var ci = 0, nc = chunks.length; ci < nc; ++ci) {

+    var chunk = chunks[ci];

+    if (PR_PLAIN != chunk.style) {

+      chunksOut.push(chunk);

+      continue;

+    }

+    var s = chunk.token;

+    var pos = 0;

+    var start;

+    for (var i = 0; i < s.length; ++i) {

+      var ch = s.charAt(i);

+      switch (state) {

+        case 0:

+          if ('&' == ch) { state = 1; }

+          break;

+        case 1:

+          if ('#' == ch || PR_isWordChar(ch)) {

+            start = i - 1;

+            state = 2;

+          } else {

+            state = 0;

+          }

+          break;

+        case 2:

+          if (';' == ch) {

+            if (start > pos) {

+              chunksOut.push(

+                  new PR_Token(s.substring(pos, start), chunk.style));

+            }

+            chunksOut.push(new PR_Token(s.substring(start, i + 1), null));

+            pos = i + 1;

+            state = 0;

+          }

+          break;

+      }

+    }

+    if (s.length > pos) {

+      chunksOut.push(pos ?

+                     new PR_Token(s.substring(pos, s.length), chunk.style) :

+                     chunk);

+    }

+  }

+  return chunksOut;

+}

+

+/** walk the tokenEnds list and the chunk list in parallel to generate a list

+  * of split tokens.

+  * @private

+  */

+function PR_splitChunks(chunks, tokenEnds) {

+  var tokens = new Array();  // the output

+

+  var ci = 0;  // index into chunks

+  // position of beginning of amount written so far in absolute space.

+  var posAbs = 0;

+  // position of amount written so far in chunk space

+  var posChunk = 0;

+

+  // current chunk

+  var chunk = new PR_Token('', null);

+

+  for (var ei = 0, ne = tokenEnds.length; ei < ne; ++ei) {

+    var tokenEnd = tokenEnds[ei];

+    var end = tokenEnd.end;

+

+    var tokLen = end - posAbs;

+    var remainingInChunk = chunk.token.length - posChunk;

+    while (remainingInChunk <= tokLen) {

+      if (remainingInChunk > 0) {

+        tokens.push(

+            new PR_Token(chunk.token.substring(posChunk, chunk.token.length),

+                         null == chunk.style ? null : tokenEnd.style));

+      }

+      posAbs += remainingInChunk;

+      posChunk = 0;

+      if (ci < chunks.length) { chunk = chunks[ci++]; }

+

+      tokLen = end - posAbs;

+      remainingInChunk = chunk.token.length - posChunk;

+    }

+

+    if (tokLen) {

+      tokens.push(

+          new PR_Token(chunk.token.substring(posChunk, posChunk + tokLen),

+                       tokenEnd.style));

+      posAbs += tokLen;

+      posChunk += tokLen;

+    }

+  }

+

+  return tokens;

+}

+

+/** splits markup tokens into declarations, tags, and source chunks.

+  * @private

+  */

+function PR_splitMarkup(chunks) {

+  // A state machine to split out declarations, tags, etc.

+  // This state machine deals with absolute space in the text, indexed by k,

+  // and position in the current chunk, indexed by pos and tokenStart to

+  // generate a list of the ends of tokens.

+  // Absolute space is calculated by considering the chunks as appended into

+  // one big string, as they were before being split.

+

+  // Known failure cases

+  // Server side scripting sections such as <?...?> in attributes.

+  // i.e. <span class="<? foo ?>">

+  // Handling this would require a stack, and we don't use PHP.

+

+  // The output: a list of pairs of PR_TokenEnd instances

+  var tokenEnds = new Array();

+

+  var state = 0;  // FSM state variable

+  var k = 0;  // position in absolute space of the start of the current chunk

+  var tokenStart = -1;  // the start of the current token

+

+  // Try to find a closing tag for any open <style> or <script> tags

+  // We can't do this at a later stage because then the following case

+  // would fail:

+  // <script>document.writeln('<!--');</script>

+

+  // We use tokenChars[:tokenCharsI] to accumulate the tag name so that we

+  // can check whether to enter into a no scripting section when the tag ends.

+  var tokenChars = new Array(12);

+  var tokenCharsI = 0;

+  // if non null, the tag prefix that we need to see to break out.

+  var endScriptTag = null;

+  var decodeHelper = new PR_DecodeHelper();

+

+  for (var ci = 0, nc = chunks.length; ci < nc; ++ci) {

+    var chunk = chunks[ci];

+    if (PR_PLAIN != chunk.style) {

+      k += chunk.token.length;

+      continue;

+    }

+

+    var s = chunk.token;

+    var pos = 0;  // the position past the last character processed so far in s

+

+    for (var i = 0, n = s.length; i < n; /* i = next at bottom */) {

+      decodeHelper.decode(s, i);

+      var ch = decodeHelper.ch;

+      var next = decodeHelper.next;

+

+      var tokenStyle = null;

+      switch (state) {

+        case 0:

+          if ('<' == ch) { state = 1; }

+          break;

+        case 1:

+          tokenCharsI = 0;

+          if ('/' == ch) {  // only consider close tags if we're in script/style

+            state = 7;

+          } else if (null == endScriptTag) {

+            if ('!' == ch) {

+              state = 2;

+            } else if (PR_isWordChar(ch)) {

+              state = 8;

+            } else if ('?' == ch) {

+              state = 9;

+            } else if ('%' == ch) {

+              state = 11;

+            } else if ('<' != ch) {

+              state = 0;

+            }

+          } else if ('<' != ch) {

+            state = 0;

+          }

+          break;

+        case 2:

+          if ('-' == ch) {

+            state = 4;

+          } else if (PR_isWordChar(ch)) {

+            state = 3;

+          } else if ('<' == ch) {

+            state = 1;

+          } else {

+            state = 0;

+          }

+          break;

+        case 3:

+          if ('>' == ch) {

+            state = 0;

+            tokenStyle = PR_DECLARATION;

+          }

+          break;

+        case 4:

+          if ('-' == ch) { state = 5; }

+          break;

+        case 5:

+          if ('-' == ch) { state = 6; }

+          break;

+        case 6:

+          if ('>' == ch) {

+            state = 0;

+            tokenStyle = PR_COMMENT;

+          } else if ('-' == ch) {

+            state = 6;

+          } else {

+            state = 4;

+          }

+          break;

+        case 7:

+          if (PR_isWordChar(ch)) {

+            state = 8;

+          } else if ('<' == ch) {

+            state = 1;

+          } else {

+            state = 0;

+          }

+          break;

+        case 8:

+          if ('>' == ch) {

+            state = 0;

+            tokenStyle = PR_TAG;

+          }

+          break;

+        case 9:

+          if ('?' == ch) { state = 10; }

+          break;

+        case 10:

+          if ('>' == ch) {

+            state = 0;

+            tokenStyle = PR_SOURCE;

+          } else if ('?' != ch) {

+            state = 9;

+          }

+          break;

+        case 11:

+          if ('%' == ch) { state = 12; }

+          break;

+        case 12:

+          if ('>' == ch) {

+            state = 0;

+            tokenStyle = PR_SOURCE;

+          } else if ('%' != ch) {

+            state = 11;

+          }

+          break;

+      }

+

+      if (tokenCharsI < tokenChars.length) {

+        tokenChars[tokenCharsI++] = ch.toLowerCase();

+      }

+      if (1 == state) { tokenStart = k + i; }

+      i = next;

+      if (tokenStyle != null) {

+        if (null != tokenStyle) {

+          if (endScriptTag) {

+            if (PR_prefixMatch(tokenChars, tokenCharsI, endScriptTag)) {

+              endScriptTag = null;

+            }

+          } else {

+            if (PR_prefixMatch(tokenChars, tokenCharsI, 'script')) {

+              endScriptTag = '/script';

+            } else if (PR_prefixMatch(tokenChars, tokenCharsI, 'style')) {

+              endScriptTag = '/style';

+            } else if (PR_prefixMatch(tokenChars, tokenCharsI, 'xmp')) {

+              endScriptTag = '/xmp';

+            }

+          }

+          // disallow the tag if endScriptTag is set and this was not an open

+          // tag.

+          if (endScriptTag && tokenCharsI && '/' == tokenChars[0]) {

+            tokenStyle = null;

+          }

+        }

+        if (null != tokenStyle) {

+          tokenEnds.push(new PR_TokenEnd(tokenStart, PR_PLAIN));

+          tokenEnds.push(new PR_TokenEnd(k + next, tokenStyle));

+        }

+      }

+    }

+    k += chunk.token.length;

+  }

+  tokenEnds.push(new PR_TokenEnd(k, PR_PLAIN));

+

+  return tokenEnds;

+}

+

+/** splits the given string into comment, string, and "other" tokens.

+  * @return an array of PR_Tokens with style in

+  *   (PR_STRING, PR_COMMENT, PR_PLAIN, null)

+  *   The result array may contain spurious zero length tokens.  Ignore them.

+  *

+  * @private

+  */

+function PR_splitStringAndCommentTokens(chunks) {

+  // a state machine to split out comments, strings, and other stuff

+  var tokenEnds = new Array();  // positions of ends of tokens in absolute space

+  var state = 0;  // FSM state variable

+  var delim = -1;  // string delimiter

+  var k = 0;  // absolute position of beginning of current chunk

+  for (var ci = 0, nc = chunks.length; ci < nc; ++ci) {

+    var chunk = chunks[ci];

+    var s = chunk.token;

+    if (PR_PLAIN == chunk.style) {

+      for (var i = 0, n = s.length; i < n; ++i) {

+        var ch = s.charAt(i);

+        if (0 == state) {

+          if (ch == '"' || ch == '\'' || ch == '`') {

+            tokenEnds.push(new PR_TokenEnd(k + i, PR_PLAIN));

+            state = 1;

+            delim = ch;

+          } else if (ch == '/') {

+            state = 3;

+          } else if (ch == '#') {

+            tokenEnds.push(new PR_TokenEnd(k + i, PR_PLAIN));

+            state = 4;

+          }

+        } else if (1 == state) {

+          if (ch == delim) {

+            state = 0;

+            tokenEnds.push(new PR_TokenEnd(k + i + 1, PR_STRING));

+          } else if (ch == '\\') {

+            state = 2;

+          }

+        } else if (2 == state) {

+          state = 1;

+        } else if (3 == state) {

+          if (ch == '/') {

+            state = 4;

+            tokenEnds.push(new PR_TokenEnd(k + i - 1, PR_PLAIN));

+          } else if (ch == '*') {

+            state = 5;

+            tokenEnds.push(new PR_TokenEnd(k + i - 1, PR_PLAIN));

+          } else {

+            state = 0;

+            // next loop will reenter state 0 without same value of i, so

+            // ch will be reconsidered as start of new token.

+            --i;

+          }

+        } else if (4 == state) {

+          if (ch == '\r' || ch == '\n') {

+            state = 0;

+            tokenEnds.push(new PR_TokenEnd(k + i, PR_COMMENT));

+          }

+        } else if (5 == state) {

+          if (ch == '*') {

+            state = 6;

+          }

+        } else if (6 == state) {

+          if (ch == '/') {

+            state = 0;

+            tokenEnds.push(new PR_TokenEnd(k + i + 1, PR_COMMENT));

+          } else if (ch != '*') {

+            state = 5;

+          }

+        }

+      }

+    }

+    k += s.length;

+  }

+  tokenEnds.push(new PR_TokenEnd(k, PR_PLAIN));  // a token ends at the end

+

+  return PR_splitChunks(chunks, tokenEnds);

+}

+

+/** used by lexSource to split a non string, non comment token.

+  * @private

+  */

+function PR_splitNonStringNonCommentToken(s, outlist) {

+  var pos = 0;

+  var state = 0;

+  for (var i = 0; i <= s.length; i++) {

+    var ch = s.charAt(i);

+    // the next state.

+    // if set to -1 then it will cause a reentry to state 0 without consuming

+    // another character.

+    var nstate = state;

+

+    if (i == s.length) {

+      // nstate will not be equal to state, so it will append the token

+      nstate = -2;

+    } else {

+      switch (state) {

+      case 0:  // whitespace state

+        if (PR_isIdentifierStart(ch)) {

+          nstate = 1;

+        } else if (PR_isDigitChar(ch)) {

+          nstate = 2;

+        } else if (!PR_isSpaceChar(ch)) {

+          nstate = 3;

+        }

+        if (nstate && pos < i) {

+          var t = s.substring(pos, i);

+          outlist.push(new PR_Token(t, PR_PLAIN));

+          pos = i;

+        }

+        break;

+      case 1:  // identifier state

+        if (!PR_isIdentifierPart(ch)) {

+          nstate = -1;

+        }

+        break;

+      case 2:  // number literal state

+        // handle numeric literals like

+        // 0x7f 300UL 100_000

+

+        // this does not treat floating point values as a single literal

+        //   0.1 and 3e-6

+        // are each split into multiple tokens

+        if (!(PR_isDigitChar(ch) || PR_isWordChar(ch) || ch == '_')) {

+          nstate = -1;

+        }

+        break;

+      case 3:  // punctuation state

+        if (PR_isIdentifierStart(ch) || PR_isDigitChar(ch) ||

+            PR_isSpaceChar(ch)) {

+          nstate = -1;

+        }

+        break;

+      }

+    }

+

+    if (nstate != state) {

+      if (nstate < 0) {

+        if (i > pos) {

+          var t = s.substring(pos, i);

+          var ch0 = t.charAt(0);

+          var style;

+          if (PR_isIdentifierStart(ch0)) {

+            if (PR_keywords[t]) {

+              style = PR_KEYWORD;

+            }

+            else if (ch0 == '@') {

+              style = PR_LITERAL;

+            } else {

+              // Treat any word that starts with an uppercase character and

+              // contains at least one lowercase character as a type, or

+              // ends with _t.

+              // This works perfectly for Java, pretty well for C++, and

+              // passably for Python.  The _t catches C structs.

+              var isType = false;

+              if (ch0 >= 'A' && ch0 <= 'Z') {

+                for (var j = 1; j < t.length; j++) {

+                  var ch1 = t.charAt(j);

+                  if (ch1 >= 'a' && ch1 <= 'z') {

+                    isType = true;

+                    break;

+                  }

+                }

+                if (!isType && t.length >= 2 &&

+                    t.substring(t.length - 2) == '_t') {

+                  isType = true;

+                }

+              }

+              style = isType ? PR_TYPE : PR_PLAIN;

+            }

+          } else if (PR_isDigitChar(ch0)) {

+            style = PR_LITERAL;

+          } else if (!PR_isSpaceChar(ch0)) {

+            style = PR_PUNCTUATION;

+          } else {

+            style = PR_PLAIN;

+          }

+          pos = i;

+          outlist.push(new PR_Token(t, style));

+        }

+

+        state = 0;

+        if (nstate == -1) {

+          // don't increment.  This allows us to use state 0 to redispatch based

+          // on the current character.

+          i--;

+          continue;

+        }

+      }

+      state = nstate;

+    }

+  }

+}

+

+/** split a group of chunks of markup.

+  * @private

+  */

+function PR_tokenizeMarkup(chunks) {

+  if (!(chunks && chunks.length)) { return chunks; }

+

+  var tokenEnds = PR_splitMarkup(chunks);

+  return PR_splitChunks(chunks, tokenEnds);

+}

+

+/** split tags attributes and their values out from the tag name, and

+  * recursively lex source chunks.

+  * @private

+  */

+function PR_splitTagAttributes(tokens) {

+  var tokensOut = new Array();

+  var state = 0;

+  var stateStyle = PR_TAG;

+  var delim = null;  // attribute delimiter for quoted value state.

+  var decodeHelper = new PR_DecodeHelper();

+  for (var ci = 0; ci < tokens.length; ++ci) {

+    var tok = tokens[ci];

+    if (PR_TAG == tok.style) {

+      var s = tok.token;

+      var start = 0;

+      for (var i = 0; i < s.length; /* i = next at bottom */) {

+        decodeHelper.decode(s, i);

+        var ch = decodeHelper.ch;

+        var next = decodeHelper.next;

+

+        var emitEnd = null;  // null or position of end of chunk to emit.

+        var nextStyle = null;  // null or next value of stateStyle

+        if (ch == '>') {

+          if (PR_TAG != stateStyle) {

+            emitEnd = i;

+            nextStyle = PR_TAG;

+          }

+        } else {

+          switch (state) {

+            case 0:

+              if ('<' == ch) { state = 1; }

+              break;

+            case 1:

+              if (PR_isSpaceChar(ch)) { state = 2; }

+              break;

+            case 2:

+              if (!PR_isSpaceChar(ch)) {

+                nextStyle = PR_ATTRIB_NAME;

+                emitEnd = i;

+                state = 3;

+              }

+              break;

+            case 3:

+              if ('=' == ch) {

+                emitEnd = i;

+                nextStyle = PR_TAG;

+                state = 5;

+              } else if (PR_isSpaceChar(ch)) {

+                emitEnd = i;

+                nextStyle = PR_TAG;

+                state = 4;

+              }

+              break;

+            case 4:

+              if ('=' == ch) {

+                state = 5;

+              } else if (!PR_isSpaceChar(ch)) {

+                emitEnd = i;

+                nextStyle = PR_ATTRIB_NAME;

+                state = 3;

+              }

+              break;

+            case 5:

+              if ('"' == ch || '\'' == ch) {

+                emitEnd = i;

+                nextStyle = PR_ATTRIB_VALUE;

+                state = 6;

+                delim = ch;

+              } else if (!PR_isSpaceChar(ch)) {

+                emitEnd = i;

+                nextStyle = PR_ATTRIB_VALUE;

+                state = 7;

+              }

+              break;

+            case 6:

+              if (ch == delim) {

+                emitEnd = next;

+                nextStyle = PR_TAG;

+                state = 2;

+              }

+              break;

+            case 7:

+              if (PR_isSpaceChar(ch)) {

+                emitEnd = i;

+                nextStyle = PR_TAG;

+                state = 2;

+              }

+              break;

+          }

+        }

+        if (emitEnd) {

+          if (emitEnd > start) {

+            tokensOut.push(

+                new PR_Token(s.substring(start, emitEnd), stateStyle));

+            start = emitEnd;

+          }

+          stateStyle = nextStyle;

+        }

+        i = next;

+      }

+      if (s.length > start) {

+        tokensOut.push(new PR_Token(s.substring(start, s.length), stateStyle));

+      }

+    } else {

+      if (tok.style) {

+        state = 0;

+        stateStyle = PR_TAG;

+      }

+      tokensOut.push(tok);

+    }

+  }

+  return tokensOut;

+}

+

+/** identify regions of markup that are really source code, and recursivley

+  * lex them.

+  * @private

+  */

+function PR_splitSourceNodes(tokens) {

+  var tokensOut = new Array();

+  // when we see a <script> tag, store '/' here so that we know to end the

+  // source processing

+  var endScriptTag = null;

+  var decodeHelper = new PR_DecodeHelper();

+

+  var sourceChunks = null;

+

+  for (var ci = 0, nc = tokens.length; ci < nc; ++ci) {

+    var tok = tokens[ci];

+    if (null == tok.style) {

+      tokens.push(tok);

+      continue;

+    }

+

+    var s = tok.token;

+

+    if (null == endScriptTag) {

+      if (PR_SOURCE == tok.style) {

+        // split off any starting and trailing <?, <%

+        if ('<' == decodeHelper.decode(s, 0)) {

+          decodeHelper.decode(s, decodeHelper.next);

+          if ('%' == decodeHelper.ch || '?' == decodeHelper.ch) {

+            endScriptTag = decodeHelper.ch;

+            tokensOut.push(new PR_Token(s.substring(0, decodeHelper.next),

+                                        PR_TAG));

+            s = s.substring(decodeHelper.next, s.length);

+          }

+        }

+      } else if (PR_TAG == tok.style) {

+        if ('<' == decodeHelper.decode(s, 0) &&

+            '/' != s.charAt(decodeHelper.next)) {

+          var tagContent = s.substring(decodeHelper.next).toLowerCase();

+          // FIXME(msamuel): this does not mirror exactly the code in

+          // in PR_splitMarkup that defers splitting tags inside script and

+          // style blocks.

+          if (PR_startsWith(tagContent, 'script') ||

+              PR_startsWith(tagContent, 'style') ||

+              PR_startsWith(tagContent, 'xmp')) {

+            endScriptTag = '/';

+          }

+        }

+      }

+    }

+

+    if (null != endScriptTag) {

+      var endTok = null;

+      if (PR_SOURCE == tok.style) {

+        if (endScriptTag == '%' || endScriptTag == '?') {

+          var pos = s.lastIndexOf(endScriptTag);

+          if (pos >= 0 && '>' == decodeHelper.decode(s, pos + 1) &&

+              s.length == decodeHelper.next) {

+            endTok = new PR_Token(s.substring(pos, s.length), PR_TAG);

+            s = s.substring(0, pos);

+          }

+        }

+        if (null == sourceChunks) { sourceChunks = new Array(); }

+        sourceChunks.push(new PR_Token(s, PR_PLAIN));

+      } else if (PR_PLAIN == tok.style) {

+        if (null == sourceChunks) { sourceChunks = new Array(); }

+        sourceChunks.push(tok);

+      } else if (PR_TAG == tok.style) {

+        // if it starts with </ then it must be the end tag.

+        if ('<' == decodeHelper.decode(tok.token, 0) &&

+            tok.token.length > decodeHelper.next &&

+            '/' == decodeHelper.decode(tok.token, decodeHelper.next)) {

+          endTok = tok;

+        } else {

+          tokensOut.push(tok);

+        }

+      } else {

+        if (sourceChunks) {

+          sourceChunks.push(tok);

+        } else {

+          // push remaining tag and attribute tokens from the opening tag

+          tokensOut.push(tok);

+        }

+      }

+      if (endTok) {

+        if (sourceChunks) {

+          var sourceTokens = PR_lexSource(sourceChunks);

+          tokensOut.push(new PR_Token('<span class=embsrc>', null));

+          for (var si = 0, ns = sourceTokens.length; si < ns; ++si) {

+            tokensOut.push(sourceTokens[si]);

+          }

+          tokensOut.push(new PR_Token('</span>', null));

+          sourceChunks = null;

+        }

+        tokensOut.push(endTok);

+        endScriptTag = null;

+      }

+    } else {

+      tokensOut.push(tok);

+    }

+  }

+  return tokensOut;

+}

+

+/** splits the quotes from an attribute value.

+  * ['"foo"'] -> ['"', 'foo', '"']

+  * @private

+  */

+function PR_splitAttributeQuotes(tokens) {

+  var firstPlain = null, lastPlain = null;

+  for (var i = 0; i < tokens.length; ++i) {

+    if (PR_PLAIN = tokens[i].style) {

+      firstPlain = i;

+      break;

+    }

+  }

+  for (var i = tokens.length; --i >= 0;) {

+    if (PR_PLAIN = tokens[i].style) {

+      lastPlain = i;

+      break;

+    }

+  }

+  if (null == firstPlain) { return tokens; }

+

+  var decodeHelper = new PR_DecodeHelper();

+  var fs = tokens[firstPlain].token;

+  var fc = decodeHelper.decode(fs, 0);

+  if ('"' != fc && '\'' != fc) {

+    return tokens;

+  }

+  var fpos = decodeHelper.next;

+

+  var ls = tokens[lastPlain].token;

+  var lpos = ls.lastIndexOf('&');

+  if (lpos < 0) { lpos = ls.length - 1; }

+  var lc = decodeHelper.decode(ls, lpos);

+  if (lc != fc || decodeHelper.next != ls.length) {

+    lc = null;

+    lpos = ls.length;

+  }

+

+  var tokensOut = new Array();

+  for (var i = 0; i < firstPlain; ++i) {

+    tokensOut.push(tokens[i]);

+  }

+  tokensOut.push(new PR_Token(fs.substring(0, fpos), PR_ATTRIB_VALUE));

+  if (lastPlain == firstPlain) {

+    tokensOut.push(new PR_Token(fs.substring(fpos, lpos), PR_PLAIN));

+  } else {

+    tokensOut.push(new PR_Token(fs.substring(fpos, fs.length), PR_PLAIN));

+    for (var i = firstPlain + 1; i < lastPlain; ++i) {

+      tokensOut.push(tokens[i]);

+    }

+    if (lc) {

+      tokens.push(new PR_Token(ls.substring(0, lpos), PR_PLAIN));

+    } else {

+      tokens.push(tokens[lastPlain]);

+    }

+  }

+  if (lc) {

+    tokensOut.push(new PR_Token(ls.substring(lpos, ls.length), PR_PLAIN));

+  }

+  for (var i = lastPlain + 1; i < tokens.length; ++i) {

+    tokensOut.push(tokens[i]);

+  }

+  return tokensOut;

+}

+

+/** identify attribute values that really contain source code and recursively

+  * lex them.

+  * @private

+  */

+function PR_splitSourceAttributes(tokens) {

+  var tokensOut = new Array();

+

+  var sourceChunks = null;

+  var inSource = false;

+  var name = '';

+

+  for (var ci = 0, nc = tokens.length; ci < nc; ++ci) {

+    var tok = tokens[ci];

+    var outList = tokensOut;

+    if (PR_TAG == tok.style) {

+      if (inSource) {

+        inSource = false;

+        name = '';

+        if (sourceChunks) {

+          tokensOut.push(new PR_Token('<span class=embsrc>', null));

+          var sourceTokens =

+            PR_lexSource(PR_splitAttributeQuotes(sourceChunks));

+          for (var si = 0, ns = sourceTokens.length; si < ns; ++si) {

+            tokensOut.push(sourceTokens[si]);

+          }

+          tokensOut.push(new PR_Token('</span>', null));

+          sourceChunks = null;

+        }

+      } else if (name && tok.token.indexOf('=') >= 0) {

+        var nameLower = name.toLowerCase();

+        if (PR_startsWith(nameLower, 'on') || 'style' == nameLower) {

+          inSource = true;

+        }

+      } else {

+        name = '';

+      }

+    } else if (PR_ATTRIB_NAME == tok.style) {

+      name += tok.token;

+    } else if (PR_ATTRIB_VALUE == tok.style) {

+      if (inSource) {

+        if (null == sourceChunks) { sourceChunks = new Array(); }

+        outList = sourceChunks;

+        tok = new PR_Token(tok.token, PR_PLAIN);

+      }

+    } else {

+      if (sourceChunks) {

+        outList = sourceChunks;

+      }

+    }

+    outList.push(tok);

+  }

+  return tokensOut;

+}

+

+/** returns a list of PR_Token objects given chunks of source code.

+  *

+  * This code assumes that < tokens are html escaped, but " are not.

+  * It will do a resonable job with <, but will not recognize an &quot;

+  * as starting a string.

+  *

+  * This code treats ", ', and ` as string delimiters, and \ as a string escape.

+  * It does not recognize double delimiter escapes, or perl's qq() style

+  * strings.

+  *

+  * It recognizes C, C++, and shell style comments.

+  *

+  * @param chunks PR_Tokens with style in (null, PR_PLAIN)

+  */

+function PR_lexSource(chunks) {

+  // positions of ends of tokens in order

+  var tokensIn = PR_splitStringAndCommentTokens(chunks);

+

+  // split entities out of so that we know to treat them as single units.

+  tokensIn = PR_splitEntities(tokensIn);

+

+  // split non comment|string tokens on whitespace and word boundaries

+  var tokensOut = new Array();

+  for (var i = 0; i < tokensIn.length; ++i) {

+    var tok = tokensIn[i];

+    var t = tok.token;

+    var s = tok.style;

+

+    if (PR_PLAIN == s) {

+      PR_splitNonStringNonCommentToken(t, tokensOut);

+      continue;

+    }

+    tokensOut.push(tok);

+  }

+

+  return tokensOut;

+}

+

+/** returns a list of PR_Token objects given a string of markup.

+  *

+  * This code assumes that < tokens are html escaped, but " are not.

+  * It will do a resonable job with <, but will not recognize an &quot;

+  * as starting a string.

+  *

+  * This code recognizes a number of constructs.

+  * <!-- ... --> comment

+  * <!\w ... >   declaration

+  * <\w ... >    tag

+  * </\w ... >   tag

+  * <?...?>      embedded source

+  * &[#\w]...;   entity

+  *

+  * It does not recognizes %foo; entities.

+  *

+  * It will recurse into any <style>, <script>, and on* attributes using

+  * PR_lexSource.

+  */

+function PR_lexMarkup(chunks) {

+  // This function works as follows:

+  // 1) Start by splitting the markup into text and tag chunks

+  //    Input:  String s

+  //    Output: List<PR_Token> where style in (PR_PLAIN, null)

+  // 2) Then split the text chunks further into comments, declarations,

+  //    tags, etc.

+  //    After each split, consider whether the token is the start of an

+  //    embedded source section, i.e. is an open <script> tag.  If it is,

+  //    find the corresponding close token, and don't bother to lex in between.

+  //    Input:  List<String>

+  //    Output: List<PR_Token> with style in (PR_TAG, PR_PLAIN, PR_SOURCE, null)

+  // 3) Finally go over each tag token and split out attribute names and values.

+  //    Input:  List<PR_Token>

+  //    Output: List<PR_Token> where style in

+  //            (PR_TAG, PR_PLAIN, PR_SOURCE, NAME, VALUE, null)

+  var tokensOut = PR_tokenizeMarkup(chunks);

+  tokensOut = PR_splitTagAttributes(tokensOut);

+  tokensOut = PR_splitSourceNodes(tokensOut);

+  tokensOut = PR_splitSourceAttributes(tokensOut);

+  return tokensOut;

+}

+

+/** classify the string as either source or markup and lex appropriately. */

+function PR_lexOne(s) {

+  var chunks = PR_chunkify(s);

+  // treat it as markup if the first non whitespace character is a < and the

+  // last non-whitespace character is a >

+  var isMarkup = false;

+  for (var i = 0; i < chunks.length; ++i) {

+    if (PR_PLAIN == chunks[i].style) {

+      if (PR_startsWith(PR_trim(chunks[i].token), '&lt;')) {

+        for (var j = chunks.length; --j >= 0;) {

+          if (PR_PLAIN == chunks[j].style) {

+            isMarkup = PR_endsWith(PR_trim(chunks[j].token), '&gt;');

+            break;

+          }

+        }

+      }

+      break;

+    }

+  }

+  return isMarkup ? PR_lexMarkup(chunks) : PR_lexSource(chunks);

+}

+

+/** pretty print a chunk of code.

+  *

+  * @param s code as html

+  * @return code as html, but prettier

+  */

+function prettyPrintOne(s) {

+  try {

+    var tokens = PR_lexOne(s);

+    var out = '';

+    var lastStyle = null;

+    for (var i = 0; i < tokens.length; i++) {

+      var t = tokens[i];

+      if (t.style != lastStyle) {

+        if (lastStyle != null) {

+          out += '</span>';

+        }

+        if (t.style != null) {

+          out += '<span class=' + t.style + '>';

+        }

+        lastStyle = t.style;

+      }

+      var html = t.token;

+      if (null != t.style) {

+        // This interacts badly with the wiki which introduces paragraph tags

+        // int pre blocks for some strange reason.

+        // It's necessary for IE though which seems to lose the preformattedness

+        // of <pre> tags when their innerHTML is assigned.

+        html = html.replace(/(?:\r\n?)|\n/g, '<br>').replace(/  /g, '&nbsp; ');

+      }

+      out += html;

+    }

+    if (lastStyle != null) {

+      out += '</span>';

+    }

+    return out;

+  } catch (e) {

+    //alert(e.stack);  // DISABLE in production

+    return s;

+  }

+}

+

+/** find all the < pre > and < code > tags in the DOM with class=prettyprint and

+  * prettify them.

+  */

+function prettyPrint() {

+  // fetch a list of nodes to rewrite

+  var codeSegments = [

+      document.getElementsByTagName('pre'),

+      document.getElementsByTagName('code'),

+      document.getElementsByTagName('xmp') ];

+  var elements = [];

+  for (var i = 0; i < codeSegments.length; ++i) {

+    for (var j = 0; j < codeSegments[i].length; ++j) {

+      elements.push(codeSegments[i][j]);

+    }

+  }

+  codeSegments = null;

+

+  // the loop is broken into a series of continuations to make sure that we

+  // don't make the browser unresponsive when rewriting a large page.

+  var k = 0;

+

+  function doWork() {

+    var endTime = new Date().getTime() + 250;

+    for (; k < elements.length && new Date().getTime() < endTime; k++) {

+      var cs = elements[k];

+      if (cs.className && cs.className.indexOf('prettyprint') >= 0) {

+

+        // make sure this is not nested in an already prettified element

+        var nested = false;

+        for (var p = cs.parentNode; p != null; p = p.parentNode) {

+          if ((p.tagName == 'pre' || p.tagName == 'code' ||

+               p.tagName == 'xmp') &&

+              p.className && p.className.indexOf('prettyprint') >= 0) {

+            nested = true;

+            break;

+          }

+        }

+        if (!nested) {

+          // XMP tags contain unescaped entities so require special handling.

+          var isRawContent = 'XMP' == cs.tagName;

+

+          // fetch the content as a snippet of properly escaped HTML

+          var content = cs.innerHTML;

+          if (isRawContent) {

+            content = PR_textToHtml(content);

+          }

+

+          // do the pretty printing

+          var newContent = prettyPrintOne(content);

+

+          // push the prettified html back into the tag.

+          if (!isRawContent) {

+            // just replace the old html with the new

+            cs.innerHTML = newContent;

+          } else {

+            // we need to change the tag to a <pre> since <xmp>s do not allow

+            // embedded tags such as the span tags used to attach styles to

+            // sections of source code.

+            var pre = document.createElement('PRE');

+            for (var i = 0; i < cs.attributes.length; ++i) {

+              var a = cs.attributes[i];

+              if (a.specified) {

+                pre.setAttribute(a.name, a.value);

+              }

+            }

+            pre.innerHTML = newContent;

+            // remove the old

+            cs.parentNode.replaceChild(pre, cs);

+          }

+        }

+      }

+    }

+    if (k < elements.length) {

+      // finish up in a continuation

+      setTimeout(doWork, 250);

+    }

+  }

+

+  doWork();

+}

diff --git a/doc/samplereport/css/maven-base.css b/doc/samplereport/css/maven-base.css
new file mode 100644
index 0000000..313328b
--- /dev/null
+++ b/doc/samplereport/css/maven-base.css
@@ -0,0 +1,147 @@
+body {

+  margin: 0px;

+  padding: 0px;

+}

+img {

+  border:none;

+}

+table {

+  padding:0px;

+  width: 100%;

+  margin-left: -2px;

+  margin-right: -2px;

+}

+acronym {

+  cursor: help;

+  border-bottom: 1px dotted #feb;

+}

+table.bodyTable th, table.bodyTable td {

+  padding: 2px 4px 2px 4px;

+  vertical-align: top;

+}

+div.clear{

+  clear:both;

+  visibility: hidden;

+}

+div.clear hr{

+  display: none;

+}

+#bannerLeft, #bannerRight {

+  font-size: xx-large; 

+  font-weight: bold;

+}

+#bannerLeft img, #bannerRight img {

+  margin: 0px;

+}

+.xleft, #bannerLeft img {

+  float:left;

+  text-shadow: #7CFC00;

+}

+.xright, #bannerRight img {

+  float:right;

+  text-shadow: #7CFC00;

+}

+#banner {

+  padding: 0px;

+}

+#banner img {

+  border: none;

+}

+#breadcrumbs {

+  padding: 3px 10px 3px 10px;

+}

+#leftColumn {

+ width: 150px;

+ float:left;

+}

+#bodyColumn {

+  margin-right: 1.5em;

+  margin-left: 177px;

+}

+#legend {

+  padding: 8px 0 8px 0;

+}

+#navcolumn {

+  padding: 8px 4px 0 8px;

+}

+#navcolumn h5 {

+  margin: 0;

+  padding: 0;

+  font-size: small;

+}

+#navcolumn ul {

+  margin: 0;

+  padding: 0;

+  font-size: small;

+}

+#navcolumn li {

+  list-style-type: none;

+  background-image: none;

+  background-repeat: no-repeat;

+  background-position: 0 0.4em;

+  padding-left: 16px;

+  list-style-position: ouside;

+  line-height: 1.2em;

+  font-size: smaller;

+}

+#navcolumn li.expanded {

+  background-image: url(../images/expanded.gif);

+}

+#navcolumn li.collapsed {

+  background-image: url(../images/collapsed.gif);

+}

+#poweredBy {

+  text-align: center;

+}

+#navcolumn img {

+  margin-top: 10px;

+  margin-bottom: 3px;

+}

+#poweredBy img {

+  display:block;

+  margin: 20px 0 20px 17px;

+  border: 1px solid black;

+  width: 90px;

+  height: 30px;

+}

+#search img {

+    margin: 0px;

+    display: block;

+}

+#search #q, #search #btnG {

+    border: 1px solid #999;

+    margin-bottom:10px;

+}

+#search form {

+    margin: 0px;

+}

+#lastPublished {

+  font-size: x-small;

+}

+.navSection {

+  margin-bottom: 2px;

+  padding: 8px;

+}

+.navSectionHead {

+  font-weight: bold;

+  font-size: x-small;

+}

+.section {

+  padding: 4px;

+}

+#footer {

+  padding: 3px 10px 3px 10px;

+  font-size: x-small;

+}

+#breadcrumbs {

+  font-size: x-small;

+  margin: 0pt;

+}

+.source {

+  padding: 12px;

+  margin: 1em 7px 1em 7px;

+}

+.source pre {

+  margin: 0px;

+  padding: 0px;

+}

diff --git a/doc/samplereport/css/maven-classic.css b/doc/samplereport/css/maven-classic.css
new file mode 100644
index 0000000..40539df
--- /dev/null
+++ b/doc/samplereport/css/maven-classic.css
@@ -0,0 +1,881 @@
+body {

+ background: #fff;

+ color: #000;

+  padding: 0px 0px 10px 0px;

+ }

+

+.contentBox h2 {

+ color: #fff;

+ background-color: #036;

+ }

+

+.contentBox h3 {

+ color: #fff;

+ background-color: #888;

+ }

+

+.a td { 

+ background: #ddd;

+ color: #000;

+ }

+

+.b td { 

+ background: #efefef;

+ color: #000;

+ }

+

+.contentBox th {

+ background-color: #bbb;

+ color: #fff;

+ }

+

+div#banner {

+ border-top: 1px solid #369;

+ border-bottom: 1px solid #003;

+ }

+

+#banner, #banner td { 

+ background: #036;

+ color: #fff;

+ }

+#banner {

+  border-bottom: 1px solid #fff;

+}

+

+#leftColumn {

+ background: #eee;

+ color: #000;

+ border-right: 1px solid #aaa;

+ border-bottom: 1px solid #aaa;

+ border-top: 1px solid #fff;

+}

+

+#navcolumn {

+/* bad for IE

+ background: #eee;

+*/

+ color: #000;

+ border-right: none;

+ border-bottom: none;

+ border-top: none;

+ }

+

+#breadcrumbs {

+ background-color: #ddd;

+ color: #000;

+ border-top: 1px solid #fff;

+ border-bottom: 1px solid #aaa;

+ }

+

+.source {

+ background-color: #fff;

+ color: #000;

+ border-right: 1px solid #888; 

+ border-left: 1px solid #888; 

+ border-top: 1px solid #888; 

+ border-bottom: 1px solid #888; 

+ margin-right: 7px;

+ margin-left: 7px;

+ margin-top: 1em;

+ }

+

+.source pre {

+ margin-right: 7px;

+ margin-left: 7px;

+ }

+

+a[name]:hover, #leftColumn a[name]:hover {

+ color: inherit !important;

+ }

+

+a:link, #breadcrumbs a:visited, #navcolumn a:visited, .contentBox a:visited, .tasknav a:visited {

+ color: blue;

+ }

+

+a:active, a:hover, #leftColumn a:active, #leftColumn a:hover {

+ color: #f30 !important;

+ }

+

+a:link.selfref, a:visited.selfref {

+ color: #555 !important;

+ }

+

+#legend li.externalLink {

+  background: url(../images/external-classic.png) left top no-repeat;

+  padding-left: 18px;

+}

+a.externalLink, a.externalLink:link, a.externalLink:visited, a.externalLink:active, a.externalLink:hover {

+  background: url(../images/external-classic.png) right center no-repeat;

+  padding-right: 18px;

+}

+

+#legend li.newWindow {

+  background: url(../images/newwindow-classic.png) left top no-repeat;

+  padding-left: 18px;

+}

+a.newWindow, a.newWindow:link, a.newWindow:visited, a.newWindow:active, a.newWindow:hover {

+  background: url(../images/newwindow-classic.png) right center no-repeat;

+  padding-right: 18px;

+}

+

+h2, h3 {

+ margin-top: 1em;

+ margin-bottom: 0;

+ }

+

+img.handle {

+ border: 0;

+ padding-right: 2px;

+}

+

+#navcolumn div div  {

+ background-image: none;

+ background-repeat: no-repeat;

+}

+

+#navcolumn div div {

+  padding-left: 10px;

+}

+/* $Id$

+  

+  This file defines basic default formatting for HTML conforming to Tigris application style. To extend or override these rules for your instance, edit inst.css instead of this file. */

+

+/* colors, backgrounds, borders, link indication */

+.contentBox h2, .contentBox h3, .tabs td, .tabs th, .functnbar {

+  background-image: url(../images/nw_maj_rond.gif);

+  background-repeat: no-repeat;

+}

+

+.functnbar, .functnbar2 {

+  background-color: #aaa;

+}

+

+.functnbar2, .functnbar3 {

+  background-color: #aaa;

+  background-image: url(../images/sw_maj_rond.gif);

+  background-repeat: no-repeat;

+  background-position: bottom left;

+}

+

+.functnbar3 {

+  background-color: #ddd;

+  background-image: url(../images/sw_med_rond.gif);

+}

+

+.functnbar, .functnbar2, .functnbar3 {

+  color: #000;

+}

+

+.functnbar a, .functnbar2 a, .functnbar3 a {

+  color: #000;

+  text-decoration: underline;

+}

+

+#navcolumn .body div, body.docs #toc li li {

+  background-image: url(../images/strich.gif);

+  background-repeat: no-repeat;

+  background-position: .5em .5em;

+}

+

+#searchbox .body div, #navcolumn .body .heading {

+  background-image: none;

+}

+

+a:link.selfref, a:visited.selfref {

+  text-decoration: none;

+}

+

+#leftColumn a, #breadcrumbs a {

+  text-decoration: none;

+}

+

+/* Unsure of this. TODO */

+.contentBox h2 a:link, .contentBox h2 a:visited, .contentBox h3 a:link, .contentBox h3 a:visited {

+  color: #fff !important;

+  text-decoration: underline;

+}

+

+table, th, td {

+  border: none;

+}

+

+div.colbar {

+  background: #eee;

+  border-color: #999 #EEE #EEE #999;

+  border-width: 1px;

+  border-style: solid;

+}

+

+.toolgroup {

+  background: #efefef;

+}

+

+.toolgroup .label {

+  border-bottom: 1px solid #666;

+  border-right: 1px solid #666;

+  background: #ddd;

+  color: #555;

+}

+

+.toolgroup .body {

+  border-right: 1px solid #aaa;

+  border-bottom: 1px solid #aaa;

+}

+

+#main {

+  border-top: 1px solid #999;

+}

+

+#rightcol div.www, #rightcol div.help {

+  border: 1px solid #ddd;

+}

+

+body.docs div.docs {

+  background-color: #fff;

+  border-left: 1px solid #ddd;

+  border-top: 1px solid #ddd;

+}

+

+#helptext .label {

+  background-image: url(../images/icon_help_sml.gif);

+  background-repeat: no-repeat;

+  background-position: 97%;

+}

+

+body.docs {

+  background: #eee url(../images/help_logo.gif) top right no-repeat !important;

+}

+

+.docs h2, .docs h3 {

+  border-top: solid 1px #000;

+}

+

+#apphead h2 em {

+  color: #777;

+}

+

+.tabs th {

+  border-right: 1px solid #333;

+  background-color: #ddd;

+  color: #fff;

+  border-left: 1px solid #fff;

+}

+

+.tabs td {

+  background-color: #999;

+  border-bottom: 1px solid #fff;

+  border-right: 1px solid #fff;

+  border-left: 1px solid #fff;

+}

+

+.tabs {

+  border-bottom: 6px #ddd solid;

+}

+

+.tabs th, .tabs th a:link, .tabs th a:visited {

+  color: #555;

+}

+

+.tabs td, .tabs td a:link, .tabs td a:visited {

+  color: #fff;

+}

+

+.tabs a {

+  text-decoration: none;

+}

+

+.axial th {

+  background-color: #ddd;

+  color: black;

+}

+

+.alert {

+  background-color: #ff9;

+}

+

+.expandedwaste {

+  background: url(../images/icon_arrowwaste2_sml.gif) no-repeat;

+}

+

+.collapsedwaste {

+  background: url(../images/icon_arrowwaste1_sml.gif) no-repeat;

+}

+

+.filebrowse .expanded, .filebrowse-alt .expanded {

+  background-image: url(../images/icon_arrowfolderopen2_sml.gif);

+  background-repeat: no-repeat;

+}

+

+.filebrowse .collapsed, .filebrowse-alt .collapsed {

+  background-image: url(../images/icon_arrowfolderclosed1_sml.gif);

+  background-repeat: no-repeat;

+}

+

+.filebrowse .leafnode, .filebrowse-alt .leafnode {

+  background-image: url(../images/icon_folder_sml.gif);

+  background-repeat: no-repeat;

+}

+

+.filebrowse .leaf, .filebrowse-alt .leaf {

+  background-image: url(../images/icon_doc_sml.gif);

+  background-repeat: no-repeat;

+}

+

+.sortup {

+  background: url(../images/icon_sortup.gif) no-repeat;

+}

+

+.sortdown {

+  background: url(../images/icon_sortdown.gif) no-repeat;

+}

+

+.collapsedwaste {

+  background: url(../images/icon_arrowwaste1_sml.gif) no-repeat;

+}

+

+body .grid td {

+  border-top: 1px solid #ccc;

+  border-left: 1px solid #ccc;

+  background-color: transparent;

+}

+

+.confirm {

+  color: #090;

+}

+

+.info {

+  color: #069;

+}

+

+.errormessage, .warningmessage, .donemessage, .infomessage {

+  border-top: 5px solid #900;

+  border-left: 1px solid #900;

+  background-image: url(../images/icon_error_lrg.gif);

+  background-repeat: no-repeat;

+  background-position: 5px 1.33em;

+}

+

+.warningmessage {

+  background-image: url(../images/icon_warning_lrg.gif);

+  border-color: #c60;

+}

+

+.donemessage {

+  background-image: url(../images/icon_success_lrg.gif);

+  border-color: #090;

+}

+

+.infomessage {

+  background-image: url(../images/icon_info_lrg.gif);

+  border-color: #069;

+}

+

+.docinfo {

+  background: url(../images/icon_doc_lrg.gif) no-repeat;

+}

+

+.dirinfo {

+  background: url(../images/icon_folder_lrg.gif) no-repeat;

+}

+

+.memberinfo {

+  background: url(../images/icon_members_lrg.gif) no-repeat;

+}

+

+.usergroupinfo {

+  background: url(../images/icon_usergroups_lrg.gif) no-repeat;

+}

+

+.errormark, .warningmark, .donemark, .infomark {

+  background: url(../images/icon_error_sml.gif) no-repeat;

+}

+

+.warningmark {

+  background-image: url(../images/icon_warning_sml.gif);

+}

+

+.donemark {

+  background-image: url(../images/icon_success_sml.gif);

+}

+

+.infomark {

+  background-image: url(../images/icon_info_sml.gif);

+}

+

+.cvsdiff, .cvsblame {

+  background-color: #ccc;

+}

+

+.cvsdiffadd {

+  background-color: #afa;

+}

+

+.cvsdiffremove {

+  background-color: #faa;

+}

+

+.cvsdiffchanges1 {

+  background-color: #ff7;

+}

+

+.cvsdiffchanges2 {

+  background-color: #ff7;

+}

+

+li.selection ul a {

+  background: #fff;

+}

+

+.band1 {

+  color: #fff;

+  background-color: #663;

+}

+

+.band2 {

+  color: #fff;

+  background-color: #66C;

+}

+

+.band3 {

+  background-color: #C99;

+}

+

+.band4 {

+  background-color: #CFF;

+}

+

+.band5 {

+  color: #fff;

+  background-color: #336;

+}

+

+.band6 {

+  color: #fff;

+  background-color: #966;

+}

+

+.band7 {

+  background-color: #9CC;

+}

+

+.band8 {

+  background-color: #FFC;

+}

+

+.band9 {

+  color: #fff;

+  background-color: #633;

+}

+

+.band10 {

+  color: #fff;

+  background-color: #699;

+}

+

+.band11 {

+  background-color: #CC9;

+}

+

+.band12 {

+  background-color: #CCF;

+}

+

+.band13 {

+  color: #fff;

+  background-color: #366;

+}

+

+.band14 {

+  color: #fff;

+  background-color: #996;

+}

+

+.band15 {

+  background-color: #99C;

+}

+

+.band16 {

+  background-color: #FCC;

+}

+

+.contentBox .helplink, #helptext .helplink {

+  cursor: help;

+}

+

+.legend th, .bars th {

+  background-color: #fff;

+}

+

+/* font and text properties, exclusive of link indication, alignment, text-indent */

+body, th, td, input, select {

+  font-family: Verdana, Helvetica, Arial, sans-serif;

+}

+

+code, pre {

+  font-family: 'Andale Mono', Courier, monospace;

+}

+

+body, .contentBox h2, .contentBox h3, #rightcol h2, pre, code, #apphead h2 small, h3, th, td {

+  font-size: x-small;

+  voice-family: "\"}\"";

+  voice-family: inherit;

+  font-size: small;

+}

+

+small, div#footer, div#login, div.tabs th, div.tabs td, input, select, .paginate, .functnbar, .functnbar2, .functnbar3, #breadcrumbs, .courtesylinks, #rightcol div.help, .colbar, .tasknav, body.docs div#toc, #leftColumn, .legend, .bars {

+  font-size: xx-small;

+  voice-family: "\"}\"";

+  voice-family: inherit;

+  font-size: x-small;

+}

+

+.tabs td, .tabs th, dt, .tasknav .selfref, #login .username, .selection {

+  font-weight: bold;

+}

+

+li.selection ul {

+  font-weight: normal;

+}

+

+#apphead h2 em {

+  font-style: normal;

+}

+

+#banner h1 {

+  font-size: 1.25em;

+}

+

+/* box properties (exclusive of borders), positioning, alignments, list types, text-indent */

+#bodyColumn h2 {

+  margin-top: .3em;

+  margin-bottom: .5em;

+}

+

+p, ul, ol, dl, .bars table {

+  margin-top: .67em;

+  margin-bottom: .67em;

+}

+

+form {

+  margin: 0;

+}

+

+#bodyColumn {

+  padding-left: 12px;

+  padding-right: 12px;

+  width: 100%;

+  voice-family: "\"}\"";

+  voice-family: inherit;

+  width: auto;

+}

+

+html>body #bodyColumn {

+  width: auto;

+}

+

+.docs {

+  line-height: 1.4;

+}

+

+ol ol {

+  list-style-type: lower-alpha;

+}

+

+ol ol ol {

+  list-style-type: lower-roman;

+}

+

+.contentBox h2, .contentBox h3 {

+  padding: 5px;

+  margin-right: 2px;

+}

+

+.contentBox td, .contentBox th {

+  padding: 2px 3px;

+}

+

+.h2 p, .h3 p, .h2 dt, .h3 dt {

+  margin-right: 7px;

+  margin-left: 7px;

+}

+

+.tasknav {

+  margin-bottom: 1.33em;

+}

+

+div.colbar {

+  padding: 3px;

+  margin: 2px 2px 0;

+}

+

+.tabs {

+  margin-top: .67em;

+  margin-right: 2px;

+  margin-left: 2px;

+  padding-left: 8px;

+}

+

+.tabs td, .tabs th {

+  padding: 3px 9px;

+}

+

+#rightcol div.www, #rightcol div.help {

+  padding: 0 .5em;

+}

+

+body.docs #toc {

+  position: absolute;

+  top: 15px;

+  left: 0px;

+  width: 120px;

+  padding: 0 20px 0 0;

+}

+

+body.docs #toc ul, #toc ol {

+  margin-left: 0;

+  padding-left: 0;

+}

+

+body.docs #toc li {

+  margin-top: 7px;

+  padding-left: 10px;

+  list-style-type: none;

+}

+

+body.docs div.docs {

+  margin: 61px 0 0 150px;

+  padding: 1em 2em 1em 1em !important;

+}

+

+.docs p+p {

+  text-indent: 5%;

+  margin-top: -.67em;

+}

+

+.docs h2, .docs h3 {

+  margin-bottom: .1em;

+  padding-top: .3em;

+}

+

+.functnbar, .functnbar2, .functnbar3 {

+  padding: 5px;

+  margin: .67em 2px;

+}

+

+.functnbar3 {

+  margin-top: 0;

+}

+

+body {

+  padding: 1em;

+}

+

+body.composite, body.docs {

+  margin: 0;

+  padding: 0;

+}

+

+th, td {

+  text-align: left;

+  vertical-align: top;

+}

+

+.right {

+  text-align: right !important;

+}

+

+.center {

+  text-align: center !important;

+}

+

+.axial th, .axial th .strut {

+  text-align: right;

+}

+

+.contentBox .axial td th {

+  text-align: left;

+}

+

+body .stb {

+  margin-top: 1em;

+  text-indent: 0;

+}

+

+body .mtb {

+  margin-top: 2em;

+  text-indent: 0;

+}

+

+.courtesylinks {

+  margin-top: 1em;

+  padding-top: 1em;

+}

+

+dd {

+  margin-bottom: .67em;

+}

+

+.toolgroup {

+  margin-bottom: 6px;

+}

+

+.toolgroup .body {

+  padding: 4px 4px 4px 0;

+}

+

+.toolgroup .label {

+  padding: 4px;

+}

+

+.toolgroup .body div {

+  padding-bottom: .3em;

+  padding-left: 1em;

+}

+

+.toolgroup .body div div {

+  margin-top: .3em;

+  padding-bottom: 0;

+}

+

+.tier1 {

+  margin-left: 0;

+}

+

+.tier2 {

+  margin-left: 1.5em;

+}

+

+.tier3 {

+  margin-left: 3em;

+}

+

+.tier4 {

+  margin-left: 4.5em;

+}

+

+.tier5 {

+  margin-left: 6em;

+}

+

+.tier6 {

+  margin-left: 7.5em;

+}

+

+.tier7 {

+  margin-left: 9em;

+}

+

+.tier8 {

+  margin-left: 10.5em;

+}

+

+.tier9 {

+  margin-left: 12em;

+}

+

+.tier10 {

+  margin-left: 13.5em;

+}

+

+.filebrowse .expanded, .filebrowse .collapsed {

+  padding-left: 34px;

+}

+

+.filebrowse .leafnode, .filebrowse .leaf {

+  padding-left: 20px;

+}

+

+.messagechild {

+  padding-left: 34px;

+}

+

+.filebrowse-alt .expanded, .filebrowse-alt .collapsed, .filebrowse-alt .leaf, .filebrowse-alt .leafnode, .expandedwaste, .collapsedwaste, .sortup, .sortdown {

+  /* hide from macie5\*/

+  float: left;

+  /* resume */

+  display: inline-block;

+  height: 15px;

+  width: 34px;

+  padding-left: 0 !important;

+}

+

+.filebrowse-alt .leaf, .filebrowse-alt .leafnode, .sortup, .sortdown {

+  width: 20px;

+}

+

+.filebrowse ul, .filebrowse-alt ul {

+  list-style-type: none;

+  padding-left: 0;

+  margin-left: 0;

+}

+

+.filebrowse ul ul, .filebrowse-alt ul ul {

+  margin-left: 1.5em;

+  margin-top: 0;

+  padding-top: .67em;

+}

+

+.filebrowse li, .filebrowse-alt li {

+  margin-bottom: .67em;

+}

+

+td.filebrowse h2 {

+  margin-top: 0;

+}

+

+.errormessage, .warningmessage, .donemessage, .infomessage, .docinfo, .dirinfo, .memberinfo, .usergroupinfo {

+  margin: .67em 0;

+  padding: .33em 0 .67em 42px;

+  min-height: 32px;

+}

+

+.errormark, .warningmark, .donemark, .infomark {

+  padding-left: 20px;

+  min-height: 15px;

+}

+

+.alt {

+  display: none;

+}

+

+#banner h1 {

+  margin: 0;

+}

+

+.axial th, .axial th .strut, #leftColumn .strut {

+  width: 12em;

+}

+

+#breadcrumbs {

+  padding: 2px 8px;

+}

+

+/* Bad for IE

+.contentBox h2, .contentBox h3, .bars {

+  clear: both;

+}

+*/

+

+.legend {

+  float: right;

+}

+

+.legend th, .bars th {

+  text-align: right;

+  padding-left: 1em;

+}

+

+.bars table {

+  table-layout: fixed;

+}

+

+.bars th {

+  width: 12em;

+}

+

+#projectdocumentlist td.filebrowse-alt {

+  padding-right: .75em;

+}

+#organizationLogo img, #projectLogo img, #projectLogo span{

+  margin: 8px;

+}

diff --git a/doc/samplereport/css/maven-theme.css b/doc/samplereport/css/maven-theme.css
new file mode 100644
index 0000000..ea20b09
--- /dev/null
+++ b/doc/samplereport/css/maven-theme.css
@@ -0,0 +1,125 @@
+body {

+  padding: 0px 0px 10px 0px;

+}

+body, td, select, input, li{

+  font-family: Verdana, Helvetica, Arial, sans-serif;

+  font-size: 13px;

+}

+code{

+  font-family: Courier, monospace;

+  font-size: 13px;

+}

+a {

+  text-decoration: none;

+}

+a:link {

+  color:#36a;

+}

+a:visited  {

+  color:#47a;

+}

+a:active, a:hover {

+  color:#69c;

+}

+#legend li.externalLink {

+  background: url(../images/external.png) left top no-repeat;

+  padding-left: 18px;

+}

+a.externalLink, a.externalLink:link, a.externalLink:visited, a.externalLink:active, a.externalLink:hover {

+  background: url(../images/external.png) right center no-repeat;

+  padding-right: 18px;

+}

+#legend li.newWindow {

+  background: url(../images/newwindow.png) left top no-repeat;

+  padding-left: 18px;

+}

+a.newWindow, a.newWindow:link, a.newWindow:visited, a.newWindow:active, a.newWindow:hover {

+  background: url(../images/newwindow.png) right center no-repeat;

+  padding-right: 18px;

+}

+h2 {

+  padding: 4px 4px 4px 6px;

+  border: 1px solid #999;

+  color: #900;

+  background-color: #ddd;

+  font-weight:900;

+  font-size: x-large;

+}

+h3 {

+  padding: 4px 4px 4px 6px;

+  border: 1px solid #aaa;

+  color: #900;

+  background-color: #eee;

+  font-weight: normal;

+  font-size: large;

+}

+h4 {

+  padding: 4px 4px 4px 6px;

+  border: 1px solid #bbb;

+  color: #900;

+  background-color: #fff;

+  font-weight: normal;

+  font-size: large;

+}

+h5 {

+  padding: 4px 4px 4px 6px;

+  color: #900;

+  font-size: normal;

+}

+p {

+  line-height: 1.3em;

+  font-size: small;

+}

+#breadcrumbs {

+  border-top: 1px solid #aaa;

+  border-bottom: 1px solid #aaa;

+  background-color: #ccc;

+}

+#leftColumn {

+  margin: 10px 0 0 5px;

+  border: 1px solid #999;

+  background-color: #eee;

+}

+#navcolumn h5 {

+  font-size: smaller;

+  border-bottom: 1px solid #aaaaaa;

+  padding-top: 2px;

+  color: #000;

+}

+

+table.bodyTable th {

+  color: white;

+  background-color: #bbb;

+  text-align: left;

+  font-weight: bold;

+}

+

+table.bodyTable th, table.bodyTable td {

+  font-size: 1em;

+}

+

+table.bodyTable tr.a {

+  background-color: #ddd;

+}

+

+table.bodyTable tr.b {

+  background-color: #eee;

+}

+

+.source {

+  border: 1px solid #999;

+}

+dl {

+  padding: 4px 4px 4px 6px;

+  border: 1px solid #aaa;

+  background-color: #ffc;

+}

+dt {

+  color: #900;

+}

+#organizationLogo img, #projectLogo img, #projectLogo span{

+  margin: 8px;

+}

+#banner {

+  border-bottom: 1px solid #fff;

+}

diff --git a/doc/samplereport/css/print.css b/doc/samplereport/css/print.css
new file mode 100644
index 0000000..26ad7f0
--- /dev/null
+++ b/doc/samplereport/css/print.css
@@ -0,0 +1,7 @@
+#banner, #footer, #leftcol, #breadcrumbs, .docs #toc, .docs .courtesylinks, #leftColumn, #navColumn {

+	display: none !important;

+}

+#bodyColumn, body.docs div.docs {

+	margin: 0 !important;

+	border: none !important

+}

diff --git a/doc/samplereport/images/add.gif b/doc/samplereport/images/add.gif
new file mode 100644
index 0000000..ac0bdcc
--- /dev/null
+++ b/doc/samplereport/images/add.gif
Binary files differ
diff --git a/doc/samplereport/images/collapsed.gif b/doc/samplereport/images/collapsed.gif
new file mode 100644
index 0000000..6e71084
--- /dev/null
+++ b/doc/samplereport/images/collapsed.gif
Binary files differ
diff --git a/doc/samplereport/images/expanded.gif b/doc/samplereport/images/expanded.gif
new file mode 100644
index 0000000..0fef3d8
--- /dev/null
+++ b/doc/samplereport/images/expanded.gif
Binary files differ
diff --git a/doc/samplereport/images/external-classic.png b/doc/samplereport/images/external-classic.png
new file mode 100644
index 0000000..09a5425
--- /dev/null
+++ b/doc/samplereport/images/external-classic.png
Binary files differ
diff --git a/doc/samplereport/images/external.png b/doc/samplereport/images/external.png
new file mode 100644
index 0000000..3f999fc
--- /dev/null
+++ b/doc/samplereport/images/external.png
Binary files differ
diff --git a/doc/samplereport/images/file.gif b/doc/samplereport/images/file.gif
new file mode 100644
index 0000000..72c13cc
--- /dev/null
+++ b/doc/samplereport/images/file.gif
Binary files differ
diff --git a/doc/samplereport/images/fix.gif b/doc/samplereport/images/fix.gif
new file mode 100644
index 0000000..2585f13
--- /dev/null
+++ b/doc/samplereport/images/fix.gif
Binary files differ
diff --git a/doc/samplereport/images/folder-closed.gif b/doc/samplereport/images/folder-closed.gif
new file mode 100644
index 0000000..2baed62
--- /dev/null
+++ b/doc/samplereport/images/folder-closed.gif
Binary files differ
diff --git a/doc/samplereport/images/folder-open.gif b/doc/samplereport/images/folder-open.gif
new file mode 100644
index 0000000..3955d19
--- /dev/null
+++ b/doc/samplereport/images/folder-open.gif
Binary files differ
diff --git a/doc/samplereport/images/help_logo.gif b/doc/samplereport/images/help_logo.gif
new file mode 100644
index 0000000..cc25c03
--- /dev/null
+++ b/doc/samplereport/images/help_logo.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_alert.gif b/doc/samplereport/images/icon_alert.gif
new file mode 100644
index 0000000..4968bca
--- /dev/null
+++ b/doc/samplereport/images/icon_alert.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_alertsml.gif b/doc/samplereport/images/icon_alertsml.gif
new file mode 100644
index 0000000..641308d
--- /dev/null
+++ b/doc/samplereport/images/icon_alertsml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_arrowfolder1_sml.gif b/doc/samplereport/images/icon_arrowfolder1_sml.gif
new file mode 100644
index 0000000..89408af
--- /dev/null
+++ b/doc/samplereport/images/icon_arrowfolder1_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_arrowfolder2_sml.gif b/doc/samplereport/images/icon_arrowfolder2_sml.gif
new file mode 100644
index 0000000..d9e7e1c
--- /dev/null
+++ b/doc/samplereport/images/icon_arrowfolder2_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_arrowfolderclosed1_sml.gif b/doc/samplereport/images/icon_arrowfolderclosed1_sml.gif
new file mode 100644
index 0000000..c48e8b6
--- /dev/null
+++ b/doc/samplereport/images/icon_arrowfolderclosed1_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_arrowfolderopen2_sml.gif b/doc/samplereport/images/icon_arrowfolderopen2_sml.gif
new file mode 100644
index 0000000..477e327
--- /dev/null
+++ b/doc/samplereport/images/icon_arrowfolderopen2_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_arrowmembers1_sml.gif b/doc/samplereport/images/icon_arrowmembers1_sml.gif
new file mode 100644
index 0000000..b4dea83
--- /dev/null
+++ b/doc/samplereport/images/icon_arrowmembers1_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_arrowmembers2_sml.gif b/doc/samplereport/images/icon_arrowmembers2_sml.gif
new file mode 100644
index 0000000..0e74357
--- /dev/null
+++ b/doc/samplereport/images/icon_arrowmembers2_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_arrowusergroups1_sml.gif b/doc/samplereport/images/icon_arrowusergroups1_sml.gif
new file mode 100644
index 0000000..5177959
--- /dev/null
+++ b/doc/samplereport/images/icon_arrowusergroups1_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_arrowusergroups2_sml.gif b/doc/samplereport/images/icon_arrowusergroups2_sml.gif
new file mode 100644
index 0000000..574bb45
--- /dev/null
+++ b/doc/samplereport/images/icon_arrowusergroups2_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_arrowwaste1_sml.gif b/doc/samplereport/images/icon_arrowwaste1_sml.gif
new file mode 100644
index 0000000..25d75f7
--- /dev/null
+++ b/doc/samplereport/images/icon_arrowwaste1_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_arrowwaste2_sml.gif b/doc/samplereport/images/icon_arrowwaste2_sml.gif
new file mode 100644
index 0000000..54dbf42
--- /dev/null
+++ b/doc/samplereport/images/icon_arrowwaste2_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_confirmsml.gif b/doc/samplereport/images/icon_confirmsml.gif
new file mode 100644
index 0000000..ca3c810
--- /dev/null
+++ b/doc/samplereport/images/icon_confirmsml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_doc_lrg.gif b/doc/samplereport/images/icon_doc_lrg.gif
new file mode 100644
index 0000000..b458267
--- /dev/null
+++ b/doc/samplereport/images/icon_doc_lrg.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_doc_sml.gif b/doc/samplereport/images/icon_doc_sml.gif
new file mode 100644
index 0000000..239bfaa
--- /dev/null
+++ b/doc/samplereport/images/icon_doc_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_error_lrg.gif b/doc/samplereport/images/icon_error_lrg.gif
new file mode 100644
index 0000000..fccffd1
--- /dev/null
+++ b/doc/samplereport/images/icon_error_lrg.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_error_sml.gif b/doc/samplereport/images/icon_error_sml.gif
new file mode 100644
index 0000000..61132ef
--- /dev/null
+++ b/doc/samplereport/images/icon_error_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_folder_lrg.gif b/doc/samplereport/images/icon_folder_lrg.gif
new file mode 100644
index 0000000..3683e75
--- /dev/null
+++ b/doc/samplereport/images/icon_folder_lrg.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_folder_sml.gif b/doc/samplereport/images/icon_folder_sml.gif
new file mode 100644
index 0000000..8e26f89
--- /dev/null
+++ b/doc/samplereport/images/icon_folder_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_help_lrg.gif b/doc/samplereport/images/icon_help_lrg.gif
new file mode 100644
index 0000000..c216295
--- /dev/null
+++ b/doc/samplereport/images/icon_help_lrg.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_help_sml.gif b/doc/samplereport/images/icon_help_sml.gif
new file mode 100644
index 0000000..43bfd56
--- /dev/null
+++ b/doc/samplereport/images/icon_help_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_info_lrg.gif b/doc/samplereport/images/icon_info_lrg.gif
new file mode 100644
index 0000000..b390fd4
--- /dev/null
+++ b/doc/samplereport/images/icon_info_lrg.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_info_sml.gif b/doc/samplereport/images/icon_info_sml.gif
new file mode 100644
index 0000000..c6cb9ad
--- /dev/null
+++ b/doc/samplereport/images/icon_info_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_infosml.gif b/doc/samplereport/images/icon_infosml.gif
new file mode 100644
index 0000000..1aa2d30
--- /dev/null
+++ b/doc/samplereport/images/icon_infosml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_members_lrg.gif b/doc/samplereport/images/icon_members_lrg.gif
new file mode 100644
index 0000000..7ba2df6
--- /dev/null
+++ b/doc/samplereport/images/icon_members_lrg.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_members_sml.gif b/doc/samplereport/images/icon_members_sml.gif
new file mode 100644
index 0000000..997e699
--- /dev/null
+++ b/doc/samplereport/images/icon_members_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_sortdown.gif b/doc/samplereport/images/icon_sortdown.gif
new file mode 100644
index 0000000..9561bbe
--- /dev/null
+++ b/doc/samplereport/images/icon_sortdown.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_sortleft.gif b/doc/samplereport/images/icon_sortleft.gif
new file mode 100644
index 0000000..4fd21e8
--- /dev/null
+++ b/doc/samplereport/images/icon_sortleft.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_sortright.gif b/doc/samplereport/images/icon_sortright.gif
new file mode 100644
index 0000000..ea8076e
--- /dev/null
+++ b/doc/samplereport/images/icon_sortright.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_sortup.gif b/doc/samplereport/images/icon_sortup.gif
new file mode 100644
index 0000000..61942d6
--- /dev/null
+++ b/doc/samplereport/images/icon_sortup.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_success_lrg.gif b/doc/samplereport/images/icon_success_lrg.gif
new file mode 100644
index 0000000..9af317d
--- /dev/null
+++ b/doc/samplereport/images/icon_success_lrg.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_success_sml.gif b/doc/samplereport/images/icon_success_sml.gif
new file mode 100644
index 0000000..52e85a4
--- /dev/null
+++ b/doc/samplereport/images/icon_success_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_usergroups_lrg.gif b/doc/samplereport/images/icon_usergroups_lrg.gif
new file mode 100644
index 0000000..3a4e356
--- /dev/null
+++ b/doc/samplereport/images/icon_usergroups_lrg.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_usergroups_sml.gif b/doc/samplereport/images/icon_usergroups_sml.gif
new file mode 100644
index 0000000..9236101
--- /dev/null
+++ b/doc/samplereport/images/icon_usergroups_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_warning_lrg.gif b/doc/samplereport/images/icon_warning_lrg.gif
new file mode 100644
index 0000000..83359d8
--- /dev/null
+++ b/doc/samplereport/images/icon_warning_lrg.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_warning_sml.gif b/doc/samplereport/images/icon_warning_sml.gif
new file mode 100644
index 0000000..873bbb5
--- /dev/null
+++ b/doc/samplereport/images/icon_warning_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_waste_lrg.gif b/doc/samplereport/images/icon_waste_lrg.gif
new file mode 100644
index 0000000..e5434aa
--- /dev/null
+++ b/doc/samplereport/images/icon_waste_lrg.gif
Binary files differ
diff --git a/doc/samplereport/images/icon_waste_sml.gif b/doc/samplereport/images/icon_waste_sml.gif
new file mode 100644
index 0000000..6dd046d
--- /dev/null
+++ b/doc/samplereport/images/icon_waste_sml.gif
Binary files differ
diff --git a/doc/samplereport/images/logos/maven-feather.png b/doc/samplereport/images/logos/maven-feather.png
new file mode 100644
index 0000000..5beac16
--- /dev/null
+++ b/doc/samplereport/images/logos/maven-feather.png
Binary files differ
diff --git a/doc/samplereport/images/newwindow-classic.png b/doc/samplereport/images/newwindow-classic.png
new file mode 100644
index 0000000..5987117
--- /dev/null
+++ b/doc/samplereport/images/newwindow-classic.png
Binary files differ
diff --git a/doc/samplereport/images/newwindow.png b/doc/samplereport/images/newwindow.png
new file mode 100644
index 0000000..6287f72
--- /dev/null
+++ b/doc/samplereport/images/newwindow.png
Binary files differ
diff --git a/doc/samplereport/images/none.png b/doc/samplereport/images/none.png
new file mode 100644
index 0000000..fef6d18
--- /dev/null
+++ b/doc/samplereport/images/none.png
Binary files differ
diff --git a/doc/samplereport/images/nw_maj.gif b/doc/samplereport/images/nw_maj.gif
new file mode 100644
index 0000000..452296b
--- /dev/null
+++ b/doc/samplereport/images/nw_maj.gif
Binary files differ
diff --git a/doc/samplereport/images/nw_maj_hi.gif b/doc/samplereport/images/nw_maj_hi.gif
new file mode 100644
index 0000000..54c3933
--- /dev/null
+++ b/doc/samplereport/images/nw_maj_hi.gif
Binary files differ
diff --git a/doc/samplereport/images/nw_maj_rond.gif b/doc/samplereport/images/nw_maj_rond.gif
new file mode 100644
index 0000000..add42a4
--- /dev/null
+++ b/doc/samplereport/images/nw_maj_rond.gif
Binary files differ
diff --git a/doc/samplereport/images/nw_med.gif b/doc/samplereport/images/nw_med.gif
new file mode 100644
index 0000000..d2b6d91
--- /dev/null
+++ b/doc/samplereport/images/nw_med.gif
Binary files differ
diff --git a/doc/samplereport/images/nw_med_hi.gif b/doc/samplereport/images/nw_med_hi.gif
new file mode 100644
index 0000000..0c2db4c
--- /dev/null
+++ b/doc/samplereport/images/nw_med_hi.gif
Binary files differ
diff --git a/doc/samplereport/images/nw_med_rond.gif b/doc/samplereport/images/nw_med_rond.gif
new file mode 100644
index 0000000..8aaa564
--- /dev/null
+++ b/doc/samplereport/images/nw_med_rond.gif
Binary files differ
diff --git a/doc/samplereport/images/nw_min.gif b/doc/samplereport/images/nw_min.gif
new file mode 100644
index 0000000..bf4bc75
--- /dev/null
+++ b/doc/samplereport/images/nw_min.gif
Binary files differ
diff --git a/doc/samplereport/images/nw_min_036.gif b/doc/samplereport/images/nw_min_036.gif
new file mode 100644
index 0000000..eb344a8
--- /dev/null
+++ b/doc/samplereport/images/nw_min_036.gif
Binary files differ
diff --git a/doc/samplereport/images/nw_min_hi.gif b/doc/samplereport/images/nw_min_hi.gif
new file mode 100644
index 0000000..a8a30ab
--- /dev/null
+++ b/doc/samplereport/images/nw_min_hi.gif
Binary files differ
diff --git a/doc/samplereport/images/pdf.gif b/doc/samplereport/images/pdf.gif
new file mode 100644
index 0000000..7bce338
--- /dev/null
+++ b/doc/samplereport/images/pdf.gif
Binary files differ
diff --git a/doc/samplereport/images/poweredby_036.gif b/doc/samplereport/images/poweredby_036.gif
new file mode 100644
index 0000000..49d0345
--- /dev/null
+++ b/doc/samplereport/images/poweredby_036.gif
Binary files differ
diff --git a/doc/samplereport/images/product_logo.gif b/doc/samplereport/images/product_logo.gif
new file mode 100644
index 0000000..327b838
--- /dev/null
+++ b/doc/samplereport/images/product_logo.gif
Binary files differ
diff --git a/doc/samplereport/images/remove.gif b/doc/samplereport/images/remove.gif
new file mode 100644
index 0000000..ad4b238
--- /dev/null
+++ b/doc/samplereport/images/remove.gif
Binary files differ
diff --git a/doc/samplereport/images/se_maj_rond.gif b/doc/samplereport/images/se_maj_rond.gif
new file mode 100644
index 0000000..da2510e
--- /dev/null
+++ b/doc/samplereport/images/se_maj_rond.gif
Binary files differ
diff --git a/doc/samplereport/images/strich.gif b/doc/samplereport/images/strich.gif
new file mode 100644
index 0000000..a33e79d
--- /dev/null
+++ b/doc/samplereport/images/strich.gif
Binary files differ
diff --git a/doc/samplereport/images/sw_maj_rond.gif b/doc/samplereport/images/sw_maj_rond.gif
new file mode 100644
index 0000000..110bdf4
--- /dev/null
+++ b/doc/samplereport/images/sw_maj_rond.gif
Binary files differ
diff --git a/doc/samplereport/images/sw_med_rond.gif b/doc/samplereport/images/sw_med_rond.gif
new file mode 100644
index 0000000..6671c3d
--- /dev/null
+++ b/doc/samplereport/images/sw_med_rond.gif
Binary files differ
diff --git a/doc/samplereport/images/sw_min.gif b/doc/samplereport/images/sw_min.gif
new file mode 100644
index 0000000..d96369d
--- /dev/null
+++ b/doc/samplereport/images/sw_min.gif
Binary files differ
diff --git a/doc/samplereport/images/update.gif b/doc/samplereport/images/update.gif
new file mode 100644
index 0000000..71ca225
--- /dev/null
+++ b/doc/samplereport/images/update.gif
Binary files differ
diff --git a/doc/samplereport/index.html b/doc/samplereport/index.html
new file mode 100644
index 0000000..b5eb4f1
--- /dev/null
+++ b/doc/samplereport/index.html
@@ -0,0 +1,87 @@
+

+

+

+

+

+

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

+<html>

+  <head>

+    <title></title>

+    <style type="text/css" media="all">

+      @import url("./css/maven-base.css");

+      @import url("./css/maven-theme.css");

+      @import url("./css/site.css");

+    </style>

+    <link rel="stylesheet" href="./css/print.css" type="text/css" media="print" />

+        <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />

+  </head>

+  <body class="composite">

+    <div id="banner">

+                          <span id="bannerLeft">

+    

+            Core Library

+    

+            </span>

+                    <div class="clear">

+        <hr/>

+      </div>

+    </div>

+    <div id="breadcrumbs">

+      <div class="xleft">Last Published: Tue Jan 24 22:40:49 EST 2006</div>

+      <div class="xright">      <a href="http://tacos.sourceforge.net">Core Library</a>

+      </div>

+      <div class="clear">

+        <hr/>

+      </div>

+    </div>

+    <div id="leftColumn"><div id="navcolumn">      <h5>Project Documentation</h5>

+    <ul>

+              

+        <li class="collapsed">

+          <ul>

+                  

+    <li class="none">

+          <strong><a href="surefire-report.html">Maven Surefire Report</a></strong>

+        </li>

+              </ul>

+        </li>

+          </ul>

+  <a href="http://maven.apache.org/" title="Built by Maven" id="poweredBy">

+              <img alt="Built by Maven" src="./images/logos/maven-feather.png"></img>

+            </a>

+    </div></div>

+    <div id="bodyColumn">

+      <div id="contentBox">

+        <script type="text/javascript">
+function toggleDisplay(elementId) {
+ var elm = document.getElementById(elementId + 'error');
+ if (elm && typeof elm.style != "undefined") {
+ if (elm.style.display == "none") {
+ elm.style.display = "";
+ document.getElementById(elementId + 'off').style.display = "none";
+ document.getElementById(elementId + 'on').style.display = "inline";
+ } else if (elm.style.display == "") { elm.style.display = "none";
+ document.getElementById(elementId + 'off').style.display = "inline";
+ document.getElementById(elementId + 'on').style.display = "none";
+ } 
+ } 
+ }
+</script><h2><a name="Summary"></a>Summary</h2><div class="section">[<a href="#Summary">Summary</a>][<a href="#Package_List">Package List</a>][<a href="#Test_Cases">Test Cases</a>]</div><table class="bodyTable"><tr class="a"></tr><tr class="b"></tr></table><table class="bodyTable"><tr class="a"><th>Tests</th><th>Errors </th><th>Failures</th><th>Success Rate</th><th>Time</th></tr><tr class="b"><td>7</td><td>0</td><td>1</td><td>85.714%</td><td>190.072</td></tr></table><br />Note: failures are anticipated and checked for with assertions while errors are unanticipated.<table class="bodyTable"><tr class="a"></tr><tr class="b"></tr></table><h2><a name="Package_List"></a>Package List</h2><div class="section">[<a href="#Summary">Summary</a>][<a href="#Package_List">Package List</a>][<a href="#Test_Cases">Test Cases</a>]</div><table class="bodyTable"><tr class="a"></tr><tr class="b"></tr></table><table class="bodyTable"><tr class="a"><th>Package</th><th>Tests</th><th>Errors </th><th>Failures</th><th>Success Rate</th><th>Time</th></tr><tr class="b"><td><a href="#TestNG Suite">TestNG Suite</a></td><td>7</td><td>0</td><td>1</td><td>85.714%</td><td>190.072</td></tr></table><br />Note: package statistics are not computed recursively, they only sum up all of its testsuites numbers.<h3><a name="TestNG Suite"></a>TestNG Suite</h3><table class="bodyTable"><tr class="a"><th></th><th>Class</th><th>Tests</th><th>Errors </th><th>Failures</th><th>Success Rate</th><th>Time</th></tr><tr class="b"><td><a href="#TestNG SuiteTestNG Suite"><img src="images/icon_warning_sml.gif" /></a></td><td><a href="#TestNG SuiteTestNG Suite">TestNG Suite</a></td><td>7</td><td>0</td><td>1</td><td>85.714%</td><td>190.072</td></tr></table><table class="bodyTable"><tr class="a"></tr><tr class="b"></tr></table><h2><a name="Test_Cases"></a>Test Cases</h2><div class="section">[<a href="#Summary">Summary</a>][<a href="#Package_List">Package List</a>][<a href="#Test_Cases">Test Cases</a>]</div><h3><a name="TestNG SuiteTestNG Suite"></a>TestNG Suite</h3><table class="bodyTable"><tr class="a"><td><a href="#TestNG Suite.TestNG Suite.net.sf.tacos.ajax.components.DialogTest#testClickDialog"><img src="images/icon_error_sml.gif" /></a></td><td>net.sf.tacos.ajax.components.DialogTest#testClickDialog  <div class="detailToggle" style="display:inline"><a href="javascript:toggleDisplay('net.sf.tacos.ajax.components.DialogTest#testClickDialog');"><span style="display: inline;" id="net.sf.tacos.ajax.components.DialogTest#testClickDialogoff">+</span><span id="net.sf.tacos.ajax.components.DialogTest#testClickDialogon" style="display: none;">-</span> [ Detail ]</a></div></td><td>189.174</td></tr><tr class="b"><td></td><td>Connection timed out</td><td></td></tr><tr class="a"><td></td><td>  <div id="net.sf.tacos.ajax.components.DialogTest#testClickDialogerror" style="display:none;">java.net.ConnectException: Connection timed out<br />at java.net.PlainSocketImpl.socketConnect(Native Method)<br />at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:372)<br />at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:233)<br />at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:220)<br />at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:378)<br />at java.net.Socket.connect(Socket.java:536)<br />at java.net.Socket.connect(Socket.java:486)<br />at java.net.Socket.<init>(Socket.java:394)<br />at java.net.Socket.<init>(Socket.java:267)<br />at org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory.createSocket(DefaultProtocolSocketFactory.java:79)<br />at org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory.createSocket(DefaultProtocolSocketFactory.java:121)<br />at org.apache.commons.httpclient.HttpConnection.open(HttpConnection.java:704)<br />at org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(HttpMethodDirector.java:384)<br />at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:170)<br />at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:396)<br />at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:324)<br />at com.gargoylesoftware.htmlunit.HttpWebConnection.getResponse(HttpWebConnection.java:139)<br />at com.gargoylesoftware.htmlunit.WebClient.loadWebResponse(WebClient.java:1523)<br />at com.gargoylesoftware.htmlunit.WebClient.getPage(WebClient.java:325)<br />at com.gargoylesoftware.htmlunit.WebClient.getPage(WebClient.java:377)<br />at net.sf.tacos.ajax.components.DialogTest.testClickDialog(DialogTest.java:50)<br />at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)<br />at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)<br />at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)<br />at java.lang.reflect.Method.invoke(Method.java:615)<br />at org.testng.internal.MethodHelper.invokeMethod(MethodHelper.java:461)<br />at org.testng.internal.Invoker.invokeMethod(Invoker.java:378)<br />at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:580)<br />at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:89)<br />at org.testng.TestRunner.privateRun(TestRunner.java:612)<br />at org.testng.TestRunner.run(TestRunner.java:503)<br />at org.testng.SuiteRunner.privateRun(SuiteRunner.java:218)<br />at org.testng.SuiteRunner.run(SuiteRunner.java:144)<br />at org.testng.TestNG.createAndRunSuiteRunners(TestNG.java:576)<br />at org.testng.TestNG.runSuitesLocally(TestNG.java:539)<br />at org.apache.maven.surefire.Surefire.run(Surefire.java:236)<br />at org.apache.maven.surefire.Surefire.run(Surefire.java:128)<br />at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)<br />at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)<br />at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)<br />at java.lang.reflect.Method.invoke(Method.java:615)<br />at org.apache.maven.surefire.SurefireBooter.runTestsInProcess(SurefireBooter.java:345)<br />at org.apache.maven.surefire.SurefireBooter.run(SurefireBooter.java:258)<br />at org.apache.maven.test.SurefirePlugin.execute(SurefirePlugin.java:477)<br />at org.apache.maven.plugin.DefaultPluginManager.executeMojo(DefaultPluginManager.java:415)<br />at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoals(DefaultLifecycleExecutor.java:531)<br />at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoalWithLifecycle(DefaultLifecycleExecutor.java:472)<br />at org.apache.maven.lifecycle.DefaultLifecycleExecutor.forkProjectLifecycle(DefaultLifecycleExecutor.java:859)<br />at org.apache.maven.lifecycle.DefaultLifecycleExecutor.forkLifecycle(DefaultLifecycleExecutor.java:731)<br />at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoals(DefaultLifecycleExecutor.java:522)<br />at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoalWithLifecycle(DefaultLifecycleExecutor.java:472)<br />at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoal(DefaultLifecycleExecutor.java:451)<br />at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoalAndHandleFailures(DefaultLifecycleExecutor.java:303)<br />at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeTaskSegments(DefaultLifecycleExecutor.java:270)<br />at org.apache.maven.lifecycle.DefaultLifecycleExecutor.execute(DefaultLifecycleExecutor.java:139)<br />at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:322)<br />at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:115)<br />at org.apache.maven.cli.MavenCli.main(MavenCli.java:249)<br />at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)<br />at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)<br />at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)<br />at java.lang.reflect.Method.invoke(Method.java:615)<br />at org.codehaus.classworlds.Launcher.launchEnhanced(Launcher.java:315)<br />at org.codehaus.classworlds.Launcher.launch(Launcher.java:255)<br />at org.codehaus.classworlds.Launcher.mainWithExitCode(Launcher.java:430)<br />at org.codehaus.classworlds.Launcher.main(Launcher.java:375)<br /></div></td><td></td></tr><tr class="b"><td><img src="images/icon_success_sml.gif" /></td><td>net.sf.tacos.services.SiteMapTest#testInitialize</td><td>0.006</td></tr><tr class="a"><td><img src="images/icon_success_sml.gif" /></td><td>net.sf.tacos.services.SiteMapTest#testCategory</td><td>0.569</td></tr><tr class="b"><td><img src="images/icon_success_sml.gif" /></td><td>net.sf.tacos.services.SiteMapTest#testContains</td><td>0.138</td></tr><tr class="a"><td><img src="images/icon_success_sml.gif" /></td><td>net.sf.tacos.services.SiteMapTest#testSitemapXml1</td><td>0.05</td></tr><tr class="b"><td><img src="images/icon_success_sml.gif" /></td><td>net.sf.tacos.services.SiteMapTest#testCategoryLists</td><td>0.117</td></tr><tr class="a"><td><img src="images/icon_success_sml.gif" /></td><td>net.sf.tacos.services.SiteMapTest#testUnsetResource</td><td>0</td></tr></table><table class="bodyTable"><tr class="b"></tr><tr class="a"></tr></table><table class="bodyTable"><tr class="b"></tr><tr class="a"></tr></table>

+      </div>

+    </div>

+    <div class="clear">

+      <hr/>

+    </div>

+    <div id="footer">

+      <div class="xright">&#169;  

+          2001-2006

+    

+          Sourceforge

+      </div>

+      <div class="clear">

+        <hr/>

+      </div>

+    </div>

+  </body>

+</html>

diff --git a/doc/selenium.html b/doc/selenium.html
new file mode 100644
index 0000000..dadc41d
--- /dev/null
+++ b/doc/selenium.html
@@ -0,0 +1,276 @@
+
+
+<html>
+    <head>
+        <title>TestNG</title>
+
+        <link rel="stylesheet" href="testng.css" type="text/css" />
+        <link type="text/css" rel="stylesheet" href="http://beust.com/beust.css"  />
+        <script type="text/javascript" src="banner.js"></script>
+
+      <script type="text/javascript" src="http://beust.com/scripts/shCore.js"></script>
+      <script type="text/javascript" src="http://beust.com/scripts/shBrushJava.js"></script>
+      <script type="text/javascript" src="http://beust.com/scripts/shBrushXml.js"></script>
+      <script type="text/javascript" src="http://beust.com/scripts/shBrushBash.js"></script>
+      <script type="text/javascript" src="http://beust.com/scripts/shBrushPlain.js"></script>
+      <link type="text/css" rel="stylesheet" href="http://beust.com/styles/shCore.css"/>
+      <link type="text/css" rel="stylesheet" href="http://beust.com/styles/shThemeCedric.css"/>
+      <script type="text/javascript">
+        SyntaxHighlighter.config.clipboardSwf = 'scripts/clipboard.swf';
+        SyntaxHighlighter.defaults['gutter'] = false;
+        SyntaxHighlighter.all();
+      </script>
+
+        <style type="text/css">
+            /* Set the command-line table option column width. */
+            #command-line colgroup.option {
+                 width: 7em;
+            }
+        </style>
+    </head>
+<body onLoad="prettyPrint()">
+
+<script type="text/javascript">
+    displayMenu("selenium.html")
+</script>
+
+<h2 align="center">Selenium and TestNG</h2>
+
+<em>This documentation was written by Felipe Knorr Kuhn and is adapted from <a href="http://knorrium.info/2010/08/31/using-testng-to-launch-your-tests-and-the-selenium-server/">a series of articles</a> posted on <a href="http://knorrium.info">his blog</a></em>.
+
+
+<h3>Content</h3>
+
+<ol>
+  <li><a href="#modeling">How to use TestNG configuration methods with parameters</a>
+  <li><a href="#configuration_methods">How to configure your test</a>
+  <li><a href="#creating_xml">Creating the XML file for TestNG</a>
+  <li><a href="#launching">Lauching your tests with Eclipse</a>
+  <li><a href="#future">How to make the test design a little better for the future</a>
+</ol>
+
+<h3><a name="#modeling">Modeling your test case</a></h3>
+
+<p>Before writing a test case, you need to know how and what will be validated. Let's use the WordPress <a href="http://knorrium.info/2010/05/19/a-java-approach-to-selenium">"Create New Post" test case</a>.
+
+<ol>
+  
+  <li>Go to <a href="http://demo.opensourcecms.com/wordpress/wp-login.php">http://demo.opensourcecms.com/wordpress/wp-login.php</a>
+  <li>Enter "admin" in the "Username" field
+  <li>Enter "demo123" in the "Password" field
+  <li>Click on the "Log In" button
+  <li>Verify that the text "Howdy, admin" is present
+  <li>Click on the "Posts" link
+  <li>Click on the "Add New" button
+  <li>Type "Selenium Demo Post" in the title field
+  <li>Click on the "Publish" button
+  <li> Verify that the text "Post published" is present
+
+</ol>
+
+<p>Considering this scenario, the first thing that comes to mind is creating a long test case that goes through all the steps. This might be a good approach if you are writing a manual test case. However, since we are writing an automated test, we want to write our script as modular as possible to be able to reuse parts of it in future scenarios.</p>
+
+<p>This is how I would break down the test:</p>
+
+<ol>
+  <li>Launch the WordPress site
+  <li>Open the Admin Login page
+  <li>Enter valid login data
+  <li>Navigate to the Write Post page
+  <li>Write the post
+  <li>Publish the post
+  <li>Verify that it was actually post
+</ol>
+
+<p>Keep in mind that this is just an example. You are free to model your tests in any way you want, as long as they have business value and will validate your business logic.</p> 
+<p>Let's see how to do that with actual Java code:</p>
+
+<pre class="brush:java">
+@Test(description="Launches the WordPress site")
+public void launchSite(){
+  selenium.open("");
+  selenium.waitForPageToLoad("30000");
+  assertEquals(selenium.getTitle(), "Demo | Just another WordPress site");
+}
+ 
+@Test(description="Navigates to the admin page")
+  public void openAdminPage() {
+  selenium.open("wp-admin");
+  selenium.waitForPageToLoad("30000");
+  assertEquals(selenium.getTitle(), "Demo › Log In");
+}
+ 
+@Test(description="Enters valid login data")
+  public void loginAsAdmin() {
+  selenium.type("user_login", "admin");
+  selenium.type("user_pass", "demo123");
+  selenium.click("wp-submit");
+  selenium.waitForPageToLoad("30000");
+  assertTrue(selenium.isTextPresent("Howdy, admin"));
+}
+ 
+@Test(description="Navigates to the New Post screen")
+public void navigateNewPost() {
+  selenium.click("//a[contains(text(),'Posts')]/following::a[contains(text(),'Add New')][1]");
+  selenium.waitForPageToLoad("30000");
+  assertTrue(selenium.isTextPresent("Add New Post"));
+}
+ 
+@Test(description="Writes the new post")
+public void writeBlogPost() {
+  selenium.type("title", "New Blog Post");
+  selenium.click("edButtonHTML");
+  selenium.type("content", "This is a new post");
+  //TODO:Assert
+}
+ 
+@Test(description="Publishes the post")
+public void publishBlogPost() {
+  selenium.click("submitdiv");
+  selenium.click("publish");
+  selenium.waitForPageToLoad("30000");
+  assertTrue(selenium.isTextPresent("Post published."));
+}
+ 
+@Test(description="Verifies the post")
+public void verifyBlogPost() {
+  selenium.click("//a[contains(text(),'Posts') and contains(@class,'wp-first-item')]");
+  selenium.waitForPageToLoad("30000");
+  assertTrue(selenium.isElementPresent("//a[text()='New Blog Post']"));
+}
+ 
+@Test(description="Logs out")
+public void logout() {
+  selenium.click("//a[text()='Log Out']");
+  //TODO:Assert
+}
+</pre>
+
+<p>These are the test methods (or steps) we are going to use.
+
+<h3><a name="configuration_methods">Configuration methods</a></h3>
+
+<p>If you are familiar with unit testing frameworks, you probably know about the setup and teardown methods. TestNG goes beyond that idea and allows you to define methods that will be run after or before your test suites, test groups or test methods. This is very useful for our Selenium tests because you can create a Selenium server and browser instance before you start running your test suite.)</p>
+
+<p>To achieve this, we will use two TestNG <a href="http://testng.org/doc/documentation-main.html#annotations">annotations</a>: <tt>@BeforeSuite</tt> and <tt>@AfterSuite</tt>:</p>
+
+<pre class="brush:java "> 
+@BeforeSuite(alwaysRun = true)
+public void setupBeforeSuite(ITestContext context) {
+  String seleniumHost = context.getCurrentXmlTest().getParameter("selenium.host");
+  String seleniumPort = context.getCurrentXmlTest().getParameter("selenium.port");
+  String seleniumBrowser = context.getCurrentXmlTest().getParameter("selenium.browser");
+  String seleniumUrl = context.getCurrentXmlTest().getParameter("selenium.url");
+ 
+  RemoteControlConfiguration rcc = new RemoteControlConfiguration();
+  rcc.setSingleWindow(true);
+  rcc.setPort(Integer.parseInt(seleniumPort));
+ 
+  try {
+    server = new SeleniumServer(false, rcc);
+    server.boot();
+  } catch (Exception e) {
+    throw new IllegalStateException("Can't start selenium server", e);
+  }
+ 
+  proc = new HttpCommandProcessor(seleniumHost, Integer.parseInt(seleniumPort),
+      seleniumBrowser, seleniumUrl);
+  selenium = new DefaultSelenium(proc);
+  selenium.start();
+}
+ 
+@AfterSuite(alwaysRun = true)
+public void setupAfterSuite() {
+  selenium.stop();
+  server.stop();
+}
+</pre>
+
+<p>PS: Did you notice those weird parameters? They are stored in the XML file (we are going to see in the next section) and accessed by a <tt>ITestContext</tt> object, which was <a href="http://testng.org/doc/documentation-main.html#dependency-injection">injected</a>. </p>
+
+<p>By adding these annotations, the TestNG engine will invoke the configuration methods automatically before/after your test suite (make sure the test methods are annotated with <tt>@Test</tt>), launching the Selenium server and instantiating the Selenium client object only once, reusing the same browser session across the tests.</p>
+
+<h3><a name="creating_xml">Creating the XML file</a></h3>
+
+<p>To define the order of the tests, we will have to create an XML file listing the test methods we would like to run. Make sure that the test methods are annotated with <tt>@Test</tt>, or else the TestNG engine will not invoke them.</p>
+
+<p>Before TestNG 5.13.1, you had to use Method Interceptors if you wanted to run the tests in the order defined in the XML file. I have posted <a href="http://gist.github.com/416310">my implementation of a Method Interceptor</a> on my Github account. From TestNG 5.13.1+, you can just add the <tt>preserve-order</tt> parameter to your test tag and include the methods you would like to run, reducing unecessary code in your test suite.</p>
+
+<p>Here is the XML file:</p>
+
+<pre class="brush: xml">
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
+
+<suite name="Knorrium.info - Wordpress Demo" verbose="10">
+  &lt;parameter name="selenium.host" value="localhost" /&gt;
+  &lt;parameter name="selenium.port" value="3737" /&gt;
+  &lt;parameter name="selenium.browser" value="*firefox" /&gt;
+  &lt;parameter name="selenium.url" value="http://demo.opensourcecms.com/wordpress/" /&gt;
+
+  <test name="Write new post" preserve-order="true">
+    <classes>
+      <class name="test.Wordpress">
+        <methods>
+          &lt;include name="launchSite" /&gt;
+          &lt;include name="openAdminPage" /&gt;
+          &lt;include name="loginAsAdmin" /&gt;
+          &lt;include name="navigateNewPost" /&gt;
+          &lt;include name="writeBlogPost" /&gt;
+          &lt;include name="publishBlogPost" /&gt;
+          &lt;include name="verifyBlogPost" /&gt;
+        </methods>
+      </class>
+    </classes>
+  </test>
+</suite>
+</pre>
+
+<h3><a name="launching">Launching your tests in Eclipse</a></h3>
+
+<p>We finished writing our tests, now how can we run them?</p> 
+<p>You can launch TestNG from the command line, using a Eclipse plugin or even programatically. We are going to use the Eclipse plugin. Follow the steps described on the official TestNG documentation <a href="http://testng.org/doc/download.html">over here</a></p>
+
+<p>If you installed TestNG correctly, you will see this menu when you right click on the XML file:<br />
+
+<p align="center">
+  <a href="http://testng.org/pictures/testNG-run.png">
+    <img src="http://testng.org/pictures/testNG-run.png" alt="" title="testNG-run" width="464" height="59" class="aligncenter size-full wp-image-19" />
+</a>
+</p>
+
+<p>Click on &#8220;Run as TestNG Suite&#8221; and your test will start running. You will then see this nice results tree:</p>
+
+<p align="center">
+  <a href="http://testng.org/pictures/testNG-exec.png">
+    <img src="http://testng.org/pictures/testNG-exec.png" alt="" title="testNG-exec" width="591" height="256" class="aligncenter size-full wp-image-20" /></a>
+</p>
+
+<h3><a name="future">Thinking about the future</a></h3>
+
+
+<p>If you really want to think about the future of your test suite, I would recommend you to read <a href="http://adam.goucher.ca/">Adam</a> <a href="http://twitter.com/adamgoucher">Goucher&#8217;s</a> <a href="http://www.pragprog.com/magazines">article</a> published on PragPub. He talks about Selenium 2 and the Page Objects Model (a very nice way to model your tests, especially if you use Selenium 2).</p>
+
+<p>Since there are lots of people still using Selenium 1, I'll stick to that for a while, but Selenium 2 will eventually be covered here.</p>
+
+<p>As the number of tests in your test suite grows, you will find that grouping them in different test classes is a good idea. If you do that, you can take advantage of object oriented programming and create a new class named BaseTest (for example), and leave your configuration logic there. That way, every test class must extend the BaseTest class and use static attributes.</p>
+
+
+<pre class="brush: java">
+public class WordPressAdmin extends BaseTest {
+@Test
+public void test1(){
+  selenium.open("");
+  //...
+}
+
+@Test
+public void test2(){
+  selenium.open("");
+  //...
+}
+}
+</pre>
+
+<p>This is better than leaving your configuration methods in the test class.</p>
+
+
diff --git a/doc/testng.css b/doc/testng.css
new file mode 100644
index 0000000..cfd78f9
--- /dev/null
+++ b/doc/testng.css
@@ -0,0 +1,163 @@
+body, td, th {

+  font-family: Lucida Grande, Tahoma, Verdana, Arial, sans-serif;

+  font-size: 9pt;

+}

+

+table,th,tr,td {

+        border: none;

+}

+

+th {

+        background: #aaa;

+}

+

+td {

+        background: #efefef;

+}

+

+td {

+        padding-top: 3px;

+        padding-right: 4px;

+        padding-left: 4px;

+        padding-bottom: 3px;

+}

+

+h2 {

+        text-align: center;

+}

+

+h3 {

+        background: #efefef;

+        letter-spacing: 0.12em;

+        border-bottom: 1px dotted #aaa;

+        padding-top: 8px;

+        padding-left: 4px;

+        width: 70%;

+}

+

+hr {

+        height: 5px;

+        border: 0;

+        color: #aaa;

+        background-color: #aaa;

+}

+

+.doctype {

+  color: red

+}

+

+.xml-comment {

+  color: green;

+}

+

+.attribute {

+  color: blue;

+}

+

+.tag {

+  color: #7f0055;

+  font-weight: bold;

+}

+

+.float-right {

+  float: right;

+}

+

+.code {

+  margin-left: 20px;

+  margin-top: 10px;

+  background-color: #ffffdd;

+  border: 1pt solid silver;

+  border-color: #ccc #999 #999 #CCC;

+  color: #000000;

+  padding: 5px 5px 5px 7px;

+  font-size: 11px;

+  padding: .3em .6em;

+  font-family: Courier New

+}

+

+.box {

+  border: 1pt solid silver;

+  border-color: #ccc #999 #999 #CCC;

+  background-color: #ffffdd;

+}

+

+#menu {

+        font-size: small;

+        line-height: 0.9em;

+        clear: both;

+        margin-top: 10px;

+        }

+

+#menu li {

+        margin-bottom: 7px;

+        }

+

+#menu li li {

+        margin-top: 2px;

+        margin-bottom: 2px;

+        }

+

+#menu li.submenuitems {

+        margin-bottom: 2px;

+        }

+

+#menu a {

+        text-decoration: none;

+        }

+

+.e{margin-left:1em;text-indent:-1em;margin-right:1em}

+.m{color:blue}

+.t{color:#990000}

+

+A:link {

+  text-decoration: none;

+}

+

+A:hover.summary {

+  text-decoration: none;

+  color: #ff9900;

+}

+

+A:visited {

+  text-decoration: none;

+}

+

+

+

+

+

+

+/*********************************************/

+/* The following rules apply to the top menu */

+/*********************************************/

+#topmenu a {

+	text-decoration: none;

+}

+#topmenu a:link {

+	color: #0066bb; 

+}

+#topmenu a:visited {

+	color: #006699; 

+}

+#topmenu a:active {

+	color: red; 

+}

+#topmenu a:hover {

+	color: red; 

+}

+#topmenu table {

+	background-color: #ffcc66; 

+	text-align: center;

+}

+#topmenu td {

+  font-size: small;

+  font-weight: bold;

+}

+#topmenu td.current {

+  background: #ffff00;

+}

+/* The next rule is a patch because other rules are not scoped correctly */

+#topmenu td {

+    background-color: #ffcc66;

+}

diff --git a/eclipse-projects/beust.com/.project b/eclipse-projects/beust.com/.project
new file mode 100644
index 0000000..04906ab
--- /dev/null
+++ b/eclipse-projects/beust.com/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>beust.com</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.pde.UpdateSiteBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.UpdateSiteNature</nature>
+	</natures>
+</projectDescription>
diff --git a/eclipse-projects/beust.com/index.html b/eclipse-projects/beust.com/index.html
new file mode 100644
index 0000000..70c1b8c
--- /dev/null
+++ b/eclipse-projects/beust.com/index.html
@@ -0,0 +1,60 @@
+<html>
+<head>
+<title>beust.com</title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<style>@import url("web/site.css");</style>
+<script type="text/javascript">
+	var returnval = 0;
+	var stylesheet, xmlFile, cache, doc;
+	function init(){
+		// NSCP 7.1+ / Mozilla 1.4.1+ / Safari
+		// Use the standard DOM Level 2 technique, if it is supported
+		if (document.implementation && document.implementation.createDocument) {
+			xmlFile = document.implementation.createDocument("", "", null);
+			stylesheet = document.implementation.createDocument("", "", null);
+			if (xmlFile.load){
+				xmlFile.load("site.xml");
+				stylesheet.load("web/site.xsl");
+			} else {
+				alert("Document could not be loaded by browser.");
+			}
+			xmlFile.addEventListener("load", transform, false);
+			stylesheet.addEventListener("load", transform, false);
+		}
+		//IE 6.0+ solution
+		else if (window.ActiveXObject) {
+			xmlFile = new ActiveXObject("msxml2.DOMDocument.3.0");
+			xmlFile.async = false;
+			xmlFile.load("site.xml");
+			stylesheet = new ActiveXObject("msxml2.FreeThreadedDOMDocument.3.0");
+			stylesheet.async = false;
+			stylesheet.load("web/site.xsl");
+			cache = new ActiveXObject("msxml2.XSLTemplate.3.0");
+			cache.stylesheet = stylesheet;
+			transformData();
+		}
+	}
+	// separate transformation function for IE 6.0+
+	function transformData(){
+		var processor = cache.createProcessor();
+		processor.input = xmlFile;
+		processor.transform();
+		data.innerHTML = processor.output;
+	}
+	// separate transformation function for NSCP 7.1+ and Mozilla 1.4.1+ 
+	function transform(){
+		returnval+=1;
+		if (returnval==2){
+			var processor = new XSLTProcessor();
+			processor.importStylesheet(stylesheet); 
+			doc = processor.transformToDocument(xmlFile);
+			document.getElementById("data").innerHTML = doc.documentElement.innerHTML;
+		}
+	}
+</script>
+</head>
+<body onload="init();">
+<!--[insert static HTML here]-->
+<div id="data"><!-- this is where the transformed data goes --></div>
+</body>
+</html>
diff --git a/eclipse-projects/beust.com/site.xml b/eclipse-projects/beust.com/site.xml
new file mode 100644
index 0000000..3c48252
--- /dev/null
+++ b/eclipse-projects/beust.com/site.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<site>
+   <feature url="features/org.testng.feature_5.8.0.2.jar" id="org.testng.feature" version="5.8.0.2">
+      <category name="TestNG"/>
+   </feature>
+   <category-def name="TestNG" label="TestNG"/>
+</site>
diff --git a/eclipse-projects/beust.com/web/site.css b/eclipse-projects/beust.com/web/site.css
new file mode 100644
index 0000000..62c6f9f
--- /dev/null
+++ b/eclipse-projects/beust.com/web/site.css
@@ -0,0 +1,12 @@
+<STYLE type="text/css">
+td.spacer {padding-bottom: 10px; padding-top: 10px;}
+.title { font-family: sans-serif; color: #99AACC;}
+.bodyText { font-family: sans-serif; font-size: 9pt; color:#000000;  }
+.sub-header { font-family: sans-serif; font-style: normal; font-weight: bold; font-size: 9pt; color: white;}
+.log-text {font-family: sans-serif; font-style: normal; font-weight: lighter; font-size: 8pt; color:black;}
+.big-header { font-family: sans-serif; font-style: normal; font-weight: bold; font-size: 9pt; color: white; border-top:10px solid white;}
+.light-row {background:#FFFFFF}
+.dark-row {background:#EEEEFF}
+.header {background:#99AADD}
+#indent {word-wrap : break-word;width :300px;text-indent:10px;}
+</STYLE>
diff --git a/eclipse-projects/beust.com/web/site.xsl b/eclipse-projects/beust.com/web/site.xsl
new file mode 100644
index 0000000..de497b1
--- /dev/null
+++ b/eclipse-projects/beust.com/web/site.xsl
@@ -0,0 +1,214 @@
+<xsl:stylesheet version = '1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:msxsl="urn:schemas-microsoft-com:xslt">
+<xsl:output method="html" encoding="UTF-8"/>
+<xsl:key name="cat" match="category" use="@name"/>
+<xsl:template match="/">
+<xsl:for-each select="site">
+	<html>
+	<head>
+	<title>beust.com</title>
+	<style>@import url("web/site.css");</style>
+	</head>
+	<body>
+	<h1 class="title">beust.com</h1>
+	<p class="bodyText"><xsl:value-of select="description"/></p>
+	<table width="100%" border="0" cellspacing="1" cellpadding="2">
+	<xsl:for-each select="category-def">
+		<xsl:sort select="@label" order="ascending" case-order="upper-first"/>
+		<xsl:sort select="@name" order="ascending" case-order="upper-first"/>
+	<xsl:if test="count(key('cat',@name)) != 0">
+			<tr class="header">
+				<td class="sub-header" width="30%">
+					<xsl:value-of select="@name"/>
+				</td>
+				<td class="sub-header" width="70%">
+					<xsl:value-of select="@label"/>
+				</td>
+			</tr>
+			<xsl:for-each select="key('cat',@name)">
+			<xsl:sort select="ancestor::feature//@version" order="ascending"/>
+			<xsl:sort select="ancestor::feature//@id" order="ascending" case-order="upper-first"/>
+			<tr>
+				<xsl:choose>
+				<xsl:when test="(position() mod 2 = 1)">
+					<xsl:attribute name="class">dark-row</xsl:attribute>
+				</xsl:when>
+				<xsl:otherwise>
+					<xsl:attribute name="class">light-row</xsl:attribute>
+				</xsl:otherwise>
+				</xsl:choose>
+				<td class="log-text" id="indent">
+						<xsl:choose>
+						<xsl:when test="ancestor::feature//@label">
+							<a href="{ancestor::feature//@url}"><xsl:value-of select="ancestor::feature//@label"/></a>
+							<br/>
+							<div id="indent">
+							(<xsl:value-of select="ancestor::feature//@id"/> - <xsl:value-of select="ancestor::feature//@version"/>)
+							</div>
+						</xsl:when>
+						<xsl:otherwise>
+						<a href="{ancestor::feature//@url}"><xsl:value-of select="ancestor::feature//@id"/> - <xsl:value-of select="ancestor::feature//@version"/></a>
+						</xsl:otherwise>
+						</xsl:choose>
+						<br />
+				</td>
+				<td>
+					<table>
+						<xsl:if test="ancestor::feature//@os">
+							<tr><td class="log-text" id="indent">Operating Systems:</td>
+							<td class="log-text" id="indent"><xsl:value-of select="ancestor::feature//@os"/></td>
+							</tr>
+						</xsl:if>
+						<xsl:if test="ancestor::feature//@ws">
+							<tr><td class="log-text" id="indent">Windows Systems:</td>
+							<td class="log-text" id="indent"><xsl:value-of select="ancestor::feature//@ws"/></td>
+							</tr>
+						</xsl:if>
+						<xsl:if test="ancestor::feature//@nl">
+							<tr><td class="log-text" id="indent">Languages:</td>
+							<td class="log-text" id="indent"><xsl:value-of select="ancestor::feature//@nl"/></td>
+							</tr>
+						</xsl:if>
+						<xsl:if test="ancestor::feature//@arch">
+							<tr><td class="log-text" id="indent">Architecture:</td>
+							<td class="log-text" id="indent"><xsl:value-of select="ancestor::feature//@arch"/></td>
+							</tr>
+						</xsl:if>
+					</table>
+				</td>
+			</tr>
+			</xsl:for-each>
+			<tr><td class="spacer"><br/></td><td class="spacer"><br/></td></tr>
+		</xsl:if>
+	</xsl:for-each>
+	<xsl:if test="count(feature)  &gt; count(feature/category)">
+	<tr class="header">
+		<td class="sub-header" colspan="2">
+		Uncategorized
+		</td>
+	</tr>
+	</xsl:if>
+	<xsl:choose>
+	<xsl:when test="function-available('msxsl:node-set')">
+	   <xsl:variable name="rtf-nodes">
+		<xsl:for-each select="feature[not(category)]">
+			<xsl:sort select="@id" order="ascending" case-order="upper-first"/>
+			<xsl:sort select="@version" order="ascending" />
+			<xsl:value-of select="."/>
+			<xsl:copy-of select="." />
+		</xsl:for-each>
+	   </xsl:variable>
+	   <xsl:variable name="myNodeSet" select="msxsl:node-set($rtf-nodes)/*"/>
+	<xsl:for-each select="$myNodeSet">
+	<tr>
+		<xsl:choose>
+		<xsl:when test="position() mod 2 = 1">
+		<xsl:attribute name="class">dark-row</xsl:attribute>
+		</xsl:when>
+		<xsl:otherwise>
+		<xsl:attribute name="class">light-row</xsl:attribute>
+		</xsl:otherwise>
+		</xsl:choose>
+		<td class="log-text" id="indent">
+			<xsl:choose>
+			<xsl:when test="@label">
+				<a href="{@url}"><xsl:value-of select="@label"/></a>
+				<br />
+				<div id="indent">
+				(<xsl:value-of select="@id"/> - <xsl:value-of select="@version"/>)
+				</div>
+			</xsl:when>
+			<xsl:otherwise>
+				<a href="{@url}"><xsl:value-of select="@id"/> - <xsl:value-of select="@version"/></a>
+			</xsl:otherwise>
+			</xsl:choose>
+			<br /><br />
+		</td>
+		<td>
+			<table>
+				<xsl:if test="@os">
+					<tr><td class="log-text" id="indent">Operating Systems:</td>
+					<td class="log-text" id="indent"><xsl:value-of select="@os"/></td>
+					</tr>
+				</xsl:if>
+				<xsl:if test="@ws">
+					<tr><td class="log-text" id="indent">Windows Systems:</td>
+					<td class="log-text" id="indent"><xsl:value-of select="@ws"/></td>
+					</tr>
+				</xsl:if>
+				<xsl:if test="@nl">
+					<tr><td class="log-text" id="indent">Languages:</td>
+					<td class="log-text" id="indent"><xsl:value-of select="@nl"/></td>
+					</tr>
+				</xsl:if>
+				<xsl:if test="@arch">
+					<tr><td class="log-text" id="indent">Architecture:</td>
+					<td class="log-text" id="indent"><xsl:value-of select="@arch"/></td>
+					</tr>
+				</xsl:if>
+			</table>
+		</td>
+	</tr>
+	</xsl:for-each>
+	</xsl:when>
+	<xsl:otherwise>
+	<xsl:for-each select="feature[not(category)]">
+	<xsl:sort select="@id" order="ascending" case-order="upper-first"/>
+	<xsl:sort select="@version" order="ascending" />
+	<tr>
+		<xsl:choose>
+		<xsl:when test="count(preceding-sibling::feature[not(category)]) mod 2 = 1">
+		<xsl:attribute name="class">dark-row</xsl:attribute>
+		</xsl:when>
+		<xsl:otherwise>
+		<xsl:attribute name="class">light-row</xsl:attribute>
+		</xsl:otherwise>
+		</xsl:choose>
+		<td class="log-text" id="indent">
+			<xsl:choose>
+			<xsl:when test="@label">
+				<a href="{@url}"><xsl:value-of select="@label"/></a>
+				<br />
+				<div id="indent">
+				(<xsl:value-of select="@id"/> - <xsl:value-of select="@version"/>)
+				</div>
+			</xsl:when>
+			<xsl:otherwise>
+				<a href="{@url}"><xsl:value-of select="@id"/> - <xsl:value-of select="@version"/></a>
+			</xsl:otherwise>
+			</xsl:choose>
+			<br /><br />
+		</td>
+		<td>
+			<table>
+				<xsl:if test="@os">
+					<tr><td class="log-text" id="indent">Operating Systems:</td>
+					<td class="log-text" id="indent"><xsl:value-of select="@os"/></td>
+					</tr>
+				</xsl:if>
+				<xsl:if test="@ws">
+					<tr><td class="log-text" id="indent">Windows Systems:</td>
+					<td class="log-text" id="indent"><xsl:value-of select="@ws"/></td>
+					</tr>
+				</xsl:if>
+				<xsl:if test="@nl">
+					<tr><td class="log-text" id="indent">Languages:</td>
+					<td class="log-text" id="indent"><xsl:value-of select="@nl"/></td>
+					</tr>
+				</xsl:if>
+				<xsl:if test="@arch">
+					<tr><td class="log-text" id="indent">Architecture:</td>
+					<td class="log-text" id="indent"><xsl:value-of select="@arch"/></td>
+					</tr>
+				</xsl:if>
+			</table>
+		</td>
+	</tr>
+	</xsl:for-each>
+	</xsl:otherwise>
+	</xsl:choose>
+	</table>
+	</body>
+	</html>
+</xsl:for-each>
+</xsl:template>
+</xsl:stylesheet>
diff --git a/eclipse-projects/org.testng.eclipse/.project b/eclipse-projects/org.testng.eclipse/.project
new file mode 100644
index 0000000..b2e6900
--- /dev/null
+++ b/eclipse-projects/org.testng.eclipse/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.testng.feature</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.pde.FeatureBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.FeatureNature</nature>
+	</natures>
+</projectDescription>
diff --git a/eclipse-projects/org.testng.eclipse/build.properties b/eclipse-projects/org.testng.eclipse/build.properties
new file mode 100644
index 0000000..64f93a9
--- /dev/null
+++ b/eclipse-projects/org.testng.eclipse/build.properties
@@ -0,0 +1 @@
+bin.includes = feature.xml
diff --git a/eclipse-projects/org.testng.eclipse/feature.xml b/eclipse-projects/org.testng.eclipse/feature.xml
new file mode 100644
index 0000000..7c0366d
--- /dev/null
+++ b/eclipse-projects/org.testng.eclipse/feature.xml
@@ -0,0 +1,230 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<feature
+      id="org.testng.feature"
+      label="TestNG"
+      version="5.8.0.2"
+      provider-name="Cedric Beust">
+
+   <description url="http://testng.org">
+      This is the Eclise plug-in for TestNG (http://testng.org).
+   </description>
+
+   <copyright url="http://www.example.com/copyright">
+      [Enter Copyright Description here.]
+   </copyright>
+
+   <license>
+      Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      &quot;License&quot; shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      &quot;Licensor&quot; shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      &quot;Legal Entity&quot; shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      &quot;control&quot; means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      &quot;You&quot; (or &quot;Your&quot;) shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      &quot;Source&quot; form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      &quot;Object&quot; form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      &quot;Work&quot; shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      &quot;Derivative Works&quot; shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      &quot;Contribution&quot; shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, &quot;submitted&quot;
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as &quot;Not a Contribution.&quot;
+
+      &quot;Contributor&quot; shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a &quot;NOTICE&quot; text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an &quot;AS IS&quot; BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets &quot;[]&quot;
+      replaced with your own identifying information. (Don&apos;t include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same &quot;printed page&quot; as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+   </license>
+
+   <url>
+      <update url="http://beust.com/eclipse"/>
+   </url>
+
+   <plugin
+         id="org.testng.eclipse"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"/>
+
+</feature>
diff --git a/gradle/buildWithTravis.sh b/gradle/buildWithTravis.sh
new file mode 100644
index 0000000..9f0ddf1
--- /dev/null
+++ b/gradle/buildWithTravis.sh
@@ -0,0 +1 @@
+../gradlew check
diff --git a/gradle/publishing-jcenter.gradle b/gradle/publishing-jcenter.gradle
new file mode 100644
index 0000000..d79a417
--- /dev/null
+++ b/gradle/publishing-jcenter.gradle
@@ -0,0 +1,70 @@
+//
+// Artifactory
+// ./gradlew artifactoryPublish (upload snapshot to jfrog)
+// ./gradlew bintrayUpload (upload release to JCenter)
+//
+
+apply plugin: 'com.jfrog.bintray'
+apply plugin: 'com.jfrog.artifactory'
+
+Properties properties = new Properties()
+try {
+    properties.load(project.rootProject.file('local.properties').newDataInputStream())
+} catch (FileNotFoundException ignore) {}
+
+group = 'org.testng'
+
+bintray {
+    user = properties.getProperty("bintray.user")
+    key = properties.getProperty("bintray.apikey")
+    publications = ['mavenCustom']
+    // Without this, javadocs don't get uploaded
+    configurations = ['archives']
+    pkg {
+        repo = 'maven'
+        name = 'testng'
+        desc = 'Testing framework for Java'
+        licenses = ['Apache-2.0']
+        labels = ['testng']
+
+        version {
+            name = project.version //Bintray logical version name
+            gpg {
+                // Without this, .asc files don't get generated
+                sign = true
+            }
+
+        }
+    }
+}
+
+artifactory {
+    def a_user = hasProperty('artifactory_user') ? artifactory_user : System.getenv('artifactory_user')
+    def a_password = hasProperty('artifactory_password') ? artifactory_password : System.getenv('artifactory_password')
+    def a_contextUrl = hasProperty('artifactory_contextUrl') ? artifactory_password : System.getenv('artifactory_contextUrl')
+
+    contextUrl = "${a_contextUrl}"
+    //The base Artifactory URL if not overridden by the publisher/resolver
+    publish {
+        repository {
+            repoKey = 'oss-snapshot-local'
+            username = "${a_user}"
+            password = "${a_password}"
+            maven = true
+
+        }
+        defaults {
+            publications('mavenCustom')
+        }
+
+    }
+    resolve {
+        repository {
+            repoKey = 'libs-snapshot'
+            username = "${a_user}"
+            password = "${a_password}"
+            maven = true
+
+        }
+    }
+}
diff --git a/gradle/publishing-maven.gradle b/gradle/publishing-maven.gradle
new file mode 100644
index 0000000..1473ca0
--- /dev/null
+++ b/gradle/publishing-maven.gradle
@@ -0,0 +1,87 @@
+//
+// Publish to Maven Central
+//
+
+apply plugin: 'maven-publish'
+apply plugin: 'maven'
+
+apply plugin: 'io.codearte.nexus-staging'
+
+nexusStaging {
+    packageGroup 'org.testng'
+    username System.getenv('SONATYPE_USER')
+    password System.getenv('SONATYPE_PASSWORD')
+}
+
+javadoc {
+    failOnError false
+}
+
+signing {
+    required { gradle.taskGraph.hasTask("uploadArchives") }
+    sign configurations.archives
+}
+
+publishing {
+    publications {
+        mavenCustom(MavenPublication) {
+            from components.java
+            artifact sourcesJar
+
+            groupId 'org.testng'
+            artifactId 'testng'
+            version project.version
+        }
+    }
+}
+
+// ./gradlew uploadArchives (upload snapshot to Maven Central's snapshot repo)
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
+
+            repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2") {
+                authentication(userName: System.getenv('SONATYPE_USER'), password: System.getenv('SONATYPE_PASSWORD'))
+            }
+            snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots") {
+                authentication(userName: System.getenv('SONATYPE_USER'), password: System.getenv('SONATYPE_PASSWORD'))
+            }
+            pom {
+                version = project.version
+                artifactId = 'testng'
+                groupId = 'org.testng'
+                project {
+                    name project.name
+                    description 'Testing framework for Java'
+                    url 'http://github.com/cbeust/testng'
+                    scm {
+                        connection 'scm:git:https://github.com/cbeust/testng.git'
+                        developerConnection 'scm:git:git@github.com:cbeust/testng.git'
+                        url 'https://github.com/cbeust/testng.git'
+                    }
+                    licenses {
+                        license {
+                            name 'Apache  Version 2.0, January 2004'
+                            distribution 'repo'
+                        }
+                    }
+                    developers {
+                        developer {
+                            id = 'cbeust'
+                            name = 'Cedric Beust'
+                            email = 'cedric@beust.com'
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+uploadArchives.doLast {
+    if (! version.contains("SNAPSHOT")) {
+        println("Now go to https://oss.sonatype.org/index.html#stagingRepositories to close" +
+                " and publish the distribution")
+    }
+}
\ No newline at end of file
diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle
new file mode 100644
index 0000000..fd74e50
--- /dev/null
+++ b/gradle/publishing.gradle
@@ -0,0 +1,57 @@
+import java.text.SimpleDateFormat
+
+apply plugin: 'signing'
+
+Date buildTimeAndDate = new Date()
+ext {
+    buildTime = new SimpleDateFormat('yyyy-MM-dd').format(buildTimeAndDate)
+    buildDate = new SimpleDateFormat('HH:mm:ss.SSSZ').format(buildTimeAndDate)
+}
+
+jar {
+    manifest {
+        attributes(
+                'Built-By': System.properties['user.name'],
+                'Created-By': System.properties['java.version'] + " (" + System.properties['java.vendor'] + " " + System.getProperty("java.vm.version") + ")",
+                'Build-Date': project.buildTime,
+                'Build-Time': project.buildDate,
+                'Specification-Title': project.name,
+                'Specification-Version': project.version,
+        )
+    }
+}
+
+javadoc {
+    failOnError false
+}
+
+task javadocJar(type: Jar, dependsOn: javadoc) {
+    classifier = 'javadoc'
+    from 'build/docs/javadoc'
+}
+
+task sourcesJar(type: Jar) {
+    from sourceSets.main.allSource
+    classifier = 'sources'
+}
+
+artifacts {
+    archives jar
+    archives javadocJar
+    archives sourcesJar
+}
+
+buildscript {
+    repositories {
+        jcenter()
+
+    }
+    dependencies {
+        //Check for the latest version here: http://plugins.gradle.org/plugin/com.jfrog.artifactory
+        classpath "org.jfrog.buildinfo:build-info-extractor-gradle:3.0.3"
+    }
+}
+
+apply from: 'gradle/publishing-maven.gradle'
+apply from: 'gradle/publishing-jcenter.gradle'
+
diff --git a/gradle/uploadSnapshot.sh b/gradle/uploadSnapshot.sh
new file mode 100755
index 0000000..8e3665d
--- /dev/null
+++ b/gradle/uploadSnapshot.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+echo "TRAVIS_HDK_HOME" $TRAVIS_JDK_HOME
+
+if [ $TRAVIS_JDK_HOME == 'oraclejdk7' ]
+then
+  echo "Uploading snapshot"
+  ./gradlew uploadArchives
+else
+  echo "Current JDK is ${TRAVIS_JDK_HOME}, not uploading snapshot"
+fi
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..085a1cd
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..430d56c
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sun May 10 15:22:08 PDT 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-bin.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/kobalt/src/Build.kt b/kobalt/src/Build.kt
new file mode 100644
index 0000000..c72cb22
--- /dev/null
+++ b/kobalt/src/Build.kt
@@ -0,0 +1,64 @@
+
+import com.beust.kobalt.TaskResult
+import com.beust.kobalt.api.Project
+import com.beust.kobalt.api.annotation.Task
+import com.beust.kobalt.plugin.java.javaProject
+import com.beust.kobalt.plugin.packaging.assemble
+import com.beust.kobalt.plugins
+import com.beust.kobalt.test
+import java.io.File
+
+//import com.beust.kobalt.plugin.linecount.*
+
+val VERSION = "6.9.10-SNAPSHOT"
+
+//val plugins = plugins("com.beust.kobalt:kobalt-line-count:0.17")
+
+val p = javaProject {
+
+//    line
+    name = "testng"
+    group = "org.testng"
+    artifactId = name
+    version = VERSION
+
+    sourceDirectories {
+        path("src/generated/java")
+    }
+
+    dependencies {
+        compile("com.beust:jcommander:1.48",
+                "com.google.inject:guice:4.0",
+                "junit:junit:4.10",
+                "org.apache.ant:ant:1.7.0",
+                "org.beanshell:bsh:2.0b4",
+                "org.yaml:snakeyaml:1.15")
+    }
+
+    dependenciesTest {
+        compile("org.assertj:assertj-core:2.0.0",
+                "org.testng:testng:6.9.9")
+    }
+
+    test {
+        jvmArgs("-Dtest.resources.dir=src/test/resources")
+    }
+
+    assemble {
+        jar {
+        }
+    }
+}
+
+@Task(name = "createVersion", runBefore = arrayOf("compile"), runAfter = arrayOf("clean"), description = "")
+fun taskCreateVersion(project: Project) : TaskResult {
+    val path = "org/testng/internal"
+    with(arrayListOf<String>()) {
+        File("src/main/resources/$path/VersionTemplateJava").forEachLine {
+            add(it.replace("@version@", VERSION))
+        }
+        File("src/generated/java/$path/Version.java").writeText(joinToString("\n"))
+    }
+    return TaskResult()
+}
+
diff --git a/kobalt/wrapper/kobalt-wrapper.jar b/kobalt/wrapper/kobalt-wrapper.jar
new file mode 100644
index 0000000..f03dc81
--- /dev/null
+++ b/kobalt/wrapper/kobalt-wrapper.jar
Binary files differ
diff --git a/kobalt/wrapper/kobalt-wrapper.properties b/kobalt/wrapper/kobalt-wrapper.properties
new file mode 100644
index 0000000..a811bb3
--- /dev/null
+++ b/kobalt/wrapper/kobalt-wrapper.properties
@@ -0,0 +1 @@
+kobalt.version=0.330
\ No newline at end of file
diff --git a/kobaltw b/kobaltw
new file mode 100755
index 0000000..1fd228d
--- /dev/null
+++ b/kobaltw
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+java -jar $(dirname $0)/kobalt/wrapper/kobalt-wrapper.jar $*
diff --git a/lib-supplied/guice-2.0.jar b/lib-supplied/guice-2.0.jar
new file mode 100644
index 0000000..f941a6c
--- /dev/null
+++ b/lib-supplied/guice-2.0.jar
Binary files differ
diff --git a/lib-supplied/jarjar-issue-21.jar b/lib-supplied/jarjar-issue-21.jar
new file mode 100644
index 0000000..3e9622b
--- /dev/null
+++ b/lib-supplied/jarjar-issue-21.jar
Binary files differ
diff --git a/maven/build-with-maven b/maven/build-with-maven
new file mode 100644
index 0000000..9db93e8
--- /dev/null
+++ b/maven/build-with-maven
@@ -0,0 +1,13 @@
+mvn clean install -Dgpg.skip=true
+#or if you want to sign the jar, uncomment this:
+#mvn clean install
+
+echo
+echo "To deploy to the snapshot repository: mvn deploy"
+echo "To deploy to the release directory: mvn release:clean release:prepare release:perform"
+echo "Nexus UI:  https://oss.sonatype.org/index.html"
+echo "Wiki: https://docs.sonatype.org/display/Repository/Sonatype+OSS+Maven+Repository+Usage+Guide"
+
+# deploy without tagging: mvn deploy -DperformRelease
+
+
diff --git a/maven/bundle-pom.xml b/maven/bundle-pom.xml
new file mode 100644
index 0000000..c969d80
--- /dev/null
+++ b/maven/bundle-pom.xml
@@ -0,0 +1,59 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <!--
+    This POM cannot be used to build TestNG; it should only be used as part of a Maven
+    repository upload bundle.
+    
+    See the guide to creating a bundle here:
+    http://maven.apache.org/guides/mini/guide-central-repository-upload.html
+    -->
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.testng</groupId>
+    <artifactId>testng</artifactId>
+    <name>TestNG</name>
+    <version>5.12.1</version>
+    <description>TestNG is a testing framework inspired from JUnit and NUnit but introducing some new functionalities that make it more powerful and easier to use.</description>
+    <url>http://testng.org</url>
+    
+    <licenses>
+        <license>
+            <name>Apache License, Version 2.0</name>
+            <url>http://apache.org/licenses/LICENSE-2.0</url>
+        </license>
+    </licenses>
+
+    <scm>
+        <connection>scm:svn:http://testng.googlecode.com/svn/trunk/</connection>
+        <developerConnection>scm:svn:http://testng.googlecode.com/svn/trunk/</developerConnection>
+        <url>http://testng.googlecode.com/svn/trunk</url>
+    </scm>
+    
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.ant</groupId>
+            <artifactId>ant</artifactId>
+            <version>1.7.0</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.10</version>
+        </dependency>
+        <dependency>
+            <groupId>org.beanshell</groupId>
+            <artifactId>bsh</artifactId>
+            <version>2.0b4</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+          <groupId>com.google.inject</groupId>
+           <artifactId>guice</artifactId>
+           <version>2.0</version>
+           <scope>provided</scope>
+        </dependency>
+
+    </dependencies>    
+    
+</project>
diff --git a/pictures/classes.png b/pictures/classes.png
new file mode 100755
index 0000000..33a79d2
--- /dev/null
+++ b/pictures/classes.png
Binary files differ
diff --git a/pictures/convert1.png b/pictures/convert1.png
new file mode 100644
index 0000000..7f799be
--- /dev/null
+++ b/pictures/convert1.png
Binary files differ
diff --git a/pictures/convert2.png b/pictures/convert2.png
new file mode 100644
index 0000000..9e34650
--- /dev/null
+++ b/pictures/convert2.png
Binary files differ
diff --git a/pictures/failure.png b/pictures/failure.png
new file mode 100755
index 0000000..bd7a1db
--- /dev/null
+++ b/pictures/failure.png
Binary files differ
diff --git a/pictures/groups.png b/pictures/groups.png
new file mode 100755
index 0000000..89feaf3
--- /dev/null
+++ b/pictures/groups.png
Binary files differ
diff --git a/pictures/idea-output.png b/pictures/idea-output.png
new file mode 100755
index 0000000..ee2004d
--- /dev/null
+++ b/pictures/idea-output.png
Binary files differ
diff --git a/pictures/idea-rundialog.png b/pictures/idea-rundialog.png
new file mode 100755
index 0000000..518d104
--- /dev/null
+++ b/pictures/idea-rundialog.png
Binary files differ
diff --git a/pictures/launch.png b/pictures/launch.png
new file mode 100755
index 0000000..556b3ee
--- /dev/null
+++ b/pictures/launch.png
Binary files differ
diff --git a/pictures/new-1.png b/pictures/new-1.png
new file mode 100644
index 0000000..db36476
--- /dev/null
+++ b/pictures/new-1.png
Binary files differ
diff --git a/pictures/new-2.png b/pictures/new-2.png
new file mode 100644
index 0000000..83921f7
--- /dev/null
+++ b/pictures/new-2.png
Binary files differ
diff --git a/pictures/outline.png b/pictures/outline.png
new file mode 100755
index 0000000..f57b703
--- /dev/null
+++ b/pictures/outline.png
Binary files differ
diff --git a/pictures/preferences.png b/pictures/preferences.png
new file mode 100644
index 0000000..69bd987
--- /dev/null
+++ b/pictures/preferences.png
Binary files differ
diff --git a/pictures/project_properties.png b/pictures/project_properties.png
new file mode 100644
index 0000000..26cc974
--- /dev/null
+++ b/pictures/project_properties.png
Binary files differ
diff --git a/pictures/refactoring1.png b/pictures/refactoring1.png
new file mode 100644
index 0000000..40d4a97
--- /dev/null
+++ b/pictures/refactoring1.png
Binary files differ
diff --git a/pictures/refactoring2.png b/pictures/refactoring2.png
new file mode 100644
index 0000000..7f1da17
--- /dev/null
+++ b/pictures/refactoring2.png
Binary files differ
diff --git a/pictures/search.png b/pictures/search.png
new file mode 100644
index 0000000..98c65ac
--- /dev/null
+++ b/pictures/search.png
Binary files differ
diff --git a/pictures/success.png b/pictures/success.png
new file mode 100755
index 0000000..40cad30
--- /dev/null
+++ b/pictures/success.png
Binary files differ
diff --git a/pictures/suites.png b/pictures/suites.png
new file mode 100644
index 0000000..d65d986
--- /dev/null
+++ b/pictures/suites.png
Binary files differ
diff --git a/pictures/summary1.png b/pictures/summary1.png
new file mode 100644
index 0000000..c76b7fd
--- /dev/null
+++ b/pictures/summary1.png
Binary files differ
diff --git a/pictures/summary2.png b/pictures/summary2.png
new file mode 100644
index 0000000..5fa0d12
--- /dev/null
+++ b/pictures/summary2.png
Binary files differ
diff --git a/pictures/template.png b/pictures/template.png
new file mode 100644
index 0000000..541c9e6
--- /dev/null
+++ b/pictures/template.png
Binary files differ
diff --git a/pictures/testNG-exec.png b/pictures/testNG-exec.png
new file mode 100644
index 0000000..4164796
--- /dev/null
+++ b/pictures/testNG-exec.png
Binary files differ
diff --git a/pictures/testNG-run.png b/pictures/testNG-run.png
new file mode 100644
index 0000000..4f406a9
--- /dev/null
+++ b/pictures/testNG-run.png
Binary files differ
diff --git a/pictures/view.png b/pictures/view.png
new file mode 100755
index 0000000..3551c43
--- /dev/null
+++ b/pictures/view.png
Binary files differ
diff --git a/pictures/wp-indexit.php b/pictures/wp-indexit.php
new file mode 100644
index 0000000..b28b04f
--- /dev/null
+++ b/pictures/wp-indexit.php
@@ -0,0 +1,3 @@
+
+
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..82d743e
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,419 @@
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+  <!-- Refer to the file ./build-with-maven for instruction on how to use this pom.xml -->
+
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.testng</groupId>
+  <artifactId>testng</artifactId>
+  <packaging>jar</packaging>
+  <name>TestNG</name>
+  <version>6.9.10-SNAPSHOT</version>
+  <description>TestNG is a testing framework.</description>
+  <url>http://testng.org</url>
+    
+  <licenses>
+    <license>
+      <name>Apache License, Version 2.0</name>
+      <url>http://apache.org/licenses/LICENSE-2.0</url>
+      <distribution>repo</distribution>
+    </license>
+  </licenses>
+
+  <scm>
+    <connection>scm:git:git@github.com:cbeust/testng.git</connection>
+    <developerConnection>scm:git:git@github.com:cbeust/testng.git</developerConnection>
+    <url>git@github.com:cbeust/testng.git</url>
+  </scm>
+    
+  <developers>
+    <developer>
+      <name>Cedric Beust</name>
+    </developer>
+  </developers>
+
+  <parent>
+    <groupId>org.sonatype.oss</groupId>
+    <artifactId>oss-parent</artifactId>
+    <version>3</version>
+  </parent>
+
+  <distributionManagement>
+    <repository>
+      <id>bintray</id>
+      <url>https://api.bintray.com/maven/cbeust/maven/testng</url>
+    </repository>
+  </distributionManagement>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.ant</groupId>
+      <artifactId>ant</artifactId>
+      <version>1.7.0</version>
+      <optional>true</optional>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.10</version>
+      <optional>true</optional>
+    </dependency>
+
+    <dependency>
+      <groupId>org.beanshell</groupId>
+      <artifactId>bsh</artifactId>
+      <version>2.0b4</version>
+<!--
+      <scope>provided</scope>
+-->
+    </dependency>
+
+    <dependency>
+      <groupId>com.google.inject</groupId>
+      <artifactId>guice</artifactId>
+      <version>4.0</version>
+      <classifier>no_aop</classifier>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>com.beust</groupId>
+      <artifactId>jcommander</artifactId>
+      <version>1.48</version>
+    </dependency>
+
+	  <dependency>
+	    <groupId>org.yaml</groupId>
+	    <artifactId>snakeyaml</artifactId>
+	    <version>1.15</version>
+      <optional>true</optional>
+	  </dependency>
+
+    <dependency>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
+      <version>2.0.0</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>     
+  
+  <properties>
+  	<version.build.directory>${project.build.directory}/generated-sources/version</version.build.directory>
+  </properties>
+  
+  <build>
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+        <includes>
+          <include>**/VersionTemplateJava</include>
+        </includes>
+        <filtering>true</filtering>
+        <targetPath>${version.build.directory}</targetPath>
+      </resource>
+      <resource>
+        <directory>src/main/resources</directory>
+      </resource>
+    </resources>
+    <plugins>
+
+      <!-- Rename Version.java -->
+      <plugin>
+        <groupId>com.coderplus.maven.plugins</groupId>
+        <artifactId>copy-rename-maven-plugin</artifactId>
+        <version>1.0.1</version>
+        <executions>
+          <execution>
+		    <id>rename-file</id>
+			<phase>process-resources</phase>
+			<goals>
+			  <goal>rename</goal>
+			</goals>
+			<configuration>
+			  <sourceFile>${version.build.directory}/org/testng/internal/VersionTemplateJava</sourceFile>
+			  <destinationFile>${version.build.directory}/org/testng/internal/Version.java</destinationFile>
+			</configuration>
+		  </execution>
+        </executions>
+      </plugin>
+      
+      <!-- Release for bintray -->
+      <plugin>
+        <artifactId>maven-release-plugin</artifactId>
+        <configuration>
+          <useReleaseProfile>false</useReleaseProfile>
+          <releaseProfiles>release</releaseProfiles>
+          <autoVersionSubmodules>true</autoVersionSubmodules>
+        </configuration>
+      </plugin>
+
+      <!-- Generating Javadoc -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>2.10.3</version>
+        <configuration>
+          <failOnError>false</failOnError>
+          <excludePackageNames>*internal</excludePackageNames>
+         </configuration>
+        <executions>
+          <execution>
+            <id>attach-javadocs</id>
+              <goals>
+                <goal>jar</goal>
+              </goals>
+            </execution>
+        </executions>
+      </plugin>
+
+      <!-- Bundle sources -->
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-source-plugin</artifactId>
+        <version>2.1.1</version>
+        <executions>
+          <execution>
+            <id>attach-sources</id>
+              <goals>
+                <goal>jar</goal>
+              </goals>
+            </execution>
+        </executions>
+      </plugin>
+
+      <!-- Compilation -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.1</version>
+        <configuration>
+          <source>1.7</source>
+          <target>1.7</target>
+        </configuration>
+      </plugin>
+
+      <!-- Resource handling -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-resources-plugin</artifactId>
+        <version>2.4.1</version>
+        <configuration>
+          <encoding>UTF-8</encoding>
+        </configuration>
+        <executions>
+          <execution>
+            <phase>process-sources</phase>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>build-helper-maven-plugin</artifactId>
+        <version>1.9.1</version>
+        <executions>
+          <execution>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>add-source</goal>
+            </goals>
+            <configuration>
+              <sources>
+                <source>${version.build.directory}</source>
+              </sources>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <!-- OSGi manifest creation -->
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.1.0</version>
+        <executions>
+          <execution>
+            <id>bundle-manifest</id>
+            <phase>process-classes</phase>
+            <goals>
+              <goal>manifest</goal>
+            </goals>
+            <configuration>
+              <instructions>
+                <_versionpolicy>$(@)</_versionpolicy>
+                <Import-Package>
+                  bsh.*;version="[2.0.0,3.0.0)";resolution:=optional,
+                  com.beust.jcommander.*;version="[1.7.0,3.0.0)";resolution:=optional,
+                  com.google.inject.*;version="[1.2,1.3)";resolution:=optional,
+                  junit.framework;version="[3.8.1, 5.0.0)";resolution:=optional,
+                  org.junit.*;resolution:=optional,
+                  org.apache.tools.ant.*;version="[1.7.0, 2.0.0)";resolution:=optional,
+                  org.yaml.*;version="[1.6,2.0)";resolution:=optional,
+                  !com.beust.testng,
+                  !org.testng.*,
+                  !com.sun.*,
+                  *
+                </Import-Package>
+              </instructions>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <!-- Add OSGi manifest in JAR -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <version>2.3.1</version>
+        <configuration>
+          <archive>
+            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+          </archive>
+        </configuration>
+      </plugin>
+
+      <!-- Tests -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>2.19</version>
+        <configuration>
+          <suiteXmlFiles>
+            <suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile>
+          </suiteXmlFiles>
+          <properties>
+            <property>
+              <name>listener</name>
+              <value>test.invokedmethodlistener.MyListener</value>
+            </property>
+          </properties>
+          <systemPropertyVariables>
+            <test.resources.dir>${project.build.testOutputDirectory}</test.resources.dir>
+          </systemPropertyVariables>
+        </configuration>
+      </plugin>
+
+      <!-- Signing with gpg -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-gpg-plugin</artifactId>
+        <version>1.4</version>
+        <executions>
+          <execution>
+            <id>sign-artifacts</id>
+            <phase>verify</phase>
+            <goals>
+              <goal>sign</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+    </plugins>
+
+  </build>
+
+
+  <profiles>
+
+    <!-- bintray profile -->
+
+    <profile>
+      <id>bintray</id>
+      <build>
+        <plugins>
+          <plugin>
+            <artifactId>maven-source-plugin</artifactId>
+            <executions>
+              <execution>
+                <id>attach-sources</id>
+                <goals>
+                  <goal>jar</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-javadoc-plugin</artifactId>
+            <version>2.10.3</version>
+            <configuration>
+              <failOnError>false</failOnError>
+              <excludePackageNames>*internal</excludePackageNames>
+             </configuration>
+            <executions>
+              <execution>
+                <id>attach-javadocs</id>
+                  <goals>
+                    <goal>jar</goal>
+                  </goals>
+                </execution>
+            </executions>
+          </plugin>
+<!-- 
+          <plugin>
+            <artifactId>maven-javadoc-plugin</artifactId>
+            <executions>
+              <execution>
+                <id>attach-javadocs</id>
+                <goals>
+                  <goal>jar</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+ -->
+          </plugins>
+      </build>
+    </profile>
+
+    <profile>
+      <!-- When activated, download dependencies from the snapshot repo -->
+      <id>snapshot</id>
+
+      <repositories>
+        <repository>
+          <id>nexus-snapshot-repository</id>
+          <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
+        </repository>
+      </repositories>
+
+      <dependencies>
+        <dependency>
+            <groupId>org.apache.ant</groupId>
+            <artifactId>ant</artifactId>
+            <version>1.7.0</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.11</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.beanshell</groupId>
+            <artifactId>bsh</artifactId>
+            <version>2.0b4</version>
+<!--
+            <scope>provided</scope>
+-->
+        </dependency>
+        <dependency>
+          <groupId>com.google.inject</groupId>
+           <artifactId>guice</artifactId>
+           <version>2.0</version>
+           <scope>provided</scope>
+        </dependency>
+        <dependency>
+          <groupId>com.beust</groupId>
+          <artifactId>jcommander</artifactId>
+          <version>1.48</version>
+        </dependency>
+      </dependencies>
+      
+    </profile>
+  </profiles>
+
+</project>
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..31ab3e7
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'testng'
diff --git a/sitemap.xml b/sitemap.xml
new file mode 100644
index 0000000..32f1354
--- /dev/null
+++ b/sitemap.xml
@@ -0,0 +1,64 @@
+
+<?xml version="1.0" encoding="UTF-8"?>
+<urlset xmlns="http://www.google.com/schemas/sitemap/0.90">
+  <url>
+    <loc>http://testng.org/doc/index.html</loc>
+    <changefreq>daily</changefreq>
+    <priority>1.0</priority>
+  </url>
+
+  <url>
+    <loc>http://testng.org/doc/download.html</loc>
+    <changefreq>daily</changefreq>
+  </url>
+
+  <url>
+    <loc>http://testng.org/doc/migrating.html</loc>
+    <changefreq>daily</changefreq>
+  </url>
+
+
+  <url>
+    <loc>http://testng.org/javadocs/index.html</loc>
+    <changefreq>daily</changefreq>
+  </url>
+
+
+  <url>
+    <loc>http://testng.org/doc/book.html</loc>
+    <changefreq>daily</changefreq>
+  </url>
+
+
+  <url>
+    <loc>http://testng.org/doc/eclipse.html</loc>
+    <changefreq>daily</changefreq>
+  </url>
+
+
+  <url>
+    <loc>http://testng.org/doc/idea.html</loc>
+    <changefreq>daily</changefreq>
+  </url>
+
+
+  <url>
+    <loc>http://testng.org/doc/maven.html</loc>
+    <changefreq>daily</changefreq>
+  </url>
+
+
+  <url>
+    <loc>http://testng.org/doc/ant.html</loc>
+    <changefreq>daily</changefreq>
+  </url>
+
+
+  <url>
+    <loc>http://testng.org/doc/misc.html</loc>
+    <changefreq>daily</changefreq>
+  </url>
+
+</urlset>
+
+
diff --git a/src/generated/java/org/testng/internal/VersionTemplateJava b/src/generated/java/org/testng/internal/VersionTemplateJava
new file mode 100644
index 0000000..e5185a6
--- /dev/null
+++ b/src/generated/java/org/testng/internal/VersionTemplateJava
@@ -0,0 +1,9 @@
+package org.testng.internal;
+
+public class Version {
+  public static final String VERSION = "6.9.47-SNAPSHOT";
+
+  public static void displayBanner() {
+    System.out.println("...\n... TestNG " + VERSION + " by Cédric Beust (cedric@beust.com)\n...\n");
+  }
+}
diff --git a/src/main/java/com/beust/testng/TestNG.java b/src/main/java/com/beust/testng/TestNG.java
new file mode 100755
index 0000000..2636832
--- /dev/null
+++ b/src/main/java/com/beust/testng/TestNG.java
@@ -0,0 +1,13 @@
+package com.beust.testng;
+
+/**
+ * For backward compatibility.
+ *
+ * Created on Jun 18, 2005
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ * @deprecated Use org.testng.TestNG
+ */
+@Deprecated
+public class TestNG extends org.testng.TestNG {
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/beust/testng/TestNGAntTask.java b/src/main/java/com/beust/testng/TestNGAntTask.java
new file mode 100755
index 0000000..db89ea0
--- /dev/null
+++ b/src/main/java/com/beust/testng/TestNGAntTask.java
@@ -0,0 +1,12 @@
+package com.beust.testng;
+
+/**
+ * For backward compatibility.
+ *
+ * Created on Jun 18, 2005
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ * @deprecated Use org.testng.TestNGAntTask
+ */
+@Deprecated
+public class TestNGAntTask extends org.testng.TestNGAntTask {
+}
diff --git a/src/main/java/main.iml b/src/main/java/main.iml
new file mode 100644
index 0000000..d37e196
--- /dev/null
+++ b/src/main/java/main.iml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$/../../..">
+      <sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/../../generated/java" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/../../test/java" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/../../../target/generated-sources/version" isTestSource="false" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" scope="TEST" name="kobalt (Test)" level="project" />
+    <orderEntry type="library" name="kobalt (Compile)" level="project" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/src/main/java/org/testng/Assert.java b/src/main/java/org/testng/Assert.java
new file mode 100644
index 0000000..fe37b4b
--- /dev/null
+++ b/src/main/java/org/testng/Assert.java
@@ -0,0 +1,998 @@
+package org.testng;
+
+import static org.testng.internal.EclipseInterface.ASSERT_LEFT;
+import static org.testng.internal.EclipseInterface.ASSERT_LEFT2;
+import static org.testng.internal.EclipseInterface.ASSERT_MIDDLE;
+import static org.testng.internal.EclipseInterface.ASSERT_RIGHT;
+
+import org.testng.collections.Lists;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * Assertion tool class. Presents assertion methods with a more natural parameter order.
+ * The order is always <B>actualValue</B>, <B>expectedValue</B> [, message].
+ *
+ * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
+ */
+public class Assert {
+
+  /**
+   * Protect constructor since it is a static only class
+   */
+  protected Assert() {
+    // hide constructor
+  }
+
+  /**
+   * Asserts that a condition is true. If it isn't,
+   * an AssertionError, with the given message, is thrown.
+   * @param condition the condition to evaluate
+   * @param message the assertion error message
+   */
+  static public void assertTrue(boolean condition, String message) {
+    if(!condition) {
+      failNotEquals(condition, Boolean.TRUE, message);
+    }
+  }
+
+  /**
+   * Asserts that a condition is true. If it isn't,
+   * an AssertionError is thrown.
+   * @param condition the condition to evaluate
+   */
+  static public void assertTrue(boolean condition) {
+    assertTrue(condition, null);
+  }
+
+  /**
+   * Asserts that a condition is false. If it isn't,
+   * an AssertionError, with the given message, is thrown.
+   * @param condition the condition to evaluate
+   * @param message the assertion error message
+   */
+  static public void assertFalse(boolean condition, String message) {
+    if(condition) {
+      failNotEquals(condition, Boolean.FALSE, message); // TESTNG-81
+    }
+  }
+
+  /**
+   * Asserts that a condition is false. If it isn't,
+   * an AssertionError is thrown.
+   * @param condition the condition to evaluate
+   */
+  static public void assertFalse(boolean condition) {
+    assertFalse(condition, null);
+  }
+
+  /**
+   * Fails a test with the given message and wrapping the original exception.
+   *
+   * @param message the assertion error message
+   * @param realCause the original exception
+   */
+  static public void fail(String message, Throwable realCause) {
+    AssertionError ae = new AssertionError(message);
+    ae.initCause(realCause);
+
+    throw ae;
+  }
+
+  /**
+   * Fails a test with the given message.
+   * @param message the assertion error message
+   */
+  static public void fail(String message) {
+    throw new AssertionError(message);
+  }
+
+  /**
+   * Fails a test with no message.
+   */
+  static public void fail() {
+    fail(null);
+  }
+
+  /**
+   * Asserts that two objects are equal. If they are not,
+   * an AssertionError, with the given message, is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   * @param message the assertion error message
+   */
+  static public void assertEquals(Object actual, Object expected, String message) {
+    if (expected != null && expected.getClass().isArray()) {
+       assertArrayEquals(actual, expected, message);
+       return;
+    }
+    assertEqualsImpl(actual, expected, message);
+  }
+
+  /**
+   * Differs from {@link #assertEquals(Object, Object, String)} by not taking arrays into
+   * special consideration hence comparing them by reference. Intended to be called directly
+   * to test equality of collections content.
+   */
+  private static void assertEqualsImpl(Object actual, Object expected,
+          String message) {
+      if((expected == null) && (actual == null)) {
+        return;
+      }
+      if(expected == null ^ actual == null) {
+        failNotEquals(actual, expected, message);
+      }
+      if (expected.equals(actual) && actual.equals(expected)) {
+        return;
+      }
+      failNotEquals(actual, expected, message);
+    }
+
+  private static void assertArrayEquals(Object actual, Object expected, String message) {
+    if (expected == actual) {
+      return;
+    }
+    if (null == expected) {
+      fail("expected a null array, but not null found. " + message);
+    }
+    if (null == actual) {
+      fail("expected not null array, but null found. " + message);
+    }
+    //is called only when expected is an array
+    if (actual.getClass().isArray()) {
+      int expectedLength = Array.getLength(expected);
+      if (expectedLength == Array.getLength(actual)) {
+         for (int i = 0 ; i < expectedLength ; i++) {
+            Object _actual = Array.get(actual, i);
+            Object _expected = Array.get(expected, i);
+            try {
+               assertEquals(_actual, _expected);
+            } catch (AssertionError ae) {
+               failNotEquals(actual, expected, message == null ? "" : message
+                        + " (values at index " + i + " are not the same)");
+            }
+         }
+         //array values matched
+         return;
+      } else {
+         failNotEquals(Array.getLength(actual), expectedLength, message == null ? "" : message
+                  + " (Array lengths are not the same)");
+      }
+    }
+    failNotEquals(actual, expected, message);
+  }
+
+  /**
+   * Asserts that two objects are equal. If they are not,
+   * an AssertionError is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   */
+  static public void assertEquals(Object actual, Object expected) {
+    assertEquals(actual, expected, null);
+  }
+
+  /**
+   * Asserts that two Strings are equal. If they are not,
+   * an AssertionError, with the given message, is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   * @param message the assertion error message
+   */
+  static public void assertEquals(String actual, String expected, String message) {
+    assertEquals((Object) actual, (Object) expected, message);
+  }
+
+  /**
+   * Asserts that two Strings are equal. If they are not,
+   * an AssertionError is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   */
+  static public void assertEquals(String actual, String expected) {
+    assertEquals(actual, expected, null);
+  }
+
+  /**
+   * Asserts that two doubles are equal concerning a delta.  If they are not,
+   * an AssertionError, with the given message, is thrown.  If the expected
+   * value is infinity then the delta value is ignored.
+   * @param actual the actual value
+   * @param expected the expected value
+   * @param delta the absolute tolerable difference between the actual and expected values
+   * @param message the assertion error message
+   */
+  static public void assertEquals(double actual, double expected, double delta, String message) {
+    // handle infinity specially since subtracting to infinite values gives NaN and the
+    // the following test fails
+    if(Double.isInfinite(expected)) {
+      if(!(expected == actual)) {
+        failNotEquals(actual, expected, message);
+      }
+    }
+    else if (Double.isNaN(expected)) {
+      if (!Double.isNaN(actual)) {
+        failNotEquals(actual, expected, message);
+      }
+    }
+    else if(!(Math.abs(expected - actual) <= delta)) {
+      failNotEquals(actual, expected, message);
+    }
+  }
+
+  /**
+   * Asserts that two doubles are equal concerning a delta. If they are not,
+   * an AssertionError is thrown. If the expected value is infinity then the
+   * delta value is ignored.
+   * @param actual the actual value
+   * @param expected the expected value
+   * @param delta the absolute tolerable difference between the actual and expected values
+   */
+  static public void assertEquals(double actual, double expected, double delta) {
+    assertEquals(actual, expected, delta, null);
+  }
+
+  /**
+   * Asserts that two floats are equal concerning a delta. If they are not,
+   * an AssertionError, with the given message, is thrown.  If the expected
+   * value is infinity then the delta value is ignored.
+   * @param actual the actual value
+   * @param expected the expected value
+   * @param delta the absolute tolerable difference between the actual and expected values
+   * @param message the assertion error message
+   */
+  static public void assertEquals(float actual, float expected, float delta, String message) {
+    // handle infinity specially since subtracting to infinite values gives NaN and the
+    // the following test fails
+    if(Float.isInfinite(expected)) {
+      if(!(expected == actual)) {
+        failNotEquals(actual, expected, message);
+      }
+    }
+    else if(!(Math.abs(expected - actual) <= delta)) {
+      failNotEquals(actual, expected, message);
+    }
+  }
+
+  /**
+   * Asserts that two floats are equal concerning a delta. If they are not,
+   * an AssertionError is thrown. If the expected
+   * value is infinity then the delta value is ignored.
+   * @param actual the actual value
+   * @param expected the expected value
+   * @param delta the absolute tolerable difference between the actual and expected values
+   */
+  static public void assertEquals(float actual, float expected, float delta) {
+    assertEquals(actual, expected, delta, null);
+  }
+
+  /**
+   * Asserts that two longs are equal. If they are not,
+   * an AssertionError, with the given message, is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   * @param message the assertion error message
+   */
+  static public void assertEquals(long actual, long expected, String message) {
+    assertEquals(Long.valueOf(actual), Long.valueOf(expected), message);
+  }
+
+  /**
+   * Asserts that two longs are equal. If they are not,
+   * an AssertionError is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   */
+  static public void assertEquals(long actual, long expected) {
+    assertEquals(actual, expected, null);
+  }
+
+  /**
+   * Asserts that two booleans are equal. If they are not,
+   * an AssertionError, with the given message, is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   * @param message the assertion error message
+   */
+  static public void assertEquals(boolean actual, boolean expected, String message) {
+    assertEquals( Boolean.valueOf(actual), Boolean.valueOf(expected), message);
+  }
+
+  /**
+   * Asserts that two booleans are equal. If they are not,
+   * an AssertionError is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   */
+  static public void assertEquals(boolean actual, boolean expected) {
+    assertEquals(actual, expected, null);
+  }
+
+  /**
+   * Asserts that two bytes are equal. If they are not,
+   * an AssertionError, with the given message, is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   * @param message the assertion error message
+   */
+  static public void assertEquals(byte actual, byte expected, String message) {
+    assertEquals(Byte.valueOf(actual), Byte.valueOf(expected), message);
+  }
+
+  /**
+   * Asserts that two bytes are equal. If they are not,
+   * an AssertionError is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   */
+  static public void assertEquals(byte actual, byte expected) {
+    assertEquals(actual, expected, null);
+  }
+
+  /**
+   * Asserts that two chars are equal. If they are not,
+   * an AssertionFailedError, with the given message, is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   * @param message the assertion error message
+   */
+  static public void assertEquals(char actual, char expected, String message) {
+    assertEquals(Character.valueOf(actual), Character.valueOf(expected), message);
+  }
+
+  /**
+   * Asserts that two chars are equal. If they are not,
+   * an AssertionError is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   */
+  static public void assertEquals(char actual, char expected) {
+    assertEquals(actual, expected, null);
+  }
+
+  /**
+   * Asserts that two shorts are equal. If they are not,
+   * an AssertionFailedError, with the given message, is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   * @param message the assertion error message
+   */
+  static public void assertEquals(short actual, short expected, String message) {
+    assertEquals(Short.valueOf(actual), Short.valueOf(expected), message);
+  }
+
+  /**
+   * Asserts that two shorts are equal. If they are not,
+   * an AssertionError is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   */
+  static public void assertEquals(short actual, short expected) {
+    assertEquals(actual, expected, null);
+  }
+
+  /**
+   * Asserts that two ints are equal. If they are not,
+   * an AssertionFailedError, with the given message, is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   * @param message the assertion error message
+   */
+  static public void assertEquals(int actual,  int expected, String message) {
+    assertEquals(Integer.valueOf(actual), Integer.valueOf(expected), message);
+  }
+
+  /**
+   * Asserts that two ints are equal. If they are not,
+   * an AssertionError is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   */
+  static public void assertEquals(int actual, int expected) {
+    assertEquals(actual, expected, null);
+  }
+
+  /**
+   * Asserts that an object isn't null. If it is,
+   * an AssertionError is thrown.
+   * @param object the assertion object
+   */
+  static public void assertNotNull(Object object) {
+    assertNotNull(object, null);
+  }
+
+  /**
+   * Asserts that an object isn't null. If it is,
+   * an AssertionFailedError, with the given message, is thrown.
+   * @param object the assertion object
+   * @param message the assertion error message
+   */
+  static public void assertNotNull(Object object, String message) {
+    if (object == null) {
+      String formatted = "";
+      if(message != null) {
+        formatted = message + " ";
+      }
+      fail(formatted + "expected object to not be null");
+    }
+    assertTrue(object != null, message);
+  }
+
+  /**
+   * Asserts that an object is null. If it is not,
+   * an AssertionError, with the given message, is thrown.
+   * @param object the assertion object
+   */
+  static public void assertNull(Object object) {
+    assertNull(object, null);
+  }
+
+  /**
+   * Asserts that an object is null. If it is not,
+   * an AssertionFailedError, with the given message, is thrown.
+   * @param object the assertion object
+   * @param message the assertion error message
+   */
+  static public void assertNull(Object object, String message) {
+    if (object != null) {
+      failNotSame(object, null, message);
+    }
+  }
+
+  /**
+   * Asserts that two objects refer to the same object. If they do not,
+   * an AssertionFailedError, with the given message, is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   * @param message the assertion error message
+   */
+  static public void assertSame(Object actual, Object expected, String message) {
+    if(expected == actual) {
+      return;
+    }
+    failNotSame(actual, expected, message);
+  }
+
+  /**
+   * Asserts that two objects refer to the same object. If they do not,
+   * an AssertionError is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   */
+  static public void assertSame(Object actual, Object expected) {
+    assertSame(actual, expected, null);
+  }
+
+  /**
+   * Asserts that two objects do not refer to the same objects. If they do,
+   * an AssertionError, with the given message, is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   * @param message the assertion error message
+   */
+  static public void assertNotSame(Object actual, Object expected, String message) {
+    if(expected == actual) {
+      failSame(actual, expected, message);
+    }
+  }
+
+  /**
+   * Asserts that two objects do not refer to the same object. If they do,
+   * an AssertionError is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   */
+  static public void assertNotSame(Object actual, Object expected) {
+    assertNotSame(actual, expected, null);
+  }
+
+  static private void failSame(Object actual, Object expected, String message) {
+    String formatted = "";
+    if(message != null) {
+      formatted = message + " ";
+    }
+    fail(formatted + ASSERT_LEFT2 + expected + ASSERT_MIDDLE + actual + ASSERT_RIGHT);
+  }
+
+  static private void failNotSame(Object actual, Object expected, String message) {
+    String formatted = "";
+    if(message != null) {
+      formatted = message + " ";
+    }
+    fail(formatted + ASSERT_LEFT + expected + ASSERT_MIDDLE + actual + ASSERT_RIGHT);
+  }
+
+  static private void failNotEquals(Object actual , Object expected, String message ) {
+    fail(format(actual, expected, message));
+  }
+
+  static String format(Object actual, Object expected, String message) {
+    String formatted = "";
+    if (null != message) {
+      formatted = message + " ";
+    }
+
+    return formatted + ASSERT_LEFT + expected + ASSERT_MIDDLE + actual + ASSERT_RIGHT;
+  }
+
+  /**
+   * Asserts that two collections contain the same elements in the same order. If they do not,
+   * an AssertionError is thrown.
+   *
+   * @param actual the actual value
+   * @param expected the expected value
+   */
+  static public void assertEquals(Collection<?> actual, Collection<?> expected) {
+    assertEquals(actual, expected, null);
+  }
+
+  /**
+   * Asserts that two collections contain the same elements in the same order. If they do not,
+   * an AssertionError, with the given message, is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   * @param message the assertion error message
+   */
+  static public void assertEquals(Collection<?> actual, Collection<?> expected, String message) {
+    if(actual == expected) {
+      return;
+    }
+
+    if (actual == null || expected == null) {
+      if (message != null) {
+        fail(message);
+      } else {
+        fail("Collections not equal: expected: " + expected + " and actual: " + actual);
+      }
+    }
+
+    assertEquals(actual.size(), expected.size(), (message == null ? "" : message + ": ") + "lists don't have the same size");
+
+    Iterator<?> actIt = actual.iterator();
+    Iterator<?> expIt = expected.iterator();
+    int i = -1;
+    while(actIt.hasNext() && expIt.hasNext()) {
+      i++;
+      Object e = expIt.next();
+      Object a = actIt.next();
+      String explanation = "Lists differ at element [" + i + "]: " + e + " != " + a;
+      String errorMessage = message == null ? explanation : message + ": " + explanation;
+
+      assertEqualsImpl(a, e, errorMessage);
+    }
+  }
+  
+  /** Asserts that two iterators return the same elements in the same order. If they do not,
+   * an AssertionError is thrown.
+   * Please note that this assert iterates over the elements and modifies the state of the iterators.
+   * @param actual the actual value
+   * @param expected the expected value
+   */
+  static public void assertEquals(Iterator<?> actual, Iterator<?> expected) {
+    assertEquals(actual, expected, null);
+  }
+  
+  /** Asserts that two iterators return the same elements in the same order. If they do not,
+   * an AssertionError, with the given message, is thrown.
+   * Please note that this assert iterates over the elements and modifies the state of the iterators.
+   * @param actual the actual value
+   * @param expected the expected value
+   * @param message the assertion error message
+   */
+  static public void assertEquals(Iterator<?> actual, Iterator<?> expected, String message) {
+    if(actual == expected) {
+      return;
+    }
+    
+    if(actual == null || expected == null) {
+      if(message != null) {
+        fail(message);
+      } else {
+        fail("Iterators not equal: expected: " + expected + " and actual: " + actual);
+      }
+    }
+
+    int i = -1;
+    while(actual.hasNext() && expected.hasNext()) {
+      
+      i++;
+      Object e = expected.next();
+      Object a = actual.next();
+      String explanation = "Iterators differ at element [" + i + "]: " + e + " != " + a;
+      String errorMessage = message == null ? explanation : message + ": " + explanation;
+      
+      assertEqualsImpl(a, e, errorMessage);
+      
+    }
+    
+    if(actual.hasNext()) { 
+      
+      String explanation = "Actual iterator returned more elements than the expected iterator.";
+      String errorMessage = message == null ? explanation : message + ": " + explanation;
+      fail(errorMessage);
+      
+    } else if(expected.hasNext()) {
+      
+      String explanation = "Expected iterator returned more elements than the actual iterator.";
+      String errorMessage = message == null ? explanation : message + ": " + explanation;
+      fail(errorMessage);
+      
+    }
+    
+  }
+  
+  /** Asserts that two iterables return iterators with the same elements in the same order. If they do not,
+   * an AssertionError is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   */
+  static public void assertEquals(Iterable<?> actual, Iterable<?> expected) {
+    assertEquals(actual, expected, null);
+  }
+  
+  /** Asserts that two iterables return iterators with the same elements in the same order. If they do not,
+   * an AssertionError, with the given message, is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   * @param message the assertion error message
+   */
+  static public void assertEquals(Iterable<?> actual, Iterable<?> expected, String message) {
+    if(actual == expected) {
+      return;
+    }
+    
+    if(actual == null || expected == null) {
+      if(message != null) {
+        fail(message);
+      } else {
+        fail("Iterables not equal: expected: " + expected + " and actual: " + actual);
+      }
+    }
+
+    Iterator<?> actIt = actual.iterator();
+    Iterator<?> expIt = expected.iterator();
+    
+    assertEquals(actIt, expIt, message);
+  }
+  
+  
+    
+
+  /**
+   * Asserts that two arrays contain the same elements in the same order. If they do not,
+   * an AssertionError, with the given message, is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   * @param message the assertion error message
+   */
+  static public void assertEquals(Object[] actual, Object[] expected, String message) {
+    if(actual == expected) {
+      return;
+    }
+
+    if ((actual == null && expected != null) || (actual != null && expected == null)) {
+      if (message != null) {
+        fail(message);
+      } else {
+        fail("Arrays not equal: " + Arrays.toString(expected) + " and " + Arrays.toString(actual));
+      }
+    }
+    assertEquals(Arrays.asList(actual), Arrays.asList(expected), message);
+  }
+
+  /**
+   * Asserts that two arrays contain the same elements in no particular order. If they do not,
+   * an AssertionError, with the given message, is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   * @param message the assertion error message
+   */
+  static public void assertEqualsNoOrder(Object[] actual, Object[] expected, String message) {
+    if(actual == expected) {
+      return;
+    }
+
+    if ((actual == null && expected != null) || (actual != null && expected == null)) {
+      failAssertNoEqual(
+          "Arrays not equal: " + Arrays.toString(expected) + " and " + Arrays.toString(actual),
+          message);
+    }
+
+    if (actual.length != expected.length) {
+      failAssertNoEqual(
+          "Arrays do not have the same size:" + actual.length + " != " + expected.length,
+          message);
+    }
+
+    List<Object> actualCollection = Lists.newArrayList();
+    for (Object a : actual) {
+      actualCollection.add(a);
+    }
+    for (Object o : expected) {
+      actualCollection.remove(o);
+    }
+    if (actualCollection.size() != 0) {
+      failAssertNoEqual(
+          "Arrays not equal: " + Arrays.toString(expected) + " and " + Arrays.toString(actual),
+          message);
+    }
+  }
+
+  private static void failAssertNoEqual(String defaultMessage, String message) {
+    if (message != null) {
+      fail(message);
+    } else {
+      fail(defaultMessage);
+    }
+  }
+
+  /**
+   * Asserts that two arrays contain the same elements in the same order. If they do not,
+   * an AssertionError is thrown.
+   *
+   * @param actual the actual value
+   * @param expected the expected value
+   */
+  static public void assertEquals(Object[] actual, Object[] expected) {
+    assertEquals(actual, expected, null);
+  }
+
+  /**
+   * Asserts that two arrays contain the same elements in no particular order. If they do not,
+   * an AssertionError is thrown.
+   * @param actual the actual value
+   * @param expected the expected value
+   */
+  static public void assertEqualsNoOrder(Object[] actual, Object[] expected) {
+    assertEqualsNoOrder(actual, expected, null);
+  }
+
+  /**
+   * Asserts that two sets are equal.
+   */
+  static public void assertEquals(Set<?> actual, Set<?> expected) {
+    assertEquals(actual, expected, null);
+  }
+  
+	/**
+	 * Assert set equals
+	 */
+  static public void assertEquals(Set<?> actual, Set<?> expected, String message) {
+    if (actual == expected) {
+      return;
+    }
+
+    if (actual == null || expected == null) {
+      // Keep the back compatible
+      if (message == null) {
+        fail("Sets not equal: expected: " + expected + " and actual: " + actual);
+      } else {
+        failNotEquals(actual, expected, message);
+      }
+    }
+
+    if (!actual.equals(expected)) {
+      if (message == null) {
+        fail("Sets differ: expected " + expected + " but got " + actual);
+      } else {
+        failNotEquals(actual, expected, message);
+      }
+    }
+  }
+
+  /**
+   * Asserts that two maps are equal.
+   */
+  static public void assertEquals(Map<?, ?> actual, Map<?, ?> expected) {
+    if (actual == expected) {
+      return;
+    }
+
+    if (actual == null || expected == null) {
+      fail("Maps not equal: expected: " + expected + " and actual: " + actual);
+    }
+
+    if (actual.size() != expected.size()) {
+      fail("Maps do not have the same size:" + actual.size() + " != " + expected.size());
+    }
+
+    Set<?> entrySet = actual.entrySet();
+    for (Object anEntrySet : entrySet) {
+      Map.Entry<?, ?> entry = (Map.Entry<?, ?>) anEntrySet;
+      Object key = entry.getKey();
+      Object value = entry.getValue();
+      Object expectedValue = expected.get(key);
+      assertEqualsImpl(value, expectedValue, "Maps do not match for key:" + key + " actual:" + value
+              + " expected:" + expectedValue);
+    }
+
+  }
+
+  /////
+  // assertNotEquals
+  //
+
+  public static void assertNotEquals(Object actual1, Object actual2, String message) {
+    boolean fail = false;
+    try {
+      Assert.assertEquals(actual1, actual2);
+      fail = true;
+    } catch (AssertionError e) {
+    }
+
+    if (fail) {
+      Assert.fail(message);
+    }
+  }
+
+  public static void assertNotEquals(Object actual1, Object actual2) {
+    assertNotEquals(actual1, actual2, null);
+  }
+
+  static void assertNotEquals(String actual1, String actual2, String message) {
+    assertNotEquals((Object) actual1, (Object) actual2, message);
+  }
+
+  static void assertNotEquals(String actual1, String actual2) {
+    assertNotEquals(actual1, actual2, null);
+  }
+
+  static void assertNotEquals(long actual1, long actual2, String message) {
+    assertNotEquals(Long.valueOf(actual1), Long.valueOf(actual2), message);
+  }
+
+  static void assertNotEquals(long actual1, long actual2) {
+    assertNotEquals(actual1, actual2, null);
+  }
+
+  static void assertNotEquals(boolean actual1, boolean actual2, String message) {
+    assertNotEquals(Boolean.valueOf(actual1), Boolean.valueOf(actual2), message);
+  }
+
+  static void assertNotEquals(boolean actual1, boolean actual2) {
+    assertNotEquals(actual1, actual2, null);
+  }
+
+  static void assertNotEquals(byte actual1, byte actual2, String message) {
+    assertNotEquals(Byte.valueOf(actual1), Byte.valueOf(actual2), message);
+  }
+
+  static void assertNotEquals(byte actual1, byte actual2) {
+    assertNotEquals(actual1, actual2, null);
+  }
+
+  static void assertNotEquals(char actual1, char actual2, String message) {
+    assertNotEquals(Character.valueOf(actual1), Character.valueOf(actual2), message);
+  }
+
+  static void assertNotEquals(char actual1, char actual2) {
+    assertNotEquals(actual1, actual2, null);
+  }
+
+  static void assertNotEquals(short actual1, short actual2, String message) {
+    assertNotEquals(Short.valueOf(actual1), Short.valueOf(actual2), message);
+  }
+
+  static void assertNotEquals(short actual1, short actual2) {
+    assertNotEquals(actual1, actual2, null);
+  }
+
+  static void assertNotEquals(int actual1, int actual2, String message) {
+    assertNotEquals(Integer.valueOf(actual1), Integer.valueOf(actual2), message);
+  }
+
+  static void assertNotEquals(int actual1, int actual2) {
+    assertNotEquals(actual1, actual2, null);
+  }
+
+  static public void assertNotEquals(float actual1, float actual2, float delta, String message) {
+    boolean fail = false;
+    try {
+      Assert.assertEquals(actual1, actual2, delta, message);
+      fail = true;
+    } catch (AssertionError e) {
+
+    }
+
+    if (fail) {
+      Assert.fail(message);
+    }
+  }
+
+  static public void assertNotEquals(float actual1, float actual2, float delta) {
+    assertNotEquals(actual1, actual2, delta, null);
+  }
+
+  static public void assertNotEquals(double actual1, double actual2, double delta, String message) {
+    boolean fail = false;
+    try {
+      Assert.assertEquals(actual1, actual2, delta, message);
+      fail = true;
+    } catch (AssertionError e) {
+
+    }
+
+    if (fail) {
+      Assert.fail(message);
+    }
+  }
+
+  static public void assertNotEquals(double actual1, double actual2, double delta) {
+    assertNotEquals(actual1, actual2, delta, null);
+  }
+
+  /**
+   * This interface facilitates the use of {@link #expectThrows} from Java 8. It allows
+   * method references to both void and non-void methods to be passed directly into
+   * expectThrows without wrapping, even if they declare checked exceptions.
+   * <p/>
+   * This interface is not meant to be implemented directly.
+   */
+  public interface ThrowingRunnable {
+    void run() throws Throwable;
+  }
+
+  /**
+   * Asserts that {@code runnable} throws an exception when invoked. If it does not, an
+   * {@link AssertionError} is thrown.
+   *
+   * @param runnable A function that is expected to throw an exception when invoked
+   * @since 6.9.5
+   */
+  public static void assertThrows(ThrowingRunnable runnable) {
+    assertThrows(Throwable.class, runnable);
+  }
+
+  /**
+   * Asserts that {@code runnable} throws an exception of type {@code throwableClass} when
+   * executed. If it does not throw an exception, an {@link AssertionError} is thrown. If it
+   * throws the wrong type of exception, an {@code AssertionError} is thrown describing the
+   * mismatch; the exception that was actually thrown can be obtained by calling {@link
+   * AssertionError#getCause}.
+   *
+   * @param throwableClass the expected type of the exception
+   * @param runnable       A function that is expected to throw an exception when invoked
+   * @since 6.9.5
+   */
+  @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+  public static <T extends Throwable> void assertThrows(Class<T> throwableClass, ThrowingRunnable runnable) {
+    expectThrows(throwableClass, runnable);
+  }
+
+  /**
+   * Asserts that {@code runnable} throws an exception of type {@code throwableClass} when
+   * executed and returns the exception. If {@code runnable} does not throw an exception, an
+   * {@link AssertionError} is thrown. If it throws the wrong type of exception, an {@code
+   * AssertionError} is thrown describing the mismatch; the exception that was actually thrown can
+   * be obtained by calling {@link AssertionError#getCause}.
+   *
+   * @param throwableClass the expected type of the exception
+   * @param runnable       A function that is expected to throw an exception when invoked
+   * @return The exception thrown by {@code runnable}
+   * @since 6.9.5
+   */
+  public static <T extends Throwable> T expectThrows(Class<T> throwableClass, ThrowingRunnable runnable) {
+    try {
+      runnable.run();
+    } catch (Throwable t) {
+      if (throwableClass.isInstance(t)) {
+        return throwableClass.cast(t);
+      } else {
+        String mismatchMessage = String.format("Expected %s to be thrown, but %s was thrown",
+                throwableClass.getSimpleName(), t.getClass().getSimpleName());
+
+        throw new AssertionError(mismatchMessage, t);
+      }
+    }
+    String message = String.format("Expected %s to be thrown, but nothing was thrown",
+            throwableClass.getSimpleName());
+    throw new AssertionError(message);
+  }
+}
diff --git a/src/main/java/org/testng/AssertJUnit.java b/src/main/java/org/testng/AssertJUnit.java
new file mode 100755
index 0000000..b4391af
--- /dev/null
+++ b/src/main/java/org/testng/AssertJUnit.java
@@ -0,0 +1,375 @@
+package org.testng;
+
+import org.testng.internal.junit.ArrayAsserts;
+
+
+/**
+ * A set of assert methods.  Messages are only displayed when an assert fails.
+ * Renamed from <CODE>junit.framework.Assert</CODE>.
+ */
+public class AssertJUnit extends ArrayAsserts {
+
+  /**
+   * Protect constructor since it is a static only class
+   */
+  protected AssertJUnit() {
+  }
+
+  /**
+   * Asserts that a condition is true. If it isn't it throws
+   * an AssertionFailedError with the given message.
+   */
+  static public void assertTrue(String message, boolean condition) {
+    if(!condition) {
+      fail(message);
+    }
+  }
+
+  /**
+   * Asserts that a condition is true. If it isn't it throws
+   * an AssertionFailedError.
+   */
+  static public void assertTrue(boolean condition) {
+    assertTrue(null, condition);
+  }
+
+  /**
+   * Asserts that a condition is false. If it isn't it throws
+   * an AssertionFailedError with the given message.
+   */
+  static public void assertFalse(String message, boolean condition) {
+    assertTrue(message, !condition);
+  }
+
+  /**
+   * Asserts that a condition is false. If it isn't it throws
+   * an AssertionFailedError.
+   */
+  static public void assertFalse(boolean condition) {
+    assertFalse(null, condition);
+  }
+
+  /**
+   * Fails a test with the given message.
+   */
+  static public void fail(String message) {
+    if (null == message) {
+      message = "";
+    }
+    throw new AssertionError(message);
+  }
+
+  /**
+   * Fails a test with no message.
+   */
+  static public void fail() {
+    fail(null);
+  }
+
+  /**
+   * Asserts that two objects are equal. If they are not
+   * an AssertionFailedError is thrown with the given message.
+   */
+  static public void assertEquals(String message, Object expected, Object actual) {
+    if((expected == null) && (actual == null)) {
+      return;
+    }
+    if((expected != null) && expected.equals(actual)) {
+      return;
+    }
+    failNotEquals(message, expected, actual);
+  }
+
+  /**
+   * Asserts that two objects are equal. If they are not
+   * an AssertionFailedError is thrown.
+   */
+  static public void assertEquals(Object expected, Object actual) {
+    assertEquals(null, expected, actual);
+  }
+
+  /**
+   * Asserts that two Strings are equal.
+   */
+  static public void assertEquals(String message, String expected, String actual) {
+    if((expected == null) && (actual == null)) {
+      return;
+    }
+    if((expected != null) && expected.equals(actual)) {
+      return;
+    }
+    throw new AssertionError(format(message, expected, actual));
+  }
+
+  /**
+   * Asserts that two Strings are equal.
+   */
+  static public void assertEquals(String expected, String actual) {
+    assertEquals(null, expected, actual);
+  }
+
+  /**
+   * Asserts that two doubles are equal concerning a delta.  If they are not
+   * an AssertionFailedError is thrown with the given message.  If the expected
+   * value is infinity then the delta value is ignored.
+   */
+  static public void assertEquals(String message, double expected, double actual, double delta) {
+
+    // handle infinity specially since subtracting to infinite values gives NaN and the
+    // the following test fails
+    if(Double.isInfinite(expected)) {
+      if(!(expected == actual)) {
+        failNotEquals(message, expected, actual);
+      }
+    }
+    else if(!(Math.abs(expected - actual) <= delta)) { // Because comparison with NaN always returns false
+      failNotEquals(message, expected, actual);
+    }
+  }
+
+  /**
+   * Asserts that two doubles are equal concerning a delta. If the expected
+   * value is infinity then the delta value is ignored.
+   */
+  static public void assertEquals(double expected, double actual, double delta) {
+    assertEquals(null, expected, actual, delta);
+  }
+
+  /**
+   * Asserts that two floats are equal concerning a delta. If they are not
+   * an AssertionFailedError is thrown with the given message.  If the expected
+   * value is infinity then the delta value is ignored.
+   */
+  static public void assertEquals(String message, float expected, float actual, float delta) {
+
+    // handle infinity specially since subtracting to infinite values gives NaN and the
+    // the following test fails
+    if(Float.isInfinite(expected)) {
+      if(!(expected == actual)) {
+        failNotEquals(message, expected, actual);
+      }
+    }
+    else if(!(Math.abs(expected - actual) <= delta)) {
+      failNotEquals(message, expected, actual);
+    }
+  }
+
+  /**
+   * Asserts that two floats are equal concerning a delta. If the expected
+   * value is infinity then the delta value is ignored.
+   */
+  static public void assertEquals(float expected, float actual, float delta) {
+    assertEquals(null, expected, actual, delta);
+  }
+
+  /**
+   * Asserts that two longs are equal. If they are not
+   * an AssertionFailedError is thrown with the given message.
+   */
+  static public void assertEquals(String message, long expected, long actual) {
+    assertEquals(message, Long.valueOf(expected), Long.valueOf(actual));
+  }
+
+  /**
+   * Asserts that two longs are equal.
+   */
+  static public void assertEquals(long expected, long actual) {
+    assertEquals(null, expected, actual);
+  }
+
+  /**
+   * Asserts that two booleans are equal. If they are not
+   * an AssertionFailedError is thrown with the given message.
+   */
+  static public void assertEquals(String message, boolean expected, boolean actual) {
+    assertEquals(message, Boolean.valueOf(expected), Boolean.valueOf(actual));
+  }
+
+  /**
+   * Asserts that two booleans are equal.
+   */
+  static public void assertEquals(boolean expected, boolean actual) {
+    assertEquals(null, expected, actual);
+  }
+
+  /**
+   * Asserts that two bytes are equal. If they are not
+   * an AssertionFailedError is thrown with the given message.
+   */
+  static public void assertEquals(String message, byte expected, byte actual) {
+    assertEquals(message, Byte.valueOf(expected), Byte.valueOf(actual));
+  }
+
+  /**
+     * Asserts that two bytes are equal.
+   */
+  static public void assertEquals(byte expected, byte actual) {
+    assertEquals(null, expected, actual);
+  }
+
+  /**
+   * Asserts that two chars are equal. If they are not
+   * an AssertionFailedError is thrown with the given message.
+   */
+  static public void assertEquals(String message, char expected, char actual) {
+    assertEquals(message, Character.valueOf(expected), Character.valueOf(actual));
+  }
+
+  /**
+   * Asserts that two chars are equal.
+   */
+  static public void assertEquals(char expected, char actual) {
+    assertEquals(null, expected, actual);
+  }
+
+  /**
+   * Asserts that two shorts are equal. If they are not
+   * an AssertionFailedError is thrown with the given message.
+   */
+  static public void assertEquals(String message, short expected, short actual) {
+    assertEquals(message, Short.valueOf(expected), Short.valueOf(actual));
+  }
+
+  /**
+  * Asserts that two shorts are equal.
+  */
+  static public void assertEquals(short expected, short actual) {
+    assertEquals(null, expected, actual);
+  }
+
+  /**
+   * Asserts that two ints are equal. If they are not
+   * an AssertionFailedError is thrown with the given message.
+   */
+  static public void assertEquals(String message, int expected, int actual) {
+    assertEquals(message, Integer.valueOf(expected), Integer.valueOf(actual));
+  }
+
+  /**
+   * Asserts that two ints are equal.
+  */
+  static public void assertEquals(int expected, int actual) {
+    assertEquals(null, expected, actual);
+  }
+
+  /**
+   * Asserts that an object isn't null.
+   */
+  static public void assertNotNull(Object object) {
+    assertNotNull(null, object);
+  }
+
+  /**
+   * Asserts that an object isn't null. If it is
+   * an AssertionFailedError is thrown with the given message.
+   */
+  static public void assertNotNull(String message, Object object) {
+    assertTrue(message, object != null);
+  }
+
+  /**
+   * Asserts that an object is null.
+   */
+  static public void assertNull(Object object) {
+    assertNull(null, object);
+  }
+
+  /**
+   * Asserts that an object is null.  If it is not
+   * an AssertionFailedError is thrown with the given message.
+   */
+  static public void assertNull(String message, Object object) {
+    assertTrue(message, object == null);
+  }
+
+  /**
+   * Asserts that two objects refer to the same object. If they are not
+   * an AssertionFailedError is thrown with the given message.
+   */
+  static public void assertSame(String message, Object expected, Object actual) {
+    if(expected == actual) {
+      return;
+    }
+    failNotSame(message, expected, actual);
+  }
+
+  /**
+   * Asserts that two objects refer to the same object. If they are not
+   * the same an AssertionFailedError is thrown.
+   */
+  static public void assertSame(Object expected, Object actual) {
+    assertSame(null, expected, actual);
+  }
+
+  /**
+   * Asserts that two objects refer to the same object. If they are not
+   * an AssertionFailedError is thrown with the given message.
+   */
+  static public void assertNotSame(String message, Object expected, Object actual) {
+    if(expected == actual) {
+      failSame(message);
+    }
+  }
+
+  /**
+   * Asserts that two objects refer to the same object. If they are not
+   * the same an AssertionFailedError is thrown.
+   */
+  static public void assertNotSame(Object expected, Object actual) {
+    assertNotSame(null, expected, actual);
+  }
+
+  static public void assertEquals(final byte[] expected, final byte[] actual) {
+    assertEquals("", expected, actual);
+  }
+
+  static public void assertEquals(final String message, final byte[] expected, final byte[] actual) {
+    if(expected == actual) {
+        return;
+    }
+    if(null == expected) {
+      fail("expected a null array, but not null found. " + message);
+    }
+    if(null == actual) {
+        fail("expected not null array, but null found. " + message);
+    }
+
+    assertEquals("arrays don't have the same size. " + message, expected.length, actual.length);
+
+    for(int i= 0; i < expected.length; i++) {
+        if(expected[i] != actual[i]) {
+            fail("arrays differ firstly at element [" + i +"]; "
+                + format(message, expected[i], actual[i]));
+        }
+    }
+  }
+
+  static private void failSame(String message) {
+    String formatted = "";
+    if(message != null) {
+      formatted = message + " ";
+    }
+    fail(formatted + "expected not same");
+  }
+
+  static private void failNotSame(String message, Object expected, Object actual) {
+    String formatted = "";
+    if(message != null) {
+      formatted = message + " ";
+    }
+    fail(formatted + "expected same:<" + expected + "> was not:<" + actual + ">");
+  }
+
+  static private void failNotEquals(String message, Object expected, Object actual) {
+    fail(format(message, expected, actual));
+  }
+
+  static String format(String message, Object expected, Object actual) {
+    String formatted = "";
+    if(message != null) {
+      formatted = message + " ";
+    }
+
+    return formatted + "expected:<" + expected + "> but was:<" + actual + ">";
+  }
+}
diff --git a/src/main/java/org/testng/ClassMethodMap.java b/src/main/java/org/testng/ClassMethodMap.java
new file mode 100755
index 0000000..f75b1a8
--- /dev/null
+++ b/src/main/java/org/testng/ClassMethodMap.java
@@ -0,0 +1,88 @@
+package org.testng;
+
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.internal.XmlMethodSelector;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This class maintains a map of <CODE><Class, List<ITestNGMethod>></CODE>.
+ * It is used by TestWorkers to determine if the method they just ran
+ * is the last of its class, in which case it's time to invoke all the
+ * afterClass methods.
+ *
+ * @author <a href='mailto:the[dot]mindstorm[at]gmail[dot]com'>Alex Popescu</a>
+ */
+public class ClassMethodMap {
+  private Map<Object, List<ITestNGMethod>> m_classMap = Maps.newHashMap();
+  // These two variables are used throughout the workers to keep track
+  // of what beforeClass/afterClass methods have been invoked
+  private Map<ITestClass, Set<Object>> m_beforeClassMethods = Maps.newHashMap();
+  private Map<ITestClass, Set<Object>> m_afterClassMethods = Maps.newHashMap();
+
+  public ClassMethodMap(List<ITestNGMethod> methods, XmlMethodSelector xmlMethodSelector) {
+    for (ITestNGMethod m : methods) {
+      // Only add to the class map methods that are included in the
+      // method selector. We can pass a null context here since the selector
+      // should already have been initialized
+      if (xmlMethodSelector != null){
+    	  if (! xmlMethodSelector.includeMethod(null, m, true)) continue;
+      }
+
+      Object instance = m.getInstance();
+      List<ITestNGMethod> l = m_classMap.get(instance);
+      if (l == null) {
+        l = Lists.newArrayList();
+        m_classMap.put(instance, l);
+      }
+      l.add(m);
+    }
+  }
+
+  /**
+   * Remove the method from this map and returns true if it is the last
+   * of its class.
+   */
+  public synchronized boolean removeAndCheckIfLast(ITestNGMethod m, Object instance) {
+    List<ITestNGMethod> l = m_classMap.get(instance);
+    if (l != null) {
+      l.remove(m);
+      // It's the last method of this class if all the methods remaining in the list belong to a
+      // different class
+      for (ITestNGMethod tm : l) {
+        if (tm.getEnabled() && tm.getTestClass().equals(m.getTestClass())) return false;
+      }
+      return true;
+    } else {
+      throw new AssertionError("l should not be null");
+    }
+  }
+
+  private Class<?> getMethodClass(ITestNGMethod m) {
+    return m.getTestClass().getRealClass();
+  }
+
+  public Map<ITestClass, Set<Object>> getInvokedBeforeClassMethods() {
+    return m_beforeClassMethods;
+  }
+
+  public Map<ITestClass, Set<Object>> getInvokedAfterClassMethods() {
+    return m_afterClassMethods;
+  }
+
+  public void clear() {
+    for(Set<Object> instances: m_beforeClassMethods.values()) {
+      instances.clear();
+      instances= null;
+    }
+    for(Set<Object> instances: m_afterClassMethods.values()) {
+      instances.clear();
+      instances= null;
+    }
+    m_beforeClassMethods.clear();
+    m_afterClassMethods.clear();
+  }
+}
diff --git a/src/main/java/org/testng/CommandLineArgs.java b/src/main/java/org/testng/CommandLineArgs.java
new file mode 100644
index 0000000..fafb197
--- /dev/null
+++ b/src/main/java/org/testng/CommandLineArgs.java
@@ -0,0 +1,157 @@
+package org.testng;
+
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.converters.CommaParameterSplitter;
+
+import org.testng.collections.Lists;
+import org.testng.xml.XmlSuite;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CommandLineArgs {
+
+  @Parameter(description = "The XML suite files to run")
+  public List<String> suiteFiles = Lists.newArrayList();
+
+  public static final String LOG = "-log";
+  public static final String VERBOSE = "-verbose";
+  @Parameter(names = { LOG, VERBOSE }, description = "Level of verbosity")
+  public Integer verbose;
+
+  public static final String GROUPS = "-groups";
+  @Parameter(names = GROUPS, description = "Comma-separated list of group names to be run")
+  public String groups;
+
+  public static final String EXCLUDED_GROUPS = "-excludegroups";
+  @Parameter(names = EXCLUDED_GROUPS, description ="Comma-separated list of group names to "
+      + " exclude")
+  public String excludedGroups;
+
+  public static final String OUTPUT_DIRECTORY = "-d";
+  @Parameter(names = OUTPUT_DIRECTORY, description ="Output directory")
+  public String outputDirectory;
+
+  public static final String JUNIT = "-junit";
+  @Parameter(names = JUNIT, description ="JUnit mode")
+  public Boolean junit = Boolean.FALSE;
+
+  public static final String MIXED = "-mixed";
+  @Parameter(names = MIXED, description ="Mixed mode - autodetect the type of current test" +
+      " and run it with appropriate runner")
+  public Boolean mixed = Boolean.FALSE;
+
+  public static final String LISTENER = "-listener";
+  @Parameter(names = LISTENER, description = "List of .class files or list of class names" +
+      " implementing ITestListener or ISuiteListener")
+  public String listener;
+
+  public static final String METHOD_SELECTORS = "-methodselectors";
+  @Parameter(names = METHOD_SELECTORS, description = "List of .class files or list of class " +
+        "names implementing IMethodSelector")
+  public String methodSelectors;
+
+  public static final String OBJECT_FACTORY = "-objectfactory";
+  @Parameter(names = OBJECT_FACTORY, description = "List of .class files or list of class " +
+        "names implementing ITestRunnerFactory")
+  public String objectFactory;
+
+  public static final String PARALLEL= "-parallel";
+  @Parameter(names = PARALLEL, description = "Parallel mode (methods, tests or classes)")
+  public XmlSuite.ParallelMode parallelMode;
+
+  public static final String CONFIG_FAILURE_POLICY = "-configfailurepolicy";
+  @Parameter(names = CONFIG_FAILURE_POLICY , description = "Configuration failure policy (skip or continue)")
+  public String configFailurePolicy;
+
+  public static final String THREAD_COUNT = "-threadcount";
+  @Parameter(names = THREAD_COUNT, description = "Number of threads to use when running tests " +
+      "in parallel")
+  public Integer threadCount;
+
+  public static final String DATA_PROVIDER_THREAD_COUNT = "-dataproviderthreadcount";
+  @Parameter(names = DATA_PROVIDER_THREAD_COUNT, description = "Number of threads to use when " +
+      "running data providers")
+  public Integer dataProviderThreadCount;
+
+  public static final String SUITE_NAME = "-suitename";
+  @Parameter(names = SUITE_NAME, description = "Default name of test suite, if not specified " +
+      "in suite definition file or source code")
+  public String suiteName;
+
+  public static final String TEST_NAME = "-testname";
+  @Parameter(names = TEST_NAME, description = "Default name of test, if not specified in suite" +
+      "definition file or source code")
+  public String testName;
+
+  public static final String REPORTER = "-reporter";
+  @Parameter(names = REPORTER, description = "Extended configuration for custom report listener")
+  public String reporter;
+
+  public static final String USE_DEFAULT_LISTENERS = "-usedefaultlisteners";
+  @Parameter(names = USE_DEFAULT_LISTENERS, description = "Whether to use the default listeners")
+  public String useDefaultListeners = "true";
+
+  public static final String SKIP_FAILED_INVOCATION_COUNTS = "-skipfailedinvocationcounts";
+  @Parameter(names = SKIP_FAILED_INVOCATION_COUNTS, hidden = true)
+  public Boolean skipFailedInvocationCounts;
+
+  public static final String TEST_CLASS = "-testclass";
+  @Parameter(names = TEST_CLASS, description = "The list of test classes")
+  public String testClass;
+
+  public static final String TEST_NAMES = "-testnames";
+  @Parameter(names = TEST_NAMES, description = "The list of test names to run")
+  public String testNames;
+
+  public static final String TEST_JAR = "-testjar";
+  @Parameter(names = TEST_JAR, description = "A jar file containing the tests")
+  public String testJar;
+
+  public static final String XML_PATH_IN_JAR = "-xmlpathinjar";
+  public static final String XML_PATH_IN_JAR_DEFAULT = "testng.xml";
+  @Parameter(names = XML_PATH_IN_JAR, description = "The full path to the xml file inside the jar file (only valid if -testjar was specified)")
+  public String xmlPathInJar = XML_PATH_IN_JAR_DEFAULT;
+
+  public static final String TEST_RUNNER_FACTORY = "-testrunfactory";
+  @Parameter(names = { TEST_RUNNER_FACTORY, "-testRunFactory" },
+      description = "The factory used to create tests")
+  public String testRunnerFactory;
+
+  public static final String PORT = "-port";
+  @Parameter(names = PORT, description = "The port")
+  public Integer port;
+
+  public static final String HOST = "-host";
+  @Parameter(names = HOST, description = "The host", hidden = true)
+  public String host;
+
+  public static final String MASTER = "-master";
+  @Parameter(names = MASTER, description = "Host where the master is", hidden = true)
+  public String master;
+
+  public static final String SLAVE = "-slave";
+  @Parameter(names = SLAVE, description = "Host where the slave is", hidden = true)
+  public String slave;
+
+  public static final String METHODS = "-methods";
+  @Parameter(names = METHODS, description = "Comma separated of test methods",
+      splitter = CommaParameterSplitter.class)
+  public List<String> commandLineMethods = new ArrayList<>();
+
+  public static final String SUITE_THREAD_POOL_SIZE = "-suitethreadpoolsize";
+  public static final Integer SUITE_THREAD_POOL_SIZE_DEFAULT = 1;
+  @Parameter(names = SUITE_THREAD_POOL_SIZE, description = "Size of the thread pool to use"
+        + " to run suites")
+  public Integer suiteThreadPoolSize = SUITE_THREAD_POOL_SIZE_DEFAULT;
+
+  public static final String RANDOMIZE_SUITES = "-randomizesuites";
+  @Parameter(names = RANDOMIZE_SUITES, hidden = true,
+      description = "Whether to run suites in same order as specified in XML or not")
+  public Boolean randomizeSuites = Boolean.FALSE;
+
+  public static final String DEBUG = "-debug";
+  @Parameter(names = DEBUG, hidden = true, description = "Used to debug TestNG")
+  public Boolean debug = Boolean.FALSE;
+
+}
diff --git a/src/main/java/org/testng/ConversionUtils.java b/src/main/java/org/testng/ConversionUtils.java
new file mode 100644
index 0000000..8395ee8
--- /dev/null
+++ b/src/main/java/org/testng/ConversionUtils.java
@@ -0,0 +1,46 @@
+package org.testng;
+
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Helper methods used by the Eclipse plug-in when converting tests from JUnit.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ *
+ */
+public class ConversionUtils {
+  /**
+   * Turns the output of a JUnit 4 @Parameters style data provider into
+   * one that is suitable for TestNG's @DataProvider.
+   */
+  public static Object[] wrapDataProvider(Class cls, Collection<Object[]> data) {
+    List result = new ArrayList();
+    for (Object o : data) {
+      Object[] parameters = (Object[]) o;
+      Constructor ctor = null;
+      try {
+        for (Constructor c : cls.getConstructors()) {
+          // Just comparing parameter array sizes. Comparing the parameter types
+          // is more error prone since we need to take conversions into account
+          // (int -> Integer, etc...).
+          if (c.getParameterTypes().length == parameters.length) {
+            ctor = c;
+            break;
+          }
+        }
+        if (ctor == null) {
+          throw new TestNGException("Couldn't find a constructor in " + cls);
+        }
+
+        result.add(ctor.newInstance(parameters));
+      } catch (Exception ex) {
+        ex.printStackTrace();
+      }
+    }
+    return result.toArray();
+  }
+
+}
diff --git a/src/main/java/org/testng/Converter.java b/src/main/java/org/testng/Converter.java
new file mode 100644
index 0000000..b009b54
--- /dev/null
+++ b/src/main/java/org/testng/Converter.java
@@ -0,0 +1,98 @@
+package org.testng;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParameterException;
+
+import org.testng.collections.Sets;
+import org.testng.internal.Yaml;
+import org.testng.xml.Parser;
+import org.testng.xml.XmlSuite;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Convert XML files to YAML and vice versa.
+ * 
+ * @author cbeust
+ */
+public class Converter {
+
+  @Parameter(description = "file1 [file2 file3...]", required = true)
+  private List<String> m_files;
+
+  @Parameter(names = "-d", description = "The directory where the file(s) will be created")
+  private String m_outputDirectory = ".";
+
+  public static void main(String[] args)
+      throws ParserConfigurationException, SAXException, IOException {
+    Converter c = new Converter();
+    c.run(args);
+  }
+
+  private void findAllSuites(Collection<XmlSuite> suites, Set<XmlSuite> result) {
+    for (XmlSuite s : suites) {
+      result.add(s);
+      for (XmlSuite xs : s.getChildSuites()) {
+        findAllSuites(Arrays.asList(xs), result);
+      }
+    }
+  }
+
+  private void run(String[] args)
+      throws ParserConfigurationException, SAXException, IOException {
+    JCommander jc = new JCommander(this);
+    try {
+      jc.parse(args);
+      File f = new File(m_outputDirectory);
+      if (! f.exists()) f.mkdir();
+
+      for (String file : m_files) {
+        Set<XmlSuite> allSuites = Sets.newHashSet();
+        Parser parser = new Parser(file);
+        parser.setLoadClasses(false);  // we might not have these classes on the classpath
+        findAllSuites(parser.parse(), allSuites);
+
+        for (XmlSuite suite : allSuites) {
+          String fileName = suite.getFileName();
+          int ind = fileName.lastIndexOf(".");
+          String bn = fileName.substring(0, ind);
+          int ind2 = bn.lastIndexOf(File.separatorChar);
+          String baseName = bn.substring(ind2 + 1);
+
+          if (file.endsWith(".xml")) {
+            File newFile = new File(m_outputDirectory, baseName + ".yaml");
+            writeFile(newFile, Yaml.toYaml(suite).toString());
+          }
+          else if (file.endsWith(".yaml")) {
+            File newFile = new File(m_outputDirectory, baseName + ".xml");
+            writeFile(newFile, suite.toXml());
+          }
+          else {
+            throw new TestNGException("Unknown file type:" + file);
+          }
+        }
+      }
+    }
+    catch(ParameterException ex) {
+      System.out.println("Error: " + ex.getMessage());
+      jc.usage();
+    }
+  }
+
+  private void writeFile(File newFile, String content) throws IOException {
+    try (FileWriter bw = new FileWriter(newFile)) {
+      bw.write(content);
+    }
+    System.out.println("Wrote " + newFile);
+  }
+}
diff --git a/src/main/java/org/testng/DependencyMap.java b/src/main/java/org/testng/DependencyMap.java
new file mode 100644
index 0000000..ee3896f
--- /dev/null
+++ b/src/main/java/org/testng/DependencyMap.java
@@ -0,0 +1,72 @@
+package org.testng;
+
+import org.testng.collections.ListMultiMap;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.collections.Sets;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * Helper class to keep track of dependencies.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public class DependencyMap {
+  private ListMultiMap<String, ITestNGMethod> m_dependencies = Maps.newListMultiMap();
+  private ListMultiMap<String, ITestNGMethod> m_groups = Maps.newListMultiMap();
+
+  public DependencyMap(ITestNGMethod[] methods) {
+    for (ITestNGMethod m : methods) {
+    	m_dependencies.put( m.getRealClass().getName() + "." +  m.getMethodName(), m);
+      for (String g : m.getGroups()) {
+        m_groups.put(g, m);
+      }
+    }
+  }
+
+  public List<ITestNGMethod> getMethodsThatBelongTo(String group, ITestNGMethod fromMethod) {
+    Set<String> uniqueKeys = m_groups.keySet();
+
+    List<ITestNGMethod> result = Lists.newArrayList();
+
+    for (String k : uniqueKeys) {
+      if (Pattern.matches(group, k)) {
+        List<ITestNGMethod> temp = m_groups.get(k);
+        if (temp != null)
+          result.addAll(m_groups.get(k));
+      }
+    }
+
+    if (result.isEmpty() && !fromMethod.ignoreMissingDependencies()) {
+      throw new TestNGException("DependencyMap::Method \"" + fromMethod
+          + "\" depends on nonexistent group \"" + group + "\"");
+    } else {
+      return result;
+    }
+  }
+
+  public ITestNGMethod getMethodDependingOn(String methodName, ITestNGMethod fromMethod) {
+    List<ITestNGMethod> l = m_dependencies.get(methodName);
+    if (l == null && fromMethod.ignoreMissingDependencies()){
+    	return fromMethod;
+    }
+    if (l != null) {
+      for (ITestNGMethod m : l) {
+        // If they are in the same class hierarchy, they must belong to the same instance,
+        // otherwise, it's a method depending on a method in a different class so we
+        // don't bother checking the instance
+        if (fromMethod.getRealClass().isAssignableFrom(m.getRealClass())) {
+          if (m.getInstance() == fromMethod.getInstance()) return m;
+        } else {
+          return m;
+        }
+      }
+    }
+    throw new TestNGException("Method \"" + fromMethod
+        + "\" depends on nonexistent method \"" + methodName + "\"");
+  }
+}
diff --git a/src/main/java/org/testng/FileAssert.java b/src/main/java/org/testng/FileAssert.java
new file mode 100755
index 0000000..f49a3f9
--- /dev/null
+++ b/src/main/java/org/testng/FileAssert.java
@@ -0,0 +1,331 @@
+package org.testng;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Assertion tool for File centric assertions.
+ * Conceptually this is an extension of {@link Assert}
+ * Presents assertion methods with a more natural parameter order.
+ * The order is always <B>actualValue</B>, <B>expectedValue</B> [, message].
+ *
+ * @author <a href='mailto:pmendelson@trueoutcomes.com'>Paul Mendelon</a>
+ * @since 5.6
+ * @version $Revision: 650 $, $Date: 2009-01-05 03:51:54 -0800 (Mon, 05 Jan 2009) $
+ */
+public class FileAssert {
+
+  /**
+   * Protect constructor since it is a static only class
+   */
+  private FileAssert() {
+  	// hide constructor
+  }
+
+  /**
+   * Asserts that a {@code tstvalue} is a proper directory. If it isn't,
+   * an AssertionError, with the given message, is thrown.
+   * @param tstvalue the file to evaluate
+   * @param message the assertion error message
+   */
+  static public void assertDirectory(File tstvalue, String message) {
+  	boolean condition=false;
+  	try {
+  	condition=tstvalue.isDirectory();
+  	} catch(SecurityException e) {
+  	failSecurity(e,tstvalue,fileType(tstvalue),"Directory", message);
+  	}
+  	if(!condition) {
+  	failFile(tstvalue,fileType(tstvalue),"Directory", message);
+  	}
+  }
+
+  static public void assertDirectory(File tstvalue) {
+  	assertDirectory(tstvalue, null);
+  }
+
+  /**
+   * Asserts that a {@code tstvalue} is a proper file. If it isn't,
+   * an AssertionError, with the given message, is thrown.
+   * @param tstvalue the file to evaluate
+   * @param message the assertion error message
+   */
+  static public void assertFile(File tstvalue, String message) {
+  	boolean condition=false;
+  	try {
+  	condition=tstvalue.isFile();
+  	} catch(SecurityException e) {
+  	failSecurity(e,tstvalue,fileType(tstvalue),"File", message);
+  	}
+  	if(!condition) {
+  	failFile(tstvalue,fileType(tstvalue),"File", message);
+  	}
+  }
+
+  /**
+   * @see #assertFile(File, String)
+   */
+  static public void assertFile(File tstvalue) {
+  	assertFile(tstvalue, null);
+  }
+
+  /**
+   * Asserts that a {@code tstvalue} is a file of exactly {@code expected} characters
+   * or a directory of exactly {@code expected} entries. If it isn't,
+   * an AssertionError, with the given message, is thrown.
+   * @param tstvalue the file to evaluate
+   * @param message the assertion error message
+   */
+  static public void assertLength(File tstvalue, long expected, String message) {
+  	long actual=-1L;
+  	try {
+  	actual=tstvalue.isDirectory()?tstvalue.list().length:tstvalue.length();
+  	} catch(SecurityException e) {
+  	failSecurity(e,tstvalue,String.valueOf(actual),String.valueOf(expected), message);
+  	}
+  	if(actual!=expected) {
+  	failFile(tstvalue,String.valueOf(actual),String.valueOf(expected), message);
+  	}
+  }
+
+  /**
+   * @see #assertLength(File, long, String)
+   */
+  static public void assertLength(File tstvalue, long expected) {
+  	assertLength(tstvalue, expected, null);
+  }
+
+  /**
+   * Asserts that a {@code tstvalue} is a file of at least {@code expected} characters
+   * or a directory of at least {@code expected} entries. If it isn't,
+   * an AssertionError, with the given message, is thrown.
+   * @param tstvalue the file to evaluate
+   * @param message the assertion error message
+   */
+  static public void assertMinLength(File tstvalue, long expected, String message) {
+  	long actual=-1L;
+  	try {
+  	actual=tstvalue.isDirectory()?tstvalue.list().length:tstvalue.length();
+  	} catch(SecurityException e) {
+  	failSecurity(e,tstvalue,String.valueOf(actual),"at least "+String.valueOf(expected), message);
+  	}
+  	if(actual<expected) {
+  	failFile(tstvalue,String.valueOf(actual),"at least "+String.valueOf(expected), message);
+  	}
+  }
+
+  /**
+   * @see #assertMinLength(File, long, String)
+   */
+  static public void assertMinLength(File tstvalue, long expected) {
+  	assertMinLength(tstvalue, expected, null);
+  }
+
+  /**
+   * Asserts that a {@code tstvalue} is a file of at most {@code expected} characters
+   * or a directory of at most {@code expected} entries. If it isn't,
+   * an AssertionError, with the given message, is thrown.
+   * @param tstvalue the file to evaluate
+   * @param message the assertion error message
+   */
+  static public void assertMaxLength(File tstvalue, long expected, String message) {
+  	long actual=-1L;
+  	try {
+  	actual=tstvalue.isDirectory()?tstvalue.list().length:tstvalue.length();
+  	} catch(SecurityException e) {
+  	failSecurity(e,tstvalue,String.valueOf(actual),"at most "+String.valueOf(expected), message);
+  	}
+  	if(actual>expected) {
+  	failFile(tstvalue,String.valueOf(actual),"at most "+String.valueOf(expected), message);
+  	}
+  }
+
+  /**
+   * @see #assertMaxLength(File, long, String)
+   */
+  static public void assertMaxLength(File tstvalue, long expected) {
+  	assertMaxLength(tstvalue, expected, null);
+  }
+
+  /**
+   * Asserts that a {@code tstvalue} is readable. If it isn't,
+   * an AssertionError, with the given message, is thrown.
+   * @param tstvalue the file to evaluate
+   * @param message the assertion error message
+   */
+  static public void assertReadable(File tstvalue, String message) {
+  	boolean condition=false;
+  	try {
+  	condition=tstvalue.canRead();
+  	} catch(SecurityException e) {
+  	failSecurity(e,tstvalue,fileAccess(tstvalue),"Read Access", message);
+  	}
+  	if(!condition) {
+  	failFile(tstvalue,fileAccess(tstvalue),"Read Access", message);
+  	}
+  }
+
+  /**
+   * @see #assertReadable(File, String)
+   */
+  static public void assertReadable(File tstvalue) {
+  	assertReadable(tstvalue, null);
+  }
+
+  /**
+   * Asserts that a {@code tstvalue} is writeable. If it isn't,
+   * an AssertionError, with the given message, is thrown.
+   * @param tstvalue the file to evaluate
+   * @param message the assertion error message
+   */
+  static public void assertWriteable(File tstvalue, String message) {
+  	boolean condition=false;
+  	try {
+  	condition=tstvalue.canWrite();
+  	} catch(SecurityException e) {
+  	failSecurity(e,tstvalue,fileAccess(tstvalue),"Write Access", message);
+  	}
+  	if(!condition) {
+  	failFile(tstvalue,fileAccess(tstvalue),"Write Access", message);
+  	}
+  }
+
+  /**
+   * @see #assertWriteable(File, String)
+   */
+  static public void assertWriteable(File tstvalue) {
+  	assertReadable(tstvalue, null);
+  }
+
+  /**
+   * Asserts that a {@code tstvalue} is readable and writeable. If it isn't,
+   * an AssertionError, with the given message, is thrown.
+   * @param tstvalue the file to evaluate
+   * @param message the assertion error message
+   */
+  static public void assertReadWrite(File tstvalue, String message) {
+  	boolean condition=false;
+  	try {
+  	condition=tstvalue.canRead() && tstvalue.canWrite();
+  	} catch(SecurityException e) {
+  	failSecurity(e,tstvalue,fileAccess(tstvalue),"Read/Write Access", message);
+  	}
+  	if(!condition) {
+  	failFile(tstvalue,fileAccess(tstvalue),"Read/Write Access", message);
+  	}
+  }
+
+  /**
+   * @see #assertReadWrite(File, String)
+   */
+  static public void assertReadWrite(File tstvalue) {
+  	assertReadWrite(tstvalue, null);
+  }
+
+  /**
+   * Fails a test with the given message and wrapping the original exception.
+   *
+   * @param message the assertion error message
+   * @param realCause the original exception
+   */
+  static public void fail(String message, Throwable realCause) {
+  	AssertionError ae = new AssertionError(message);
+  	ae.initCause(realCause);
+
+  	throw ae;
+  }
+
+  /**
+   * Fails a test with the given message.
+   * @param message the assertion error message
+   */
+  static public void fail(String message) {
+  	throw new AssertionError(message);
+  }
+
+  /**
+   * Fails a test with no message.
+   */
+  static public void fail() {
+  	fail(null);
+  }
+
+  /**
+   * Formats failure for file assertions
+   */
+  private static void failFile(File path, String actual, String expected, String message) {
+  	String formatted = "";
+  	if(message != null) {
+  	formatted = message + " ";
+  	}
+  	fail(formatted + "expected <" + expected +"> but was <" + toString(path) + ">"
+  		+(expected!=null?"<" + expected +">":""));
+  }
+
+  /**
+   * @param tstvalue
+   * @param string
+   * @param string2
+   * @param message
+   */
+  private static void failSecurity(Exception e, File path, String actual, String expected, String message) {
+  	String formatted = "";
+  	if(message != null) {
+  	formatted = message + " ";
+  	}
+  	fail(formatted + "expected <" + expected +"> but was <" + toString(path) + ">"
+  		+"<"
+  		+ (e!=null && e.getMessage()!=null && e.getMessage().length()>0
+  			?e.getMessage()
+  			:"not authorized by JVM")
+  		+ ">");
+  }
+
+  /**
+   * String representation of what sort of file {@code path} is.
+   */
+  private static String fileType(File path) {
+  	try {
+  	if(!path.exists()) {
+      return "Non existant";
+    } else if (path.isDirectory()) {
+      return "Directory";
+    } else if (path.isFile()) {
+      return "File";
+    } else {
+      return "Special File";
+    }
+  	} catch (SecurityException e) {
+  	return "Unauthorized";
+  	}
+  }
+
+  /**
+   * String representation of what sort of file {@code path} is.
+   */
+  private static String fileAccess(File path) {
+  	try {
+  	if(!path.exists()) {
+      return "Non existant";
+    } else if (path.canWrite() && path.canRead()) {
+      return "Read/Write Access";
+    } else if (path.canRead()) {
+      return "Read only Access";
+    } else if (path.canWrite()) {
+      return "Write only Access";
+    } else {
+      return "No Access";
+    }
+  	} catch (SecurityException e) {
+  	return "Unauthorized";
+  	}
+  }
+
+  private static String toString(File path) {
+  	try {
+  	return path.getCanonicalPath();
+  	} catch(IOException e) {
+  	return path.getAbsolutePath();
+  	}
+  }
+}
diff --git a/src/main/java/org/testng/IAlterSuiteListener.java b/src/main/java/org/testng/IAlterSuiteListener.java
new file mode 100644
index 0000000..4c760a0
--- /dev/null
+++ b/src/main/java/org/testng/IAlterSuiteListener.java
@@ -0,0 +1,25 @@
+package org.testng;
+
+import org.testng.xml.XmlSuite;
+
+import java.util.List;
+
+/**
+ * Implementations of this interface will gain access to the {@link XmlSuite} object and thus let users be able to
+ * alter a suite or a test based on their own needs.
+ * This listener can be added ONLY via the following two ways :
+ * <ol>
+ * <li>&lt;<code>listeners</code>&gt; tag in a suite file.</li>
+ * <li>via Service loaders</li>
+ * </ol>
+ * <p/>
+ * <b>Note: </b>This listener <b><u>will NOT be invoked</u></b> if it is wired in via the &#064;<code>Listeners</code>
+ * annotation.
+ */
+public interface IAlterSuiteListener extends ITestNGListener {
+    /**
+     * @param suites - The list of {@link XmlSuite}s that are part of the current execution.
+     */
+    void alter(List<XmlSuite> suites);
+
+}
diff --git a/src/main/java/org/testng/IAnnotationTransformer.java b/src/main/java/org/testng/IAnnotationTransformer.java
new file mode 100755
index 0000000..ee413af
--- /dev/null
+++ b/src/main/java/org/testng/IAnnotationTransformer.java
@@ -0,0 +1,31 @@
+package org.testng;
+
+import org.testng.annotations.ITestAnnotation;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+public interface IAnnotationTransformer extends ITestNGListener {
+
+  /**
+   * This method will be invoked by TestNG to give you a chance
+   * to modify a TestNG annotation read from your test classes.
+   * You can change the values you need by calling any of the
+   * setters on the ITest interface.
+   *
+   * Note that only one of the three parameters testClass,
+   * testConstructor and testMethod will be non-null.
+   *
+   * @param annotation The annotation that was read from your
+   * test class.
+   * @param testClass If the annotation was found on a class, this
+   * parameter represents this class (null otherwise).
+   * @param testConstructor If the annotation was found on a constructor,
+   * this parameter represents this constructor (null otherwise).
+   * @param testMethod If the annotation was found on a method,
+   * this parameter represents this method (null otherwise).
+   */
+  public void transform(ITestAnnotation annotation, Class testClass,
+      Constructor testConstructor, Method testMethod);
+
+}
diff --git a/src/main/java/org/testng/IAnnotationTransformer2.java b/src/main/java/org/testng/IAnnotationTransformer2.java
new file mode 100755
index 0000000..9bb5321
--- /dev/null
+++ b/src/main/java/org/testng/IAnnotationTransformer2.java
@@ -0,0 +1,46 @@
+package org.testng;
+
+import org.testng.annotations.IConfigurationAnnotation;
+import org.testng.annotations.IDataProviderAnnotation;
+import org.testng.annotations.IFactoryAnnotation;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/**
+ * Use this interface instead of IAnnotationTransformer if you want to modify any TestNG
+ * annotation besides @Test.
+ */
+public interface IAnnotationTransformer2 extends IAnnotationTransformer {
+  /**
+   * Transform an IConfiguration annotation.
+   *
+   * Note that only one of the three parameters testClass,
+   * testConstructor and testMethod will be non-null.
+   *
+   * @param annotation The annotation that was read from your
+   * test class.
+   * @param testClass If the annotation was found on a class, this
+   * parameter represents this class (null otherwise).
+   * @param testConstructor If the annotation was found on a constructor,
+   * this parameter represents this constructor (null otherwise).
+   * @param testMethod If the annotation was found on a method,
+   * this parameter represents this method (null otherwise).
+   */
+  public void transform(IConfigurationAnnotation annotation, Class testClass,
+      Constructor testConstructor, Method testMethod);
+
+  /**
+   * Transform an IDataProvider annotation.
+   *
+   * @param method The method annotated with the IDataProvider annotation.
+   */
+  public void transform(IDataProviderAnnotation annotation, Method method);
+
+  /**
+   * Transform an IFactory annotation.
+   *
+   * @param method The method annotated with the IFactory annotation.
+   */
+  public void transform(IFactoryAnnotation annotation, Method method);
+}
diff --git a/src/main/java/org/testng/IAnnotationTransformer3.java b/src/main/java/org/testng/IAnnotationTransformer3.java
new file mode 100644
index 0000000..eb10508
--- /dev/null
+++ b/src/main/java/org/testng/IAnnotationTransformer3.java
@@ -0,0 +1,9 @@
+package org.testng;
+
+import org.testng.annotations.IListenersAnnotation;
+
+public interface IAnnotationTransformer3 extends IAnnotationTransformer2 {
+
+  void transform(IListenersAnnotation annotation, Class testClass);
+
+}
diff --git a/src/main/java/org/testng/IAttributes.java b/src/main/java/org/testng/IAttributes.java
new file mode 100755
index 0000000..caffb3f
--- /dev/null
+++ b/src/main/java/org/testng/IAttributes.java
@@ -0,0 +1,32 @@
+package org.testng;
+
+import java.io.Serializable;
+import java.util.Set;
+
+/**
+ * A trait that is used by all interfaces that lets the user add or remove their
+ * own attributes.
+ */
+public interface IAttributes extends Serializable {
+  /**
+   * @param name The name of the attribute to return
+   */
+  public Object getAttribute(String name);
+
+  /**
+   * Set a custom attribute.
+   */
+  public void setAttribute(String name, Object value);
+
+  /**
+   * @return all the attributes names.
+   */
+  public Set<String> getAttributeNames();
+
+  /**
+   * Remove the attribute
+   *
+   * @return the attribute value if found, null otherwise
+   */
+  public Object removeAttribute(String name);
+}
diff --git a/src/main/java/org/testng/IClass.java b/src/main/java/org/testng/IClass.java
new file mode 100755
index 0000000..328b418
--- /dev/null
+++ b/src/main/java/org/testng/IClass.java
@@ -0,0 +1,48 @@
+package org.testng;
+
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlTest;
+
+import java.io.Serializable;
+
+/**
+ * <code>IClass</code> represents a test class and a collection of its instances.
+ *
+ * @author <a href = "mailto:cedric&#64;beust.com">Cedric Beust</a>
+ */
+public interface IClass extends Serializable {
+
+  /**
+   * @return this test class name.  This is the name of the
+   * corresponding Java class.
+   */
+  String getName();
+
+  /**
+   * @return the &lt;test&gt; tag this class was found in.
+   */
+  XmlTest getXmlTest();
+
+  /**
+   * @return the *lt;class&gt; tag this class was found in.
+   */
+  XmlClass getXmlClass();
+
+  /**
+   * If this class implements ITest, returns its test name, otherwise returns null.
+   */
+  String getTestName();
+
+  /**
+   * @return the Java class corresponding to this IClass.
+   */
+  Class getRealClass();
+
+  Object[] getInstances(boolean create);
+
+  int getInstanceCount();
+
+  long[] getInstanceHashCodes();
+
+  void addInstance(Object instance);
+}
diff --git a/src/main/java/org/testng/IClassListener.java b/src/main/java/org/testng/IClassListener.java
new file mode 100644
index 0000000..fb56a78
--- /dev/null
+++ b/src/main/java/org/testng/IClassListener.java
@@ -0,0 +1,7 @@
+package org.testng;
+
+public interface IClassListener extends ITestNGListener {
+
+  void onBeforeClass(ITestClass testClass, IMethodInstance mi);
+  void onAfterClass(ITestClass testClass, IMethodInstance mi);
+}
diff --git a/src/main/java/org/testng/IConfigurable.java b/src/main/java/org/testng/IConfigurable.java
new file mode 100644
index 0000000..5c8b564
--- /dev/null
+++ b/src/main/java/org/testng/IConfigurable.java
@@ -0,0 +1,14 @@
+package org.testng;
+
+/**
+ * If a test class implements this interface, its run() method
+ * will be invoked instead of each configuration method found.  The invocation of
+ * the configuration method will then be performed upon invocation of the callBack()
+ * method of the IConfigureCallBack parameter.
+ *
+ * @author cbeust
+ * Sep 07, 2010
+ */
+public interface IConfigurable extends ITestNGListener {
+  public void run(IConfigureCallBack callBack, ITestResult testResult);
+}
diff --git a/src/main/java/org/testng/IConfigurationListener.java b/src/main/java/org/testng/IConfigurationListener.java
new file mode 100755
index 0000000..eba3033
--- /dev/null
+++ b/src/main/java/org/testng/IConfigurationListener.java
@@ -0,0 +1,22 @@
+package org.testng;

+

+/**

+ * Listener interface for events related to configuration methods.

+ */

+public interface IConfigurationListener extends ITestNGListener {

+

+  /**

+   * Invoked whenever a configuration method succeeded.

+   */

+  void onConfigurationSuccess(ITestResult itr);

+

+  /**

+   * Invoked whenever a configuration method failed.

+   */

+  void onConfigurationFailure(ITestResult itr);

+

+  /**

+   * Invoked whenever a configuration method was skipped.

+   */

+  void onConfigurationSkip(ITestResult itr);

+}

diff --git a/src/main/java/org/testng/IConfigurationListener2.java b/src/main/java/org/testng/IConfigurationListener2.java
new file mode 100644
index 0000000..9cd16a9
--- /dev/null
+++ b/src/main/java/org/testng/IConfigurationListener2.java
@@ -0,0 +1,10 @@
+package org.testng;
+
+public interface IConfigurationListener2 extends IConfigurationListener {
+
+  /**
+   * Invoked before a configuration method is invoked.
+   */
+  void beforeConfiguration(ITestResult tr);
+
+}
diff --git a/src/main/java/org/testng/IConfigureCallBack.java b/src/main/java/org/testng/IConfigureCallBack.java
new file mode 100644
index 0000000..e493ce8
--- /dev/null
+++ b/src/main/java/org/testng/IConfigureCallBack.java
@@ -0,0 +1,26 @@
+package org.testng;
+
+/**
+ * A parameter of this type will be passed to the run() method of a IConfigurable.
+ * Invoking runConfigurationMethod() on that parameter will cause the test method currently
+ * being diverted to be invoked.
+ *
+ * <b>This interface is not meant to be implemented by clients, only by TestNG.</b>
+ *
+ * @see org.testng.IConfigurable
+ *
+ * @author cbeust
+ * Sep 07, 2010
+ */
+public interface IConfigureCallBack {
+
+  /**
+   * Invoke the test method currently being hijacked.
+   */
+  public void runConfigurationMethod(ITestResult testResult);
+
+  /**
+   * @return the parameters that will be used to invoke the configuration method.
+   */
+  public Object[] getParameters();
+}
diff --git a/src/main/java/org/testng/IExecutionListener.java b/src/main/java/org/testng/IExecutionListener.java
new file mode 100644
index 0000000..1ccae7c
--- /dev/null
+++ b/src/main/java/org/testng/IExecutionListener.java
@@ -0,0 +1,20 @@
+package org.testng;
+
+/**
+ * A listener used to monitor when a TestNG run starts and ends.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public interface IExecutionListener extends ITestNGListener {
+
+  /**
+   * Invoked before the TestNG run starts.
+   */
+  void onExecutionStart();
+
+  /**
+   * Invoked once all the suites have been run.
+   */
+  void onExecutionFinish();
+
+}
diff --git a/src/main/java/org/testng/IExpectedExceptionsHolder.java b/src/main/java/org/testng/IExpectedExceptionsHolder.java
new file mode 100644
index 0000000..18a174b
--- /dev/null
+++ b/src/main/java/org/testng/IExpectedExceptionsHolder.java
@@ -0,0 +1,20 @@
+package org.testng;
+
+public interface IExpectedExceptionsHolder {
+
+  /**
+   * Get the message in case the Throwable thrown by the test is not matching.
+   *
+   * @param ite The Throwable thrown by the test
+   * @return The message which will be displayed as test result
+   */
+  String getWrongExceptionMessage(Throwable ite);
+
+  /**
+   * Check if the Throwable thrown by the test is matching with the holder logic
+   *
+   * @param ite The Throwable thrown by the test
+   * @return true if the Throwable is matching with the holder logic, false otherwise
+   */
+  boolean isThrowableMatching(Throwable ite);
+}
diff --git a/src/main/java/org/testng/IExtraOutput.java b/src/main/java/org/testng/IExtraOutput.java
new file mode 100755
index 0000000..1912e01
--- /dev/null
+++ b/src/main/java/org/testng/IExtraOutput.java
@@ -0,0 +1,24 @@
+package org.testng;
+
+import java.io.Serializable;
+import java.util.List;
+
+
+/**
+ * This class is used by Reporter to store the extra output to be later
+ * included in the HTML report:
+ * - User-generated report
+ * - Parameter info
+ *
+ * Created on Feb 16, 2006
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public interface IExtraOutput extends Serializable {
+
+  /**
+   * @return a List<String> representing the parameters passed
+   * to this test method, or an empty List if no parameters were used.
+   */
+  public List<String> getParameterOutput();
+
+}
diff --git a/src/main/java/org/testng/IHookCallBack.java b/src/main/java/org/testng/IHookCallBack.java
new file mode 100755
index 0000000..dcf9425
--- /dev/null
+++ b/src/main/java/org/testng/IHookCallBack.java
@@ -0,0 +1,29 @@
+package org.testng;
+
+/**
+ * A parameter of this type will be passed to the run() method of a IHookable.
+ * Invoking runTestMethod() on that parameter will cause the test method currently
+ * being diverted to be invoked.
+ *
+ *  <p>
+ *
+ * <b>This interface is not meant to be implemented by clients, only by TestNG.</b>
+ *
+ * @see org.testng.IHookable
+ *
+ *
+ * @author cbeust
+ * Jan 28, 2006
+ */
+public interface IHookCallBack {
+
+  /**
+   * Invoke the test method currently being hijacked.
+   */
+  public void runTestMethod(ITestResult testResult);
+
+  /**
+   * @return the parameters that will be used to invoke the test method.
+   */
+  public Object[] getParameters();
+}
diff --git a/src/main/java/org/testng/IHookable.java b/src/main/java/org/testng/IHookable.java
new file mode 100755
index 0000000..c9b7f2c
--- /dev/null
+++ b/src/main/java/org/testng/IHookable.java
@@ -0,0 +1,30 @@
+package org.testng;
+
+/**
+ * If a test class implements this interface, its run() method
+ * will be invoked instead of each @Test method found.  The invocation of
+ * the test method will then be performed upon invocation of the callBack()
+ * method of the IHookCallBack parameter.
+ *
+ * This is useful to test classes that require JAAS authentication, which can
+ * be implemented as follows:
+ *
+ * <pre>
+ * public void run(final IHookCallBack icb, ITestResult testResult) {
+ *   // Preferably initialized in a @Configuration method
+ *   mySubject = authenticateWithJAAs();
+ *
+ *   Subject.doAs(mySubject, new PrivilegedExceptionAction() {
+ *     public Object run() {
+ *       icb.callback(testResult);
+ *     }
+ *   };
+ * }
+ * </pre>
+ *
+ * @author cbeust
+ * Jan 28, 2006
+ */
+public interface IHookable extends ITestNGListener {
+  public void run(IHookCallBack callBack, ITestResult testResult);
+}
diff --git a/src/main/java/org/testng/IInstanceInfo.java b/src/main/java/org/testng/IInstanceInfo.java
new file mode 100755
index 0000000..eca6c64
--- /dev/null
+++ b/src/main/java/org/testng/IInstanceInfo.java
@@ -0,0 +1,23 @@
+package org.testng;
+
+/**
+ * This class defines a pair of instance/class.  A method with @Factory
+ * can return an array of these objects instead of Object[] so that
+ * instances can be dynamic proxies or mock objects and still provide
+ * enough information to TestNG to figure out what classes the
+ * annotations should be looked up in.
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public interface IInstanceInfo {
+
+  /**
+   * @return The instance on which the tests will be invoked.
+   */
+  public Object getInstance();
+
+  /**
+   * @return The class on which the TestNG annotations should be looked for.
+   */
+  public Class getInstanceClass();
+}
diff --git a/src/main/java/org/testng/IInvokedMethod.java b/src/main/java/org/testng/IInvokedMethod.java
new file mode 100755
index 0000000..1d5dc13
--- /dev/null
+++ b/src/main/java/org/testng/IInvokedMethod.java
@@ -0,0 +1,32 @@
+package org.testng;
+
+/**
+ * An interface representing a method that has been invoked by TestNG.
+ *
+ * This interface is internal.
+ */
+public interface IInvokedMethod {
+
+  /**
+   * @return true if this method is a test method
+   */
+  public abstract boolean isTestMethod();
+
+  /**
+   * @return true if this method is a configuration method (@BeforeXXX or @AfterXXX)
+   */
+  public abstract boolean isConfigurationMethod();
+
+  /**
+   * @return the test method
+   */
+  public abstract ITestNGMethod getTestMethod();
+
+  public ITestResult getTestResult();
+
+  /**
+   * @return the date when this method was run
+   */
+  public abstract long getDate();
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/testng/IInvokedMethodListener.java b/src/main/java/org/testng/IInvokedMethodListener.java
new file mode 100755
index 0000000..c042228
--- /dev/null
+++ b/src/main/java/org/testng/IInvokedMethodListener.java
@@ -0,0 +1,12 @@
+package org.testng;
+
+/**
+ * A listener that gets invoked before and after a method is invoked by TestNG.
+ * This listener will only be invoked for configuration and test methods.
+ */
+public interface IInvokedMethodListener extends ITestNGListener {
+
+  void beforeInvocation(IInvokedMethod method, ITestResult testResult);
+
+  void afterInvocation(IInvokedMethod method, ITestResult testResult);
+}
diff --git a/src/main/java/org/testng/IInvokedMethodListener2.java b/src/main/java/org/testng/IInvokedMethodListener2.java
new file mode 100644
index 0000000..8b5ca4a
--- /dev/null
+++ b/src/main/java/org/testng/IInvokedMethodListener2.java
@@ -0,0 +1,22 @@
+package org.testng;
+
+/**
+ * Implement this interface if you need a handle to {@link ITestContext}.
+ *
+ * @author karthik.krishnan@gmail.com (Karthik Krishnan)
+ */
+public interface IInvokedMethodListener2 extends IInvokedMethodListener {
+
+  /**
+   * To be implemented if the method needs a handle to contextual information.
+   */
+  void beforeInvocation(IInvokedMethod method, ITestResult testResult,
+      ITestContext context);
+
+  /**
+   * To be implemented if the method needs a handle to contextual information.
+   */
+  void afterInvocation(IInvokedMethod method, ITestResult testResult,
+      ITestContext context);
+
+}
diff --git a/src/main/java/org/testng/IMethodInstance.java b/src/main/java/org/testng/IMethodInstance.java
new file mode 100755
index 0000000..de3727e
--- /dev/null
+++ b/src/main/java/org/testng/IMethodInstance.java
@@ -0,0 +1,18 @@
+package org.testng;
+
+
+/**
+ * This interface captures a test method along with all the instances it should
+ * be run on.
+ */
+public interface IMethodInstance {
+
+  ITestNGMethod getMethod();
+
+  /**
+   * @deprecated Use getInstance()
+   */
+  Object[] getInstances();
+
+  Object getInstance();
+}
diff --git a/src/main/java/org/testng/IMethodInterceptor.java b/src/main/java/org/testng/IMethodInterceptor.java
new file mode 100755
index 0000000..5418ea6
--- /dev/null
+++ b/src/main/java/org/testng/IMethodInterceptor.java
@@ -0,0 +1,34 @@
+package org.testng;
+
+import java.util.List;
+
+/**
+ * This class is used to alter the list of test methods that TestNG is about to run.
+ *
+ * <p>
+ *
+ * An instance of this class will be invoked right before TestNG starts invoking test methods.
+ * Only methods that have no dependents and that don't depend on any other test methods will
+ * be passed in parameter.  Implementers of this interface need to return a list of {@link IMethodInstance}
+ * that represents the list of test methods they want run.  TestNG will run these methods in the
+ * same order found in the returned value.
+ *
+ * <p>
+ *
+ * Typically, the returned list will be just the methods passed in parameter but sorted
+ * differently, but it can actually have any size (it can be empty, it can be of the
+ * same size as the original list or it can contain more methods).
+ *
+ * <p>
+ *
+ * The {@link ITestContext} is passed in the <tt>intercept</tt> method so that implementers can set user values
+ * (using {@link ITestContext#setAttribute(String, Object)}), which they can then look up
+ * later while generating the reports.
+ *
+ * @author cbeust
+ */
+public interface IMethodInterceptor extends ITestNGListener {
+
+  List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context);
+
+}
diff --git a/src/main/java/org/testng/IMethodSelector.java b/src/main/java/org/testng/IMethodSelector.java
new file mode 100755
index 0000000..dd260df
--- /dev/null
+++ b/src/main/java/org/testng/IMethodSelector.java
@@ -0,0 +1,37 @@
+package org.testng;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * This interface is used to augment or replace TestNG's algorithm to
+ * decide whether a test method should be included in a test run.
+ *
+ * Created on Sep 26, 2005
+ * @author cbeust
+ */
+public interface IMethodSelector extends Serializable {
+
+  /**
+   * @param context The selector context.  The implementation of this method
+   * can invoke setHalted(true) to indicate that no other Method Selector
+   * should be invoked by TestNG after this one.  Additionally, this
+   * implementation can manipulate the Map object returned by
+   * getUserData().
+   * @param method The test method
+   * @param isTestMethod true if this is a @Test method, false if it's a
+   * configuration method
+   * @return true if this method should be included in the test run, false
+   * otherwise
+   */
+  public boolean includeMethod(IMethodSelectorContext context,
+      ITestNGMethod method, boolean isTestMethod);
+
+  /**
+   * Invoked when all the test methods are known so that the method selector
+   * can perform additional work, such as adding the transitive closure of
+   * all the groups being included and depended upon.
+   */
+  public void setTestMethods(List<ITestNGMethod> testMethods);
+
+}
diff --git a/src/main/java/org/testng/IMethodSelectorContext.java b/src/main/java/org/testng/IMethodSelectorContext.java
new file mode 100755
index 0000000..060841d
--- /dev/null
+++ b/src/main/java/org/testng/IMethodSelectorContext.java
@@ -0,0 +1,33 @@
+package org.testng;

+

+import java.util.Map;

+

+/**

+ * An implementation of this interface is passed to all the Method Selectors

+ * when their includeMethod() is invoked.  Method selectors can invoke

+ * any method of this context at that time.

+ *

+ * Created on Jan 3, 2007

+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>

+ */

+public interface IMethodSelectorContext {

+

+  /**

+   * @return true if no more Method Selectors should be invoked after

+   * the current one.

+   */

+  public boolean isStopped();

+

+  /**

+   * Indicate that no other Method Selectors should be invoked after the

+   * current one if stopped is false.

+   * @param stopped

+   */

+  public void setStopped(boolean stopped);

+

+  /**

+   * @return a Map that can be freely manipulated by the Method Selector.

+   * This can be used to share information among several Method Selectors.

+   */

+  public Map<Object, Object> getUserData();

+}

diff --git a/src/main/java/org/testng/IModuleFactory.java b/src/main/java/org/testng/IModuleFactory.java
new file mode 100644
index 0000000..e5900aa
--- /dev/null
+++ b/src/main/java/org/testng/IModuleFactory.java
@@ -0,0 +1,22 @@
+package org.testng;
+
+import com.google.inject.Module;
+
+/**
+ * This interface is used by the moduleFactory attribute of the @Guice
+ * annotation. It allows users to use different Guice modules based on the test
+ * class waiting to be injected.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public interface IModuleFactory {
+
+  /**
+   * @param context The current test context
+   * @param testClass The test class
+   *
+   * @return The Guice module that should be used to get an instance of this
+   * test class.
+   */
+  Module createModule(ITestContext context, Class<?> testClass);
+}
diff --git a/src/main/java/org/testng/IObjectFactory.java b/src/main/java/org/testng/IObjectFactory.java
new file mode 100755
index 0000000..acab854
--- /dev/null
+++ b/src/main/java/org/testng/IObjectFactory.java
@@ -0,0 +1,17 @@
+package org.testng;
+
+import java.lang.reflect.Constructor;
+
+/**
+ * Factory used to create all test instances. This factory is passed the constructor
+ * along with the parameters that TestNG calculated based on the environment
+ * (@Parameters, etc...).
+ *
+ * @see IObjectFactory2
+ *
+ * @author Hani Suleiman
+ * @since 5.6
+ */
+public interface IObjectFactory extends ITestObjectFactory {
+  Object newInstance(Constructor constructor, Object... params);
+}
diff --git a/src/main/java/org/testng/IObjectFactory2.java b/src/main/java/org/testng/IObjectFactory2.java
new file mode 100644
index 0000000..95ccc97
--- /dev/null
+++ b/src/main/java/org/testng/IObjectFactory2.java
@@ -0,0 +1,16 @@
+package org.testng;
+
+
+/**
+ * Factory used to create all test instances. This object factory only receives the class
+ * in parameter.
+ *
+ * @see org.testng.IObjectFactory
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ *
+ * @since 5.14.6
+ */
+public interface IObjectFactory2 extends ITestObjectFactory {
+  Object newInstance(Class<?> cls);
+}
diff --git a/src/main/java/org/testng/IReporter.java b/src/main/java/org/testng/IReporter.java
new file mode 100755
index 0000000..495c307
--- /dev/null
+++ b/src/main/java/org/testng/IReporter.java
@@ -0,0 +1,20 @@
+package org.testng;
+
+import org.testng.xml.XmlSuite;
+
+import java.util.List;
+
+/**
+ * This interface can be implemented by clients to generate a report.  Its method
+ * generateReport() will be invoked after all the suite have run and the parameters
+ * give all the test results that happened during that run.
+ *
+ * @author cbeust
+ * Feb 17, 2006
+ */
+public interface IReporter extends ITestNGListener {
+  /**
+   * Generate a report for the given suites into the specified output directory.
+   */
+  void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory);
+}
diff --git a/src/main/java/org/testng/IResultMap.java b/src/main/java/org/testng/IResultMap.java
new file mode 100755
index 0000000..103c27e
--- /dev/null
+++ b/src/main/java/org/testng/IResultMap.java
@@ -0,0 +1,23 @@
+package org.testng;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Set;
+
+public interface IResultMap extends Serializable {
+
+  public void addResult(ITestResult result, ITestNGMethod method);
+
+  public Set<ITestResult> getResults(ITestNGMethod method);
+
+  public Set<ITestResult> getAllResults();
+
+  public void removeResult(ITestNGMethod m);
+
+  public void removeResult(ITestResult r);
+
+  public Collection<ITestNGMethod> getAllMethods();
+
+  public int size();
+
+}
diff --git a/src/main/java/org/testng/IRetryAnalyzer.java b/src/main/java/org/testng/IRetryAnalyzer.java
new file mode 100755
index 0000000..a8d255c
--- /dev/null
+++ b/src/main/java/org/testng/IRetryAnalyzer.java
@@ -0,0 +1,18 @@
+package org.testng;
+
+/**
+ * Interface to implement to be able to have a chance to retry a failed test.
+ *
+ * @author tocman@gmail.com (Jeremie Lenfant-Engelmann)
+ *
+ */
+public interface IRetryAnalyzer {
+
+  /**
+   * Returns true if the test method has to be retried, false otherwise.
+   *
+   * @param result The result of the test method that just ran.
+   * @return true if the test method has to be retried, false otherwise.
+   */
+  public boolean retry(ITestResult result);
+}
diff --git a/src/main/java/org/testng/ISuite.java b/src/main/java/org/testng/ISuite.java
new file mode 100755
index 0000000..f8928da
--- /dev/null
+++ b/src/main/java/org/testng/ISuite.java
@@ -0,0 +1,122 @@
+package org.testng;

+

+

+import java.util.Collection;

+import java.util.List;

+import java.util.Map;

+

+import org.testng.internal.annotations.IAnnotationFinder;

+import org.testng.xml.XmlSuite;

+

+import com.google.inject.Injector;

+

+/**

+ * Interface defining a Test Suite.

+ *

+ * @author Cedric Beust, Aug 6, 2004

+ *

+ */

+public interface ISuite extends IAttributes {

+

+  /**

+   * @return the name of this suite.

+   */

+  public String getName();

+

+  /**

+   * @return The results for this suite.

+   */

+  public Map<String, ISuiteResult> getResults();

+

+  /**

+   * @return The object factory used to create all test instances.

+   */

+  public IObjectFactory getObjectFactory();

+  public IObjectFactory2 getObjectFactory2();

+

+  /**

+   * @return The output directory used for the reports.

+   */

+  public String getOutputDirectory();

+

+  /**

+   * @return true if the tests must be run in parallel.

+   */

+  public String getParallel();

+

+  public String getParentModule();

+

+  public String getGuiceStage();

+

+  /**

+   * @return The value of this parameter, or null if none was specified.

+   */

+  public String getParameter(String parameterName);

+

+  /**

+   * Retrieves the map of groups and their associated test methods.

+   *

+   * @return A map where the key is the group and the value is a list

+   * of methods used by this group.

+   */

+  public Map<String, Collection<ITestNGMethod>> getMethodsByGroups();

+

+  /**

+   * Retrieves the list of all the methods that were invoked during this run.

+   * @return a collection of ITestNGMethods belonging to all tests included in the suite.

+   * @deprecated Use getAllInvokedMethods().

+   */

+  @Deprecated

+  public Collection<ITestNGMethod> getInvokedMethods();

+

+  /**

+   * @return a list of all the methods that were invoked in this suite.

+   */

+  public List<IInvokedMethod> getAllInvokedMethods();

+

+  /**

+   * @return All the methods that were not included in this test run.

+   */

+  public Collection<ITestNGMethod> getExcludedMethods();

+

+  /**

+   * Triggers the start of running tests included in the suite.

+   */

+  public void run();

+

+  /**

+   * @return The host where this suite was run, or null if it was run locally.  The

+   * returned string has the form:  host:port

+   */

+  public String getHost();

+

+  /**

+   * Retrieves the shared state for a suite.

+   *

+   * @return the share state of the current suite.

+   */

+  public SuiteRunState getSuiteState();

+

+  /**

+   * @return the annotation finder used for the specified type (JDK5 or javadoc)

+   */

+  public IAnnotationFinder getAnnotationFinder();

+

+  /**

+   * @return The representation of the current XML suite file.

+   */

+  public XmlSuite getXmlSuite();

+

+  public void addListener(ITestNGListener listener);

+

+  public Injector getParentInjector();

+

+  public void setParentInjector(Injector injector);

+

+  /**

+   * @return the total number of methods found in this suite. The presence of

+   * factories or data providers might cause the actual number of test methods

+   * run be bigger than this list.

+   */

+  List<ITestNGMethod> getAllMethods();

+}

diff --git a/src/main/java/org/testng/ISuiteListener.java b/src/main/java/org/testng/ISuiteListener.java
new file mode 100755
index 0000000..f834c19
--- /dev/null
+++ b/src/main/java/org/testng/ISuiteListener.java
@@ -0,0 +1,22 @@
+package org.testng;
+
+
+/**
+ * Listener for test suites.
+ *
+ * @author Cedric Beust, Aug 6, 2004
+ *
+ */
+public interface ISuiteListener extends ITestNGListener {
+  /**
+   * This method is invoked before the SuiteRunner starts.
+   */
+  public void onStart(ISuite suite);
+
+  /**
+   * This method is invoked after the SuiteRunner has run all
+   * the test suites.
+   */
+  public void onFinish(ISuite suite);
+
+}
diff --git a/src/main/java/org/testng/ISuiteResult.java b/src/main/java/org/testng/ISuiteResult.java
new file mode 100755
index 0000000..6ddcc1e
--- /dev/null
+++ b/src/main/java/org/testng/ISuiteResult.java
@@ -0,0 +1,24 @@
+package org.testng;
+
+import java.io.Serializable;
+
+
+/**
+ * This class represents the result of a suite run.
+ *
+ * @author Cedric Beust, Aug 6, 2004
+ *
+ */
+public interface ISuiteResult extends Serializable {
+
+  /**
+   * @return The name of the property file for these tests.
+   */
+  public String getPropertyFileName();
+
+  /**
+   * @return The testing context for these tests.
+   */
+  public ITestContext getTestContext();
+
+}
diff --git a/src/main/java/org/testng/ITest.java b/src/main/java/org/testng/ITest.java
new file mode 100755
index 0000000..9d04556
--- /dev/null
+++ b/src/main/java/org/testng/ITest.java
@@ -0,0 +1,19 @@
+package org.testng;
+
+/**
+ * If a test class implements this interface, it will receive a
+ * special treatment, such as having the test name displayed
+ * in the HTML reports.
+ *
+ * @author cbeust
+ * Jun 6, 2006
+ */
+public interface ITest {
+
+  /**
+   * The name of test instance(s).
+   * @return name associated with a particular instance of a test.
+   */
+  public String getTestName();
+
+}
diff --git a/src/main/java/org/testng/ITestClass.java b/src/main/java/org/testng/ITestClass.java
new file mode 100755
index 0000000..4091ba3
--- /dev/null
+++ b/src/main/java/org/testng/ITestClass.java
@@ -0,0 +1,135 @@
+package org.testng;
+
+import java.io.Serializable;
+
+/**
+ * This class represents a test class:
+ * <ul>
+ * <li> The test methods
+ * <li>The configuration methods (test and method)
+ * <li>The class file
+ * </ul>
+ *
+ * Note that the methods returned by instances of this class
+ * are expected to be correct at runtime.  In other words,
+ * they might differ from what the ITestMethodFinder returned
+ * since ITestClass will take into account the groups being
+ * included and excluded.
+ *
+ * @author <a href = "mailto:cedric&#64;beust.com">Cedric Beust</a>
+ */
+public interface ITestClass extends IClass, Serializable {
+
+  /**
+   * Returns all the instances the methods will be invoked upon.
+   * This will typically be an array of one object in the absence
+   * of a @Factory annotation.
+   *
+   * @param reuse flag if a new set of instances must be returned
+   *  (if set to <tt>false</tt>)
+   * @return All the instances the methods will be invoked upon.
+   *
+   * {@inheritDoc}
+   */
+  @Override
+  Object[] getInstances(boolean reuse);
+
+  /**
+   * TODO cquezel JavaDoc.
+   *
+   * {@inheritDoc}
+   */
+  @Override
+  long[] getInstanceHashCodes();
+
+  /**
+   * @return The number of instances used in this class.  This method
+   * is needed for serialization since we don't know ahead of time if the
+   * instances of the test classes will be serializable.
+   */
+  @Override
+  int getInstanceCount();
+
+  /**
+   * Returns all the applicable test methods.
+   * @return All the applicable test methods.
+   */
+  ITestNGMethod[] getTestMethods();
+
+  /**
+   * Returns all the methods that should be invoked
+   * before a test method is invoked.
+   * @return All the methods that should be invoked
+   * before a test method is invoked.
+   */
+  ITestNGMethod[] getBeforeTestMethods();
+
+  /**
+   * Returns all the methods that should be invoked
+   * after a test method completes.
+   * @return All the methods that should be invoked
+   * after a test method completes.
+   */
+  ITestNGMethod[] getAfterTestMethods();
+
+  /**
+   * Return all the methods that should be invoked
+   * after the test class has been created and before
+   * any of its test methods is invoked.
+   * @return All the methods that should be invoked
+   * after the test class has been created and before
+   * any of its test methods is invoked.
+   */
+  ITestNGMethod[] getBeforeClassMethods();
+
+  /**
+   * Returns all the methods that should be invoked
+   * after all the tests have been run on this class.
+   * @return All the methods that should be invoked
+   * after all the tests have been run on this class.
+   */
+  ITestNGMethod[] getAfterClassMethods();
+
+  /**
+   * Returns All the methods that should be invoked
+   * before the suite is run.
+   * @return All the methods that should be invoked
+   * before the suite is run.
+   */
+  ITestNGMethod[] getBeforeSuiteMethods();
+
+  /**
+   * Returns all the methods that should be invoked
+   * after the suite has run.
+   * @return All the methods that should be invoked
+   * after the suite has run.
+   */
+  ITestNGMethod[] getAfterSuiteMethods();
+
+  /**
+   * Returns all &#64;Configuration methods that should be invoked before any others in the
+   * current test.
+   * @return all @Configuration methods that should be invoked before any others in the current test.
+   */
+  ITestNGMethod[] getBeforeTestConfigurationMethods();
+
+  /**
+   * Returns all &#64;Configuration methods that should be invoked last before any others
+   * in the current test.
+   * @return all @Configuration methods that should be invoked last before any others
+   * in the current test.
+   */
+  ITestNGMethod[] getAfterTestConfigurationMethods();
+
+  /**
+   * Returns all &#64;Configuration methods that should be invoked before certain groups.
+   * @return all @Configuration methods that should be invoked before certain groups.
+   */
+  ITestNGMethod[] getBeforeGroupsMethods();
+
+  /**
+   * Returns all &#64;Configuration methods that should be invoked after certain groups.
+   * @return all @Configuration methods that should be invoked after certain groups.
+   */
+  ITestNGMethod[] getAfterGroupsMethods();
+}
diff --git a/src/main/java/org/testng/ITestClassFinder.java b/src/main/java/org/testng/ITestClassFinder.java
new file mode 100755
index 0000000..92c2c8d
--- /dev/null
+++ b/src/main/java/org/testng/ITestClassFinder.java
@@ -0,0 +1,24 @@
+package org.testng;
+
+/**
+ * This class is used by TestNG to locate the test classes.
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public interface ITestClassFinder {
+  /**
+   * @return An array of all the classes that contain test
+   * methods.  This method usually returns an array of one
+   * class, which is the class on which TestNG is running,
+   * except in the following cases.
+   * - TestNG:  the class contains an @Factory method
+   * - JUnit:  the class contains a suite() method
+   */
+  public IClass[] findTestClasses();
+
+  /**
+   * Return the IClass for a given class
+   */
+  public IClass getIClass(Class cls);
+
+}
diff --git a/src/main/java/org/testng/ITestContext.java b/src/main/java/org/testng/ITestContext.java
new file mode 100644
index 0000000..fe6f35d
--- /dev/null
+++ b/src/main/java/org/testng/ITestContext.java
@@ -0,0 +1,126 @@
+package org.testng;
+
+import com.google.inject.Injector;
+import com.google.inject.Module;
+
+import org.testng.internal.ClassImpl;
+import org.testng.xml.XmlTest;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+
+/**
+ * This class defines a test context which contains all the information
+ * for a given test run.  An instance of this context is passed to the
+ * test listeners so they can query information about their
+ * environment.
+ *
+ * @author Cedric Beust, Aug 6, 2004
+ * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
+ */
+public interface ITestContext extends IAttributes {
+
+  /**
+   * The name of this test.
+   */
+  public String getName();
+
+  /**
+   * When this test started running.
+   */
+  public Date getStartDate();
+
+  /**
+   * When this test stopped running.
+   */
+  public Date getEndDate();
+
+  /**
+   * @return A list of all the tests that run successfully.
+   */
+  public IResultMap getPassedTests();
+
+  /**
+   * @return A list of all the tests that were skipped
+   */
+  public IResultMap  getSkippedTests();
+
+  /**
+   * @return A list of all the tests that failed but are being ignored because
+   * annotated with a successPercentage.
+   */
+  public IResultMap  getFailedButWithinSuccessPercentageTests();
+
+  /**
+   * @return A map of all the tests that passed, indexed by
+   * their ITextMethor.
+   *
+   * @see org.testng.ITestNGMethod
+   */
+  public IResultMap getFailedTests();
+
+  /**
+   * @return All the groups that are included for this test run.
+   */
+  public String[] getIncludedGroups();
+
+  /**
+   * @return All the groups that are excluded for this test run.
+   */
+  public String[] getExcludedGroups();
+
+  /**
+   * @return Where the reports will be generated.
+   */
+  public String getOutputDirectory();
+
+  /**
+   * @return The Suite object that was passed to the runner
+   * at start-up.
+   */
+  public ISuite getSuite();
+
+  /**
+   * @return All the test methods that were run.
+   */
+  public ITestNGMethod[] getAllTestMethods();
+
+  /**
+   * @return The host where this test was run, or null if it was run locally.  The
+   * returned string has the form:  host:port
+   */
+  public String getHost();
+
+  /**
+   * @return All the methods that were not included in this test run.
+   */
+  public Collection<ITestNGMethod> getExcludedMethods();
+
+  /**
+   * Retrieves information about the successful configuration method invocations.
+   */
+  public IResultMap getPassedConfigurations();
+
+  /**
+   * Retrieves information about the skipped configuration method invocations.
+   */
+  public IResultMap getSkippedConfigurations();
+
+  /**
+   * Retrieves information about the failed configuration method invocations.
+   */
+  public IResultMap getFailedConfigurations();
+
+  /**
+   * @return the current XmlTest.
+   */
+  public XmlTest getCurrentXmlTest();
+
+  public List<Module> getGuiceModules(Class<? extends Module> cls);
+
+  public Injector getInjector(List<Module> moduleInstances);
+  Injector getInjector(IClass iClass);
+  public void addInjector(List<Module> moduleInstances, Injector injector);
+}
diff --git a/src/main/java/org/testng/ITestListener.java b/src/main/java/org/testng/ITestListener.java
new file mode 100755
index 0000000..ad89973
--- /dev/null
+++ b/src/main/java/org/testng/ITestListener.java
@@ -0,0 +1,67 @@
+package org.testng;
+
+/**
+ * A listener for test running.
+ *
+ * @author Cedric Beust
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ * @author Hani Suleiman
+ */
+public interface ITestListener extends ITestNGListener {
+  /**
+   * Invoked each time before a test will be invoked.
+   * The <code>ITestResult</code> is only partially filled with the references to
+   * class, method, start millis and status.
+   *
+   * @param result the partially filled <code>ITestResult</code>
+   * @see ITestResult#STARTED
+   */
+  void onTestStart(ITestResult result);
+
+  /**
+   * Invoked each time a test succeeds.
+   *
+   * @param result <code>ITestResult</code> containing information about the run test
+   * @see ITestResult#SUCCESS
+   */
+  public void onTestSuccess(ITestResult result);
+
+  /**
+   * Invoked each time a test fails.
+   *
+   * @param result <code>ITestResult</code> containing information about the run test
+   * @see ITestResult#FAILURE
+   */
+  public void onTestFailure(ITestResult result);
+
+  /**
+   * Invoked each time a test is skipped.
+   *
+   * @param result <code>ITestResult</code> containing information about the run test
+   * @see ITestResult#SKIP
+   */
+  public void onTestSkipped(ITestResult result);
+
+  /**
+   * Invoked each time a method fails but has been annotated with
+   * successPercentage and this failure still keeps it within the
+   * success percentage requested.
+   *
+   * @param result <code>ITestResult</code> containing information about the run test
+   * @see ITestResult#SUCCESS_PERCENTAGE_FAILURE
+   */
+  public void onTestFailedButWithinSuccessPercentage(ITestResult result);
+
+  /**
+   * Invoked after the test class is instantiated and before
+   * any configuration method is called.
+   */
+  public void onStart(ITestContext context);
+
+  /**
+   * Invoked after all the tests have run and all their
+   * Configuration methods have been called.
+   */
+  public void onFinish(ITestContext context);
+
+}
diff --git a/src/main/java/org/testng/ITestMethodFinder.java b/src/main/java/org/testng/ITestMethodFinder.java
new file mode 100755
index 0000000..9cecd35
--- /dev/null
+++ b/src/main/java/org/testng/ITestMethodFinder.java
@@ -0,0 +1,72 @@
+package org.testng;
+
+import org.testng.xml.XmlTest;
+
+
+
+
+/**
+ * This interface allows to modify the strategy used by TestRunner
+ * to find its test methods.  At the time of this writing, TestNG
+ * supports two different strategies:  TestNG (using annotations to
+ * locate these methods) and JUnit (setUp()/tearDown() and all
+ * methods that start with "test" or have a suite() method).
+ *
+ * @author Cedric Beust, May 3, 2004
+ *
+ */
+public interface ITestMethodFinder {
+
+  /**
+   * @return All the applicable test methods.
+   */
+  ITestNGMethod[] getTestMethods(Class<?> cls, XmlTest xmlTest);
+
+  /**
+   * @return All the methods that should be invoked
+   * before a test method is invoked.
+   */
+  ITestNGMethod[] getBeforeTestMethods(Class<?> cls);
+
+  /**
+   * @return All the methods that should be invoked
+   * after a test method completes.
+   */
+  ITestNGMethod[] getAfterTestMethods(Class<?> cls);
+
+  /**
+   * @return All the methods that should be invoked
+   * after the test class has been created and before
+   * any of its test methods is invoked.
+   */
+  ITestNGMethod[] getBeforeClassMethods(Class<?> cls);
+
+  /**
+   * @return All the methods that should be invoked
+   * after the test class has been created and after
+   * all its test methods have completed.
+   */
+  ITestNGMethod[] getAfterClassMethods(Class<?> cls);
+
+  /**
+   * @return All the methods that should be invoked
+   * before the suite starts running.
+   */
+  ITestNGMethod[] getBeforeSuiteMethods(Class<?> cls);
+
+  /**
+   * @return All the methods that should be invoked
+   * after the suite has run all its tests.
+   */
+  ITestNGMethod[] getAfterSuiteMethods(Class<?> cls);
+
+  ITestNGMethod[] getBeforeTestConfigurationMethods(Class<?> testClass);
+
+  ITestNGMethod[] getAfterTestConfigurationMethods(Class<?> testClass);
+
+  ITestNGMethod[] getBeforeGroupsConfigurationMethods(Class<?> testClass);
+
+  ITestNGMethod[] getAfterGroupsConfigurationMethods(Class<?> testClass);
+
+
+}
diff --git a/src/main/java/org/testng/ITestNGListener.java b/src/main/java/org/testng/ITestNGListener.java
new file mode 100755
index 0000000..ed4344c
--- /dev/null
+++ b/src/main/java/org/testng/ITestNGListener.java
@@ -0,0 +1,10 @@
+package org.testng;
+
+/**
+ * This is a marker interface for all objects that can be passed
+ * as a -listener argument.
+ *
+ * @author cbeust
+ */
+public interface ITestNGListener {
+}
diff --git a/src/main/java/org/testng/ITestNGListenerFactory.java b/src/main/java/org/testng/ITestNGListenerFactory.java
new file mode 100644
index 0000000..c66fd25
--- /dev/null
+++ b/src/main/java/org/testng/ITestNGListenerFactory.java
@@ -0,0 +1,14 @@
+package org.testng;
+
+/**
+ * A factory used to create instances of ITestNGListener.  Users can implement this interface
+ * in any of their test classes but there can be only one such instance.
+ */
+public interface ITestNGListenerFactory {
+
+  /**
+   * Create and return an instance of the listener class passed in parameter.  Return null
+   * if you want to use the default factory.
+   */
+  ITestNGListener createListener(Class<? extends ITestNGListener> listenerClass);
+}
diff --git a/src/main/java/org/testng/ITestNGMethod.java b/src/main/java/org/testng/ITestNGMethod.java
new file mode 100755
index 0000000..0738b9a
--- /dev/null
+++ b/src/main/java/org/testng/ITestNGMethod.java
@@ -0,0 +1,268 @@
+package org.testng;

+

+

+import org.testng.internal.ConstructorOrMethod;

+import org.testng.xml.XmlTest;

+

+import java.io.Serializable;

+import java.lang.reflect.Method;

+import java.util.List;

+import java.util.Map;

+

+/**

+ * Describes a TestNG annotated method and the instance on which it will be invoked.

+ *

+ * This interface is not meant to be implemented by users.

+ *

+ * @author Cedric Beust, May 3, 2004

+ */

+public interface ITestNGMethod extends Comparable, Serializable, Cloneable {

+

+  /**

+   * @return The real class on which this method was declared

+   * (can be different from getMethod().getDeclaringClass() if

+   * the test method was defined in a superclass).

+   */

+  Class getRealClass();

+

+  ITestClass getTestClass();

+

+  /**

+   * Sets the test class having this method. This is not necessarily the declaring class.

+   *

+   * @param cls The test class having this method.

+   */

+  void setTestClass(ITestClass cls);

+

+  /**

+   * @return the corresponding Java test method.

+   * @deprecated This method is deprecated and can now return null. Use

+   * getConstructorOrMethod() instead.

+   */

+  @Deprecated

+  Method getMethod();

+

+  /**

+   * Returns the method name. This is needed for serialization because

+   * methods are not Serializable.

+   * @return the method name.

+   */

+  String getMethodName();

+

+  /**

+   * @return All the instances the methods will be invoked upon.

+   * This will typically be an array of one object in the absence

+   * of an @Factory annotation.

+   *

+   * @deprecated Use getInstance().

+   */

+  @Deprecated

+  Object[] getInstances();

+

+  Object getInstance();

+

+  /**

+   * Needed for serialization.

+   */

+  long[] getInstanceHashCodes();

+

+  /**

+   * @return The groups this method belongs to, possibly added to the groups

+   * declared on the class.

+   */

+  String[] getGroups();

+

+  /**

+   * @return The groups this method depends on, possibly added to the groups

+   * declared on the class.

+   */

+  String[] getGroupsDependedUpon();

+

+  /**

+   * If a group was not found.

+   */

+  String getMissingGroup();

+  public void setMissingGroup(String group);

+

+  /**

+   * Before and After groups

+   */

+  public String[] getBeforeGroups();

+  public String[] getAfterGroups();

+

+  /**

+   * @return The methods  this method depends on, possibly added to the methods

+   * declared on the class.

+   */

+  String[] getMethodsDependedUpon();

+  void addMethodDependedUpon(String methodName);

+

+  /**

+   * @return true if this method was annotated with @Test

+   */

+  boolean isTest();

+

+  /**

+   * @return true if this method was annotated with @Configuration

+   * and beforeTestMethod = true

+   */

+  boolean isBeforeMethodConfiguration();

+

+  /**

+   * @return true if this method was annotated with @Configuration

+   * and beforeTestMethod = false

+   */

+  boolean isAfterMethodConfiguration();

+

+  /**

+   * @return true if this method was annotated with @Configuration

+   * and beforeClassMethod = true

+   */

+  boolean isBeforeClassConfiguration();

+

+  /**

+   * @return true if this method was annotated with @Configuration

+   * and beforeClassMethod = false

+   */

+  boolean isAfterClassConfiguration();

+

+  /**

+   * @return true if this method was annotated with @Configuration

+   * and beforeSuite = true

+   */

+  boolean isBeforeSuiteConfiguration();

+

+  /**

+   * @return true if this method was annotated with @Configuration

+   * and afterSuite = true

+   */

+  boolean isAfterSuiteConfiguration();

+

+  /**

+   * @return <tt>true</tt> if this method is a @BeforeTest (@Configuration beforeTest=true)

+   */

+  boolean isBeforeTestConfiguration();

+

+  /**

+   * @return <tt>true</tt> if this method is an @AfterTest (@Configuration afterTest=true)

+   */

+  boolean isAfterTestConfiguration();

+

+  boolean isBeforeGroupsConfiguration();

+

+  boolean isAfterGroupsConfiguration();

+

+  /**

+   * @return The timeout in milliseconds.

+   */

+  long getTimeOut();

+  void setTimeOut(long timeOut);

+

+  /**

+   * @return the number of times this method needs to be invoked.

+   */

+  int getInvocationCount();

+  void setInvocationCount(int count);

+

+  /**

+   * @return the total number of thimes this method needs to be invoked, including possible

+   *         clones of this method - this is relevant when threadPoolSize is bigger than 1

+   *         where each clone of this method is only invoked once individually, i.e.

+   *         {@link org.testng.ITestNGMethod#getInvocationCount()} would always return 1.

+   */

+  int getTotalInvocationCount();

+

+  /**

+   * @return the success percentage for this method (between 0 and 100).

+   */

+  int getSuccessPercentage();

+

+  /**

+   * @return The id of the thread this method was run in.

+   */

+  String getId();

+

+  void setId(String id);

+

+  long getDate();

+

+  void setDate(long date);

+

+  /**

+   * Returns if this ITestNGMethod can be invoked from within IClass.

+   */

+  boolean canRunFromClass(IClass testClass);

+

+  /**

+   * @return true if this method is alwaysRun=true

+   */

+  boolean isAlwaysRun();

+

+  /**

+   * @return the number of threads to be used when invoking the method on parallel

+   */

+  int getThreadPoolSize();

+

+  void setThreadPoolSize(int threadPoolSize);

+

+  boolean getEnabled();

+

+  public String getDescription();

+  void setDescription(String description);

+

+  public void incrementCurrentInvocationCount();

+  public int getCurrentInvocationCount();

+  public void setParameterInvocationCount(int n);

+  public int getParameterInvocationCount();

+

+  public ITestNGMethod clone();

+

+  public IRetryAnalyzer getRetryAnalyzer();

+  public void setRetryAnalyzer(IRetryAnalyzer retryAnalyzer);

+

+  public boolean skipFailedInvocations();

+  public void setSkipFailedInvocations(boolean skip);

+

+  /**

+   * The time under which all invocationCount methods need to complete by.

+   */

+  public long getInvocationTimeOut();

+

+  public boolean ignoreMissingDependencies();

+  public void setIgnoreMissingDependencies(boolean ignore);

+

+  /**

+   * Which invocation numbers of this method should be used (only applicable

+   * if it uses a data provider). If this value is an empty list, use all the values

+   * returned from the data provider.  These values are read from the XML file in

+   * the <include invocationNumbers="..."> tag.

+   */

+  public List<Integer> getInvocationNumbers();

+  public void setInvocationNumbers(List<Integer> numbers);

+

+  /**

+   * The list of invocation numbers that failed, which is only applicable for

+   * methods that have a data provider.

+   */

+  public void addFailedInvocationNumber(int number);

+  public List<Integer> getFailedInvocationNumbers();

+

+  /**

+   * The scheduling priority. Lower priorities get scheduled first.

+   */

+  public int getPriority();

+  public void setPriority(int priority);

+

+  /**

+   * @return the XmlTest this method belongs to.

+   */

+  public XmlTest getXmlTest();

+

+  ConstructorOrMethod getConstructorOrMethod();

+

+  /**

+   * @return the parameters found in the include tag, if any

+   * @param test

+   */

+  Map<String, String> findMethodParameters(XmlTest test);

+}

diff --git a/src/main/java/org/testng/ITestObjectFactory.java b/src/main/java/org/testng/ITestObjectFactory.java
new file mode 100644
index 0000000..fedb5bf
--- /dev/null
+++ b/src/main/java/org/testng/ITestObjectFactory.java
@@ -0,0 +1,12 @@
+package org.testng;
+
+import java.io.Serializable;
+
+/**
+ * Parent interface of all the object factories.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public interface ITestObjectFactory extends Serializable {
+
+}
diff --git a/src/main/java/org/testng/ITestResult.java b/src/main/java/org/testng/ITestResult.java
new file mode 100755
index 0000000..78ba7e4
--- /dev/null
+++ b/src/main/java/org/testng/ITestResult.java
@@ -0,0 +1,97 @@
+package org.testng;
+
+
+/**
+ * This class describes the result of a test.
+ *
+ * @author Cedric Beust, May 2, 2004
+ * @since May 2, 2004
+ * @version $Revision: 721 $, $Date: 2009-05-23 09:55:46 -0700 (Sat, 23 May 2009) $
+ *
+ */
+public interface ITestResult extends IAttributes, Comparable<ITestResult> {
+
+  //
+  // Test status
+  //
+  public static final int SUCCESS = 1;
+  public static final int FAILURE = 2;
+  public static final int SKIP = 3;
+  public static final int SUCCESS_PERCENTAGE_FAILURE = 4;
+  public static final int STARTED= 16;
+
+  /**
+   * @return The status of this result, using one of the constants
+   * above.
+   */
+  public int getStatus();
+  public void setStatus(int status);
+
+  /**
+   * @return The test method this result represents.
+   */
+  public ITestNGMethod getMethod();
+
+  /**
+   * @return The parameters this method was invoked with.
+   */
+  public Object[] getParameters();
+  public void setParameters(Object[] parameters);
+
+  /**
+   * @return The test class used this object is a result for.
+   */
+  public IClass getTestClass();
+
+  /**
+   * @return The throwable that was thrown while running the
+   * method, or null if no exception was thrown.
+   */
+  public Throwable getThrowable();
+  public void setThrowable(Throwable throwable);
+
+  /**
+   * @return the start date for this test, in milliseconds.
+   */
+  public long getStartMillis();
+
+  /**
+   * @return the end date for this test, in milliseconds.
+   */
+  public long getEndMillis();
+  public void setEndMillis(long millis);
+
+  /**
+   * @return The name of this TestResult, typically identical to the name
+   * of the method.
+   */
+  public String getName();
+
+  /**
+   * @return true if if this test run is a SUCCESS
+   */
+  public boolean isSuccess();
+
+  /**
+   * @return The host where this suite was run, or null if it was run locally.  The
+   * returned string has the form:  host:port
+   */
+  public String getHost();
+
+  /**
+   * The instance on which this method was run.
+   */
+  public Object getInstance();
+
+  /**
+   * If this result's related instance implements ITest or use @Test(testName=...), returns its test name, otherwise returns null.
+   */
+  public String getTestName();
+
+  public String getInstanceName();
+  
+  /**
+   * @return the {@link ITestContext} for this test result.
+   */
+  public ITestContext getTestContext();
+}
diff --git a/src/main/java/org/testng/ITestRunnerFactory.java b/src/main/java/org/testng/ITestRunnerFactory.java
new file mode 100644
index 0000000..3e83cea
--- /dev/null
+++ b/src/main/java/org/testng/ITestRunnerFactory.java
@@ -0,0 +1,15 @@
+package org.testng;
+
+
+import org.testng.xml.XmlTest;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A factory for TestRunners to be used by SuiteRunners.
+ */
+public interface ITestRunnerFactory {
+	TestRunner newTestRunner(final ISuite suite, final XmlTest test,
+	    Collection<IInvokedMethodListener> listeners, List<IClassListener> classListeners);
+}
diff --git a/src/main/java/org/testng/InstanceOrderingMethodInterceptor.java b/src/main/java/org/testng/InstanceOrderingMethodInterceptor.java
new file mode 100644
index 0000000..2b7816e
--- /dev/null
+++ b/src/main/java/org/testng/InstanceOrderingMethodInterceptor.java
@@ -0,0 +1,51 @@
+package org.testng;
+
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A method interceptor that sorts its methods per instances (i.e. per class).
+ *
+ *
+ */
+class InstanceOrderingMethodInterceptor implements IMethodInterceptor {
+
+  @Override
+  public List<IMethodInstance> intercept(List<IMethodInstance> methods,
+      ITestContext context)  {
+    return groupMethodsByInstance(methods);
+  }
+
+  /**
+   * The default method interceptor which sorts methods by instances (i.e. by class).
+   */
+  private List<IMethodInstance> groupMethodsByInstance(List<IMethodInstance> methods) {
+    List<Object> instanceList = Lists.newArrayList();
+    Map<Object, List<IMethodInstance>> map = Maps.newHashMap();
+    for (IMethodInstance mi : methods) {
+      Object[] methodInstances = mi.getInstances();
+      for (Object instance : methodInstances) {
+        if (!instanceList.contains(instance)) {
+          instanceList.add(instance);
+        }
+        List<IMethodInstance> l = map.get(instance);
+        if (l == null) {
+          l = Lists.newArrayList();
+          map.put(instance, l);
+        }
+        l.add(mi);
+      }
+    }
+
+    List<IMethodInstance> result = Lists.newArrayList();
+    for (Object instance : instanceList) {
+      result.addAll(map.get(instance));
+    }
+
+    return result;
+  }
+
+}
diff --git a/src/main/java/org/testng/PreserveOrderMethodInterceptor.java b/src/main/java/org/testng/PreserveOrderMethodInterceptor.java
new file mode 100644
index 0000000..049996b
--- /dev/null
+++ b/src/main/java/org/testng/PreserveOrderMethodInterceptor.java
@@ -0,0 +1,33 @@
+package org.testng;
+
+import org.testng.internal.MethodInstance;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A method interceptor that preserves the order in which test classes were found in
+ * the &lt;test&gt; tag.
+ *
+ * @author cbeust
+ *
+ */
+class PreserveOrderMethodInterceptor implements IMethodInterceptor {
+
+  private void p(List<IMethodInstance> methods, String s) {
+    System.out.println("[PreserveOrderMethodInterceptor] " + s);
+    for (IMethodInstance mi : methods) {
+      System.out.println("  " + mi.getMethod().getMethodName()
+          + " index:" + mi.getMethod().getTestClass().getXmlClass().getIndex());
+    }
+  }
+
+  @Override
+  public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) {
+//    p(methods, "Before");
+    Collections.sort(methods, MethodInstance.SORT_BY_INDEX);
+//    p(methods, "After");
+    return methods;
+  }
+
+}
diff --git a/src/main/java/org/testng/Reporter.java b/src/main/java/org/testng/Reporter.java
new file mode 100755
index 0000000..3b16a20
--- /dev/null
+++ b/src/main/java/org/testng/Reporter.java
@@ -0,0 +1,184 @@
+package org.testng;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.util.Strings;
+
+/**
+ * This class is used for test methods to log messages that will be
+ * included in the HTML reports generated by TestNG.
+ * <br>
+ * <br>
+ * <b>Implementation details.</b>
+ * <br>
+ * <br>
+ * The reporter keeps a combined output of strings (in m_output) and also
+ * a record of which method output which line.  In order to do this, callers
+ * specify what the current method is with setCurrentTestResult() and the
+ * Reporter maintains a mapping of each test result with a list of integers.
+ * These integers are indices in the combined output (avoids duplicating
+ * the output).
+ *
+ * Created on Nov 2, 2005
+ * @author cbeust
+ */
+public class Reporter {
+  // when tests are run in parallel, each thread may be working with different
+  // 'current test result'. Also, this value should be inherited if the test code
+  // spawns its own thread.
+  private static ThreadLocal<ITestResult> m_currentTestResult = new InheritableThreadLocal<>();
+
+  /**
+   * All output logged in a sequential order.
+   */
+  private static List<String> m_output = new Vector<>();
+
+  /** The key is the hashCode of the ITestResult */
+  private static Map<Integer, List<Integer>> m_methodOutputMap = Maps.newHashMap();
+
+  private static boolean m_escapeHtml = false;
+  //This variable is responsible for persisting all output that is yet to be associated with any
+  //valid TestResult objects.
+  private static ThreadLocal<List<String>> m_orphanedOutput = new InheritableThreadLocal<>();
+
+  public static void setCurrentTestResult(ITestResult m) {
+    m_currentTestResult.set(m);
+  }
+
+  public static List<String> getOutput() {
+    return m_output;
+  }
+
+  /**
+   * Erase the content of all the output generated so far.
+   */
+  public static void clear() {
+    m_methodOutputMap.clear();
+    m_output.clear();
+  }
+
+  /**
+   * @param escapeHtml If true, use HTML entities for special HTML characters (<, >, &, ...).
+   */
+  public static void setEscapeHtml(boolean escapeHtml) {
+    m_escapeHtml = escapeHtml;
+  }
+
+  private static synchronized void log(String s, ITestResult m) {
+    // Escape for the HTML reports
+    if (m_escapeHtml) {
+      s = Strings.escapeHtml(s);
+    }
+
+    if (m == null) {
+      //Persist the output temporarily into a Threadlocal String list.
+      if (m_orphanedOutput.get() == null) {
+        m_orphanedOutput.set(new ArrayList<String>());
+      }
+      m_orphanedOutput.get().add(s);
+      return;
+    }
+
+    // synchronization needed to ensure the line number and m_output are updated atomically
+    int n = getOutput().size();
+
+    List<Integer> lines = m_methodOutputMap.get(m.hashCode());
+    if (lines == null) {
+      lines = Lists.newArrayList();
+      m_methodOutputMap.put(m.hashCode(), lines);
+    }
+
+    // Check if there was already some orphaned output for the current thread.
+    if (m_orphanedOutput.get() != null) {
+      n = n + m_orphanedOutput.get().size();
+      getOutput().addAll(m_orphanedOutput.get());
+      // Since we have already added all of the orphaned output to the current
+      // TestResult, lets clear it off
+      m_orphanedOutput.remove();
+    }
+    lines.add(n);
+    getOutput().add(s);
+  }
+
+  /**
+   * Log the passed string to the HTML reports
+   * @param s The message to log
+   */
+  public static void log(String s) {
+    log(s, getCurrentTestResult());
+  }
+
+  /**
+   * Log the passed string to the HTML reports if the current verbosity
+   * is equal or greater than the one passed in parameter. If logToStandardOut
+   * is true, the string will also be printed on standard out.
+   *
+   * @param s The message to log
+   * @param level The verbosity of this message
+   * @param logToStandardOut Whether to print this string on standard
+   * out too
+   */
+  public static void log(String s, int level, boolean logToStandardOut) {
+    if (TestRunner.getVerbose() >= level) {
+      log(s, getCurrentTestResult());
+      if (logToStandardOut) {
+        System.out.println(s);
+      }
+    }
+  }
+
+  /**
+   * Log the passed string to the HTML reports.  If logToStandardOut
+   * is true, the string will also be printed on standard out.
+   *
+   * @param s The message to log
+   * @param logToStandardOut Whether to print this string on standard
+   * out too
+   */
+  public static void log(String s, boolean logToStandardOut) {
+    log(s, getCurrentTestResult());
+    if (logToStandardOut) {
+      System.out.println(s);
+    }
+  }
+  /**
+   * Log the passed string to the HTML reports if the current verbosity
+   * is equal or greater than the one passed in parameter
+   *
+   * @param s The message to log
+   * @param level The verbosity of this message
+   */
+  public static void log(String s, int level) {
+    if (TestRunner.getVerbose() >= level) {
+      log(s, getCurrentTestResult());
+    }
+  }
+
+  /**
+   * @return the current test result.
+   */
+  public static ITestResult getCurrentTestResult() {
+    return m_currentTestResult.get();
+  }
+
+  public static synchronized List<String> getOutput(ITestResult tr) {
+    List<String> result = Lists.newArrayList();
+    if (tr == null) {
+      //guard against a possible NPE in scenarios wherein the test result object itself could be a null value.
+      return result;
+    }
+    List<Integer> lines = m_methodOutputMap.get(tr.hashCode());
+    if (lines != null) {
+      for (Integer n : lines) {
+        result.add(getOutput().get(n));
+      }
+    }
+
+    return result;
+  }
+}
diff --git a/src/main/java/org/testng/ReporterConfig.java b/src/main/java/org/testng/ReporterConfig.java
new file mode 100755
index 0000000..de7f362
--- /dev/null
+++ b/src/main/java/org/testng/ReporterConfig.java
@@ -0,0 +1,138 @@
+package org.testng;
+
+import org.testng.collections.Lists;
+import org.testng.internal.ClassHelper;
+import org.testng.internal.PropertyUtils;
+import org.testng.internal.Utils;
+
+import java.util.List;
+
+/**
+ * Stores the information regarding the configuration of a pluggable report listener. Used also
+ * in conjunction with the &lt;reporter&gt; sub-element of the Ant task
+ *
+ * NOTE: this class needs to be public. It's used by TestNG Ant task
+ *
+ * @author Cosmin Marginean, Apr 12, 2007
+ */
+public class ReporterConfig {
+
+  /**
+   * The class name of the reporter listener
+   */
+  private String m_className;
+
+  /**
+   * The properties of the reporter listener
+   */
+  private List<Property> m_properties = Lists.newArrayList();
+
+  public void addProperty(Property property) {
+    m_properties.add(property);
+  }
+
+  public List<Property> getProperties() {
+    return m_properties;
+  }
+
+  public String getClassName() {
+    return m_className;
+  }
+
+  public void setClassName(String className) {
+    this.m_className = className;
+  }
+
+  public String serialize() {
+    StringBuffer sb = new StringBuffer();
+    sb.append(m_className);
+    if (!m_properties.isEmpty()) {
+      sb.append(":");
+
+      for (int i = 0; i < m_properties.size(); i++) {
+        ReporterConfig.Property property = m_properties.get(i);
+        sb.append(property.getName());
+        sb.append("=");
+        sb.append(property.getValue());
+        if (i < m_properties.size() - 1) {
+          sb.append(",");
+        }
+      }
+    }
+    return sb.toString();
+  }
+
+  public static ReporterConfig deserialize(String inputString) {
+    ReporterConfig reporterConfig = null;
+    if (!Utils.isStringEmpty(inputString)) {
+      reporterConfig = new ReporterConfig();
+      int clsNameEndIndex = inputString.indexOf(":");
+      if (clsNameEndIndex == -1) {
+        reporterConfig.setClassName(inputString);
+      } else {
+        reporterConfig.setClassName(inputString.substring(0, clsNameEndIndex));
+        String propString = inputString.substring(clsNameEndIndex + 1, inputString.length());
+        String[] props = propString.split(",");
+        if ((props != null) && (props.length > 0)) {
+          for (String prop : props) {
+            String[] propNameAndVal = prop.split("=");
+            if ((propNameAndVal != null) && (propNameAndVal.length == 2)) {
+              Property property = new Property();
+              property.setName(propNameAndVal[0]);
+              property.setValue(propNameAndVal[1]);
+              reporterConfig.addProperty(property);
+            }
+          }
+        }
+      }
+
+    }
+    return reporterConfig;
+  }
+
+  /**
+   * Creates a reporter based on the current configuration
+   */
+  public Object newReporterInstance() {
+    Object result = null;
+    Class reporterClass = ClassHelper.forName(m_className);
+    if (reporterClass != null) {
+      result = ClassHelper.newInstance(reporterClass);
+      for (ReporterConfig.Property property : m_properties) {
+        PropertyUtils.setProperty(result, property.getName(), property.getValue());
+      }
+    }
+    return result;
+  }
+
+  public static class Property {
+    private String name;
+    private String value;
+
+    public String getName() {
+      return name;
+    }
+
+    public void setName(String name) {
+      this.name = name;
+    }
+
+    public String getValue() {
+      return value;
+    }
+
+    public void setValue(String value) {
+      this.value = value;
+    }
+  }
+
+  @Override
+  public String toString() {
+    StringBuffer buf = new StringBuffer();
+    buf.append("\nClass = " + m_className);
+    for (Property prop : m_properties) {
+      buf.append("\n\t " + prop.getName() + "=" + prop.getValue());
+    }
+    return buf.toString();
+  }
+}
diff --git a/src/main/java/org/testng/SkipException.java b/src/main/java/org/testng/SkipException.java
new file mode 100755
index 0000000..35ec5be
--- /dev/null
+++ b/src/main/java/org/testng/SkipException.java
@@ -0,0 +1,72 @@
+package org.testng;
+
+/**
+ * The root exception for special skip handling. In case a @Test or @Configuration
+ * throws this exception the method will be considered a skip or a failure according to the
+ * return of {@link #isSkip()}.
+ * Users may provide extensions to this mechanism by extending this class.
+ *
+ * @author <a href='mailto:the[dot]mindstorm[at]gmail[dot]com'>Alex Popescu</a>
+ * @since 5.6
+ */
+public class SkipException extends RuntimeException {
+  private static final long serialVersionUID = 4052142657885527260L;
+  private StackTraceElement[] m_stackTrace;
+  private volatile boolean m_stackReduced;
+
+  public SkipException(String skipMessage) {
+    super(skipMessage);
+  }
+
+  public SkipException(String skipMessage, Throwable cause) {
+    super(skipMessage, cause);
+  }
+
+  /**
+   * Flag if the current exception marks a skipped method (<tt>true</tt>)
+   * or a failure (<tt>false</tt>). By default Subclasses should override this method
+   * in order to provide smarter behavior.
+   *
+   * @return <tt>true</tt> if the method should be considered a skip,
+   *    <tt>false</tt> if the method should be considered failed. If not
+   *    overwritten it returns <tt>true</tt>
+   */
+  public boolean isSkip() {
+    return true;
+  }
+
+  /**
+   * Subclasses may use this method to reduce the printed stack trace.
+   * This method keeps only the last frame.
+   * <b>Important</b>: after calling this method the preserved internally
+   * and can be restored called {@link #restoreStackTrace}.
+   */
+  protected void reduceStackTrace() {
+    if(!m_stackReduced) {
+      synchronized(this) {
+        StackTraceElement[] newStack= new StackTraceElement[1];
+        StackTraceElement[] originalStack= getStackTrace();
+        if(originalStack.length > 0) {
+          m_stackTrace= originalStack;
+          newStack[0]= getStackTrace()[0];
+          setStackTrace(newStack);
+        }
+        m_stackReduced= true;
+      }
+    }
+  }
+
+  /**
+   * Restores the original exception stack trace after a
+   * previous call to {@link #reduceStackTrace()}.
+   *
+   */
+  protected void restoreStackTrace() {
+    if(m_stackReduced && null != m_stackTrace) {
+      synchronized(this) {
+        setStackTrace(m_stackTrace);
+        m_stackReduced= false;
+      }
+    }
+  }
+}
diff --git a/src/main/java/org/testng/SuiteResult.java b/src/main/java/org/testng/SuiteResult.java
new file mode 100755
index 0000000..4a53c75
--- /dev/null
+++ b/src/main/java/org/testng/SuiteResult.java
@@ -0,0 +1,75 @@
+package org.testng;
+
+import org.testng.collections.Objects;
+import org.testng.xml.XmlSuite;
+
+/**
+ * This class logs the result of an entire Test Suite (defined by a
+ * property file).
+ *
+ * @author Cedric Beust, May 10, 2004
+ *
+ */
+class SuiteResult implements ISuiteResult, Comparable {
+	/* generated */
+	private static final long serialVersionUID = 6778513869858860756L;
+  //FIXME: Is m_propertyFileName needed?
+	private String m_propertyFileName =  null;
+  private XmlSuite m_suite = null;
+  private ITestContext m_testContext = null;
+
+  protected SuiteResult(XmlSuite suite, ITestContext tr) {
+    m_suite = suite;
+    m_testContext = tr;
+  }
+
+  /**
+   * @return Returns the propertyFileName.
+   */
+  @Override
+  public String getPropertyFileName() {
+    return m_propertyFileName;
+  }
+
+  /**
+   * @return Returns the singleTestRunner.
+   */
+  @Override
+  public ITestContext getTestContext() {
+    return m_testContext;
+  }
+  /**
+   * @return Returns the suite.
+   */
+  public XmlSuite getSuite() {
+    return m_suite;
+  }
+
+  @Override
+  public int compareTo(Object o) {
+    int result = 0;
+    try {
+      SuiteResult other = (SuiteResult) o;
+      String n1 = getTestContext().getName();
+      String n2 = other.getTestContext().getName();
+      result = n1.compareTo(n2);
+    }
+    catch(Exception ex) {
+      ex.printStackTrace();
+    }
+
+    return result;
+  }
+
+  /**
+   * Returns the test context name.
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString() {
+    return Objects.toStringHelper(getClass())
+        .add("context", getTestContext().getName())
+        .toString();
+  }
+
+}
diff --git a/src/main/java/org/testng/SuiteRunState.java b/src/main/java/org/testng/SuiteRunState.java
new file mode 100755
index 0000000..74af24a
--- /dev/null
+++ b/src/main/java/org/testng/SuiteRunState.java
@@ -0,0 +1,26 @@
+package org.testng;

+

+import java.io.Serializable;

+

+

+/**

+ * A state object that records the status of the suite run. Mainly used to

+ * figure out if there are any @BeforeSuite failures.

+ *

+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>

+ */

+public class SuiteRunState implements Serializable {

+  /**

+   *

+   */

+  private static final long serialVersionUID = -2716934905049123874L;

+  private boolean m_hasFailures;

+

+  public synchronized void failed() {

+    m_hasFailures= true;

+  }

+

+  public synchronized boolean isFailed() {

+    return m_hasFailures;

+  }

+}

diff --git a/src/main/java/org/testng/SuiteRunner.java b/src/main/java/org/testng/SuiteRunner.java
new file mode 100644
index 0000000..5bb1397
--- /dev/null
+++ b/src/main/java/org/testng/SuiteRunner.java
@@ -0,0 +1,682 @@
+package org.testng;
+
+import static org.testng.internal.Utils.isStringBlank;
+
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.internal.Attributes;
+import org.testng.internal.IConfiguration;
+import org.testng.internal.IInvoker;
+import org.testng.internal.Utils;
+import org.testng.internal.annotations.IAnnotationFinder;
+import org.testng.internal.thread.ThreadUtil;
+import org.testng.reporters.JUnitXMLReporter;
+import org.testng.reporters.TestHTMLReporter;
+import org.testng.reporters.TextReporter;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import java.io.File;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import com.google.inject.Injector;
+
+/**
+ * <CODE>SuiteRunner</CODE> is responsible for running all the tests included in one
+ * suite. The test start is triggered by {@link #run()} method.
+ *
+ * @author Cedric Beust, Apr 26, 2004
+ */
+public class SuiteRunner implements ISuite, Serializable, IInvokedMethodListener {
+
+  /* generated */
+  private static final long serialVersionUID = 5284208932089503131L;
+
+  private static final String DEFAULT_OUTPUT_DIR = "test-output";
+
+  private Map<String, ISuiteResult> m_suiteResults = Collections.synchronizedMap(Maps.<String, ISuiteResult>newLinkedHashMap());
+  transient private List<TestRunner> m_testRunners = Lists.newArrayList();
+  transient private Map<Class<? extends ISuiteListener>, ISuiteListener> m_listeners = Maps.newHashMap();
+  transient private TestListenerAdapter m_textReporter = new TestListenerAdapter();
+
+  private String m_outputDir; // DEFAULT_OUTPUT_DIR;
+  private XmlSuite m_suite;
+  private Injector m_parentInjector;
+
+  transient private List<ITestListener> m_testListeners = Lists.newArrayList();
+  transient private List<IClassListener> m_classListeners = Lists.newArrayList();
+  transient private ITestRunnerFactory m_tmpRunnerFactory;
+
+  transient private ITestRunnerFactory m_runnerFactory;
+  transient private boolean m_useDefaultListeners = true;
+
+  // The remote host where this suite was run, or null if run locally
+  private String m_host;
+
+  // The configuration
+  transient private IConfiguration m_configuration;
+
+  transient private ITestObjectFactory m_objectFactory;
+  transient private Boolean m_skipFailedInvocationCounts = Boolean.FALSE;
+
+  transient private List<IMethodInterceptor> m_methodInterceptors;
+  private Map<Class<? extends IInvokedMethodListener>, IInvokedMethodListener> m_invokedMethodListeners;
+
+  /** The list of all the methods invoked during this run */
+  private List<IInvokedMethod> m_invokedMethods =
+      Collections.synchronizedList(Lists.<IInvokedMethod>newArrayList());
+
+  private List<ITestNGMethod> m_allTestMethods = Lists.newArrayList();
+
+//  transient private IAnnotationTransformer m_annotationTransformer = null;
+
+  public SuiteRunner(IConfiguration configuration, XmlSuite suite,
+      String outputDir)
+  {
+    this(configuration, suite, outputDir, null);
+  }
+
+  public SuiteRunner(IConfiguration configuration, XmlSuite suite, String outputDir,
+      ITestRunnerFactory runnerFactory)
+  {
+    this(configuration, suite, outputDir, runnerFactory, false);
+  }
+
+  public SuiteRunner(IConfiguration configuration,
+      XmlSuite suite,
+      String outputDir,
+      ITestRunnerFactory runnerFactory,
+      boolean useDefaultListeners)
+  {
+    this(configuration, suite, outputDir, runnerFactory, useDefaultListeners,
+        new ArrayList<IMethodInterceptor>() /* method interceptor */,
+        null /* invoked method listeners */,
+        null /* test listeners */,
+        null /* class listeners */);
+  }
+
+  protected SuiteRunner(IConfiguration configuration,
+      XmlSuite suite,
+      String outputDir,
+      ITestRunnerFactory runnerFactory,
+      boolean useDefaultListeners,
+      List<IMethodInterceptor> methodInterceptors,
+      List<IInvokedMethodListener> invokedMethodListeners,
+      List<ITestListener> testListeners,
+      List<IClassListener> classListeners)
+  {
+    init(configuration, suite, outputDir, runnerFactory, useDefaultListeners, methodInterceptors, invokedMethodListeners, testListeners, classListeners);
+  }
+
+  private void init(IConfiguration configuration,
+    XmlSuite suite,
+    String outputDir,
+    ITestRunnerFactory runnerFactory,
+    boolean useDefaultListeners,
+    List<IMethodInterceptor> methodInterceptors,
+    List<IInvokedMethodListener> invokedMethodListener,
+    List<ITestListener> testListeners,
+    List<IClassListener> classListeners)
+  {
+    m_configuration = configuration;
+    m_suite = suite;
+    m_useDefaultListeners = useDefaultListeners;
+    m_tmpRunnerFactory= runnerFactory;
+    m_methodInterceptors = methodInterceptors != null ? methodInterceptors : new ArrayList<IMethodInterceptor>();
+    setOutputDir(outputDir);
+    m_objectFactory = m_configuration.getObjectFactory();
+    if(m_objectFactory == null) {
+      m_objectFactory = suite.getObjectFactory();
+    }
+    // Add our own IInvokedMethodListener
+    m_invokedMethodListeners = Maps.newHashMap();
+    if (invokedMethodListener != null) {
+      for (IInvokedMethodListener listener : invokedMethodListener) {
+        m_invokedMethodListeners.put(listener.getClass(), listener);
+      }
+    }
+    m_invokedMethodListeners.put(getClass(), this);
+
+    m_skipFailedInvocationCounts = suite.skipFailedInvocationCounts();
+    if (null != testListeners) {
+      m_testListeners.addAll(testListeners);
+    }
+    if (null != classListeners) {
+      m_classListeners.addAll(classListeners);
+    }
+    m_runnerFactory = buildRunnerFactory();
+
+    // Order the <test> tags based on their order of appearance in testng.xml
+    List<XmlTest> xmlTests = m_suite.getTests();
+    Collections.sort(xmlTests, new Comparator<XmlTest>() {
+      @Override
+      public int compare(XmlTest arg0, XmlTest arg1) {
+        return arg0.getIndex() - arg1.getIndex();
+      }
+    });
+
+    for (XmlTest test : xmlTests) {
+      TestRunner tr = m_runnerFactory.newTestRunner(this, test, m_invokedMethodListeners.values(), m_classListeners);
+
+      //
+      // Install the method interceptor, if any was passed
+      //
+      for (IMethodInterceptor methodInterceptor : m_methodInterceptors) {
+        tr.addMethodInterceptor(methodInterceptor);
+      }
+
+      // Reuse the same text reporter so we can accumulate all the results
+      // (this is used to display the final suite report at the end)
+      tr.addListener(m_textReporter);
+      m_testRunners.add(tr);
+
+      // Add the methods found in this test to our global count
+      m_allTestMethods.addAll(Arrays.asList(tr.getAllTestMethods()));
+    }
+  }
+
+  @Override
+  public XmlSuite getXmlSuite() {
+    return m_suite;
+  }
+
+  @Override
+  public String getName() {
+    return m_suite.getName();
+  }
+
+  public void setObjectFactory(ITestObjectFactory objectFactory) {
+    m_objectFactory = objectFactory;
+  }
+
+  public void setReportResults(boolean reportResults) {
+    m_useDefaultListeners = reportResults;
+  }
+
+  private void invokeListeners(boolean start) {
+    for (ISuiteListener sl : m_listeners.values()) {
+      if (start) {
+        sl.onStart(this);
+      }
+      else {
+        sl.onFinish(this);
+      }
+    }
+  }
+
+  private void setOutputDir(String outputdir) {
+    if (isStringBlank(outputdir) && m_useDefaultListeners) {
+      outputdir = DEFAULT_OUTPUT_DIR;
+    }
+
+    m_outputDir = (null != outputdir) ? new File(outputdir).getAbsolutePath()
+        : null;
+  }
+
+  private ITestRunnerFactory buildRunnerFactory() {
+    ITestRunnerFactory factory = null;
+
+    if (null == m_tmpRunnerFactory) {
+      factory = new DefaultTestRunnerFactory(m_configuration,
+          m_testListeners.toArray(new ITestListener[m_testListeners.size()]),
+          m_useDefaultListeners, m_skipFailedInvocationCounts);
+    }
+    else {
+      factory = new ProxyTestRunnerFactory(
+          m_testListeners.toArray(new ITestListener[m_testListeners.size()]),
+          m_tmpRunnerFactory);
+    }
+
+    return factory;
+  }
+
+  @Override
+  public String getParallel() {
+    return m_suite.getParallel().toString();
+  }
+
+  public String getParentModule() {
+    return m_suite.getParentModule();
+  }
+
+  @Override
+  public String getGuiceStage() {
+    return m_suite.getGuiceStage();
+  }
+
+  public Injector getParentInjector() {
+    return m_parentInjector;
+  }
+
+  public void setParentInjector(Injector injector) {
+    m_parentInjector = injector;
+  }
+
+  @Override
+  public void run() {
+    invokeListeners(true /* start */);
+    try {
+      privateRun();
+    }
+    finally {
+      invokeListeners(false /* stop */);
+    }
+  }
+
+  private void privateRun() {
+
+    // Map for unicity, Linked for guaranteed order
+    Map<Method, ITestNGMethod> beforeSuiteMethods= new LinkedHashMap<>();
+    Map<Method, ITestNGMethod> afterSuiteMethods = new LinkedHashMap<>();
+
+    IInvoker invoker = null;
+
+    // Get the invoker and find all the suite level methods
+    for (TestRunner tr: m_testRunners) {
+      // TODO: Code smell.  Invoker should belong to SuiteRunner, not TestRunner
+      // -- cbeust
+      invoker = tr.getInvoker();
+
+      for (ITestNGMethod m : tr.getBeforeSuiteMethods()) {
+        beforeSuiteMethods.put(m.getMethod(), m);
+      }
+
+      for (ITestNGMethod m : tr.getAfterSuiteMethods()) {
+        afterSuiteMethods.put(m.getMethod(), m);
+      }
+    }
+
+    //
+    // Invoke beforeSuite methods (the invoker can be null
+    // if the suite we are currently running only contains
+    // a <file-suite> tag and no real tests)
+    //
+    if (invoker != null) {
+      if(beforeSuiteMethods.values().size() > 0) {
+        invoker.invokeConfigurations(null,
+            beforeSuiteMethods.values().toArray(new ITestNGMethod[beforeSuiteMethods.size()]),
+            m_suite, m_suite.getParameters(), null, /* no parameter values */
+            null /* instance */
+        );
+      }
+
+      Utils.log("SuiteRunner", 3, "Created " + m_testRunners.size() + " TestRunners");
+
+      //
+      // Run all the test runners
+      //
+      boolean testsInParallel = XmlSuite.ParallelMode.TESTS.equals(m_suite.getParallel());
+      if (!testsInParallel) {
+        runSequentially();
+      }
+      else {
+        runInParallelTestMode();
+      }
+
+//      SuitePlan sp = new SuitePlan();
+//      for (TestRunner tr : m_testRunners) {
+//        sp.addTestPlan(tr.getTestPlan());
+//      }
+
+//      sp.dump();
+
+      //
+      // Invoke afterSuite methods
+      //
+      if (afterSuiteMethods.values().size() > 0) {
+        invoker.invokeConfigurations(null,
+              afterSuiteMethods.values().toArray(new ITestNGMethod[afterSuiteMethods.size()]),
+              m_suite, m_suite.getAllParameters(), null, /* no parameter values */
+
+              null /* instance */);
+      }
+    }
+  }
+
+  private List<IReporter> m_reporters = Lists.newArrayList();
+
+  private void addReporter(IReporter listener) {
+    m_reporters.add(listener);
+  }
+
+  void addConfigurationListener(IConfigurationListener listener) {
+    m_configuration.addConfigurationListener(listener);
+  }
+
+  public List<IReporter> getReporters() {
+    return m_reporters;
+  }
+
+  private void runSequentially() {
+    for (TestRunner tr : m_testRunners) {
+      runTest(tr);
+    }
+  }
+
+  private void runTest(TestRunner tr) {
+    tr.run();
+
+    ISuiteResult sr = new SuiteResult(m_suite, tr);
+    m_suiteResults.put(tr.getName(), sr);
+  }
+
+  /**
+   * Implement <suite parallel="tests">.
+   * Since this kind of parallelism happens at the suite level, we need a special code path
+   * to execute it.  All the other parallelism strategies are implemented at the test level
+   * in TestRunner#createParallelWorkers (but since this method deals with just one <test>
+   * tag, it can't implement <suite parallel="tests">, which is why we're doing it here).
+   */
+  private void runInParallelTestMode() {
+    List<Runnable> tasks= Lists.newArrayList(m_testRunners.size());
+    for(TestRunner tr: m_testRunners) {
+      tasks.add(new SuiteWorker(tr));
+    }
+
+    ThreadUtil.execute(tasks, m_suite.getThreadCount(),
+        m_suite.getTimeOut(XmlTest.DEFAULT_TIMEOUT_MS), false);
+  }
+
+  private class SuiteWorker implements Runnable {
+      private TestRunner m_testRunner;
+
+      public SuiteWorker(TestRunner tr) {
+        m_testRunner = tr;
+      }
+
+      @Override
+      public void run() {
+        Utils.log("[SuiteWorker]", 4, "Running XML Test '"
+                  +  m_testRunner.getTest().getName() + "' in Parallel");
+        runTest(m_testRunner);
+      }
+  }
+
+  /**
+   * Registers ISuiteListeners interested in reporting the result of the current
+   * suite.
+   *
+   * @param reporter
+   */
+  protected void addListener(ISuiteListener reporter) {
+    m_listeners.put(reporter.getClass(), reporter);
+  }
+
+  @Override
+  public void addListener(ITestNGListener listener) {
+    if (listener instanceof IInvokedMethodListener) {
+      IInvokedMethodListener invokedMethodListener = (IInvokedMethodListener) listener;
+      m_invokedMethodListeners.put(invokedMethodListener.getClass(), invokedMethodListener);
+    }
+    if (listener instanceof ISuiteListener) {
+      addListener((ISuiteListener) listener);
+    }
+    if (listener instanceof IReporter) {
+      addReporter((IReporter) listener);
+    }
+    if (listener instanceof IConfigurationListener) {
+      addConfigurationListener((IConfigurationListener) listener);
+    }
+  }
+
+  @Override
+  public String getOutputDirectory() {
+    return m_outputDir + File.separatorChar + getName();
+  }
+
+  @Override
+  public Map<String, ISuiteResult> getResults() {
+    return m_suiteResults;
+  }
+
+  /**
+   * FIXME: should be removed?
+   *
+   * @see org.testng.ISuite#getParameter(java.lang.String)
+   */
+  @Override
+  public String getParameter(String parameterName) {
+    return m_suite.getParameter(parameterName);
+  }
+
+  /**
+   * @see org.testng.ISuite#getMethodsByGroups()
+   */
+  @Override
+  public Map<String, Collection<ITestNGMethod>> getMethodsByGroups() {
+    Map<String, Collection<ITestNGMethod>> result = Maps.newHashMap();
+
+    for (TestRunner tr : m_testRunners) {
+      ITestNGMethod[] methods = tr.getAllTestMethods();
+      for (ITestNGMethod m : methods) {
+        String[] groups = m.getGroups();
+        for (String groupName : groups) {
+          Collection<ITestNGMethod> testMethods = result.get(groupName);
+          if (null == testMethods) {
+            testMethods = Lists.newArrayList();
+            result.put(groupName, testMethods);
+          }
+          testMethods.add(m);
+        }
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * @see org.testng.ISuite#getInvokedMethods()
+   */
+  @Override
+  public Collection<ITestNGMethod> getInvokedMethods() {
+    return getIncludedOrExcludedMethods(true /* included */);
+  }
+
+  /**
+   * @see org.testng.ISuite#getExcludedMethods()
+   */
+  @Override
+  public Collection<ITestNGMethod> getExcludedMethods() {
+    return getIncludedOrExcludedMethods(false/* included */);
+  }
+
+  private Collection<ITestNGMethod> getIncludedOrExcludedMethods(boolean included) {
+    List<ITestNGMethod> result= Lists.newArrayList();
+
+    for (TestRunner tr : m_testRunners) {
+      Collection<ITestNGMethod> methods = included ? tr.getInvokedMethods() : tr.getExcludedMethods();
+      for (ITestNGMethod m : methods) {
+        result.add(m);
+      }
+    }
+
+    return result;
+  }
+
+  @Override
+  public IObjectFactory getObjectFactory() {
+    return m_objectFactory instanceof IObjectFactory ? (IObjectFactory) m_objectFactory : null;
+  }
+
+  @Override
+  public IObjectFactory2 getObjectFactory2() {
+    return m_objectFactory instanceof IObjectFactory2 ? (IObjectFactory2) m_objectFactory : null;
+  }
+
+  /**
+   * Returns the annotation finder for the given annotation type.
+   * @return the annotation finder for the given annotation type.
+   */
+  @Override
+  public IAnnotationFinder getAnnotationFinder() {
+    return m_configuration.getAnnotationFinder();
+  }
+
+  public static void ppp(String s) {
+    System.out.println("[SuiteRunner] " + s);
+  }
+
+  /**
+   * The default implementation of {@link ITestRunnerFactory}.
+   */
+  private static class DefaultTestRunnerFactory implements ITestRunnerFactory {
+    private ITestListener[] m_failureGenerators;
+    private boolean m_useDefaultListeners;
+    private boolean m_skipFailedInvocationCounts;
+    private IConfiguration m_configuration;
+
+    public DefaultTestRunnerFactory(IConfiguration configuration,
+        ITestListener[] failureListeners,
+        boolean useDefaultListeners,
+        boolean skipFailedInvocationCounts)
+    {
+      m_configuration = configuration;
+      m_failureGenerators = failureListeners;
+      m_useDefaultListeners = useDefaultListeners;
+      m_skipFailedInvocationCounts = skipFailedInvocationCounts;
+    }
+
+    @Override
+    public TestRunner newTestRunner(ISuite suite, XmlTest test,
+        Collection<IInvokedMethodListener> listeners, List<IClassListener> classListeners) {
+      boolean skip = m_skipFailedInvocationCounts;
+      if (! skip) {
+        skip = test.skipFailedInvocationCounts();
+      }
+      TestRunner testRunner = new TestRunner(m_configuration, suite, test,
+          suite.getOutputDirectory(), suite.getAnnotationFinder(), skip,
+          listeners, classListeners);
+
+      if (m_useDefaultListeners) {
+        testRunner.addListener(new TestHTMLReporter());
+        testRunner.addListener(new JUnitXMLReporter());
+
+        //TODO: Moved these here because maven2 has output reporters running
+        //already, the output from these causes directories to be created with
+        //files. This is not the desired behaviour of running tests in maven2.
+        //Don't know what to do about this though, are people relying on these
+        //to be added even with defaultListeners set to false?
+        testRunner.addListener(new TextReporter(testRunner.getName(), TestRunner.getVerbose()));
+      }
+
+      for (ITestListener itl : m_failureGenerators) {
+        testRunner.addListener(itl);
+      }
+      for (IConfigurationListener cl : m_configuration.getConfigurationListeners()) {
+        testRunner.addConfigurationListener(cl);
+      }
+
+      return testRunner;
+    }
+  }
+
+  private static class ProxyTestRunnerFactory implements ITestRunnerFactory {
+    private ITestListener[] m_failureGenerators;
+    private ITestRunnerFactory m_target;
+
+    public ProxyTestRunnerFactory(ITestListener[] failureListeners, ITestRunnerFactory target) {
+      m_failureGenerators = failureListeners;
+      m_target= target;
+    }
+
+    @Override
+    public TestRunner newTestRunner(ISuite suite, XmlTest test,
+        Collection<IInvokedMethodListener> listeners, List<IClassListener> classListeners) {
+      TestRunner testRunner= m_target.newTestRunner(suite, test, listeners, classListeners);
+
+      testRunner.addListener(new TextReporter(testRunner.getName(), TestRunner.getVerbose()));
+
+      for (ITestListener itl : m_failureGenerators) {
+        testRunner.addListener(itl);
+      }
+
+      return testRunner;
+    }
+  }
+
+  public void setHost(String host) {
+    m_host = host;
+  }
+
+  @Override
+  public String getHost() {
+    return m_host;
+  }
+
+  private SuiteRunState m_suiteState= new SuiteRunState();
+
+  /**
+   * @see org.testng.ISuite#getSuiteState()
+   */
+  @Override
+  public SuiteRunState getSuiteState() {
+    return m_suiteState;
+  }
+
+  public void setSkipFailedInvocationCounts(Boolean skipFailedInvocationCounts) {
+    if (skipFailedInvocationCounts != null) {
+      m_skipFailedInvocationCounts = skipFailedInvocationCounts;
+    }
+  }
+
+  private IAttributes m_attributes = new Attributes();
+
+  @Override
+  public Object getAttribute(String name) {
+    return m_attributes.getAttribute(name);
+  }
+
+  @Override
+  public void setAttribute(String name, Object value) {
+    m_attributes.setAttribute(name, value);
+  }
+
+  @Override
+  public Set<String> getAttributeNames() {
+    return m_attributes.getAttributeNames();
+  }
+
+  @Override
+  public Object removeAttribute(String name) {
+    return m_attributes.removeAttribute(name);
+  }
+
+  /////
+  // implements IInvokedMethodListener
+  //
+
+  @Override
+  public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
+  }
+
+  @Override
+  public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
+    if (method == null) {
+      throw new NullPointerException("Method should not be null");
+    }
+    m_invokedMethods.add(method);
+  }
+
+  //
+  // implements IInvokedMethodListener
+  /////
+
+  @Override
+  public List<IInvokedMethod> getAllInvokedMethods() {
+    return m_invokedMethods;
+  }
+
+  @Override
+  public List<ITestNGMethod> getAllMethods() {
+    return m_allTestMethods;
+  }
+}
diff --git a/src/main/java/org/testng/SuiteRunnerWorker.java b/src/main/java/org/testng/SuiteRunnerWorker.java
new file mode 100644
index 0000000..c3244da
--- /dev/null
+++ b/src/main/java/org/testng/SuiteRunnerWorker.java
@@ -0,0 +1,169 @@
+package org.testng;
+
+import org.testng.collections.Lists;
+import org.testng.collections.Objects;
+import org.testng.internal.SuiteRunnerMap;
+import org.testng.internal.Utils;
+import org.testng.internal.thread.graph.IWorker;
+import org.testng.xml.XmlSuite;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An {@code IWorker} that is used to encapsulate and run Suite Runners
+ *
+ * @author cbeust, nullin
+ */
+public class SuiteRunnerWorker implements IWorker<ISuite> {
+
+  private SuiteRunner m_suiteRunner;
+  private Integer m_verbose;
+  private String m_defaultSuiteName;
+  private SuiteRunnerMap m_suiteRunnerMap;
+
+  public SuiteRunnerWorker(ISuite suiteRunner,
+      SuiteRunnerMap suiteRunnerMap,
+      int verbose,
+      String defaultSuiteName)
+  {
+    m_suiteRunnerMap = suiteRunnerMap;
+    m_suiteRunner = (SuiteRunner) suiteRunner;
+    m_verbose = verbose;
+    m_defaultSuiteName = defaultSuiteName;
+  }
+
+  /**
+   * Runs a suite
+   * @param suiteRunnerMap map of suiteRunners that are updated with test results
+   * @param xmlSuite XML suites to run
+   */
+  private void runSuite(SuiteRunnerMap suiteRunnerMap /* OUT */, XmlSuite xmlSuite)
+  {
+    if (m_verbose > 0) {
+      StringBuffer allFiles = new StringBuffer();
+      allFiles.append("  ").append(xmlSuite.getFileName() != null
+          ? xmlSuite.getFileName() : m_defaultSuiteName).append('\n');
+      Utils.log("TestNG", 0, "Running:\n" + allFiles.toString());
+    }
+
+    SuiteRunner suiteRunner = (SuiteRunner) suiteRunnerMap.get(xmlSuite);
+    suiteRunner.run();
+
+    //TODO: this should be handled properly
+    //    for (IReporter r : suiteRunner.getReporters()) {
+    //      addListener(r);
+    //    }
+
+    // PoolService.getInstance().shutdown();
+
+    //
+    // Display the final statistics
+    //
+    if (xmlSuite.getVerbose() > 0) {
+      SuiteResultCounts counts = new SuiteResultCounts();
+      synchronized (suiteRunnerMap) {
+        counts.calculateResultCounts(xmlSuite, suiteRunnerMap);
+      }
+
+      StringBuffer bufLog = new StringBuffer("\n===============================================\n")
+          .append(xmlSuite.getName());
+      bufLog.append("\nTotal tests run: ")
+          .append(counts.m_total).append(", Failures: ").append(counts.m_failed)
+          .append(", Skips: ").append(counts.m_skipped);
+      if(counts.m_confFailures > 0 || counts.m_confSkips > 0) {
+        bufLog.append("\nConfiguration Failures: ").append(counts.m_confFailures)
+             .append(", Skips: ").append(counts.m_confSkips);
+      }
+      bufLog.append("\n===============================================\n");
+      System.out.println(bufLog.toString());
+    }
+  }
+
+  @Override
+  public void run() {
+    runSuite(m_suiteRunnerMap, m_suiteRunner.getXmlSuite());
+  }
+
+  @Override
+  public int compareTo(IWorker<ISuite> arg0) {
+    /*
+     * Dummy Implementation
+     *
+     * Used by IWorkers to prioritize execution in parallel. Not required by
+     * this Worker in current implementation
+     */
+    return 0;
+  }
+
+  @Override
+  public List<ISuite> getTasks() {
+    List<ISuite> suiteRunnerList = Lists.newArrayList();
+    suiteRunnerList.add(m_suiteRunner);
+    return suiteRunnerList;
+  }
+
+  @Override
+  public String toString() {
+    return Objects.toStringHelper(getClass())
+        .add("name", m_suiteRunner.getName())
+        .toString();
+  }
+
+  @Override
+  public long getTimeOut()
+  {
+    return m_suiteRunner.getXmlSuite().getTimeOut(Long.MAX_VALUE);
+  }
+
+  @Override
+  public int getPriority()
+  {
+    // this class doesnt support priorities yet
+    return 0;
+  }
+
+}
+
+/**
+ * Class to help calculate result counts for tests run as part of a suite and
+ * its children suites
+ *
+ * @author nullin
+ *
+ */
+class SuiteResultCounts {
+
+  int m_total = 0;
+  int m_skipped = 0;
+  int m_failed = 0;
+  int m_confFailures = 0;
+  int m_confSkips = 0;
+
+  public void calculateResultCounts(XmlSuite xmlSuite, SuiteRunnerMap suiteRunnerMap)
+  {
+    ISuite iSuite = suiteRunnerMap.get(xmlSuite);
+    if (iSuite != null) {
+      Map<String, ISuiteResult> results = iSuite.getResults();
+      if (results != null) {
+        Collection<ISuiteResult> tempSuiteResult = results.values();
+        for (ISuiteResult isr : tempSuiteResult) {
+          ITestContext ctx = isr.getTestContext();
+          int skipped = ctx.getSkippedTests().size();
+          int failed = ctx.getFailedTests().size() + ctx.getFailedButWithinSuccessPercentageTests().size();
+          m_skipped += skipped;
+          m_failed += failed;
+          m_confFailures += ctx.getFailedConfigurations().size();
+          m_confSkips += ctx.getSkippedConfigurations().size();
+          m_total += ctx.getPassedTests().size() + failed + skipped;
+        }
+
+        for (XmlSuite childSuite : xmlSuite.getChildSuites()) {
+          calculateResultCounts(childSuite, suiteRunnerMap);
+        }
+      }
+    }
+  }
+}
+
diff --git a/src/main/java/org/testng/TestClass.java b/src/main/java/org/testng/TestClass.java
new file mode 100755
index 0000000..a982fc1
--- /dev/null
+++ b/src/main/java/org/testng/TestClass.java
@@ -0,0 +1,253 @@
+package org.testng;
+
+import org.testng.collections.Lists;
+import org.testng.collections.Objects;
+import org.testng.internal.ConfigurationMethod;
+import org.testng.internal.NoOpTestClass;
+import org.testng.internal.RunInfo;
+import org.testng.internal.TestNGMethod;
+import org.testng.internal.Utils;
+import org.testng.internal.annotations.IAnnotationFinder;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlTest;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+/**
+ * This class represents a test class:
+ * - The test methods
+ * - The configuration methods (test and method)
+ * - The class file
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
+ */
+class TestClass extends NoOpTestClass implements ITestClass {
+	/* generated */
+	private static final long serialVersionUID = -8077917128278361294L;
+  transient private IAnnotationFinder m_annotationFinder = null;
+  // The Strategy used to locate test methods (TestNG, JUnit, etc...)
+  transient private ITestMethodFinder m_testMethodFinder = null;
+
+  private IClass m_iClass = null;
+  private RunInfo m_runInfo = null;
+  private String m_testName;
+  private XmlTest m_xmlTest;
+  private XmlClass m_xmlClass;
+
+  protected TestClass(IClass cls,
+                   ITestMethodFinder testMethodFinder,
+                   IAnnotationFinder annotationFinder,
+                   RunInfo runInfo,
+                   XmlTest xmlTest,
+                   XmlClass xmlClass) {
+    init(cls, testMethodFinder, annotationFinder, runInfo, xmlTest, xmlClass);
+  }
+
+  /**
+   * @return the name of this test if the class implements org.testng.ITest, null otherwise.
+   */
+  @Override
+  public String getTestName() {
+    return m_testName;
+  }
+
+  @Override
+  public XmlTest getXmlTest() {
+    return m_xmlTest;
+  }
+
+  @Override
+  public XmlClass getXmlClass() {
+    return m_xmlClass;
+  }
+
+  public IAnnotationFinder getAnnotationFinder() {
+    return m_annotationFinder;
+  }
+
+  private void init(IClass cls,
+                    ITestMethodFinder testMethodFinder,
+                    IAnnotationFinder annotationFinder,
+                    RunInfo runInfo,
+                    XmlTest xmlTest,
+                    XmlClass xmlClass)
+  {
+    log(3, "Creating TestClass for " + cls);
+    m_iClass = cls;
+    m_testClass = cls.getRealClass();
+    m_xmlTest = xmlTest;
+    m_xmlClass = xmlClass;
+    m_runInfo = runInfo;
+    m_testMethodFinder = testMethodFinder;
+    m_annotationFinder = annotationFinder;
+    initTestClassesAndInstances();
+    initMethods();
+  }
+
+  private void initTestClassesAndInstances() {
+    //
+    // TestClasses and instances
+    //
+    Object[] instances = getInstances(false);
+    for (Object instance : instances) {
+      if (instance instanceof ITest) {
+        m_testName = ((ITest) instance).getTestName();
+        break;
+      }
+    }
+    if (m_testName == null) {
+      m_testName = m_iClass.getTestName();
+    }
+  }
+
+  @Override
+  public Object[] getInstances(boolean create) {
+    return m_iClass.getInstances(create);
+  }
+
+  @Override
+  public long[] getInstanceHashCodes() {
+    return m_iClass.getInstanceHashCodes();
+  }
+
+  @Override
+  public int getInstanceCount() {
+    return m_iClass.getInstanceCount();
+  }
+
+  @Override
+  public void addInstance(Object instance) {
+    m_iClass.addInstance(instance);
+  }
+
+  private void initMethods() {
+    ITestNGMethod[] methods = m_testMethodFinder.getTestMethods(m_testClass, m_xmlTest);
+    m_testMethods = createTestMethods(methods);
+
+    for (Object instance : m_iClass.getInstances(false)) {
+      m_beforeSuiteMethods = ConfigurationMethod
+          .createSuiteConfigurationMethods(m_testMethodFinder.getBeforeSuiteMethods(m_testClass),
+                                           m_annotationFinder,
+                                           true,
+                                           instance);
+      m_afterSuiteMethods = ConfigurationMethod
+          .createSuiteConfigurationMethods(m_testMethodFinder.getAfterSuiteMethods(m_testClass),
+                                           m_annotationFinder,
+                                           false,
+                                           instance);
+      m_beforeTestConfMethods = ConfigurationMethod
+          .createTestConfigurationMethods(m_testMethodFinder.getBeforeTestConfigurationMethods(m_testClass),
+                                          m_annotationFinder,
+                                          true,
+                                          instance);
+      m_afterTestConfMethods = ConfigurationMethod
+          .createTestConfigurationMethods(m_testMethodFinder.getAfterTestConfigurationMethods(m_testClass),
+                                          m_annotationFinder,
+                                          false,
+                                          instance);
+      m_beforeClassMethods = ConfigurationMethod
+          .createClassConfigurationMethods(m_testMethodFinder.getBeforeClassMethods(m_testClass),
+                                           m_annotationFinder,
+                                           true,
+                                           instance);
+      m_afterClassMethods = ConfigurationMethod
+          .createClassConfigurationMethods(m_testMethodFinder.getAfterClassMethods(m_testClass),
+                                           m_annotationFinder,
+                                           false,
+                                           instance);
+      m_beforeGroupsMethods = ConfigurationMethod
+          .createBeforeConfigurationMethods(m_testMethodFinder.getBeforeGroupsConfigurationMethods(m_testClass),
+                                            m_annotationFinder,
+                                            true,
+                                            instance);
+      m_afterGroupsMethods = ConfigurationMethod
+          .createAfterConfigurationMethods(m_testMethodFinder.getAfterGroupsConfigurationMethods(m_testClass),
+                                           m_annotationFinder,
+                                           false,
+                                           instance);
+      m_beforeTestMethods = ConfigurationMethod
+          .createTestMethodConfigurationMethods(m_testMethodFinder.getBeforeTestMethods(m_testClass),
+                                                m_annotationFinder,
+                                                true,
+                                                instance);
+      m_afterTestMethods = ConfigurationMethod
+          .createTestMethodConfigurationMethods(m_testMethodFinder.getAfterTestMethods(m_testClass),
+                                                m_annotationFinder,
+                                                false,
+                                                instance);
+    }
+  }
+
+  /**
+   * Create the test methods that belong to this class (rejects
+   * all those that belong to a different class).
+   */
+  private ITestNGMethod[] createTestMethods(ITestNGMethod[] methods) {
+    List<ITestNGMethod> vResult = Lists.newArrayList();
+    for (ITestNGMethod tm : methods) {
+      Method m = tm.getMethod();
+      if (m.getDeclaringClass().isAssignableFrom(m_testClass)) {
+        for (Object o : m_iClass.getInstances(false)) {
+          log(4, "Adding method " + tm + " on TestClass " + m_testClass);
+          vResult.add(new TestNGMethod(/* tm.getRealClass(), */ m, m_annotationFinder, m_xmlTest,
+              o));
+        }
+      }
+      else {
+        log(4, "Rejecting method " + tm + " for TestClass " + m_testClass);
+      }
+    }
+
+    ITestNGMethod[] result = vResult.toArray(new ITestNGMethod[vResult.size()]);
+    return result;
+  }
+
+  private RunInfo getRunInfo() {
+    return m_runInfo;
+  }
+
+  public ITestMethodFinder getTestMethodFinder() {
+    return m_testMethodFinder;
+  }
+
+  private void log(int level, String s) {
+    Utils.log("TestClass", level, s);
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[TestClass] " + s);
+  }
+
+  protected void dump() {
+    System.out.println("===== Test class\n" + m_testClass.getName());
+    for (ITestNGMethod m : m_beforeClassMethods) {
+      System.out.println("  @BeforeClass " + m);
+    }
+    for (ITestNGMethod m : m_beforeTestMethods) {
+      System.out.println("  @BeforeMethod " + m);
+    }
+    for (ITestNGMethod m : m_testMethods) {
+      System.out.println("    @Test " + m);
+    }
+    for (ITestNGMethod m : m_afterTestMethods) {
+      System.out.println("  @AfterMethod " + m);
+    }
+    for (ITestNGMethod m : m_afterClassMethods) {
+      System.out.println("  @AfterClass " + m);
+    }
+    System.out.println("======");
+  }
+
+  @Override
+  public String toString() {
+    return Objects.toStringHelper(getClass())
+        .add("name", m_testClass)
+        .toString();
+  }
+
+  public IClass getIClass() {
+    return m_iClass;
+  }
+}
\ No newline at end of file
diff --git a/src/main/java/org/testng/TestException.java b/src/main/java/org/testng/TestException.java
new file mode 100755
index 0000000..a6b4e59
--- /dev/null
+++ b/src/main/java/org/testng/TestException.java
@@ -0,0 +1,24 @@
+package org.testng;
+
+/**
+ * Exception thrown when an exception happens while running a test
+ * method.
+ *
+ * @author Cedric Beust, Apr 26, 2004
+ *
+ */
+public class TestException extends TestNGException {
+	private static final long serialVersionUID = -7946644025188038804L;
+
+  public TestException(String s) {
+    super(s);
+  }
+
+	public TestException(Throwable t) {
+		super(t);
+	}
+
+    public TestException(String message, Throwable t) {
+        super(message, t);
+    }
+}
diff --git a/src/main/java/org/testng/TestListenerAdapter.java b/src/main/java/org/testng/TestListenerAdapter.java
new file mode 100755
index 0000000..add0652
--- /dev/null
+++ b/src/main/java/org/testng/TestListenerAdapter.java
@@ -0,0 +1,188 @@
+package org.testng;
+
+import org.testng.collections.Lists;
+import org.testng.collections.Objects;
+import org.testng.internal.IResultListener2;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * A simple ITestListener adapter that stores all the tests
+ * that were run.  You can retrieve these results with the
+ * following methods:
+ * getPassedTests()
+ * getFailedTests()
+ * getSkippedTests()
+ *
+ * If you extend this class in order to override any of these
+ * methods, remember to call their super equivalent if you want
+ * this list of tests to be maintained.
+ *
+ * @author Cedric Beust, Aug 6, 2004
+ * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
+ */
+public class TestListenerAdapter implements IResultListener2 {
+  private List<ITestNGMethod> m_allTestMethods =
+      Collections.synchronizedList(Lists.<ITestNGMethod>newArrayList());
+  private List<ITestResult> m_passedTests = Collections.synchronizedList(Lists.<ITestResult>newArrayList());
+  private List<ITestResult> m_failedTests = Collections.synchronizedList(Lists.<ITestResult>newArrayList());
+  private List<ITestResult> m_skippedTests = Collections.synchronizedList(Lists.<ITestResult>newArrayList());
+  private List<ITestResult> m_failedButWSPerTests = Collections.synchronizedList(Lists.<ITestResult>newArrayList());
+  private List<ITestContext> m_testContexts= Collections.synchronizedList(new ArrayList<ITestContext>());
+  private List<ITestResult> m_failedConfs= Collections.synchronizedList(Lists.<ITestResult>newArrayList());
+  private List<ITestResult> m_skippedConfs= Collections.synchronizedList(Lists.<ITestResult>newArrayList());
+  private List<ITestResult> m_passedConfs= Collections.synchronizedList(Lists.<ITestResult>newArrayList());
+
+  @Override
+  public void onTestSuccess(ITestResult tr) {
+    m_allTestMethods.add(tr.getMethod());
+    m_passedTests.add(tr);
+  }
+
+  @Override
+  public void onTestFailure(ITestResult tr) {
+    m_allTestMethods.add(tr.getMethod());
+    m_failedTests.add(tr);
+  }
+
+  @Override
+  public void onTestSkipped(ITestResult tr) {
+    m_allTestMethods.add(tr.getMethod());
+    m_skippedTests.add(tr);
+  }
+
+  @Override
+  public void onTestFailedButWithinSuccessPercentage(ITestResult tr) {
+    m_allTestMethods.add(tr.getMethod());
+    m_failedButWSPerTests.add(tr);
+  }
+
+  protected ITestNGMethod[] getAllTestMethods() {
+    return m_allTestMethods.toArray(new ITestNGMethod[m_allTestMethods.size()]);
+  }
+
+  @Override
+  public void onStart(ITestContext testContext) {
+	  m_testContexts.add(testContext);
+  }
+
+  @Override
+  public void onFinish(ITestContext testContext) {
+  }
+
+  /**
+   * @return Returns the failedButWithinSuccessPercentageTests.
+   */
+  public List<ITestResult> getFailedButWithinSuccessPercentageTests() {
+    return m_failedButWSPerTests;
+  }
+  /**
+   * @return Returns the failedTests.
+   */
+  public List<ITestResult> getFailedTests() {
+    return m_failedTests;
+  }
+  /**
+   * @return Returns the passedTests.
+   */
+  public List<ITestResult> getPassedTests() {
+    return m_passedTests;
+  }
+  /**
+   * @return Returns the skippedTests.
+   */
+  public List<ITestResult> getSkippedTests() {
+    return m_skippedTests;
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[TestListenerAdapter] " + s);
+  }
+  /**
+   * @param allTestMethods The allTestMethods to set.
+   */
+  public void setAllTestMethods(List<ITestNGMethod> allTestMethods) {
+    m_allTestMethods = allTestMethods;
+  }
+  /**
+   * @param failedButWithinSuccessPercentageTests The failedButWithinSuccessPercentageTests to set.
+   */
+  public void setFailedButWithinSuccessPercentageTests(
+      List<ITestResult> failedButWithinSuccessPercentageTests) {
+    m_failedButWSPerTests = failedButWithinSuccessPercentageTests;
+  }
+  /**
+   * @param failedTests The failedTests to set.
+   */
+  public void setFailedTests(List<ITestResult> failedTests) {
+    m_failedTests = failedTests;
+  }
+  /**
+   * @param passedTests The passedTests to set.
+   */
+  public void setPassedTests(List<ITestResult> passedTests) {
+    m_passedTests = passedTests;
+  }
+  /**
+   * @param skippedTests The skippedTests to set.
+   */
+  public void setSkippedTests(List<ITestResult> skippedTests) {
+    m_skippedTests = skippedTests;
+  }
+
+  @Override
+  public void onTestStart(ITestResult result) {
+  }
+
+  public List<ITestContext> getTestContexts() {
+    return m_testContexts;
+  }
+
+  public List<ITestResult> getConfigurationFailures() {
+    return m_failedConfs;
+  }
+
+  /**
+   * @see org.testng.IConfigurationListener#onConfigurationFailure(org.testng.ITestResult)
+   */
+  @Override
+  public void onConfigurationFailure(ITestResult itr) {
+    m_failedConfs.add(itr);
+  }
+
+  public List<ITestResult> getConfigurationSkips() {
+    return m_skippedConfs;
+  }
+
+  @Override
+  public void beforeConfiguration(ITestResult tr) {
+  }
+
+  /**
+   * @see org.testng.IConfigurationListener#onConfigurationSkip(org.testng.ITestResult)
+   */
+  @Override
+  public void onConfigurationSkip(ITestResult itr) {
+    m_skippedConfs.add(itr);
+  }
+
+  /**
+   * @see org.testng.IConfigurationListener#onConfigurationSuccess(org.testng.ITestResult)
+   */
+  @Override
+  public void onConfigurationSuccess(ITestResult itr) {
+    m_passedConfs.add(itr);
+  }
+
+  @Override
+  public String toString() {
+    return Objects.toStringHelper(getClass())
+        .add("passed", getPassedTests().size())
+        .add("failed", getFailedTests().size())
+        .add("skipped", getSkippedTests().size())
+        .toString();
+  }
+}
diff --git a/src/main/java/org/testng/TestNG.java b/src/main/java/org/testng/TestNG.java
new file mode 100644
index 0000000..1925690
--- /dev/null
+++ b/src/main/java/org/testng/TestNG.java
@@ -0,0 +1,2051 @@
+package org.testng;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URLClassLoader;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.testng.annotations.ITestAnnotation;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.collections.Sets;
+import org.testng.internal.ClassHelper;
+import org.testng.internal.Configuration;
+import org.testng.internal.DynamicGraph;
+import org.testng.internal.IConfiguration;
+import org.testng.internal.IResultListener2;
+import org.testng.internal.OverrideProcessor;
+import org.testng.internal.SuiteRunnerMap;
+import org.testng.internal.Utils;
+import org.testng.internal.Version;
+import org.testng.internal.annotations.DefaultAnnotationTransformer;
+import org.testng.internal.annotations.IAnnotationFinder;
+import org.testng.internal.annotations.JDK15AnnotationFinder;
+import org.testng.internal.thread.graph.GraphThreadPoolExecutor;
+import org.testng.internal.thread.graph.IThreadWorkerFactory;
+import org.testng.internal.thread.graph.SuiteWorkerFactory;
+import org.testng.junit.JUnitTestFinder;
+import org.testng.log4testng.Logger;
+import org.testng.remote.SuiteDispatcher;
+import org.testng.remote.SuiteSlave;
+import org.testng.reporters.EmailableReporter;
+import org.testng.reporters.EmailableReporter2;
+import org.testng.reporters.FailedReporter;
+import org.testng.reporters.JUnitReportReporter;
+import org.testng.reporters.SuiteHTMLReporter;
+import org.testng.reporters.VerboseReporter;
+import org.testng.reporters.XMLReporter;
+import org.testng.reporters.jq.Main;
+import org.testng.xml.Parser;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlInclude;
+import org.testng.xml.XmlMethodSelector;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+import org.xml.sax.SAXException;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.ParameterException;
+
+import static org.testng.internal.Utils.defaultIfStringEmpty;
+import static org.testng.internal.Utils.isStringEmpty;
+import static org.testng.internal.Utils.isStringNotEmpty;
+
+/**
+ * This class is the main entry point for running tests in the TestNG framework.
+ * Users can create their own TestNG object and invoke it in many different
+ * ways:
+ * <ul>
+ * <li>On an existing testng.xml
+ * <li>On a synthetic testng.xml, created entirely from Java
+ * <li>By directly setting the test classes
+ * </ul>
+ * You can also define which groups to include or exclude, assign parameters, etc...
+ * <P/>
+ * The command line parameters are:
+ * <UL>
+ *  <LI>-d <TT>outputdir</TT>: specify the output directory</LI>
+ *  <LI>-testclass <TT>class_name</TT>: specifies one or several class names </li>
+ *  <LI>-testjar <TT>jar_name</TT>: specifies the jar containing the tests</LI>
+ *  <LI>-sourcedir <TT>src1;src2</TT>: ; separated list of source directories
+ *    (used only when javadoc annotations are used)</LI>
+ *  <LI>-target</LI>
+ *  <LI>-groups</LI>
+ *  <LI>-testrunfactory</LI>
+ *  <LI>-listener</LI>
+ * </UL>
+ * <P/>
+ * Please consult documentation for more details.
+ *
+ * FIXME: should support more than simple paths for suite xmls
+ *
+ * @see #usage()
+ *
+ * @author <a href = "mailto:cedric&#64;beust.com">Cedric Beust</a>
+ */
+public class TestNG {
+
+  /** This class' log4testng Logger. */
+  private static final Logger LOGGER = Logger.getLogger(TestNG.class);
+
+  /** The default name for a suite launched from the command line */
+  public static final String DEFAULT_COMMAND_LINE_SUITE_NAME = "Command line suite";
+
+  /** The default name for a test launched from the command line */
+  public static final String DEFAULT_COMMAND_LINE_TEST_NAME = "Command line test";
+
+  /** The default name of the result's output directory (keep public, used by Eclipse). */
+  public static final String DEFAULT_OUTPUTDIR = "test-output";
+
+  /** System properties */
+  public static final String SHOW_TESTNG_STACK_FRAMES = "testng.show.stack.frames";
+  public static final String TEST_CLASSPATH = "testng.test.classpath";
+
+  private static TestNG m_instance;
+
+  private static JCommander m_jCommander;
+
+  private List<String> m_commandLineMethods;
+  protected List<XmlSuite> m_suites = Lists.newArrayList();
+  private List<XmlSuite> m_cmdlineSuites;
+  private String m_outputDir = DEFAULT_OUTPUTDIR;
+
+  private String[] m_includedGroups;
+  private String[] m_excludedGroups;
+
+  private Boolean m_isJUnit = XmlSuite.DEFAULT_JUNIT;
+  private Boolean m_isMixed = XmlSuite.DEFAULT_MIXED;
+  protected boolean m_useDefaultListeners = true;
+
+  private ITestRunnerFactory m_testRunnerFactory;
+
+  // These listeners can be overridden from the command line
+  private List<IClassListener> m_classListeners = Lists.newArrayList();
+  private List<ITestListener> m_testListeners = Lists.newArrayList();
+  private List<ISuiteListener> m_suiteListeners = Lists.newArrayList();
+  private Set<IReporter> m_reporters = Sets.newHashSet();
+
+  protected static final int HAS_FAILURE = 1;
+  protected static final int HAS_SKIPPED = 2;
+  protected static final int HAS_FSP = 4;
+  protected static final int HAS_NO_TEST = 8;
+
+  public static final Integer DEFAULT_VERBOSE = 1;
+
+  private int m_status;
+  private boolean m_hasTests= false;
+
+  private String m_slavefileName = null;
+  private String m_masterfileName = null;
+
+  // Command line suite parameters
+  private int m_threadCount;
+  private boolean m_useThreadCount;
+  private XmlSuite.ParallelMode m_parallelMode = XmlSuite.ParallelMode.FALSE;
+  private String m_configFailurePolicy;
+  private Class[] m_commandLineTestClasses;
+
+  private String m_defaultSuiteName=DEFAULT_COMMAND_LINE_SUITE_NAME;
+  private String m_defaultTestName=DEFAULT_COMMAND_LINE_TEST_NAME;
+
+  private Map<String, Integer> m_methodDescriptors = Maps.newHashMap();
+
+  private ITestObjectFactory m_objectFactory;
+
+  private List<IInvokedMethodListener> m_invokedMethodListeners = Lists.newArrayList();
+
+  private Integer m_dataProviderThreadCount = null;
+
+  private String m_jarPath;
+  /** The path of the testng.xml file inside the jar file */
+  private String m_xmlPathInJar = CommandLineArgs.XML_PATH_IN_JAR_DEFAULT;
+
+  private List<String> m_stringSuites = Lists.newArrayList();
+
+  private IHookable m_hookable;
+  private IConfigurable m_configurable;
+
+  protected long m_end;
+  protected long m_start;
+
+  private List<IExecutionListener> m_executionListeners = Lists.newArrayList();
+
+  private List<IAlterSuiteListener> m_alterSuiteListeners= Lists.newArrayList();
+
+  private boolean m_isInitialized = false;
+
+  /**
+   * Default constructor. Setting also usage of default listeners/reporters.
+   */
+  public TestNG() {
+    init(true);
+  }
+
+  /**
+   * Used by maven2 to have 0 output of any kind come out
+   * of testng.
+   * @param useDefaultListeners Whether or not any default reports
+   * should be added to tests.
+   */
+  public TestNG(boolean useDefaultListeners) {
+    init(useDefaultListeners);
+  }
+
+  private void init(boolean useDefaultListeners) {
+    m_instance = this;
+
+    m_useDefaultListeners = useDefaultListeners;
+    m_configuration = new Configuration();
+  }
+
+  public int getStatus() {
+    return m_status;
+  }
+
+  private void setStatus(int status) {
+    m_status |= status;
+  }
+
+  /**
+   * Sets the output directory where the reports will be created.
+   * @param outputdir The directory.
+   */
+  public void setOutputDirectory(final String outputdir) {
+    if (isStringNotEmpty(outputdir)) {
+      m_outputDir = outputdir;
+    }
+  }
+
+  /**
+   * If this method is passed true before run(), the default listeners
+   * will not be used.
+   * <ul>
+   * <li>org.testng.reporters.TestHTMLReporter
+   * <li>org.testng.reporters.JUnitXMLReporter
+   * <li>org.testng.reporters.XMLReporter
+   * </ul>
+   *
+   * @see org.testng.reporters.TestHTMLReporter
+   * @see org.testng.reporters.JUnitXMLReporter
+   * @see org.testng.reporters.XMLReporter
+   */
+  public void setUseDefaultListeners(boolean useDefaultListeners) {
+    m_useDefaultListeners = useDefaultListeners;
+  }
+
+  /**
+   * Sets a jar containing a testng.xml file.
+   *
+   * @param jarPath
+   */
+  public void setTestJar(String jarPath) {
+    m_jarPath = jarPath;
+  }
+
+  /**
+   * Sets the path to the XML file in the test jar file.
+   */
+  public void setXmlPathInJar(String xmlPathInJar) {
+    m_xmlPathInJar = xmlPathInJar;
+  }
+
+  public void initializeSuitesAndJarFile() {
+    // The Eclipse plug-in (RemoteTestNG) might have invoked this method already
+    // so don't initialize suites twice.
+    if (m_isInitialized) {
+      return;
+    }
+
+    m_isInitialized = true;
+    if (m_suites.size() > 0) {
+    	//to parse the suite files (<suite-file>), if any
+    	for (XmlSuite s: m_suites) {
+        for (String suiteFile : s.getSuiteFiles()) {
+            Path rootPath = Paths.get(s.getFileName()).getParent();
+            try {
+                Collection<XmlSuite> childSuites = getParser(rootPath.resolve(suiteFile).normalize().toString()).parse();
+                for (XmlSuite cSuite : childSuites){
+                    cSuite.setParentSuite(s);
+                    s.getChildSuites().add(cSuite);
+                }
+            } catch (ParserConfigurationException | IOException | SAXException e) {
+                e.printStackTrace(System.out);
+            }
+        }
+
+    	}
+      return;
+    }
+
+    //
+    // Parse the suites that were passed on the command line
+    //
+    for (String suitePath : m_stringSuites) {
+      if(LOGGER.isDebugEnabled()) {
+        LOGGER.debug("suiteXmlPath: \"" + suitePath + "\"");
+      }
+      try {
+        Collection<XmlSuite> allSuites = getParser(suitePath).parse();
+
+        for (XmlSuite s : allSuites) {
+          // If test names were specified, only run these test names
+          if (m_testNames != null) {
+            m_suites.add(extractTestNames(s, m_testNames));
+          }
+          else {
+            m_suites.add(s);
+          }
+        }
+      }
+      catch(SAXException | ParserConfigurationException | IOException e) {
+        e.printStackTrace(System.out);
+      } catch(Exception ex) {
+        // Probably a Yaml exception, unnest it
+        Throwable t = ex;
+        while (t.getCause() != null) t = t.getCause();
+//        t.printStackTrace();
+        if (t instanceof TestNGException) throw (TestNGException) t;
+        else throw new TestNGException(t);
+      }
+    }
+
+    //
+    // jar path
+    //
+    // If suites were passed on the command line, they take precedence over the suite file
+    // inside that jar path
+    if (m_jarPath != null && m_stringSuites.size() > 0) {
+      StringBuilder suites = new StringBuilder();
+      for (String s : m_stringSuites) {
+        suites.append(s);
+      }
+      Utils.log("TestNG", 2, "Ignoring the XML file inside " + m_jarPath + " and using "
+          + suites + " instead");
+      return;
+    }
+    if (isStringEmpty(m_jarPath)) {
+      return;
+    }
+
+    // We have a jar file and no XML file was specified: try to find an XML file inside the jar
+    File jarFile = new File(m_jarPath);
+
+    try {
+
+      Utils.log("TestNG", 2, "Trying to open jar file:" + jarFile);
+
+      boolean foundTestngXml = false;
+      List<String> classes = Lists.newArrayList();
+      try (JarFile jf = new JarFile(jarFile)) {
+//      System.out.println("   result: " + jf);
+        Enumeration<JarEntry> entries = jf.entries();
+        while (entries.hasMoreElements()) {
+          JarEntry je = entries.nextElement();
+          if (je.getName().equals(m_xmlPathInJar)) {
+            Parser parser = getParser(jf.getInputStream(je));
+            Collection<XmlSuite> suites = parser.parse();
+            for (XmlSuite suite : suites) {
+              // If test names were specified, only run these test names
+              if (m_testNames != null) {
+                m_suites.add(extractTestNames(suite, m_testNames));
+              } else {
+                m_suites.add(suite);
+              }
+            }
+
+            foundTestngXml = true;
+            break;
+          } else if (je.getName().endsWith(".class")) {
+            int n = je.getName().length() - ".class".length();
+            classes.add(je.getName().replace("/", ".").substring(0, n));
+          }
+        }
+      }
+      if (! foundTestngXml) {
+        Utils.log("TestNG", 1,
+            "Couldn't find the " + m_xmlPathInJar + " in the jar file, running all the classes");
+        XmlSuite xmlSuite = new XmlSuite();
+        xmlSuite.setVerbose(0);
+        xmlSuite.setName("Jar suite");
+        XmlTest xmlTest = new XmlTest(xmlSuite);
+        List<XmlClass> xmlClasses = Lists.newArrayList();
+        for (String cls : classes) {
+          XmlClass xmlClass = new XmlClass(cls);
+          xmlClasses.add(xmlClass);
+        }
+        xmlTest.setXmlClasses(xmlClasses);
+        m_suites.add(xmlSuite);
+      }
+    }
+    catch(ParserConfigurationException | IOException | SAXException ex) {
+      ex.printStackTrace();
+    }
+  }
+
+  private Parser getParser(String path) {
+    Parser result = new Parser(path);
+    initProcessor(result);
+    return result;
+  }
+
+  private Parser getParser(InputStream is) {
+    Parser result = new Parser(is);
+    initProcessor(result);
+    return result;
+  }
+
+  private void initProcessor(Parser result) {
+    result.setPostProcessor(new OverrideProcessor(m_includedGroups, m_excludedGroups));
+  }
+
+  /**
+   * If the XmlSuite contains at least one test named as testNames, return
+   * an XmlSuite that's made only of these tests, otherwise, return the
+   * original suite.
+   */
+  private static XmlSuite extractTestNames(XmlSuite s, List<String> testNames) {
+    extractTestNamesFromChildSuites(s, testNames);
+
+    List<XmlTest> tests = Lists.newArrayList();
+    for (XmlTest xt : s.getTests()) {
+      for (String tn : testNames) {
+        if (xt.getName().equals(tn)) {
+          tests.add(xt);
+        }
+      }
+    }
+
+    if (tests.size() == 0) {
+      return s;
+    }
+    else {
+      XmlSuite result = (XmlSuite) s.clone();
+      result.getTests().clear();
+      result.getTests().addAll(tests);
+      return result;
+    }
+  }
+
+  private static void extractTestNamesFromChildSuites(XmlSuite s, List<String> testNames) {
+    List<XmlSuite> childSuites = s.getChildSuites();
+    for (int i = 0; i < childSuites.size(); i++) {
+      XmlSuite child = childSuites.get(i);
+      XmlSuite extracted = extractTestNames(child, testNames);
+      // if a new xml suite is created, which means some tests was extracted, then we replace the child
+      if (extracted != child) {
+        childSuites.set(i, extracted);
+      }
+    }
+  }
+
+  /**
+   * Define the number of threads in the thread pool.
+   */
+  public void setThreadCount(int threadCount) {
+    if(threadCount < 1) {
+      exitWithError("Cannot use a threadCount parameter less than 1; 1 > " + threadCount);
+    }
+
+    m_threadCount = threadCount;
+    m_useThreadCount = true;
+  }
+
+  /**
+   * Define whether this run will be run in parallel mode.
+   * @deprecated Use #setParallel(XmlSuite.ParallelMode) instead
+   */
+  @Deprecated
+  public void setParallel(String parallel) {
+    if (parallel == null) {
+      setParallel(XmlSuite.ParallelMode.FALSE);
+    } else {
+      setParallel(XmlSuite.ParallelMode.getValidParallel(parallel));
+    }
+  }
+
+  public void setParallel(XmlSuite.ParallelMode parallel) {
+    m_parallelMode = parallel;
+  }
+
+  public void setCommandLineSuite(XmlSuite suite) {
+    m_cmdlineSuites = Lists.newArrayList();
+    m_cmdlineSuites.add(suite);
+    m_suites.add(suite);
+  }
+
+  /**
+   * Set the test classes to be run by this TestNG object.  This method
+   * will create a dummy suite that will wrap these classes called
+   * "Command Line Test".
+   * <p/>
+   * If used together with threadCount, parallel, groups, excludedGroups than this one must be set first.
+   *
+   * @param classes An array of classes that contain TestNG annotations.
+   */
+  public void setTestClasses(Class[] classes) {
+    m_suites.clear();
+    m_commandLineTestClasses = classes;
+  }
+
+  /**
+   * Given a string com.example.Foo.f1, return an array where [0] is the class and [1]
+   * is the method.
+   */
+  private String[] splitMethod(String m) {
+    int index = m.lastIndexOf(".");
+    if (index < 0) {
+      throw new TestNGException("Bad format for command line method:" + m
+          + ", expected <class>.<method>");
+    }
+
+    return new String[] { m.substring(0, index), m.substring(index + 1).replaceAll("\\*", "\\.\\*") };
+  }
+
+  /**
+   * @return a list of XmlSuite objects that represent the list of classes and methods passed
+   * in parameter.
+   *
+   * @param commandLineMethods a string with the form "com.example.Foo.f1,com.example.Bar.f2"
+   */
+  private List<XmlSuite> createCommandLineSuitesForMethods(List<String> commandLineMethods) {
+    //
+    // Create the <classes> tag
+    //
+    Set<Class> classes = Sets.newHashSet();
+    for (String m : commandLineMethods) {
+      Class c = ClassHelper.forName(splitMethod(m)[0]);
+      if (c != null) {
+          classes.add(c);
+      }
+    }
+
+    List<XmlSuite> result = createCommandLineSuitesForClasses(classes.toArray(new Class[0]));
+
+    //
+    // Add the method tags
+    //
+    List<XmlClass> xmlClasses = Lists.newArrayList();
+    for (XmlSuite s : result) {
+        for (XmlTest t : s.getTests()) {
+            xmlClasses.addAll(t.getClasses());
+        }
+    }
+
+    for (XmlClass xc : xmlClasses) {
+      for (String m : commandLineMethods) {
+        String[] split = splitMethod(m);
+        String className = split[0];
+        if (xc.getName().equals(className)) {
+          XmlInclude includedMethod = new XmlInclude(split[1]);
+          xc.getIncludedMethods().add(includedMethod);
+        }
+      }
+    }
+
+    return result;
+  }
+
+  private List<XmlSuite> createCommandLineSuitesForClasses(Class[] classes) {
+    //
+    // See if any of the classes has an xmlSuite or xmlTest attribute.
+    // If it does, create the appropriate XmlSuite, otherwise, create
+    // the default one
+    //
+    XmlClass[] xmlClasses = Utils.classesToXmlClasses(classes);
+    Map<String, XmlSuite> suites = Maps.newHashMap();
+    IAnnotationFinder finder = m_configuration.getAnnotationFinder();
+
+    for (int i = 0; i < classes.length; i++) {
+      Class c = classes[i];
+      ITestAnnotation test = finder.findAnnotation(c, ITestAnnotation.class);
+      String suiteName = getDefaultSuiteName();
+      String testName = getDefaultTestName();
+      boolean isJUnit = false;
+      if (test != null) {
+        suiteName = defaultIfStringEmpty(test.getSuiteName(), suiteName);
+        testName = defaultIfStringEmpty(test.getTestName(), testName);
+      } else {
+        if (m_isMixed && JUnitTestFinder.isJUnitTest(c)) {
+          isJUnit = true;
+          testName = c.getName();
+        }
+      }
+      XmlSuite xmlSuite = suites.get(suiteName);
+      if (xmlSuite == null) {
+        xmlSuite = new XmlSuite();
+        xmlSuite.setName(suiteName);
+        suites.put(suiteName, xmlSuite);
+      }
+
+      if (m_dataProviderThreadCount != null) {
+        xmlSuite.setDataProviderThreadCount(m_dataProviderThreadCount);
+      }
+      XmlTest xmlTest = null;
+      for (XmlTest xt  : xmlSuite.getTests()) {
+        if (xt.getName().equals(testName)) {
+          xmlTest = xt;
+          break;
+        }
+      }
+
+      if (xmlTest == null) {
+        xmlTest = new XmlTest(xmlSuite);
+        xmlTest.setName(testName);
+        xmlTest.setJUnit(isJUnit);
+      }
+
+      xmlTest.getXmlClasses().add(xmlClasses[i]);
+    }
+
+    return new ArrayList<>(suites.values());
+  }
+
+  public void addMethodSelector(String className, int priority) {
+    m_methodDescriptors.put(className, priority);
+  }
+
+  /**
+   * Set the suites file names to be run by this TestNG object. This method tries to load and
+   * parse the specified TestNG suite xml files. If a file is missing, it is ignored.
+   *
+   * @param suites A list of paths to one more XML files defining the tests.  For example:
+   *
+   * <pre>
+   * TestNG tng = new TestNG();
+   * List<String> suites = Lists.newArrayList();
+   * suites.add("c:/tests/testng1.xml");
+   * suites.add("c:/tests/testng2.xml");
+   * tng.setTestSuites(suites);
+   * tng.run();
+   * </pre>
+   */
+  public void setTestSuites(List<String> suites) {
+    m_stringSuites = suites;
+  }
+
+  /**
+   * Specifies the XmlSuite objects to run.
+   * @param suites
+   * @see org.testng.xml.XmlSuite
+   */
+  public void setXmlSuites(List<XmlSuite> suites) {
+    m_suites = suites;
+  }
+
+  /**
+   * Define which groups will be excluded from this run.
+   *
+   * @param groups A list of group names separated by a comma.
+   */
+  public void setExcludedGroups(String groups) {
+    m_excludedGroups = Utils.split(groups, ",");
+  }
+
+
+  /**
+   * Define which groups will be included from this run.
+   *
+   * @param groups A list of group names separated by a comma.
+   */
+  public void setGroups(String groups) {
+    m_includedGroups = Utils.split(groups, ",");
+  }
+
+
+  private void setTestRunnerFactoryClass(Class testRunnerFactoryClass) {
+    setTestRunnerFactory((ITestRunnerFactory) ClassHelper.newInstance(testRunnerFactoryClass));
+  }
+
+
+  protected void setTestRunnerFactory(ITestRunnerFactory itrf) {
+    m_testRunnerFactory= itrf;
+  }
+
+  public void setObjectFactory(Class c) {
+    m_objectFactory = (ITestObjectFactory) ClassHelper.newInstance(c);
+  }
+
+  public void setObjectFactory(ITestObjectFactory factory) {
+    m_objectFactory = factory;
+  }
+
+  /**
+   * Define which listeners to user for this run.
+   *
+   * @param classes A list of classes, which must be either ISuiteListener,
+   * ITestListener or IReporter
+   */
+  public void setListenerClasses(List<Class> classes) {
+    for (Class cls: classes) {
+      addListener(ClassHelper.newInstance(cls));
+    }
+  }
+
+  public void addListener(Object listener) {
+    if (! (listener instanceof ITestNGListener))
+    {
+      exitWithError("Listener " + listener
+          + " must be one of ITestListener, ISuiteListener, IReporter, "
+          + " IAnnotationTransformer, IMethodInterceptor or IInvokedMethodListener");
+    }
+    else {
+      if (listener instanceof ISuiteListener) {
+        addListener((ISuiteListener) listener);
+      }
+      if (listener instanceof ITestListener) {
+        addListener((ITestListener) listener);
+      }
+      if (listener instanceof IClassListener) {
+        addListener((IClassListener) listener);
+      }
+      if (listener instanceof IReporter) {
+        addListener((IReporter) listener);
+      }
+      if (listener instanceof IAnnotationTransformer) {
+        setAnnotationTransformer((IAnnotationTransformer) listener);
+      }
+      if (listener instanceof IMethodInterceptor) {
+        m_methodInterceptors.add((IMethodInterceptor) listener);
+      }
+      if (listener instanceof IInvokedMethodListener) {
+        addInvokedMethodListener((IInvokedMethodListener) listener);
+      }
+      if (listener instanceof IHookable) {
+        setHookable((IHookable) listener);
+      }
+      if (listener instanceof IConfigurable) {
+        setConfigurable((IConfigurable) listener);
+      }
+      if (listener instanceof IExecutionListener) {
+        addExecutionListener((IExecutionListener) listener);
+      }
+      if (listener instanceof IConfigurationListener) {
+        getConfiguration().addConfigurationListener((IConfigurationListener) listener);
+      }
+      if (listener instanceof IAlterSuiteListener) {
+        addAlterSuiteListener((IAlterSuiteListener) listener);
+      }
+    }
+  }
+
+  public void addListener(IInvokedMethodListener listener) {
+    m_invokedMethodListeners.add(listener);
+  }
+
+  public void addListener(ISuiteListener listener) {
+    if (null != listener) {
+      m_suiteListeners.add(listener);
+    }
+  }
+
+  public void addListener(ITestListener listener) {
+    if (null != listener) {
+      m_testListeners.add(listener);
+    }
+  }
+
+  public void addListener(IClassListener listener) {
+    if (null != listener) {
+      m_classListeners.add(listener);
+    }
+  }
+
+  public void addListener(IReporter listener) {
+    if (null != listener) {
+      m_reporters.add(listener);
+    }
+  }
+
+  public void addInvokedMethodListener(IInvokedMethodListener listener) {
+    m_invokedMethodListeners.add(listener);
+  }
+
+  public Set<IReporter> getReporters() {
+    return m_reporters;
+  }
+
+  public List<ITestListener> getTestListeners() {
+    return m_testListeners;
+  }
+
+  public List<ISuiteListener> getSuiteListeners() {
+    return m_suiteListeners;
+  }
+
+  /** If m_verbose gets set, it will override the verbose setting in testng.xml */
+  private Integer m_verbose = null;
+
+  private final IAnnotationTransformer m_defaultAnnoProcessor = new DefaultAnnotationTransformer();
+  private IAnnotationTransformer m_annotationTransformer = m_defaultAnnoProcessor;
+
+  private Boolean m_skipFailedInvocationCounts = false;
+
+  private List<IMethodInterceptor> m_methodInterceptors = new ArrayList<IMethodInterceptor>();
+
+  /** The list of test names to run from the given suite */
+  private List<String> m_testNames;
+
+  private Integer m_suiteThreadPoolSize = CommandLineArgs.SUITE_THREAD_POOL_SIZE_DEFAULT;
+
+  private boolean m_randomizeSuites = Boolean.FALSE;
+
+  private boolean m_preserveOrder = false;
+  private Boolean m_groupByInstances;
+
+  private IConfiguration m_configuration;
+
+  /**
+   * Sets the level of verbosity. This value will override the value specified
+   * in the test suites.
+   *
+   * @param verbose the verbosity level (0 to 10 where 10 is most detailed)
+   * Actually, this is a lie:  you can specify -1 and this will put TestNG
+   * in debug mode (no longer slicing off stack traces and all).
+   */
+  public void setVerbose(int verbose) {
+    m_verbose = verbose;
+  }
+
+  private void initializeCommandLineSuites() {
+    if (m_commandLineTestClasses != null || m_commandLineMethods != null) {
+      if (null != m_commandLineMethods) {
+        m_cmdlineSuites = createCommandLineSuitesForMethods(m_commandLineMethods);
+      }
+      else {
+        m_cmdlineSuites = createCommandLineSuitesForClasses(m_commandLineTestClasses);
+      }
+
+      for (XmlSuite s : m_cmdlineSuites) {
+        for (XmlTest t : s.getTests()) {
+          t.setPreserveOrder(String.valueOf(m_preserveOrder));
+        }
+        m_suites.add(s);
+        if (m_groupByInstances != null) {
+          s.setGroupByInstances(m_groupByInstances);
+        }
+      }
+    }
+  }
+
+  private void initializeCommandLineSuitesParams() {
+    if(null == m_cmdlineSuites) {
+      return;
+    }
+
+    for (XmlSuite s : m_cmdlineSuites) {
+      if(m_useThreadCount) {
+        s.setThreadCount(m_threadCount);
+      }
+      s.setParallel(m_parallelMode);
+      if(m_configFailurePolicy != null) {
+        s.setConfigFailurePolicy(m_configFailurePolicy.toString());
+      }
+    }
+
+  }
+
+  private void initializeCommandLineSuitesGroups() {
+    // If groups were specified on the command line, they should override groups
+    // specified in the XML file
+    boolean hasIncludedGroups = null != m_includedGroups && m_includedGroups.length > 0;
+    boolean hasExcludedGroups = null != m_excludedGroups && m_excludedGroups.length > 0;
+    List<XmlSuite> suites = m_cmdlineSuites != null ? m_cmdlineSuites : m_suites;
+    if (hasIncludedGroups || hasExcludedGroups) {
+      for (XmlSuite s : suites) {
+        //set on each test, instead of just the first one of the suite
+        for (XmlTest t : s.getTests()) {
+          if(hasIncludedGroups) {
+            t.setIncludedGroups(Arrays.asList(m_includedGroups));
+          }
+          if(hasExcludedGroups) {
+            t.setExcludedGroups(Arrays.asList(m_excludedGroups));
+          }
+        }
+      }
+    }
+  }
+  private void addReporter(Class<? extends IReporter> r) {
+    m_reporters.add(ClassHelper.newInstance(r));
+  }
+
+  private void initializeDefaultListeners() {
+    m_testListeners.add(new ExitCodeListener(this));
+
+    if (m_useDefaultListeners) {
+      addReporter(SuiteHTMLReporter.class);
+      addReporter(Main.class);
+      addReporter(FailedReporter.class);
+      addReporter(XMLReporter.class);
+      if (System.getProperty("oldTestngEmailableReporter") != null) {
+        addReporter(EmailableReporter.class);
+      } else if (System.getProperty("noEmailableReporter") == null) {
+        addReporter(EmailableReporter2.class);
+      }
+      addReporter(JUnitReportReporter.class);
+      if (m_verbose != null && m_verbose > 4) {
+        addListener(new VerboseReporter("[TestNG] "));
+      }
+    }
+  }
+
+  private void initializeConfiguration() {
+    ITestObjectFactory factory = m_objectFactory;
+    //
+    // Install the listeners found in ServiceLoader (or use the class
+    // loader for tests, if specified).
+    //
+    addServiceLoaderListeners();
+
+    //
+    // Install the listeners found in the suites
+    //
+    for (XmlSuite s : m_suites) {
+      for (String listenerName : s.getListeners()) {
+        Class<?> listenerClass = ClassHelper.forName(listenerName);
+
+        // If specified listener does not exist, a TestNGException will be thrown
+        if(listenerClass == null) {
+          throw new TestNGException("Listener " + listenerName
+              + " was not found in project's classpath");
+        }
+
+        Object listener = ClassHelper.newInstance(listenerClass);
+        addListener(listener);
+      }
+
+      //
+      // Install the method selectors
+      //
+      for (XmlMethodSelector methodSelector : s.getMethodSelectors() ) {
+        addMethodSelector(methodSelector.getClassName(), methodSelector.getPriority());
+      }
+
+      //
+      // Find if we have an object factory
+      //
+      if (s.getObjectFactory() != null) {
+        if (factory == null) {
+          factory = s.getObjectFactory();
+        } else {
+          throw new TestNGException("Found more than one object-factory tag in your suites");
+        }
+      }
+    }
+
+    m_configuration.setAnnotationFinder(new JDK15AnnotationFinder(getAnnotationTransformer()));
+    m_configuration.setHookable(m_hookable);
+    m_configuration.setConfigurable(m_configurable);
+    m_configuration.setObjectFactory(factory);
+  }
+
+  /**
+   * Using reflection to remain Java 5 compliant.
+   */
+  private void addServiceLoaderListeners() {
+      Iterable<ITestNGListener> loader = m_serviceLoaderClassLoader != null ?
+          ServiceLoader.load(ITestNGListener.class, m_serviceLoaderClassLoader)
+          : ServiceLoader.load(ITestNGListener.class);
+      for (ITestNGListener l : loader) {
+        Utils.log("[TestNG]", 2, "Adding ServiceLoader listener:" + l);
+        addListener(l);
+        addServiceLoaderListener(l);
+      }
+  }
+
+  /**
+   * Before suites are executed, do a sanity check to ensure all required
+   * conditions are met. If not, throw an exception to stop test execution
+   *
+   * @throws TestNGException if the sanity check fails
+   */
+  private void sanityCheck() {
+    checkTestNames(m_suites);
+    checkSuiteNames(m_suites);
+  }
+
+  /**
+   * Ensure that two XmlTest within the same XmlSuite don't have the same name
+   */
+  private void checkTestNames(List<XmlSuite> suites) {
+    for (XmlSuite suite : suites) {
+      Set<String> testNames = Sets.newHashSet();
+      for (XmlTest test : suite.getTests()) {
+        if (testNames.contains(test.getName())) {
+          throw new TestNGException("Two tests in the same suite "
+              + "cannot have the same name: " + test.getName());
+        } else {
+          testNames.add(test.getName());
+        }
+      }
+      checkTestNames(suite.getChildSuites());
+    }
+  }
+
+  /**
+   * Ensure that two XmlSuite don't have the same name
+   * Otherwise will be clash in SuiteRunnerMap
+   * See issue #302
+   */
+  private void checkSuiteNames(List<XmlSuite> suites) {
+    checkSuiteNamesInternal(suites, Sets.<String>newHashSet());
+  }
+
+  private void checkSuiteNamesInternal(List<XmlSuite> suites, Set<String> names) {
+    for (XmlSuite suite : suites) {
+      final String name = suite.getName();
+
+      int count = 0;
+      String tmpName = name;
+      while (names.contains(tmpName)) {
+        tmpName = name + " (" + count++ + ")";
+      }
+
+      if (count > 0) {
+        suite.setName(tmpName);
+        names.add(tmpName);
+      } else {
+        names.add(name);
+      }
+
+      names.add(name);
+      checkSuiteNamesInternal(suite.getChildSuites(), names);
+    }
+  }
+
+  /**
+   * Run TestNG.
+   */
+  public void run() {
+    initializeSuitesAndJarFile();
+    initializeConfiguration();
+    initializeDefaultListeners();
+    initializeCommandLineSuites();
+    initializeCommandLineSuitesParams();
+    initializeCommandLineSuitesGroups();
+
+    sanityCheck();
+
+    List<ISuite> suiteRunners = null;
+
+    runSuiteAlterationListeners();
+    runExecutionListeners(true /* start */);
+
+    m_start = System.currentTimeMillis();
+
+    //
+    // Slave mode
+    //
+    if (m_slavefileName != null) {
+       SuiteSlave slave = new SuiteSlave( m_slavefileName, this );
+       slave.waitForSuites();
+    }
+
+    //
+    // Regular mode
+    //
+    else if (m_masterfileName == null) {
+      suiteRunners = runSuitesLocally();
+    }
+
+    //
+    // Master mode
+    //
+    else {
+       SuiteDispatcher dispatcher = new SuiteDispatcher(m_masterfileName);
+       suiteRunners = dispatcher.dispatch(getConfiguration(),
+           m_suites, getOutputDirectory(),
+           getTestListeners());
+    }
+
+    m_end = System.currentTimeMillis();
+    runExecutionListeners(false /* finish */);
+
+    if(null != suiteRunners) {
+      generateReports(suiteRunners);
+    }
+
+    if(!m_hasTests) {
+      setStatus(HAS_NO_TEST);
+      if (TestRunner.getVerbose() > 1) {
+        System.err.println("[TestNG] No tests found. Nothing was run");
+        usage();
+      }
+    }
+  }
+
+  private void p(String string) {
+    System.out.println("[TestNG] " + string);
+  }
+
+  private void runSuiteAlterationListeners() {
+    for (List<IAlterSuiteListener> listeners
+        : Arrays.asList(m_alterSuiteListeners, m_configuration.getAlterSuiteListeners())) {
+      for (IAlterSuiteListener l : listeners) {
+        l.alter(m_suites);
+      }
+    }
+  }
+
+  private void runExecutionListeners(boolean start) {
+    for (List<IExecutionListener> listeners
+        : Arrays.asList(m_executionListeners, m_configuration.getExecutionListeners())) {
+      for (IExecutionListener l : listeners) {
+        if (start) l.onExecutionStart();
+        else l.onExecutionFinish();
+      }
+    }
+  }
+
+  public void addAlterSuiteListener(IAlterSuiteListener l) {
+    m_alterSuiteListeners.add(l);
+  }
+
+  public void addExecutionListener(IExecutionListener l) {
+    m_executionListeners.add(l);
+  }
+
+  private static void usage() {
+    if (m_jCommander == null) {
+      m_jCommander = new JCommander(new CommandLineArgs());
+    }
+    m_jCommander.usage();
+  }
+
+  private void generateReports(List<ISuite> suiteRunners) {
+    for (IReporter reporter : m_reporters) {
+      try {
+        long start = System.currentTimeMillis();
+        reporter.generateReport(m_suites, suiteRunners, m_outputDir);
+        Utils.log("TestNG", 2, "Time taken by " + reporter + ": "
+            + (System.currentTimeMillis() - start) + " ms");
+      }
+      catch(Exception ex) {
+        System.err.println("[TestNG] Reporter " + reporter + " failed");
+        ex.printStackTrace(System.err);
+      }
+    }
+  }
+
+  /**
+   * This needs to be public for maven2, for now..At least
+   * until an alternative mechanism is found.
+   */
+  public List<ISuite> runSuitesLocally() {
+    SuiteRunnerMap suiteRunnerMap = new SuiteRunnerMap();
+    if (m_suites.size() > 0) {
+      if (m_suites.get(0).getVerbose() >= 2) {
+        Version.displayBanner();
+      }
+
+      // First initialize the suite runners to ensure there are no configuration issues.
+      // Create a map with XmlSuite as key and corresponding SuiteRunner as value
+      for (XmlSuite xmlSuite : m_suites) {
+        createSuiteRunners(suiteRunnerMap, xmlSuite);
+      }
+
+      //
+      // Run suites
+      //
+      if (m_suiteThreadPoolSize == 1 && !m_randomizeSuites) {
+        // Single threaded and not randomized: run the suites in order
+        for (XmlSuite xmlSuite : m_suites) {
+          runSuitesSequentially(xmlSuite, suiteRunnerMap, getVerbose(xmlSuite),
+              getDefaultSuiteName());
+        }
+      } else {
+        // Multithreaded: generate a dynamic graph that stores the suite hierarchy. This is then
+        // used to run related suites in specific order. Parent suites are run only
+        // once all the child suites have completed execution
+        DynamicGraph<ISuite> suiteGraph = new DynamicGraph<>();
+        for (XmlSuite xmlSuite : m_suites) {
+          populateSuiteGraph(suiteGraph, suiteRunnerMap, xmlSuite);
+        }
+
+        IThreadWorkerFactory<ISuite> factory = new SuiteWorkerFactory(suiteRunnerMap,
+          0 /* verbose hasn't been set yet */, getDefaultSuiteName());
+        GraphThreadPoolExecutor<ISuite> pooledExecutor =
+                new GraphThreadPoolExecutor<>(suiteGraph, factory, m_suiteThreadPoolSize,
+                        m_suiteThreadPoolSize, Integer.MAX_VALUE, TimeUnit.MILLISECONDS,
+                        new LinkedBlockingQueue<Runnable>());
+
+        Utils.log("TestNG", 2, "Starting executor for all suites");
+        // Run all suites in parallel
+        pooledExecutor.run();
+        try {
+          pooledExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+          pooledExecutor.shutdownNow();
+        }
+        catch (InterruptedException handled) {
+          Thread.currentThread().interrupt();
+          error("Error waiting for concurrent executors to finish " + handled.getMessage());
+        }
+      }
+    }
+    else {
+      setStatus(HAS_NO_TEST);
+      error("No test suite found. Nothing to run");
+      usage();
+    }
+
+    //
+    // Generate the suites report
+    //
+    return Lists.newArrayList(suiteRunnerMap.values());
+  }
+
+  private static void error(String s) {
+    LOGGER.error(s);
+  }
+
+  /**
+   * @return the verbose level, checking in order: the verbose level on
+   * the suite, the verbose level on the TestNG object, or 1.
+   */
+  private int getVerbose(XmlSuite xmlSuite) {
+    int result = xmlSuite.getVerbose() != null ? xmlSuite.getVerbose()
+        : (m_verbose != null ? m_verbose : DEFAULT_VERBOSE);
+    return result;
+  }
+
+  /**
+   * Recursively runs suites. Runs the children suites before running the parent
+   * suite. This is done so that the results for parent suite can reflect the
+   * combined results of the children suites.
+   *
+   * @param xmlSuite XML Suite to be executed
+   * @param suiteRunnerMap Maps {@code XmlSuite}s to respective {@code ISuite}
+   * @param verbose verbose level
+   * @param defaultSuiteName default suite name
+   */
+  private void runSuitesSequentially(XmlSuite xmlSuite,
+      SuiteRunnerMap suiteRunnerMap, int verbose, String defaultSuiteName) {
+    for (XmlSuite childSuite : xmlSuite.getChildSuites()) {
+      runSuitesSequentially(childSuite, suiteRunnerMap, verbose, defaultSuiteName);
+    }
+    SuiteRunnerWorker srw = new SuiteRunnerWorker(suiteRunnerMap.get(xmlSuite), suiteRunnerMap,
+      verbose, defaultSuiteName);
+    srw.run();
+  }
+
+  /**
+   * Populates the dynamic graph with the reverse hierarchy of suites. Edges are
+   * added pointing from child suite runners to parent suite runners, hence making
+   * parent suite runners dependent on all the child suite runners
+   *
+   * @param suiteGraph dynamic graph representing the reverse hierarchy of SuiteRunners
+   * @param suiteRunnerMap Map with XMLSuite as key and its respective SuiteRunner as value
+   * @param xmlSuite XML Suite
+   */
+  private void populateSuiteGraph(DynamicGraph<ISuite> suiteGraph /* OUT */,
+      SuiteRunnerMap suiteRunnerMap, XmlSuite xmlSuite) {
+    ISuite parentSuiteRunner = suiteRunnerMap.get(xmlSuite);
+    if (xmlSuite.getChildSuites().isEmpty()) {
+      suiteGraph.addNode(parentSuiteRunner);
+    }
+    else {
+      for (XmlSuite childSuite : xmlSuite.getChildSuites()) {
+        suiteGraph.addEdge(parentSuiteRunner, suiteRunnerMap.get(childSuite));
+        populateSuiteGraph(suiteGraph, suiteRunnerMap, childSuite);
+      }
+    }
+  }
+
+  /**
+   * Creates the {@code SuiteRunner}s and populates the suite runner map with
+   * this information
+   * @param suiteRunnerMap Map with XMLSuite as key and it's respective
+   *   SuiteRunner as value. This is updated as part of this method call
+   * @param xmlSuite Xml Suite (and its children) for which {@code SuiteRunner}s are created
+   */
+  private void createSuiteRunners(SuiteRunnerMap suiteRunnerMap /* OUT */, XmlSuite xmlSuite) {
+    if (null != m_isJUnit && ! m_isJUnit.equals(XmlSuite.DEFAULT_JUNIT)) {
+      xmlSuite.setJUnit(m_isJUnit);
+    }
+
+    // If the skip flag was invoked on the command line, it
+    // takes precedence
+    if (null != m_skipFailedInvocationCounts) {
+      xmlSuite.setSkipFailedInvocationCounts(m_skipFailedInvocationCounts);
+    }
+
+    // Override the XmlSuite verbose value with the one from TestNG
+    if (m_verbose != null) {
+      xmlSuite.setVerbose(m_verbose);
+    }
+
+    if (null != m_configFailurePolicy) {
+      xmlSuite.setConfigFailurePolicy(m_configFailurePolicy);
+    }
+
+    for (XmlTest t : xmlSuite.getTests()) {
+      for (Map.Entry<String, Integer> ms : m_methodDescriptors.entrySet()) {
+        XmlMethodSelector xms = new XmlMethodSelector();
+        xms.setName(ms.getKey());
+        xms.setPriority(ms.getValue());
+        t.getMethodSelectors().add(xms);
+      }
+    }
+
+    suiteRunnerMap.put(xmlSuite, createSuiteRunner(xmlSuite));
+
+    for (XmlSuite childSuite : xmlSuite.getChildSuites()) {
+      createSuiteRunners(suiteRunnerMap, childSuite);
+    }
+  }
+
+  /**
+   * Creates a suite runner and configures its initial state
+   * @param xmlSuite
+   * @return returns the newly created suite runner
+   */
+  private SuiteRunner createSuiteRunner(XmlSuite xmlSuite) {
+    SuiteRunner result = new SuiteRunner(getConfiguration(), xmlSuite,
+        m_outputDir,
+        m_testRunnerFactory,
+        m_useDefaultListeners,
+        m_methodInterceptors,
+        m_invokedMethodListeners,
+        m_testListeners,
+        m_classListeners);
+
+    for (ISuiteListener isl : m_suiteListeners) {
+      result.addListener(isl);
+    }
+
+    for (IReporter r : result.getReporters()) {
+      addListener(r);
+    }
+
+    for (IConfigurationListener cl : m_configuration.getConfigurationListeners()) {
+      result.addConfigurationListener(cl);
+    }
+
+    return result;
+  }
+
+  protected IConfiguration getConfiguration() {
+    return m_configuration;
+  }
+
+  /**
+   * The TestNG entry point for command line execution.
+   *
+   * @param argv the TestNG command line parameters.
+   * @throws FileNotFoundException
+   */
+  public static void main(String[] argv) {
+    TestNG testng = privateMain(argv, null);
+    System.exit(testng.getStatus());
+  }
+
+  /**
+   * <B>Note</B>: this method is not part of the public API and is meant for internal usage only.
+   */
+  public static TestNG privateMain(String[] argv, ITestListener listener) {
+    TestNG result = new TestNG();
+
+    if (null != listener) {
+      result.addListener(listener);
+    }
+
+    //
+    // Parse the arguments
+    //
+    try {
+      CommandLineArgs cla = new CommandLineArgs();
+      m_jCommander = new JCommander(cla, argv);
+      validateCommandLineParameters(cla);
+      result.configure(cla);
+    }
+    catch(ParameterException ex) {
+      exitWithError(ex.getMessage());
+    }
+
+    //
+    // Run
+    //
+    try {
+      result.run();
+    }
+    catch(TestNGException ex) {
+      if (TestRunner.getVerbose() > 1) {
+        ex.printStackTrace(System.out);
+      }
+      else {
+        error(ex.getMessage());
+      }
+      result.setStatus(HAS_FAILURE);
+    }
+
+    return result;
+  }
+
+  /**
+   * Configure the TestNG instance based on the command line parameters.
+   */
+  protected void configure(CommandLineArgs cla) {
+    if (cla.verbose != null) {
+      setVerbose(cla.verbose);
+    }
+    setOutputDirectory(cla.outputDirectory);
+
+    String testClasses = cla.testClass;
+    if (null != testClasses) {
+      String[] strClasses = testClasses.split(",");
+      List<Class> classes = Lists.newArrayList();
+      for (String c : strClasses) {
+        classes.add(ClassHelper.fileToClass(c));
+      }
+
+      setTestClasses(classes.toArray(new Class[classes.size()]));
+    }
+
+    setOutputDirectory(cla.outputDirectory);
+
+    if (cla.testNames != null) {
+      setTestNames(Arrays.asList(cla.testNames.split(",")));
+    }
+
+//    List<String> testNgXml = (List<String>) cmdLineArgs.get(CommandLineArgs.SUITE_DEF);
+//    if (null != testNgXml) {
+//      setTestSuites(testNgXml);
+//    }
+
+    // Note: can't use a Boolean field here because we are allowing a boolean
+    // parameter with an arity of 1 ("-usedefaultlisteners false")
+    if (cla.useDefaultListeners != null) {
+      setUseDefaultListeners("true".equalsIgnoreCase(cla.useDefaultListeners));
+    }
+
+    setGroups(cla.groups);
+    setExcludedGroups(cla.excludedGroups);
+    setTestJar(cla.testJar);
+    setXmlPathInJar(cla.xmlPathInJar);
+    setJUnit(cla.junit);
+    setMixed(cla.mixed);
+    setMaster(cla.master);
+    setSlave(cla.slave);
+    setSkipFailedInvocationCounts(cla.skipFailedInvocationCounts);
+    if (cla.parallelMode != null) {
+      setParallel(cla.parallelMode);
+    }
+    if (cla.configFailurePolicy != null) {
+      setConfigFailurePolicy(cla.configFailurePolicy);
+    }
+    if (cla.threadCount != null) {
+      setThreadCount(cla.threadCount);
+    }
+    if (cla.dataProviderThreadCount != null) {
+      setDataProviderThreadCount(cla.dataProviderThreadCount);
+    }
+    if (cla.suiteName != null) {
+      setDefaultSuiteName(cla.suiteName);
+    }
+    if (cla.testName != null) {
+      setDefaultTestName(cla.testName);
+    }
+    if (cla.listener != null) {
+      String sep = ";";
+      if (cla.listener.contains(",")) {
+        sep = ",";
+      }
+      String[] strs = Utils.split(cla.listener, sep);
+      List<Class> classes = Lists.newArrayList();
+
+      for (String cls : strs) {
+        classes.add(ClassHelper.fileToClass(cls));
+      }
+
+      setListenerClasses(classes);
+    }
+
+    if (null != cla.methodSelectors) {
+      String[] strs = Utils.split(cla.methodSelectors, ",");
+      for (String cls : strs) {
+        String[] sel = Utils.split(cls, ":");
+        try {
+          if (sel.length == 2) {
+            addMethodSelector(sel[0], Integer.parseInt(sel[1]));
+          } else {
+            error("Method selector value was not in the format org.example.Selector:4");
+          }
+        }
+        catch (NumberFormatException nfe) {
+          error("Method selector value was not in the format org.example.Selector:4");
+        }
+      }
+    }
+
+    if (cla.objectFactory != null) {
+      setObjectFactory(ClassHelper.fileToClass(cla.objectFactory));
+    }
+    if (cla.testRunnerFactory != null) {
+      setTestRunnerFactoryClass(
+          ClassHelper.fileToClass(cla.testRunnerFactory));
+    }
+
+    if (cla.reporter != null) {
+      ReporterConfig reporterConfig = ReporterConfig.deserialize(cla.reporter);
+      addReporter(reporterConfig);
+    }
+
+    if (cla.commandLineMethods.size() > 0) {
+      m_commandLineMethods = cla.commandLineMethods;
+    }
+
+    if (cla.suiteFiles != null) {
+      setTestSuites(cla.suiteFiles);
+    }
+
+    setSuiteThreadPoolSize(cla.suiteThreadPoolSize);
+    setRandomizeSuites(cla.randomizeSuites);
+  }
+
+  public void setSuiteThreadPoolSize(Integer suiteThreadPoolSize) {
+    m_suiteThreadPoolSize = suiteThreadPoolSize;
+  }
+
+  public Integer getSuiteThreadPoolSize() {
+    return m_suiteThreadPoolSize;
+  }
+
+   public void setRandomizeSuites(boolean randomizeSuites) {
+     m_randomizeSuites = randomizeSuites;
+   }
+
+  /**
+   * This method is invoked by Maven's Surefire, only remove it once
+   * Surefire has been modified to no longer call it.
+   */
+  public void setSourcePath(String path) {
+    // nop
+  }
+
+  /**
+   * This method is invoked by Maven's Surefire to configure the runner,
+   * do not remove unless you know for sure that Surefire has been updated
+   * to use the new configure(CommandLineArgs) method.
+   *
+   * @deprecated use new configure(CommandLineArgs) method
+   */
+  @SuppressWarnings({"unchecked"})
+  @Deprecated
+  public void configure(Map cmdLineArgs) {
+    CommandLineArgs result = new CommandLineArgs();
+
+    Integer verbose = (Integer) cmdLineArgs.get(CommandLineArgs.LOG);
+    if (null != verbose) {
+      result.verbose = verbose;
+    }
+    result.outputDirectory = (String) cmdLineArgs.get(CommandLineArgs.OUTPUT_DIRECTORY);
+
+    String testClasses = (String) cmdLineArgs.get(CommandLineArgs.TEST_CLASS);
+    if (null != testClasses) {
+      result.testClass = testClasses;
+    }
+
+    String testNames = (String) cmdLineArgs.get(CommandLineArgs.TEST_NAMES);
+    if (testNames != null) {
+      result.testNames = testNames;
+    }
+
+    String useDefaultListeners = (String) cmdLineArgs.get(CommandLineArgs.USE_DEFAULT_LISTENERS);
+    if (null != useDefaultListeners) {
+      result.useDefaultListeners = useDefaultListeners;
+    }
+
+    result.groups = (String) cmdLineArgs.get(CommandLineArgs.GROUPS);
+    result.excludedGroups = (String) cmdLineArgs.get(CommandLineArgs.EXCLUDED_GROUPS);
+    result.testJar = (String) cmdLineArgs.get(CommandLineArgs.TEST_JAR);
+    result.xmlPathInJar = (String) cmdLineArgs.get(CommandLineArgs.XML_PATH_IN_JAR);
+    result.junit = (Boolean) cmdLineArgs.get(CommandLineArgs.JUNIT);
+    result.mixed = (Boolean) cmdLineArgs.get(CommandLineArgs.MIXED);
+    result.master = (String) cmdLineArgs.get(CommandLineArgs.MASTER);
+    result.slave = (String) cmdLineArgs.get(CommandLineArgs.SLAVE);
+    result.skipFailedInvocationCounts = (Boolean) cmdLineArgs.get(
+        CommandLineArgs.SKIP_FAILED_INVOCATION_COUNTS);
+    String parallelMode = (String) cmdLineArgs.get(CommandLineArgs.PARALLEL);
+    if (parallelMode != null) {
+      result.parallelMode = XmlSuite.ParallelMode.getValidParallel(parallelMode);
+    }
+
+    String threadCount = (String) cmdLineArgs.get(CommandLineArgs.THREAD_COUNT);
+    if (threadCount != null) {
+      result.threadCount = Integer.parseInt(threadCount);
+    }
+
+    // Not supported by Surefire yet
+    Integer dptc = (Integer) cmdLineArgs.get(CommandLineArgs.DATA_PROVIDER_THREAD_COUNT);
+    if (dptc != null) {
+      result.dataProviderThreadCount = dptc;
+    }
+    String defaultSuiteName = (String) cmdLineArgs.get(CommandLineArgs.SUITE_NAME);
+    if (defaultSuiteName != null) {
+      result.suiteName = defaultSuiteName;
+    }
+
+    String defaultTestName = (String) cmdLineArgs.get(CommandLineArgs.TEST_NAME);
+    if (defaultTestName != null) {
+      result.testName = defaultTestName;
+    }
+
+    Object listeners = cmdLineArgs.get(CommandLineArgs.LISTENER);
+    if (listeners instanceof List) {
+      result.listener = Utils.join((List<?>) listeners, ",");
+    } else {
+      result.listener = (String) listeners;
+    }
+
+    String ms = (String) cmdLineArgs.get(CommandLineArgs.METHOD_SELECTORS);
+    if (null != ms) {
+      result.methodSelectors = ms;
+    }
+
+    String objectFactory = (String) cmdLineArgs.get(CommandLineArgs.OBJECT_FACTORY);
+    if(null != objectFactory) {
+      result.objectFactory = objectFactory;
+    }
+
+    String runnerFactory = (String) cmdLineArgs.get(CommandLineArgs.TEST_RUNNER_FACTORY);
+    if (null != runnerFactory) {
+      result.testRunnerFactory = runnerFactory;
+    }
+
+    String reporterConfigs = (String) cmdLineArgs.get(CommandLineArgs.REPORTER);
+    if (reporterConfigs != null) {
+      result.reporter = reporterConfigs;
+    }
+
+    String failurePolicy = (String)cmdLineArgs.get(CommandLineArgs.CONFIG_FAILURE_POLICY);
+    if (failurePolicy != null) {
+      result.configFailurePolicy = failurePolicy;
+    }
+
+    Object  suiteThreadPoolSize = cmdLineArgs.get(CommandLineArgs.SUITE_THREAD_POOL_SIZE);
+    if (null != suiteThreadPoolSize) {
+        if (suiteThreadPoolSize instanceof String){
+            result.suiteThreadPoolSize=Integer.parseInt((String) suiteThreadPoolSize);
+        }
+        if (suiteThreadPoolSize instanceof Integer){
+            result.suiteThreadPoolSize=(Integer) suiteThreadPoolSize;
+        }
+    }
+
+    configure(result);
+  }
+
+  /**
+   * Only run the specified tests from the suite.
+   */
+  public void setTestNames(List<String> testNames) {
+    m_testNames = testNames;
+  }
+
+  public void setSkipFailedInvocationCounts(Boolean skip) {
+    m_skipFailedInvocationCounts = skip;
+  }
+
+  private void addReporter(ReporterConfig reporterConfig) {
+    Object instance = reporterConfig.newReporterInstance();
+    if (instance != null) {
+      addListener(instance);
+    } else {
+      LOGGER.warn("Could not find reporte class : " + reporterConfig.getClassName());
+    }
+  }
+
+  /**
+   * Specify if this run should be in Master-Slave mode as Master
+   *
+   * @param fileName remote.properties path
+   */
+  public void setMaster(String fileName) {
+     m_masterfileName = fileName;
+  }
+
+  /**
+   * Specify if this run should be in Master-Slave mode as slave
+   *
+   * @param fileName remote.properties path
+   */
+  public void setSlave(String fileName) {
+     m_slavefileName = fileName;
+  }
+
+  /**
+   * Specify if this run should be made in JUnit mode
+   *
+   * @param isJUnit
+   */
+  public void setJUnit(Boolean isJUnit) {
+    m_isJUnit = isJUnit;
+  }
+
+  /**
+   * Specify if this run should be made in mixed mode
+   */
+  public void setMixed(Boolean isMixed) {
+      if(isMixed==null){
+          return;
+      }
+    m_isMixed = isMixed;
+  }
+
+  /**
+   * @deprecated The TestNG version is now established at load time. This
+   * method is not required anymore and is now a no-op.
+   */
+  @Deprecated
+  public static void setTestNGVersion() {
+    LOGGER.info("setTestNGVersion has been deprecated.");
+  }
+
+  /**
+   * Returns true if this is the JDK 1.4 JAR version of TestNG, false otherwise.
+   *
+   * @return true if this is the JDK 1.4 JAR version of TestNG, false otherwise.
+   */
+  @Deprecated
+  public static boolean isJdk14() {
+    return false;
+  }
+
+  /**
+   * Double check that the command line parameters are valid.
+   */
+  protected static void validateCommandLineParameters(CommandLineArgs args) {
+    String testClasses = args.testClass;
+    List<String> testNgXml = args.suiteFiles;
+    String testJar = args.testJar;
+    String slave = args.slave;
+    List<String> methods = args.commandLineMethods;
+
+    if (testClasses == null && slave == null && testJar == null
+        && (testNgXml == null || testNgXml.isEmpty())
+        && (methods == null || methods.isEmpty())) {
+      throw new ParameterException("You need to specify at least one testng.xml, one class"
+          + " or one method");
+    }
+
+    String groups = args.groups;
+    String excludedGroups = args.excludedGroups;
+
+    if (testJar == null &&
+        (null != groups || null != excludedGroups) && testClasses == null
+        && (testNgXml == null || testNgXml.isEmpty())) {
+      throw new ParameterException("Groups option should be used with testclass option");
+    }
+
+    if (args.slave != null && args.master != null) {
+     throw new ParameterException(CommandLineArgs.SLAVE + " can't be combined with "
+         + CommandLineArgs.MASTER);
+    }
+
+    Boolean junit = args.junit;
+    Boolean mixed = args.mixed;
+    if (junit && mixed) {
+     throw new ParameterException(CommandLineArgs.MIXED + " can't be combined with "
+         + CommandLineArgs.JUNIT);
+    }
+  }
+
+  /**
+   * @return true if at least one test failed.
+   */
+  public boolean hasFailure() {
+    return (getStatus() & HAS_FAILURE) == HAS_FAILURE;
+  }
+
+  /**
+   * @return true if at least one test failed within success percentage.
+   */
+  public boolean hasFailureWithinSuccessPercentage() {
+    return (getStatus() & HAS_FSP) == HAS_FSP;
+  }
+
+  /**
+   * @return true if at least one test was skipped.
+   */
+  public boolean hasSkip() {
+    return (getStatus() & HAS_SKIPPED) == HAS_SKIPPED;
+  }
+
+  static void exitWithError(String msg) {
+    System.err.println(msg);
+    usage();
+    System.exit(1);
+  }
+
+  public String getOutputDirectory() {
+    return m_outputDir;
+  }
+
+  public IAnnotationTransformer getAnnotationTransformer() {
+    return m_annotationTransformer;
+  }
+
+  public void setAnnotationTransformer(IAnnotationTransformer t) {
+	// compare by reference!
+    if (m_annotationTransformer != m_defaultAnnoProcessor && m_annotationTransformer != t) {
+    	LOGGER.warn("AnnotationTransformer already set");
+    }
+    m_annotationTransformer = t;
+  }
+
+  /**
+   * @return the defaultSuiteName
+   */
+  public String getDefaultSuiteName() {
+    return m_defaultSuiteName;
+  }
+
+  /**
+   * @param defaultSuiteName the defaultSuiteName to set
+   */
+  public void setDefaultSuiteName(String defaultSuiteName) {
+    m_defaultSuiteName = defaultSuiteName;
+  }
+
+  /**
+   * @return the defaultTestName
+   */
+  public String getDefaultTestName() {
+    return m_defaultTestName;
+  }
+
+  /**
+   * @param defaultTestName the defaultTestName to set
+   */
+  public void setDefaultTestName(String defaultTestName) {
+    m_defaultTestName = defaultTestName;
+  }
+
+  /**
+   * Sets the policy for whether or not to ever invoke a configuration method again after
+   * it has failed once. Possible values are defined in {@link XmlSuite}.  The default
+   * value is {@link XmlSuite#SKIP}.
+   * @param failurePolicy the configuration failure policy
+   */
+  public void setConfigFailurePolicy(String failurePolicy) {
+    m_configFailurePolicy = failurePolicy;
+  }
+
+  /**
+   * Returns the configuration failure policy.
+   * @return config failure policy
+   */
+  public String getConfigFailurePolicy() {
+    return m_configFailurePolicy;
+  }
+
+  // DEPRECATED: to be removed after a major version change
+  /**
+   * @deprecated since 5.1
+   */
+  @Deprecated
+  public static TestNG getDefault() {
+    return m_instance;
+  }
+
+  /**
+   * @deprecated since 5.1
+   */
+  @Deprecated
+  public void setHasFailure(boolean hasFailure) {
+    m_status |= HAS_FAILURE;
+  }
+
+  /**
+   * @deprecated since 5.1
+   */
+  @Deprecated
+  public void setHasFailureWithinSuccessPercentage(boolean hasFailureWithinSuccessPercentage) {
+    m_status |= HAS_FSP;
+  }
+
+  /**
+   * @deprecated since 5.1
+   */
+  @Deprecated
+  public void setHasSkip(boolean hasSkip) {
+    m_status |= HAS_SKIPPED;
+  }
+
+  public static class ExitCodeListener implements IResultListener2 {
+    private TestNG m_mainRunner;
+
+    public ExitCodeListener() {
+      m_mainRunner = TestNG.m_instance;
+    }
+
+    public ExitCodeListener(TestNG runner) {
+      m_mainRunner = runner;
+    }
+
+    @Override
+    public void beforeConfiguration(ITestResult tr) {
+    }
+
+    @Override
+    public void onTestFailure(ITestResult result) {
+      setHasRunTests();
+      m_mainRunner.setStatus(HAS_FAILURE);
+    }
+
+    @Override
+    public void onTestSkipped(ITestResult result) {
+      setHasRunTests();
+      if ((m_mainRunner.getStatus() & HAS_FAILURE) != 0) {
+        m_mainRunner.setStatus(HAS_SKIPPED);
+      }
+    }
+
+    @Override
+    public void onTestFailedButWithinSuccessPercentage(ITestResult result) {
+      setHasRunTests();
+      m_mainRunner.setStatus(HAS_FSP);
+    }
+
+    @Override
+    public void onTestSuccess(ITestResult result) {
+      setHasRunTests();
+    }
+
+    @Override
+    public void onStart(ITestContext context) {
+      setHasRunTests();
+    }
+
+    @Override
+    public void onFinish(ITestContext context) {
+    }
+
+    @Override
+    public void onTestStart(ITestResult result) {
+      setHasRunTests();
+    }
+
+    private void setHasRunTests() {
+      m_mainRunner.m_hasTests= true;
+    }
+
+    /**
+     * @see org.testng.IConfigurationListener#onConfigurationFailure(org.testng.ITestResult)
+     */
+    @Override
+    public void onConfigurationFailure(ITestResult itr) {
+      m_mainRunner.setStatus(HAS_FAILURE);
+    }
+
+    /**
+     * @see org.testng.IConfigurationListener#onConfigurationSkip(org.testng.ITestResult)
+     */
+    @Override
+    public void onConfigurationSkip(ITestResult itr) {
+      m_mainRunner.setStatus(HAS_SKIPPED);
+    }
+
+    /**
+     * @see org.testng.IConfigurationListener#onConfigurationSuccess(org.testng.ITestResult)
+     */
+    @Override
+    public void onConfigurationSuccess(ITestResult itr) {
+    }
+  }
+
+  private void setConfigurable(IConfigurable c) {
+	// compare by reference!
+    if (m_configurable != null && m_configurable != c) {
+    	LOGGER.warn("Configurable already set");
+	}
+    m_configurable = c;
+  }
+
+  private void setHookable(IHookable h) {
+	// compare by reference!
+    if (m_hookable != null && m_hookable != h) {
+    	LOGGER.warn("Hookable already set");
+    }
+    m_hookable = h;
+  }
+
+  public void setMethodInterceptor(IMethodInterceptor methodInterceptor) {
+    m_methodInterceptors.add(methodInterceptor);
+  }
+
+  public void setDataProviderThreadCount(int count) {
+    m_dataProviderThreadCount = count;
+  }
+
+  /** Add a class loader to the searchable loaders. */
+  public void addClassLoader(final ClassLoader loader) {
+    if (loader != null) {
+      ClassHelper.addClassLoader(loader);
+    }
+  }
+
+  public void setPreserveOrder(boolean b) {
+    m_preserveOrder = b;
+  }
+
+  protected long getStart() {
+    return m_start;
+  }
+
+  protected long getEnd() {
+    return m_end;
+  }
+
+  public void setGroupByInstances(boolean b) {
+    m_groupByInstances = b;
+  }
+
+  /////
+  // ServiceLoader testing
+  //
+
+  private URLClassLoader m_serviceLoaderClassLoader;
+  private List<ITestNGListener> m_serviceLoaderListeners = Lists.newArrayList();
+
+  /*
+   * Used to test ServiceClassLoader
+   */
+  public void setServiceLoaderClassLoader(URLClassLoader ucl) {
+    m_serviceLoaderClassLoader = ucl;
+  }
+
+  /*
+   * Used to test ServiceClassLoader
+   */
+  private void addServiceLoaderListener(ITestNGListener l) {
+    m_serviceLoaderListeners.add(l);
+  }
+
+  /*
+   * Used to test ServiceClassLoader
+   */
+  public List<ITestNGListener> getServiceLoaderListeners() {
+    return m_serviceLoaderListeners;
+  }
+
+  //
+  // ServiceLoader testing
+  /////
+}
diff --git a/src/main/java/org/testng/TestNGAntTask.java b/src/main/java/org/testng/TestNGAntTask.java
new file mode 100755
index 0000000..bdae073
--- /dev/null
+++ b/src/main/java/org/testng/TestNGAntTask.java
@@ -0,0 +1,1174 @@
+package org.testng;
+
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.CharacterIterator;
+import java.text.StringCharacterIterator;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Target;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.taskdefs.Execute;
+import org.apache.tools.ant.taskdefs.ExecuteWatchdog;
+import org.apache.tools.ant.taskdefs.LogOutputStream;
+import org.apache.tools.ant.taskdefs.PumpStreamHandler;
+import org.apache.tools.ant.types.Commandline;
+import org.apache.tools.ant.types.CommandlineJava;
+import org.apache.tools.ant.types.Environment;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.types.PropertySet;
+import org.apache.tools.ant.types.Reference;
+import org.apache.tools.ant.types.ResourceCollection;
+import org.apache.tools.ant.types.resources.FileResource;
+import org.apache.tools.ant.types.selectors.FilenameSelector;
+import org.testng.collections.Lists;
+import org.testng.internal.Utils;
+import org.testng.reporters.VerboseReporter;
+
+import static java.lang.Boolean.TRUE;
+import static org.testng.internal.Utils.isStringNotBlank;
+
+/**
+ * TestNG settings:
+ * <ul>
+ * <li>classfileset (inner)</li>
+ * <li>classfilesetref (attribute)</li>
+ * <li>xmlfileset (inner)</li>
+ * <li>xmlfilesetref (attribute)</li>
+ * <li>enableAssert (attribute)</li>
+ * <li>excludedGroups (attribute)</li>
+ * <li>groups (attribute)</li>
+ * <li>junit (attribute)</li>
+ * <li>listener (attribute)</li>
+ * <li>outputdir (attribute)</li>
+ * <li>parallel (attribute)</li>
+ * <li>reporter (attribute)</li>
+ * <li>sourcedir (attribute)</li>
+ * <li>sourcedirref (attribute)</li>
+ * <li>suitename (attribute)</li>
+ * <li>suiterunnerclass (attribute)</li>
+ * <li>target (attribute)</li>
+ * <li>testjar (attribute)</li>
+ * <li>testname (attribute)</li>
+ * <li>threadcount (attribute)</li>
+ * <li>dataproviderthreadcount (attribute)</li>
+ * <li>verbose (attribute)</li>
+ * <li>testrunfactory (attribute)</li>
+ * <li>configFailurepolicy (attribute)</li>
+ * <li>randomizeSuites (attribute)</li>
+ * <li>methodselectors (attribute)</li>
+ * </ul>
+ *
+ * Ant settings:
+ * <ul>
+ * <li>classpath (inner)</li>
+ * <li>classpathref (attribute)</li>
+ * <li>jvm (attribute)</li>
+ * <li>workingDir (attribute)</li>
+ * <li>env (inner)</li>
+ * <li>sysproperty (inner)</li>
+ * <li>propertyset (inner)</li>
+ * <li>jvmarg (inner)</li>
+ * <li>timeout (attribute)</li>
+ * <li>haltonfailure (attribute)</li>
+ * <li>onHaltTarget (attribute)</li>
+ * <li>failureProperty (attribute)</li>
+ * <li>haltonFSP (attribute)</li>
+ * <li>FSPproperty (attribute)</li>
+ * <li>haltonskipped (attribute)</li>
+ * <li>skippedProperty (attribute)</li>
+ * <li>testRunnerFactory (attribute)</li>
+ * </ul>
+ *
+ * Debug information:
+ * <ul>
+ * <li>dumpCommand (boolean)</li>
+ * <li>dumpEnv (boolean)</li>
+ * <li>dumpSys (boolean)</li>
+ * </ul>
+ *
+ * @author <a href="mailto:the_mindstorm@evolva.ro">Alexandru Popescu</a>
+ * @author Cedric Beust
+ * @author Lukas Jungmann
+ */
+public class TestNGAntTask extends Task {
+
+  protected CommandlineJava m_javaCommand;
+
+  protected List<ResourceCollection> m_xmlFilesets= Lists.newArrayList();
+  protected List<ResourceCollection> m_classFilesets= Lists.newArrayList();
+  protected File m_outputDir;
+  protected File m_testjar;
+  protected File m_workingDir;
+  private Integer m_timeout;
+  private List<String> m_listeners= Lists.newArrayList();
+  private List<String> m_methodselectors= Lists.newArrayList();
+  private String m_objectFactory;
+  protected String m_testRunnerFactory;
+  private boolean m_delegateCommandSystemProperties = false;
+
+  protected Environment m_environment= new Environment();
+
+  /** The suite runner name (defaults to TestNG.class.getName(). */
+  protected String m_mainClass = TestNG.class.getName();
+
+  /** True if the temporary file created by the Ant Task for command line parameters
+   * to TestNG should be preserved after execution. */
+  protected boolean m_dump;
+  private boolean m_dumpEnv;
+  private boolean m_dumpSys;
+
+  protected boolean m_assertEnabled= true;
+  protected boolean m_haltOnFailure;
+  protected String m_onHaltTarget;
+  protected String m_failurePropertyName;
+  protected boolean m_haltOnSkipped;
+  protected String m_skippedPropertyName;
+  protected boolean m_haltOnFSP;
+  protected String m_fspPropertyName;
+  protected String m_includedGroups;
+  protected String m_excludedGroups;
+  protected String m_parallelMode;
+  protected String m_threadCount;
+  protected String m_dataproviderthreadCount;
+  protected String m_configFailurePolicy;
+  protected Boolean m_randomizeSuites;
+  public String m_useDefaultListeners;
+  private String m_suiteName="Ant suite";
+  private String m_testName="Ant test";
+  private Boolean m_skipFailedInvocationCounts;
+  private String m_methods;
+  private Mode mode = Mode.testng;
+
+  public enum Mode {
+      //lower-case to better look in build scripts
+      testng, junit, mixed;
+  }
+  
+  /**
+   * The list of report listeners added via &lt;reporter&gt; sub-element of the Ant task
+   */
+  private List<ReporterConfig> reporterConfigs = Lists.newArrayList();
+
+  private String m_testNames = "";
+
+  public void setParallel(String parallel) {
+    m_parallelMode= parallel;
+  }
+
+  public void setThreadCount(String threadCount) {
+    m_threadCount= threadCount;
+  }
+
+  public void setDataProviderThreadCount(String dataproviderthreadCount) {
+    m_dataproviderthreadCount = dataproviderthreadCount;
+  }
+
+  public void setUseDefaultListeners(String f) {
+    m_useDefaultListeners= f;
+  }
+
+  // Ant task settings
+  public void setHaltonfailure(boolean value) {
+    m_haltOnFailure= value;
+  }
+
+  public void setOnHaltTarget(String targetName) {
+    m_onHaltTarget= targetName;
+  }
+
+  public void setFailureProperty(String propertyName) {
+    m_failurePropertyName= propertyName;
+  }
+
+  public void setHaltonskipped(boolean value) {
+    m_haltOnSkipped= value;
+  }
+
+  public void setSkippedProperty(String propertyName) {
+    m_skippedPropertyName= propertyName;
+  }
+
+  public void setHaltonFSP(boolean value) {
+    m_haltOnFSP= value;
+  }
+
+  public void setFSPProperty(String propertyName) {
+    m_fspPropertyName= propertyName;
+  }
+
+  public void setDelegateCommandSystemProperties(boolean value){
+    m_delegateCommandSystemProperties = value;
+  }
+
+  /**
+   * Sets the flag to log the command line. When verbose is set to true
+   * the command line parameters are stored in a temporary file stored
+   * in the user's default temporary file directory. The file created is
+   * prefixed with "testng".
+   */
+  public void setDumpCommand(boolean verbose) {
+    m_dump = verbose;
+  }
+
+  /**
+   * Sets the flag to write on <code>System.out</code> the Ant
+   * Environment properties.
+   *
+   * @param verbose <tt>true</tt> for printing
+   */
+  public void setDumpEnv(boolean verbose) {
+    m_dumpEnv= verbose;
+  }
+
+  /**
+   * Sets te flag to write on <code>System.out</code> the system properties.
+   * @param verbose <tt>true</tt> for dumping the info
+   */
+  public void setDumpSys(boolean verbose) {
+    m_dumpSys= verbose;
+  }
+
+  public void setEnableAssert(boolean flag) {
+    m_assertEnabled= flag;
+  }
+
+  /**
+   * The directory to invoke the VM in.
+   * @param workingDir the directory to invoke the JVM from.
+   */
+  public void setWorkingDir(File workingDir) {
+    m_workingDir= workingDir;
+  }
+
+  /**
+   * Sets a particular JVM to be used. Default is 'java' and is solved
+   * by <code>Runtime.exec()</code>.
+   *
+   * @param jvm the new jvm
+   */
+  public void setJvm(String jvm) {
+    getJavaCommand().setVm(jvm);
+  }
+
+  /**
+   * Set the timeout value (in milliseconds).
+   *
+   * <p>If the tests are running for more than this value, the tests
+   * will be canceled.
+   *
+   * </p>
+   * @param value the maximum time (in milliseconds) allowed before declaring the test as 'timed-out'
+   */
+  public void setTimeout(Integer value) {
+    m_timeout= value;
+  }
+
+  public Commandline.Argument createJvmarg() {
+    return getJavaCommand().createVmArgument();
+  }
+
+  public void addSysproperty(Environment.Variable sysp) {
+    getJavaCommand().addSysproperty(sysp);
+  }
+
+  /**
+   * Adds an environment variable; used when forking.
+   */
+  public void addEnv(Environment.Variable var) {
+    m_environment.addVariable(var);
+  }
+
+  /**
+   * Adds path to classpath used for tests.
+   *
+   * @return reference to the classpath in the embedded java command line
+   */
+  public Path createClasspath() {
+    return getJavaCommand().createClasspath(getProject()).createPath();
+  }
+
+  /**
+   * Adds a path to the bootclasspath.
+   * @return reference to the bootclasspath in the embedded java command line
+   */
+  public Path createBootclasspath() {
+    return getJavaCommand().createBootclasspath(getProject()).createPath();
+  }
+
+  /**
+   * Set the classpath to be used when running the Java class
+   *
+   * @param s an Ant Path object containing the classpath.
+   */
+  public void setClasspath(Path s) {
+    createClasspath().append(s);
+  }
+
+  /**
+   * Classpath to use, by reference.
+   *
+   * @param r a reference to an existing classpath
+   */
+  public void setClasspathRef(Reference r) {
+    createClasspath().setRefid(r);
+  }
+
+  public void addXmlfileset(FileSet fs) {
+    m_xmlFilesets.add(fs);
+  }
+
+  public void setXmlfilesetRef(Reference ref) {
+    m_xmlFilesets.add(createResourceCollection(ref));
+  }
+
+  public void addClassfileset(FileSet fs) {
+    m_classFilesets.add(appendClassSelector(fs));
+  }
+
+  public void setClassfilesetRef(Reference ref) {
+    m_classFilesets.add(createResourceCollection(ref));
+  }
+
+  public void setTestNames(String testNames) {
+    m_testNames = testNames;
+  }
+
+  /**
+   * Sets the suite runner class to invoke
+   * @param s the name of the suite runner class
+   */
+  public void setSuiteRunnerClass(String s) {
+    m_mainClass= s;
+  }
+
+  /**
+   * Sets the suite name
+   * @param s the name of the suite
+   */
+  public void setSuiteName(String s) {
+    m_suiteName= s;
+  }
+
+  /**
+   * Sets the test name
+   * @param s the name of the test
+   */
+  public void setTestName(String s) {
+    m_testName= s;
+  }
+
+  // TestNG settings
+  public void setJUnit(boolean value) {
+    mode = value ? Mode.junit : Mode.testng;
+  }
+
+  // TestNG settings
+  public void setMode(Mode mode) {
+    this.mode = mode;
+  }
+
+  /**
+   * Sets the test output directory
+   * @param dir the name of directory
+   */
+  public void setOutputDir(File dir) {
+    m_outputDir= dir;
+  }
+
+  /**
+   * Sets the test jar
+   * @param s the name of test jar
+   */
+  public void setTestJar(File s) {
+    m_testjar= s;
+  }
+
+  public void setGroups(String groups) {
+    m_includedGroups= groups;
+  }
+
+  public void setExcludedGroups(String groups) {
+    m_excludedGroups= groups;
+  }
+
+  private Integer m_verbose= null;
+
+  private Integer m_suiteThreadPoolSize;
+
+  private String m_xmlPathInJar;
+
+  public void setVerbose(Integer verbose) {
+    m_verbose= verbose;
+  }
+
+  public void setReporter(String listener) {
+    m_listeners.add(listener);
+  }
+
+  public void setObjectFactory(String className) {
+    m_objectFactory = className;
+  }
+
+  public void setTestRunnerFactory(String testRunnerFactory) {
+    m_testRunnerFactory = testRunnerFactory;
+  }
+
+  public void setSuiteThreadPoolSize(Integer n) {
+    m_suiteThreadPoolSize = n;
+  }
+
+  /**
+   * @deprecated Use "listeners"
+   */
+  @Deprecated
+  public void setListener(String listener) {
+    m_listeners.add(listener);
+  }
+
+  public void setListeners(String listeners) {
+    StringTokenizer st= new StringTokenizer(listeners, " ,");
+    while(st.hasMoreTokens()) {
+      m_listeners.add(st.nextToken());
+    }
+  }
+
+  public void setMethodSelectors(String methodSelectors) {
+	StringTokenizer st= new StringTokenizer(methodSelectors, " ,");
+	while(st.hasMoreTokens()) {
+	 m_methodselectors.add(st.nextToken());
+	}
+  }
+
+  public void setConfigFailurePolicy(String failurePolicy) {
+    m_configFailurePolicy = failurePolicy;
+  }
+
+  public void setRandomizeSuites(Boolean randomizeSuites) {
+    m_randomizeSuites = randomizeSuites;
+  }
+
+  public void setMethods(String methods) {
+    m_methods = methods;
+  }
+
+  /**
+   * Launches TestNG in a new JVM.
+   *
+   * {@inheritDoc}
+   */
+  @Override
+  public void execute() throws BuildException {
+    validateOptions();
+
+    CommandlineJava cmd = getJavaCommand();
+    cmd.setClassname(m_mainClass);
+    if(m_assertEnabled) {
+      cmd.createVmArgument().setValue("-ea");
+    }
+    if (m_delegateCommandSystemProperties) {
+      delegateCommandSystemProperties();
+    }
+    List<String> argv = createArguments();
+
+    String fileName= "";
+    FileWriter fw= null;
+    BufferedWriter bw= null;
+    try {
+      File f= File.createTempFile("testng", "");
+      fileName= f.getAbsolutePath();
+
+      // If the user asked to see the command, preserve the file
+      if(!m_dump) {
+        f.deleteOnExit();
+      }
+      fw= new FileWriter(f);
+      bw= new BufferedWriter(fw);
+      for(String arg : argv) {
+        bw.write(arg);
+        bw.newLine();
+      }
+      bw.flush();
+    }
+    catch(IOException e) {
+      e.printStackTrace();
+    }
+    finally {
+      try {
+        if(bw != null) {
+          bw.close();
+        }
+        if(fw != null) {
+          fw.close();
+        }
+      }
+      catch(IOException e) {
+        e.printStackTrace();
+      }
+    }
+
+    printDebugInfo(fileName);
+
+    createClasspath().setLocation(findJar());
+
+    cmd.createArgument().setValue("@" + fileName);
+
+    ExecuteWatchdog watchdog= createWatchdog();
+    boolean wasKilled= false;
+    int exitValue= executeAsForked(cmd, watchdog);
+    if(null != watchdog) {
+      wasKilled= watchdog.killedProcess();
+    }
+
+    actOnResult(exitValue, wasKilled);
+  }
+
+  private List<String> createArguments() {
+	List<String> argv= Lists.newArrayList();
+    addBooleanIfTrue(argv, CommandLineArgs.JUNIT, mode == Mode.junit);
+    addBooleanIfTrue(argv, CommandLineArgs.MIXED, mode == Mode.mixed);
+    addBooleanIfTrue(argv, CommandLineArgs.SKIP_FAILED_INVOCATION_COUNTS, m_skipFailedInvocationCounts);
+    addIntegerIfNotNull(argv, CommandLineArgs.LOG, m_verbose);
+    addDefaultListeners(argv);
+    addOutputDir(argv);
+    addFileIfFile(argv, CommandLineArgs.TEST_JAR, m_testjar);
+    addStringIfNotBlank(argv, CommandLineArgs.GROUPS, m_includedGroups);
+    addStringIfNotBlank(argv, CommandLineArgs.EXCLUDED_GROUPS, m_excludedGroups);
+    addFilesOfRCollection(argv, CommandLineArgs.TEST_CLASS, m_classFilesets);
+    addListOfStringIfNotEmpty(argv, CommandLineArgs.LISTENER, m_listeners);
+    addListOfStringIfNotEmpty(argv, CommandLineArgs.METHOD_SELECTORS, m_methodselectors);
+    addStringIfNotNull(argv, CommandLineArgs.OBJECT_FACTORY, m_objectFactory);
+    addStringIfNotNull(argv, CommandLineArgs.TEST_RUNNER_FACTORY, m_testRunnerFactory);
+    addStringIfNotNull(argv, CommandLineArgs.PARALLEL, m_parallelMode);
+    addStringIfNotNull(argv, CommandLineArgs.CONFIG_FAILURE_POLICY, m_configFailurePolicy);
+    addBooleanIfTrue(argv, CommandLineArgs.RANDOMIZE_SUITES, m_randomizeSuites);
+    addStringIfNotNull(argv, CommandLineArgs.THREAD_COUNT, m_threadCount);
+    addStringIfNotNull(argv, CommandLineArgs.DATA_PROVIDER_THREAD_COUNT, m_dataproviderthreadCount);
+    addStringIfNotBlank(argv, CommandLineArgs.SUITE_NAME, m_suiteName);
+    addStringIfNotBlank(argv, CommandLineArgs.TEST_NAME, m_testName);
+    addStringIfNotBlank(argv, CommandLineArgs.TEST_NAMES, m_testNames);
+    addStringIfNotBlank(argv, CommandLineArgs.METHODS, m_methods);
+    addReporterConfigs(argv);
+    addIntegerIfNotNull(argv, CommandLineArgs.SUITE_THREAD_POOL_SIZE, m_suiteThreadPoolSize);
+    addStringIfNotNull(argv, CommandLineArgs.XML_PATH_IN_JAR, m_xmlPathInJar);
+    addXmlFiles(argv);
+	return argv;
+  }
+
+  private void addDefaultListeners(List<String> argv) {
+    if (m_useDefaultListeners != null) {
+      String useDefaultListeners = "false";
+      if ("yes".equalsIgnoreCase(m_useDefaultListeners) || "true".equalsIgnoreCase(m_useDefaultListeners)) {
+        useDefaultListeners = "true";
+      }
+      argv.add(CommandLineArgs.USE_DEFAULT_LISTENERS);
+      argv.add(useDefaultListeners);
+    }
+  }
+
+  private void addOutputDir(List<String> argv) {
+    if (null != m_outputDir) {
+      if (!m_outputDir.exists()) {
+        m_outputDir.mkdirs();
+      }
+      if (m_outputDir.isDirectory()) {
+        argv.add(CommandLineArgs.OUTPUT_DIRECTORY);
+        argv.add(m_outputDir.getAbsolutePath());
+      } else {
+        throw new BuildException("Output directory is not a directory: " + m_outputDir);
+      }
+    }
+  }
+
+  private void addReporterConfigs(List<String> argv) {
+    for (ReporterConfig reporterConfig : reporterConfigs) {
+      argv.add(CommandLineArgs.REPORTER);
+      argv.add(reporterConfig.serialize());
+    }
+  }
+
+  private void addFilesOfRCollection(List<String> argv, String name, List<ResourceCollection> resources) {
+	addArgumentsIfNotEmpty(argv, name, getFiles(resources), ",");
+  }
+
+  private void addListOfStringIfNotEmpty(List<String> argv, String name, List<String> arguments) {
+	addArgumentsIfNotEmpty(argv, name, arguments, ";");
+  }
+
+  private void addArgumentsIfNotEmpty(List<String> argv, String name, List<String> arguments, String separator) {
+	if (arguments != null && !arguments.isEmpty()) {
+      argv.add(name);
+      String value= Utils.join(arguments, separator);
+	    argv.add(value);
+  	}
+  }
+
+  private void addFileIfFile(List<String> argv, String name, File file) {
+    if ((null != file) && file.isFile()) {
+      argv.add(name);
+      argv.add(file.getAbsolutePath());
+    }
+  }
+  
+  private void addBooleanIfTrue(List<String> argv, String name, Boolean value) {
+    if (TRUE.equals(value)) {
+      argv.add(name);
+    }
+  }
+  
+  private void addIntegerIfNotNull(List<String> argv, String name, Integer value) {
+    if (value != null) {
+      argv.add(name);
+      argv.add(value.toString());
+    }
+  }
+  
+  private void addStringIfNotNull(List<String> argv, String name, String value) {
+    if (value != null) {
+      argv.add(name);
+      argv.add(value);
+    }
+  }
+  
+  private void addStringIfNotBlank(List<String> argv, String name, String value) {
+    if (isStringNotBlank(value)) {
+      argv.add(name);
+      argv.add(value);
+    }
+  }
+
+  private void addXmlFiles(List<String> argv) {
+    for (String file : getSuiteFileNames()) {
+      argv.add(file);
+    }
+  }
+
+  /**
+   * @return the list of the XML file names. This method can be overridden by subclasses.
+   */
+  protected List<String> getSuiteFileNames() {
+    List<String> result = Lists.newArrayList();
+
+    for(String file : getFiles(m_xmlFilesets)) {
+      result.add(file);
+    }
+
+    return result;
+  }
+
+  private void delegateCommandSystemProperties() {
+    // Iterate over command-line args and pass them through as sysproperty
+    // exclude any built-in properties that start with "ant."
+    for (Object propKey : getProject().getUserProperties().keySet()) {
+      String propName = (String) propKey;
+      String propVal = getProject().getUserProperty(propName);
+      if (propName.startsWith("ant.")) {
+        log("Excluding ant property: " + propName + ": " + propVal, Project.MSG_DEBUG);
+      }	else {
+        log("Including user property: " + propName + ": " + propVal, Project.MSG_DEBUG);
+        Environment.Variable var = new Environment.Variable();
+        var.setKey(propName);
+        var.setValue(propVal);
+        addSysproperty(var);
+      }
+    }
+  }
+
+  private void printDebugInfo(String fileName) {
+    if(m_dumpSys) {
+      System.out.println("* SYSTEM PROPERTIES *");
+      Properties props= System.getProperties();
+      Enumeration en= props.propertyNames();
+      while(en.hasMoreElements()) {
+        String key= (String) en.nextElement();
+        System.out.println(key + ": " + props.getProperty(key));
+      }
+      System.out.println("");
+    }
+    if(m_dumpEnv) {
+      String[] vars= m_environment.getVariables();
+      if(null != vars && vars.length > 0) {
+        System.out.println("* ENVIRONMENT *");
+        for(String v: vars) {
+          System.out.println(v);
+        }
+        System.out.println("");
+      }
+    }
+    if(m_dump) {
+      dumpCommand(fileName);
+    }
+  }
+
+  private void ppp(String string) {
+    System.out.println("[TestNGAntTask] " + string);
+  }
+
+  protected void actOnResult(int exitValue, boolean wasKilled) {
+    if(exitValue == -1) {
+      executeHaltTarget(exitValue);
+      throw new BuildException("an error occured when running TestNG tests");
+    }
+
+    if((exitValue & TestNG.HAS_NO_TEST) == TestNG.HAS_NO_TEST) {
+      if(m_haltOnFailure) {
+        executeHaltTarget(exitValue);
+        throw new BuildException("No tests were run");
+      }
+      else {
+        if(null != m_failurePropertyName) {
+          getProject().setNewProperty(m_failurePropertyName, "true");
+        }
+
+        log("TestNG haven't found any tests to be run", Project.MSG_DEBUG);
+      }
+    }
+
+    boolean failed= ((exitValue & TestNG.HAS_FAILURE) == TestNG.HAS_FAILURE) || wasKilled;
+    if(failed) {
+      final String msg= wasKilled ? "The tests timed out and were killed." : "The tests failed.";
+      if(m_haltOnFailure) {
+        executeHaltTarget(exitValue);
+        throw new BuildException(msg);
+      }
+      else {
+        if(null != m_failurePropertyName) {
+          getProject().setNewProperty(m_failurePropertyName, "true");
+        }
+
+        log(msg, Project.MSG_INFO);
+      }
+    }
+
+    if((exitValue & TestNG.HAS_SKIPPED) == TestNG.HAS_SKIPPED) {
+      if(m_haltOnSkipped) {
+        executeHaltTarget(exitValue);
+        throw new BuildException("There are TestNG SKIPPED tests");
+      }
+      else {
+        if(null != m_skippedPropertyName) {
+          getProject().setNewProperty(m_skippedPropertyName, "true");
+        }
+
+        log("There are TestNG SKIPPED tests", Project.MSG_DEBUG);
+      }
+    }
+
+    if((exitValue & TestNG.HAS_FSP) == TestNG.HAS_FSP) {
+      if(m_haltOnFSP) {
+        executeHaltTarget(exitValue);
+        throw new BuildException("There are TestNG FAILED WITHIN SUCCESS PERCENTAGE tests");
+      }
+      else {
+        if(null != m_fspPropertyName) {
+          getProject().setNewProperty(m_fspPropertyName, "true");
+        }
+
+        log("There are TestNG FAILED WITHIN SUCCESS PERCENTAGE tests", Project.MSG_DEBUG);
+      }
+    }
+  }
+
+  /** Executes the target, if any, that user designates executing before failing the test */
+  private void executeHaltTarget(int exitValue) {
+    if(m_onHaltTarget != null) {
+      if(m_outputDir != null) {
+        getProject().setProperty("testng.outputdir", m_outputDir.getAbsolutePath());
+      }
+      getProject().setProperty("testng.returncode", String.valueOf(exitValue));
+      Target t= (Target) getProject().getTargets().get(m_onHaltTarget);
+      if(t != null) {
+        t.execute();
+      }
+    }
+  }
+
+  /**
+   * Executes the command line as a new process.
+   *
+   * @param cmd the command to execute
+   * @param watchdog
+   * @return the exit status of the subprocess or INVALID.
+   */
+  protected int executeAsForked(CommandlineJava cmd, ExecuteWatchdog watchdog) {
+    Execute execute= new Execute(new TestNGLogSH(this, Project.MSG_INFO, Project.MSG_WARN, (m_verbose == null || m_verbose < 5)),
+                                 watchdog);
+    execute.setCommandline(cmd.getCommandline());
+    execute.setAntRun(getProject());
+    if(m_workingDir != null) {
+      if(m_workingDir.exists() && m_workingDir.isDirectory()) {
+        execute.setWorkingDirectory(m_workingDir);
+      }
+      else {
+        log("Ignoring invalid working directory : " + m_workingDir, Project.MSG_WARN);
+      }
+    }
+
+    String[] environment= m_environment.getVariables();
+    if(null != environment) {
+      for(String envEntry : environment) {
+        log("Setting environment variable: " + envEntry, Project.MSG_VERBOSE);
+      }
+    }
+
+    execute.setEnvironment(environment);
+
+    log(cmd.describeCommand(), Project.MSG_VERBOSE);
+    int retVal;
+    try {
+      retVal= execute.execute();
+    }
+    catch(IOException e) {
+      throw new BuildException("Process fork failed.", e, getLocation());
+    }
+
+    return retVal;
+  }
+
+  /**
+   * Creates or returns the already created <CODE>CommandlineJava</CODE>.
+   */
+  protected CommandlineJava getJavaCommand() {
+    if(null == m_javaCommand) {
+      m_javaCommand = new CommandlineJava();
+    }
+
+    return m_javaCommand;
+  }
+
+  /**
+   * @return <tt>null</tt> if there is no timeout value, otherwise the
+   * watchdog instance.
+   *
+   * @throws BuildException under unspecified circumstances
+   * @since Ant 1.2
+   */
+  protected ExecuteWatchdog createWatchdog() /*throws BuildException*/ {
+    if(m_timeout == null) {
+      return null;
+    }
+
+    return new ExecuteWatchdog(m_timeout.longValue());
+  }
+
+  protected void validateOptions() throws BuildException {
+    int suiteCount = getSuiteFileNames().size();
+    if (suiteCount == 0
+      && m_classFilesets.size() == 0
+      && Utils.isStringEmpty(m_methods)
+      && ((null == m_testjar) || !m_testjar.isFile())) {
+      throw new BuildException("No suites, classes, methods or jar file was specified.");
+    }
+
+    if((null != m_includedGroups) && (m_classFilesets.size() == 0 && suiteCount == 0)) {
+      throw new BuildException("No class filesets or xml file sets specified while using groups");
+    }
+
+    if(m_onHaltTarget != null) {
+      if(!getProject().getTargets().containsKey(m_onHaltTarget)) {
+        throw new BuildException("Target " + m_onHaltTarget + " not found in this project");
+      }
+    }
+
+  }
+
+  private ResourceCollection createResourceCollection(Reference ref) {
+    Object o = ref.getReferencedObject();
+    if (!(o instanceof ResourceCollection)) {
+        throw new BuildException("Only File based ResourceCollections are supported.");
+    }
+    ResourceCollection rc = (ResourceCollection) o;
+    if (!rc.isFilesystemOnly()) {
+        throw new BuildException("Only ResourceCollections from local file system are supported.");
+    }
+    return rc;
+  }
+
+  private FileSet appendClassSelector(FileSet fs) {
+    FilenameSelector selector= new FilenameSelector();
+    selector.setName("**/*.class");
+    selector.setProject(getProject());
+    fs.appendSelector(selector);
+
+    return fs;
+  }
+
+  private File findJar() {
+    Class thisClass= getClass();
+    String resource= thisClass.getName().replace('.', '/') + ".class";
+    URL url= thisClass.getClassLoader().getResource(resource);
+
+    if(null != url) {
+      String u= url.toString();
+      if(u.startsWith("jar:file:")) {
+        int pling= u.indexOf("!");
+        String jarName= u.substring(4, pling);
+
+        return new File(fromURI(jarName));
+      }
+      else if(u.startsWith("file:")) {
+        int tail= u.indexOf(resource);
+        String dirName= u.substring(0, tail);
+
+        return new File(fromURI(dirName));
+      }
+    }
+
+    return null;
+  }
+
+  private String fromURI(String uri) {
+    URL url= null;
+    try {
+      url= new URL(uri);
+    }
+    catch(MalformedURLException murle) {
+    }
+    if((null == url) || !("file".equals(url.getProtocol()))) {
+      throw new IllegalArgumentException("Can only handle valid file: URIs");
+    }
+
+    StringBuffer buf= new StringBuffer(url.getHost());
+    if(buf.length() > 0) {
+      buf.insert(0, File.separatorChar).insert(0, File.separatorChar);
+    }
+
+    String file= url.getFile();
+    int queryPos= file.indexOf('?');
+    buf.append((queryPos < 0) ? file : file.substring(0, queryPos));
+
+    uri= buf.toString().replace('/', File.separatorChar);
+
+    if((File.pathSeparatorChar == ';') && uri.startsWith("\\") && (uri.length() > 2)
+      && Character.isLetter(uri.charAt(1)) && (uri.lastIndexOf(':') > -1)) {
+      uri= uri.substring(1);
+    }
+
+    StringBuffer sb= new StringBuffer();
+    CharacterIterator iter= new StringCharacterIterator(uri);
+    for(char c= iter.first(); c != CharacterIterator.DONE; c= iter.next()) {
+      if(c == '%') {
+        char c1= iter.next();
+        if(c1 != CharacterIterator.DONE) {
+          int i1= Character.digit(c1, 16);
+          char c2= iter.next();
+          if(c2 != CharacterIterator.DONE) {
+            int i2= Character.digit(c2, 16);
+            sb.append((char) ((i1 << 4) + i2));
+          }
+        }
+      }
+      else {
+        sb.append(c);
+      }
+    }
+
+    return sb.toString();
+  }
+
+  /**
+   * Returns the list of files corresponding to the resource collection
+   *
+   * @param resources
+   * @return the list of files corresponding to the resource collection
+   * @throws BuildException
+   */
+  private List<String> getFiles(List<ResourceCollection> resources) throws BuildException {
+    List<String> files= Lists.newArrayList();
+    for (ResourceCollection rc : resources) {
+        for (Iterator i = rc.iterator(); i.hasNext();) {
+          Object o = i.next();
+          if (o instanceof FileResource) {
+            FileResource fr = ((FileResource) o);
+            if (fr.isDirectory()) {
+              throw new BuildException("Directory based FileResources are not supported.");
+            }
+            if (!fr.isExists()) {
+              log("'" + fr.toLongString() + "' does not exist", Project.MSG_VERBOSE);
+            }
+            files.add(fr.getFile().getAbsolutePath());
+          } else {
+              log("Unsupported Resource type: " + o.toString(), Project.MSG_VERBOSE);
+          }
+        }
+    }
+    return files;
+  }
+
+  /**
+   * Returns the list of files corresponding to the fileset
+   *
+   * @param filesets
+   * @return the list of files corresponding to the fileset
+   * @throws BuildException
+   */
+  private List<String> fileset(FileSet fileset) throws BuildException {
+    List<String> files= Lists.newArrayList();
+
+      DirectoryScanner ds= fileset.getDirectoryScanner(getProject());
+
+      for(String file : ds.getIncludedFiles()) {
+        files.add(ds.getBasedir() + File.separator + file);
+      }
+
+    return files;
+  }
+
+  /**
+   * Adds double quotes to the command line argument if it contains spaces.
+   * @param pCommandLineArg the command line argument
+   * @return pCommandLineArg in double quotes if it contains space.
+   *
+   */
+  private static String doubleQuote(String pCommandLineArg) {
+    if(pCommandLineArg.indexOf(" ") != -1 && !(pCommandLineArg.startsWith("\"") && pCommandLineArg.endsWith("\""))) {
+      return "\"" + pCommandLineArg + '\"';
+    }
+
+    return pCommandLineArg;
+  }
+
+  /**
+   * Creates a string representation of the path.
+   */
+  private String createPathString(Path path, String sep) {
+    if(path == null) {
+      return null;
+    }
+
+    final StringBuffer buf= new StringBuffer();
+
+    for(int i= 0; i < path.list().length; i++) {
+      File file= getProject().resolveFile(path.list()[i]);
+
+      if(!file.exists()) {
+        log("Classpath entry not found: " + file, Project.MSG_WARN);
+      }
+
+      buf.append(file.getAbsolutePath()).append(sep);
+    }
+
+    if(path.list().length > 0) { // cut the last ;
+      buf.deleteCharAt(buf.length() - 1);
+    }
+
+    return buf.toString();
+  }
+
+  private void dumpCommand(String fileName) {
+    ppp("TESTNG PASSED @" + fileName + " WHICH CONTAINS:");
+    readAndPrintFile(fileName);
+  }
+
+  private void readAndPrintFile(String fileName) {
+    File file = new File(fileName);
+    BufferedReader br = null;
+    try {
+      br = new BufferedReader(new FileReader(file));
+      String line = br.readLine();
+      while (line != null) {
+        System.out.println("  " + line);
+        line = br.readLine();
+      }
+    }
+    catch(IOException ex) {
+      ex.printStackTrace();
+    } finally {
+      if (br != null) {
+        try {
+          br.close();
+        } catch (IOException e) {
+          e.printStackTrace();
+        }
+      }
+    }
+  }
+
+  public void addConfiguredReporter(ReporterConfig reporterConfig) {
+    reporterConfigs.add(reporterConfig);
+  }
+
+  public void setSkipFailedInvocationCounts(boolean skip) {
+    m_skipFailedInvocationCounts = skip;
+  }
+
+  public void setXmlPathInJar(String path) {
+    m_xmlPathInJar = path;
+  }
+  /**
+   * Add the referenced property set as system properties for the TestNG JVM.
+   *
+   * @param sysPropertySet A PropertySet of system properties.
+   */
+  public void addConfiguredPropertySet(PropertySet sysPropertySet) {
+    Properties properties = sysPropertySet.getProperties();
+    log(properties.keySet().size() + " properties found in nested propertyset", Project.MSG_VERBOSE);
+    for (Object propKeyObj : properties.keySet()) {
+      String propKey = (String) propKeyObj;
+      Environment.Variable sysProp = new Environment.Variable();
+      sysProp.setKey(propKey);
+      if (properties.get(propKey) instanceof String) {
+        String propVal = (String) properties.get(propKey);
+        sysProp.setValue(propVal);
+        getJavaCommand().addSysproperty(sysProp);
+        log("Added system property " + propKey + " with value " + propVal, Project.MSG_VERBOSE);
+      } else {
+        log("Ignoring non-String property " + propKey, Project.MSG_WARN);
+      }
+    }
+  }
+
+    @Override
+    protected void handleOutput(String output) {
+        if (output.startsWith(VerboseReporter.LISTENER_PREFIX)) {
+            //send everything from VerboseReporter to verbose level unless log level is > 4
+            log(output, m_verbose < 5 ? Project.MSG_VERBOSE : Project.MSG_INFO);
+        } else {
+            super.handleOutput(output);
+        }
+    }
+
+    private static class TestNGLogOS extends LogOutputStream {
+
+        private Task task;
+        private boolean verbose;
+
+        public TestNGLogOS(Task task, int level, boolean verbose) {
+            super(task, level);
+            this.task = task;
+            this.verbose = verbose;
+        }
+
+        @Override
+        protected void processLine(String line, int level) {
+            if (line.startsWith(VerboseReporter.LISTENER_PREFIX)) {
+                task.log(line, verbose ? Project.MSG_VERBOSE : Project.MSG_INFO);
+            } else {
+                super.processLine(line, level);
+            }
+        }
+    }
+
+    protected static class TestNGLogSH extends PumpStreamHandler {
+
+        public TestNGLogSH(Task task, int outlevel, int errlevel, boolean verbose) {
+            super(new TestNGLogOS(task, outlevel, verbose),
+                    new LogOutputStream(task, errlevel));
+        }
+    }
+}
diff --git a/src/main/java/org/testng/TestNGException.java b/src/main/java/org/testng/TestNGException.java
new file mode 100755
index 0000000..a5b3d86
--- /dev/null
+++ b/src/main/java/org/testng/TestNGException.java
@@ -0,0 +1,28 @@
+package org.testng;
+
+
+/**
+ * The base class for all exceptions thrown by TestNG.
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public class TestNGException extends RuntimeException {
+
+  /* generated */
+  private static final long serialVersionUID = -422675971506425913L;
+
+  public TestNGException(Throwable t) {
+    super(t);
+  }
+
+  /**
+   * @param string
+   */
+  public TestNGException(String string) {
+    super("\n" + string);
+  }
+
+  public TestNGException(String string, Throwable t) {
+    super("\n" + string, t);
+  }
+}
diff --git a/src/main/java/org/testng/TestNGUtils.java b/src/main/java/org/testng/TestNGUtils.java
new file mode 100755
index 0000000..8e2b426
--- /dev/null
+++ b/src/main/java/org/testng/TestNGUtils.java
@@ -0,0 +1,16 @@
+package org.testng;
+
+import org.testng.internal.ClonedMethod;
+
+import java.lang.reflect.Method;
+
+public class TestNGUtils {
+
+  /**
+   * Create an ITestNGMethod for @code{method} based on @code{existingMethod}, which needs
+   * to belong to the same class.
+   */
+  public static ITestNGMethod createITestNGMethod(ITestNGMethod existingMethod, Method method) {
+    return new ClonedMethod(existingMethod, method);
+  }
+}
diff --git a/src/main/java/org/testng/TestRunner.java b/src/main/java/org/testng/TestRunner.java
new file mode 100644
index 0000000..22eb927
--- /dev/null
+++ b/src/main/java/org/testng/TestRunner.java
@@ -0,0 +1,1666 @@
+package org.testng;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.testng.annotations.Guice;
+import org.testng.annotations.IListenersAnnotation;
+import org.testng.collections.ListMultiMap;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.collections.Sets;
+import org.testng.internal.Attributes;
+import org.testng.internal.ClassHelper;
+import org.testng.internal.ClassImpl;
+import org.testng.internal.ClassInfoMap;
+import org.testng.internal.ConfigurationGroupMethods;
+import org.testng.internal.Constants;
+import org.testng.internal.DynamicGraph;
+import org.testng.internal.DynamicGraph.Status;
+import org.testng.internal.IConfiguration;
+import org.testng.internal.IInvoker;
+import org.testng.internal.ITestResultNotifier;
+import org.testng.internal.InvokedMethod;
+import org.testng.internal.Invoker;
+import org.testng.internal.MethodGroupsHelper;
+import org.testng.internal.MethodHelper;
+import org.testng.internal.MethodInstance;
+import org.testng.internal.ResultMap;
+import org.testng.internal.RunInfo;
+import org.testng.internal.TestMethodWorker;
+import org.testng.internal.TestNGClassFinder;
+import org.testng.internal.TestNGMethodFinder;
+import org.testng.internal.Utils;
+import org.testng.internal.XmlMethodSelector;
+import org.testng.internal.annotations.AnnotationHelper;
+import org.testng.internal.annotations.IAnnotationFinder;
+import org.testng.internal.annotations.IListeners;
+import org.testng.internal.thread.graph.GraphThreadPoolExecutor;
+import org.testng.internal.thread.graph.IThreadWorkerFactory;
+import org.testng.internal.thread.graph.IWorker;
+import org.testng.junit.IJUnitTestRunner;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlInclude;
+import org.testng.xml.XmlPackage;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import com.google.inject.Injector;
+import com.google.inject.Module;
+
+/**
+ * This class takes care of running one Test.
+ *
+ * @author Cedric Beust, Apr 26, 2004
+ */
+public class TestRunner
+    implements ITestContext, ITestResultNotifier, IThreadWorkerFactory<ITestNGMethod>
+{
+  /* generated */
+  private static final long serialVersionUID = 4247820024988306670L;
+  private ISuite m_suite;
+  private XmlTest m_xmlTest;
+  private String m_testName;
+
+  transient private List<XmlClass> m_testClassesFromXml= null;
+  transient private List<XmlPackage> m_packageNamesFromXml= null;
+
+  transient private IInvoker m_invoker= null;
+  transient private IAnnotationFinder m_annotationFinder= null;
+
+  /** ITestListeners support. */
+  transient private List<ITestListener> m_testListeners = Lists.newArrayList();
+  transient private Set<IConfigurationListener> m_configurationListeners = Sets.newHashSet();
+
+  transient private IConfigurationListener m_confListener= new ConfigurationListener();
+  transient private boolean m_skipFailedInvocationCounts;
+
+  transient private Collection<IInvokedMethodListener> m_invokedMethodListeners = Lists.newArrayList();
+  transient private List<IClassListener> m_classListeners = Lists.newArrayList();
+
+  /**
+   * All the test methods we found, associated with their respective classes.
+   * Note that these test methods might belong to different classes.
+   * We pick which ones to run at runtime.
+   */
+  private ITestNGMethod[] m_allTestMethods = new ITestNGMethod[0];
+
+  // Information about this test run
+
+  private Date m_startDate = null;
+  private Date m_endDate = null;
+
+  /** A map to keep track of Class <-> IClass. */
+  transient private Map<Class<?>, ITestClass> m_classMap = Maps.newLinkedHashMap();
+
+  /** Where the reports will be created. */
+  private String m_outputDirectory= Constants.getDefaultValueFor(Constants.PROP_OUTPUT_DIR);
+
+  // The XML method selector (groups/methods included/excluded in XML)
+  private XmlMethodSelector m_xmlMethodSelector = new XmlMethodSelector();
+
+  private static int m_verbose = 1;
+
+  //
+  // These next fields contain all the configuration methods found on this class.
+  // At initialization time, they just contain all the various @Configuration methods
+  // found in all the classes we are going to run.  When comes the time to run them,
+  // only a subset of them are run:  those that are enabled and belong on the same class as
+  // (or a parent of) the test class.
+  //
+  /** */
+  private ITestNGMethod[] m_beforeSuiteMethods = {};
+  private ITestNGMethod[] m_afterSuiteMethods = {};
+  private ITestNGMethod[] m_beforeXmlTestMethods = {};
+  private ITestNGMethod[] m_afterXmlTestMethods = {};
+  private List<ITestNGMethod> m_excludedMethods = Lists.newArrayList();
+  private ConfigurationGroupMethods m_groupMethods = null;
+
+  // Meta groups
+  private Map<String, List<String>> m_metaGroups = Maps.newHashMap();
+
+  // All the tests that were run along with their result
+  private IResultMap m_passedTests = new ResultMap();
+  private IResultMap m_failedTests = new ResultMap();
+  private IResultMap m_failedButWithinSuccessPercentageTests = new ResultMap();
+  private IResultMap m_skippedTests = new ResultMap();
+
+  private RunInfo m_runInfo= new RunInfo();
+
+  // The host where this test was run, or null if run locally
+  private String m_host;
+
+  // Defined dynamically depending on <test preserve-order="true/false">
+  transient private List<IMethodInterceptor> m_methodInterceptors;
+
+  private transient ClassMethodMap m_classMethodMap;
+  private transient TestNGClassFinder m_testClassFinder;
+  private transient IConfiguration m_configuration;
+  private IMethodInterceptor builtinInterceptor;
+
+  protected TestRunner(IConfiguration configuration,
+                    ISuite suite,
+                    XmlTest test,
+                    String outputDirectory,
+                    IAnnotationFinder finder,
+                    boolean skipFailedInvocationCounts,
+                    Collection<IInvokedMethodListener> invokedMethodListeners,
+                    List<IClassListener> classListeners)
+  {
+    init(configuration, suite, test, outputDirectory, finder, skipFailedInvocationCounts,
+        invokedMethodListeners, classListeners);
+  }
+
+  public TestRunner(IConfiguration configuration, ISuite suite, XmlTest test,
+      boolean skipFailedInvocationCounts,
+      Collection<IInvokedMethodListener> invokedMethodListeners,
+      List<IClassListener> classListeners) {
+    init(configuration, suite, test, suite.getOutputDirectory(),
+        suite.getAnnotationFinder(),
+        skipFailedInvocationCounts, invokedMethodListeners, classListeners);
+  }
+
+  private void init(IConfiguration configuration,
+                    ISuite suite,
+                    XmlTest test,
+                    String outputDirectory,
+                    IAnnotationFinder annotationFinder,
+                    boolean skipFailedInvocationCounts,
+                    Collection<IInvokedMethodListener> invokedMethodListeners,
+                    List<IClassListener> classListeners)
+  {
+    m_configuration = configuration;
+    m_xmlTest= test;
+    m_suite = suite;
+    m_testName = test.getName();
+    m_host = suite.getHost();
+    m_testClassesFromXml= test.getXmlClasses();
+    m_skipFailedInvocationCounts = skipFailedInvocationCounts;
+    setVerbose(test.getVerbose());
+
+
+    boolean preserveOrder = "true".equalsIgnoreCase(test.getPreserveOrder());
+    m_methodInterceptors = new ArrayList<IMethodInterceptor>();
+    builtinInterceptor = preserveOrder ? new PreserveOrderMethodInterceptor() : new InstanceOrderingMethodInterceptor();
+
+    m_packageNamesFromXml= test.getXmlPackages();
+    if(null != m_packageNamesFromXml) {
+      for(XmlPackage xp: m_packageNamesFromXml) {
+        m_testClassesFromXml.addAll(xp.getXmlClasses());
+      }
+    }
+
+    m_annotationFinder= annotationFinder;
+    m_invokedMethodListeners = invokedMethodListeners;
+    m_classListeners = classListeners;
+    m_invoker = new Invoker(m_configuration, this, this, m_suite.getSuiteState(),
+        m_skipFailedInvocationCounts, invokedMethodListeners, classListeners);
+
+    if (suite.getParallel() != null) {
+      log(3, "Running the tests in '" + test.getName() + "' with parallel mode:" + suite.getParallel());
+    }
+
+    setOutputDirectory(outputDirectory);
+
+    // Finish our initialization
+    init();
+  }
+
+  public IInvoker getInvoker() {
+    return m_invoker;
+  }
+
+  public ITestNGMethod[] getBeforeSuiteMethods() {
+    return m_beforeSuiteMethods;
+  }
+
+  public ITestNGMethod[] getAfterSuiteMethods() {
+    return m_afterSuiteMethods;
+  }
+
+  public ITestNGMethod[] getBeforeTestConfigurationMethods() {
+    return m_beforeXmlTestMethods;
+  }
+
+  public ITestNGMethod[] getAfterTestConfigurationMethods() {
+    return m_afterXmlTestMethods;
+  }
+
+  private void init() {
+    initMetaGroups(m_xmlTest);
+    initRunInfo(m_xmlTest);
+
+    // Init methods and class map
+    // JUnit behavior is different and doesn't need this initialization step
+    if(!m_xmlTest.isJUnit()) {
+      initMethods();
+    }
+
+    initListeners();
+    addConfigurationListener(m_confListener);
+  }
+
+  private static class ListenerHolder {
+    private List<Class<? extends ITestNGListener>> listenerClasses;
+    private Class<? extends ITestNGListenerFactory> listenerFactoryClass;
+  }
+
+  /**
+   * @return all the @Listeners annotations found in the current class and its
+   * superclasses.
+   */
+  private ListenerHolder findAllListeners(Class<?> cls) {
+    ListenerHolder result = new ListenerHolder();
+    result.listenerClasses = Lists.newArrayList();
+
+    do {
+      IListenersAnnotation l = m_annotationFinder.findAnnotation(cls, IListenersAnnotation.class);
+      if (l != null) {
+        Class<? extends ITestNGListener>[] classes = l.getValue();
+        for (Class<? extends ITestNGListener> c : classes) {
+          result.listenerClasses.add(c);
+
+          if (ITestNGListenerFactory.class.isAssignableFrom(c)) {
+            if (result.listenerFactoryClass == null) {
+              result.listenerFactoryClass = (Class<? extends ITestNGListenerFactory>) c;
+            }
+            else {
+              throw new TestNGException("Found more than one class implementing" +
+                  "ITestNGListenerFactory:" + c + " and " + result.listenerFactoryClass);
+            }
+          }
+        }
+      }
+      cls = cls.getSuperclass();
+    } while (cls != Object.class);
+
+    return result;
+  }
+
+  private void initListeners() {
+    //
+    // Find all the listener factories and collect all the listeners requested in a
+    // @Listeners annotation.
+    //
+    Set<Class<? extends ITestNGListener>> listenerClasses = Sets.newHashSet();
+    Class<? extends ITestNGListenerFactory> listenerFactoryClass = null;
+
+    for (IClass cls : getTestClasses()) {
+      Class<? extends ITestNGListenerFactory> realClass = cls.getRealClass();
+      ListenerHolder listenerHolder = findAllListeners(realClass);
+      if (listenerFactoryClass == null) {
+        listenerFactoryClass = listenerHolder.listenerFactoryClass;
+      }
+      listenerClasses.addAll(listenerHolder.listenerClasses);
+    }
+
+    //
+    // Now we have all the listeners collected from @Listeners and at most one
+    // listener factory collected from a class implementing ITestNGListenerFactory.
+    // Instantiate all the requested listeners.
+    //
+    ITestNGListenerFactory listenerFactory = null;
+
+    // If we found a test listener factory, instantiate it.
+    try {
+      if (m_testClassFinder != null) {
+        IClass ic = m_testClassFinder.getIClass(listenerFactoryClass);
+        if (ic != null) {
+          listenerFactory = (ITestNGListenerFactory) ic.getInstances(false)[0];
+        }
+      }
+      if (listenerFactory == null) {
+        listenerFactory = listenerFactoryClass != null ? listenerFactoryClass.newInstance() : null;
+      }
+    }
+    catch(Exception ex) {
+      throw new TestNGException("Couldn't instantiate the ITestNGListenerFactory: "
+          + ex);
+    }
+
+    // Instantiate all the listeners
+    for (Class<? extends ITestNGListener> c : listenerClasses) {
+      Object listener = listenerFactory != null ? listenerFactory.createListener(c) : null;
+      if (listener == null) {
+        listener = ClassHelper.newInstance(c);
+      }
+
+      if (listener instanceof IMethodInterceptor) {
+        m_methodInterceptors.add((IMethodInterceptor) listener);
+      }
+      if (listener instanceof ISuiteListener) {
+        m_suite.addListener((ISuiteListener) listener);
+      }
+      if (listener instanceof IInvokedMethodListener) {
+        m_suite.addListener((ITestNGListener) listener);
+      }
+      if (listener instanceof ITestListener) {
+        // At this point, the field m_testListeners has already been used in the creation
+        addTestListener((ITestListener) listener);
+      }
+      if (listener instanceof IClassListener) {
+        m_classListeners.add((IClassListener) listener);
+      }
+      if (listener instanceof IConfigurationListener) {
+        addConfigurationListener((IConfigurationListener) listener);
+      }
+      if (listener instanceof IReporter) {
+        m_suite.addListener((ITestNGListener) listener);
+      }
+      if (listener instanceof IConfigurable) {
+        m_configuration.setConfigurable((IConfigurable) listener);
+      }
+      if (listener instanceof IHookable) {
+        m_configuration.setHookable((IHookable) listener);
+      }
+      if (listener instanceof IExecutionListener) {
+        IExecutionListener iel = (IExecutionListener) listener;
+        iel.onExecutionStart();
+        m_configuration.addExecutionListener(iel);
+      }
+    }
+  }
+
+  /**
+   * Initialize meta groups
+   */
+  private void initMetaGroups(XmlTest xmlTest) {
+    Map<String, List<String>> metaGroups = xmlTest.getMetaGroups();
+
+    for (Map.Entry<String, List<String>> entry : metaGroups.entrySet()) {
+      addMetaGroup(entry.getKey(), entry.getValue());
+    }
+  }
+
+  private void initRunInfo(final XmlTest xmlTest) {
+    // Groups
+    m_xmlMethodSelector.setIncludedGroups(createGroups(m_xmlTest.getIncludedGroups()));
+    m_xmlMethodSelector.setExcludedGroups(createGroups(m_xmlTest.getExcludedGroups()));
+    m_xmlMethodSelector.setExpression(m_xmlTest.getExpression());
+
+    // Methods
+    m_xmlMethodSelector.setXmlClasses(m_xmlTest.getXmlClasses());
+
+    m_runInfo.addMethodSelector(m_xmlMethodSelector, 10);
+
+    // Add user-specified method selectors (only class selectors, we can ignore
+    // script selectors here)
+    if (null != xmlTest.getMethodSelectors()) {
+      for (org.testng.xml.XmlMethodSelector selector : xmlTest.getMethodSelectors()) {
+        if (selector.getClassName() != null) {
+          IMethodSelector s = ClassHelper.createSelector(selector);
+
+          m_runInfo.addMethodSelector(s, selector.getPriority());
+        }
+      }
+    }
+  }
+
+  private void initMethods() {
+
+    //
+    // Calculate all the methods we need to invoke
+    //
+    List<ITestNGMethod> beforeClassMethods = Lists.newArrayList();
+    List<ITestNGMethod> testMethods = Lists.newArrayList();
+    List<ITestNGMethod> afterClassMethods = Lists.newArrayList();
+    List<ITestNGMethod> beforeSuiteMethods = Lists.newArrayList();
+    List<ITestNGMethod> afterSuiteMethods = Lists.newArrayList();
+    List<ITestNGMethod> beforeXmlTestMethods = Lists.newArrayList();
+    List<ITestNGMethod> afterXmlTestMethods = Lists.newArrayList();
+
+    ClassInfoMap classMap = new ClassInfoMap(m_testClassesFromXml);
+    m_testClassFinder= new TestNGClassFinder(classMap,
+                                             null,
+                                             m_xmlTest,
+                                             m_configuration,
+                                             this);
+    ITestMethodFinder testMethodFinder
+      = new TestNGMethodFinder(m_runInfo, m_annotationFinder);
+
+    m_runInfo.setTestMethods(testMethods);
+
+    //
+    // Initialize TestClasses
+    //
+    IClass[] classes = m_testClassFinder.findTestClasses();
+
+    for (IClass ic : classes) {
+
+      // Create TestClass
+      ITestClass tc = new TestClass(ic,
+                                   testMethodFinder,
+                                   m_annotationFinder,
+                                   m_runInfo,
+                                   m_xmlTest,
+                                   classMap.getXmlClass(ic.getRealClass()));
+      m_classMap.put(ic.getRealClass(), tc);
+    }
+
+    //
+    // Calculate groups methods
+    //
+    Map<String, List<ITestNGMethod>> beforeGroupMethods =
+        MethodGroupsHelper.findGroupsMethods(m_classMap.values(), true);
+    Map<String, List<ITestNGMethod>> afterGroupMethods =
+        MethodGroupsHelper.findGroupsMethods(m_classMap.values(), false);
+
+    //
+    // Walk through all the TestClasses, store their method
+    // and initialize them with the correct ITestClass
+    //
+    for (ITestClass tc : m_classMap.values()) {
+      fixMethodsWithClass(tc.getTestMethods(), tc, testMethods);
+      fixMethodsWithClass(tc.getBeforeClassMethods(), tc, beforeClassMethods);
+      fixMethodsWithClass(tc.getBeforeTestMethods(), tc, null);
+      fixMethodsWithClass(tc.getAfterTestMethods(), tc, null);
+      fixMethodsWithClass(tc.getAfterClassMethods(), tc, afterClassMethods);
+      fixMethodsWithClass(tc.getBeforeSuiteMethods(), tc, beforeSuiteMethods);
+      fixMethodsWithClass(tc.getAfterSuiteMethods(), tc, afterSuiteMethods);
+      fixMethodsWithClass(tc.getBeforeTestConfigurationMethods(), tc, beforeXmlTestMethods);
+      fixMethodsWithClass(tc.getAfterTestConfigurationMethods(), tc, afterXmlTestMethods);
+      fixMethodsWithClass(tc.getBeforeGroupsMethods(), tc,
+          MethodHelper.uniqueMethodList(beforeGroupMethods.values()));
+      fixMethodsWithClass(tc.getAfterGroupsMethods(), tc,
+          MethodHelper.uniqueMethodList(afterGroupMethods.values()));
+    }
+
+    //
+    // Sort the methods
+    //
+    m_beforeSuiteMethods = MethodHelper.collectAndOrderMethods(beforeSuiteMethods,
+                                                              false /* forTests */,
+                                                              m_runInfo,
+                                                              m_annotationFinder,
+                                                              true /* unique */,
+                                                              m_excludedMethods);
+
+    m_beforeXmlTestMethods = MethodHelper.collectAndOrderMethods(beforeXmlTestMethods,
+                                                              false /* forTests */,
+                                                              m_runInfo,
+                                                              m_annotationFinder,
+                                                              true /* unique (CQ added by me)*/,
+                                                              m_excludedMethods);
+
+    m_allTestMethods = MethodHelper.collectAndOrderMethods(testMethods,
+                                                                true /* forTest? */,
+                                                                m_runInfo,
+                                                                m_annotationFinder,
+                                                                false /* unique */,
+                                                                m_excludedMethods);
+    m_classMethodMap = new ClassMethodMap(testMethods, m_xmlMethodSelector);
+
+    m_afterXmlTestMethods = MethodHelper.collectAndOrderMethods(afterXmlTestMethods,
+                                                              false /* forTests */,
+                                                              m_runInfo,
+                                                              m_annotationFinder,
+                                                              true /* unique (CQ added by me)*/,
+                                                              m_excludedMethods);
+
+    m_afterSuiteMethods = MethodHelper.collectAndOrderMethods(afterSuiteMethods,
+                                                              false /* forTests */,
+                                                              m_runInfo,
+                                                              m_annotationFinder,
+                                                              true /* unique */,
+                                                              m_excludedMethods);
+    // shared group methods
+    m_groupMethods = new ConfigurationGroupMethods(m_allTestMethods, beforeGroupMethods, afterGroupMethods);
+
+
+  }
+
+  private void fixMethodsWithClass(ITestNGMethod[] methods,
+                                   ITestClass testCls,
+                                   List<ITestNGMethod> methodList) {
+    for (ITestNGMethod itm : methods) {
+      itm.setTestClass(testCls);
+
+      if (methodList != null) {
+        methodList.add(itm);
+      }
+    }
+  }
+
+  public Collection<ITestClass> getTestClasses() {
+    return m_classMap.values();
+  }
+
+  public void setTestName(String name) {
+    m_testName = name;
+  }
+
+  public void setOutputDirectory(String od) {
+    m_outputDirectory= od;
+//  FIX: empty directories were created
+//    if (od == null) { m_outputDirectory = null; return; } //for maven2
+//    File file = new File(od);
+//    file.mkdirs();
+//    m_outputDirectory= file.getAbsolutePath();
+  }
+
+  private void addMetaGroup(String name, List<String> groupNames) {
+    m_metaGroups.put(name, groupNames);
+  }
+
+  /**
+   * Calculate the transitive closure of all the MetaGroups
+   *
+   * @param groups
+   * @param unfinishedGroups
+   * @param result           The transitive closure containing all the groups found
+   */
+  private void collectGroups(String[] groups,
+                             List<String> unfinishedGroups,
+                             Map<String, String> result) {
+    for (String gn : groups) {
+      List<String> subGroups = m_metaGroups.get(gn);
+      if (null != subGroups) {
+
+        for (String sg : subGroups) {
+          if (null == result.get(sg)) {
+            result.put(sg, sg);
+            unfinishedGroups.add(sg);
+          }
+        }
+      }
+    }
+  }
+
+  private Map<String, String> createGroups(List<String> groups) {
+    return createGroups(groups.toArray(new String[groups.size()]));
+  }
+
+  private Map<String, String> createGroups(String[] groups) {
+    Map<String, String> result = Maps.newHashMap();
+
+    // Groups that were passed on the command line
+    for (String group : groups) {
+      result.put(group, group);
+    }
+
+    // See if we have any MetaGroups and
+    // expand them if they match one of the groups
+    // we have just been passed
+    List<String> unfinishedGroups = Lists.newArrayList();
+
+    if (m_metaGroups.size() > 0) {
+      collectGroups(groups, unfinishedGroups, result);
+
+      // Do we need to loop over unfinished groups?
+      while (unfinishedGroups.size() > 0) {
+        String[] uGroups = unfinishedGroups.toArray(new String[unfinishedGroups.size()]);
+        unfinishedGroups = Lists.newArrayList();
+        collectGroups(uGroups, unfinishedGroups, result);
+      }
+    }
+
+    //    Utils.dumpMap(result);
+    return result;
+  }
+
+  /**
+   * The main entry method for TestRunner.
+   *
+   * This is where all the hard work is done:
+   * - Invoke configuration methods
+   * - Invoke test methods
+   * - Catch exceptions
+   * - Collect results
+   * - Invoke listeners
+   * - etc...
+   */
+  public void run() {
+    beforeRun();
+
+    try {
+      XmlTest test= getTest();
+      if(test.isJUnit()) {
+        privateRunJUnit(test);
+      }
+      else {
+        privateRun(test);
+      }
+    }
+    finally {
+      afterRun();
+    }
+  }
+
+  /** Before run preparements. */
+  private void beforeRun() {
+    //
+    // Log the start date
+    //
+    m_startDate = new Date(System.currentTimeMillis());
+
+    // Log start
+    logStart();
+
+    // Invoke listeners
+    fireEvent(true /*start*/);
+
+    // invoke @BeforeTest
+    ITestNGMethod[] testConfigurationMethods= getBeforeTestConfigurationMethods();
+    if(null != testConfigurationMethods && testConfigurationMethods.length > 0) {
+      m_invoker.invokeConfigurations(null,
+                                     testConfigurationMethods,
+                                     m_xmlTest.getSuite(),
+                                     m_xmlTest.getAllParameters(),
+                                     null, /* no parameter values */
+                                     null /* instance */);
+    }
+  }
+
+  private void privateRunJUnit(XmlTest xmlTest) {
+    final ClassInfoMap cim = new ClassInfoMap(m_testClassesFromXml, false);
+    final Set<Class<?>> classes = cim.getClasses();
+    final List<ITestNGMethod> runMethods = Lists.newArrayList();
+    List<IWorker<ITestNGMethod>> workers = Lists.newArrayList();
+    // FIXME: directly referencing JUnitTestRunner which uses JUnit classes
+    // may result in an class resolution exception under different JVMs
+    // The resolution process is not specified in the JVM spec with a specific implementation,
+    // so it can be eager => failure
+    workers.add(new IWorker<ITestNGMethod>() {
+      /**
+       * @see TestMethodWorker#getTimeOut()
+       */
+      @Override
+      public long getTimeOut() {
+        return 0;
+      }
+
+      /**
+       * @see java.lang.Runnable#run()
+       */
+      @Override
+      public void run() {
+        for(Class<?> tc: classes) {
+          List<XmlInclude> includedMethods = cim.getXmlClass(tc).getIncludedMethods();
+          List<String> methods = Lists.newArrayList();
+          for (XmlInclude inc: includedMethods) {
+              methods.add(inc.getName());
+          }
+          IJUnitTestRunner tr= ClassHelper.createTestRunner(TestRunner.this);
+          tr.setInvokedMethodListeners(m_invokedMethodListeners);
+          try {
+            tr.run(tc, methods.toArray(new String[methods.size()]));
+          }
+          catch(Exception ex) {
+            ex.printStackTrace();
+          }
+          finally {
+            runMethods.addAll(tr.getTestMethods());
+          }
+        }
+      }
+
+      @Override
+      public List<ITestNGMethod> getTasks() {
+        throw new TestNGException("JUnit not supported");
+      }
+
+      @Override
+      public int getPriority() {
+        if (m_allTestMethods.length == 1) {
+          return m_allTestMethods[0].getPriority();
+        } else {
+          return 0;
+        }
+      }
+
+      @Override
+      public int compareTo(IWorker<ITestNGMethod> other) {
+        return getPriority() - other.getPriority();
+      }
+    });
+
+    runJUnitWorkers(workers);
+    m_allTestMethods= runMethods.toArray(new ITestNGMethod[runMethods.size()]);
+  }
+
+  private static final EnumSet<XmlSuite.ParallelMode> PRIVATE_RUN_PARALLEL_MODES
+      = EnumSet.of(XmlSuite.ParallelMode.METHODS, XmlSuite.ParallelMode.TRUE,
+                   XmlSuite.ParallelMode.CLASSES, XmlSuite.ParallelMode.INSTANCES);
+  /**
+   * Main method that create a graph of methods and then pass it to the
+   * graph executor to run them.
+   */
+  private void privateRun(XmlTest xmlTest) {
+    XmlSuite.ParallelMode parallelMode = xmlTest.getParallel();
+    boolean parallel = PRIVATE_RUN_PARALLEL_MODES.contains(parallelMode);
+
+    {
+      // parallel
+      int threadCount = parallel ? xmlTest.getThreadCount() : 1;
+      // Make sure we create a graph based on the intercepted methods, otherwise an interceptor
+      // removing methods would cause the graph never to terminate (because it would expect
+      // termination from methods that never get invoked).
+      DynamicGraph<ITestNGMethod> graph = createDynamicGraph(intercept(m_allTestMethods));
+      if (parallel) {
+        if (graph.getNodeCount() > 0) {
+          GraphThreadPoolExecutor<ITestNGMethod> executor =
+                  new GraphThreadPoolExecutor<>(graph, this,
+                          threadCount, threadCount, 0, TimeUnit.MILLISECONDS,
+                          new LinkedBlockingQueue<Runnable>());
+          executor.run();
+          try {
+            long timeOut = m_xmlTest.getTimeOut(XmlTest.DEFAULT_TIMEOUT_MS);
+            Utils.log("TestRunner", 2, "Starting executor for test " + m_xmlTest.getName()
+                + " with time out:" + timeOut + " milliseconds.");
+            executor.awaitTermination(timeOut, TimeUnit.MILLISECONDS);
+            executor.shutdownNow();
+          } catch (InterruptedException handled) {
+            handled.printStackTrace();
+            Thread.currentThread().interrupt();
+          }
+        }
+      } else {
+        boolean debug = false;
+        List<ITestNGMethod> freeNodes = graph.getFreeNodes();
+        if (debug) {
+          System.out.println("Free nodes:" + freeNodes);
+        }
+
+        if (graph.getNodeCount() > 0 && freeNodes.isEmpty()) {
+          throw new TestNGException("No free nodes found in:" + graph);
+        }
+
+        while (! freeNodes.isEmpty()) {
+          List<IWorker<ITestNGMethod>> runnables = createWorkers(freeNodes);
+          for (IWorker<ITestNGMethod> r : runnables) {
+            r.run();
+          }
+          graph.setStatus(freeNodes, Status.FINISHED);
+          freeNodes = graph.getFreeNodes();
+          if (debug) {
+            System.out.println("Free nodes:" + freeNodes);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Apply the method interceptor (if applicable) to the list of methods.
+   */
+  private ITestNGMethod[] intercept(ITestNGMethod[] methods) {
+    List<IMethodInstance> methodInstances = methodsToMethodInstances(Arrays.asList(methods));
+
+    // add built-in interceptor (PreserveOrderMethodInterceptor or InstanceOrderingMethodInterceptor at the end of the list
+    m_methodInterceptors.add(builtinInterceptor);
+    for (IMethodInterceptor m_methodInterceptor : m_methodInterceptors) {
+      methodInstances = m_methodInterceptor.intercept(methodInstances, this);
+    }
+
+    List<ITestNGMethod> result = Lists.newArrayList();
+    for (IMethodInstance imi : methodInstances) {
+      result.add(imi.getMethod());
+    }
+    
+    //Since an interceptor is involved, we would need to ensure that the ClassMethodMap object is in sync with the 
+    //output of the interceptor, else @AfterClass doesn't get executed at all when interceptors are involved.
+    //so let's update the current classMethodMap object with the list of methods obtained from the interceptor.
+    this.m_classMethodMap = new ClassMethodMap(result, null);
+    
+    return result.toArray(new ITestNGMethod[result.size()]);
+  }
+
+  /**
+   * Create a list of workers to run the methods passed in parameter.
+   * Each test method is run in its own worker except in the following cases:
+   * - The method belongs to a class that has @Test(sequential=true)
+   * - The parallel attribute is set to "classes"
+   * In both these cases, all the methods belonging to that class will then
+   * be put in the same worker in order to run in the same thread.
+   */
+  @Override
+  public List<IWorker<ITestNGMethod>> createWorkers(List<ITestNGMethod> methods) {
+    List<IWorker<ITestNGMethod>> result;
+    if (XmlSuite.ParallelMode.INSTANCES.equals(m_xmlTest.getParallel())) {
+      result = createInstanceBasedParallelWorkers(methods);
+    } else {
+      result = createClassBasedParallelWorkers(methods);
+    }
+    return result;
+  }
+
+  /**
+   * Create workers for parallel="classes" and similar cases.
+   */
+  private List<IWorker<ITestNGMethod>> createClassBasedParallelWorkers(List<ITestNGMethod> methods) {
+    List<IWorker<ITestNGMethod>> result = Lists.newArrayList();
+    // Methods that belong to classes with a sequential=true or parallel=classes
+    // attribute must all be run in the same worker
+    Set<Class> sequentialClasses = Sets.newHashSet();
+    for (ITestNGMethod m : methods) {
+      Class<? extends ITestClass> cls = m.getRealClass();
+      org.testng.annotations.ITestAnnotation test =
+          m_annotationFinder.findAnnotation(cls, org.testng.annotations.ITestAnnotation.class);
+
+      // If either sequential=true or parallel=classes, mark this class sequential
+      if (test != null && (test.getSequential() || test.getSingleThreaded()) ||
+          XmlSuite.ParallelMode.CLASSES.equals(m_xmlTest.getParallel())) {
+        sequentialClasses.add(cls);
+      }
+    }
+
+    List<IMethodInstance> methodInstances = Lists.newArrayList();
+    for (ITestNGMethod tm : methods) {
+      methodInstances.addAll(methodsToMultipleMethodInstances(tm));
+    }
+
+
+    Map<String, String> params = m_xmlTest.getAllParameters();
+
+    Set<Class<?>> processedClasses = Sets.newHashSet();
+    for (IMethodInstance im : methodInstances) {
+      Class<?> c = im.getMethod().getTestClass().getRealClass();
+      if (sequentialClasses.contains(c)) {
+        if (!processedClasses.contains(c)) {
+          processedClasses.add(c);
+          if (System.getProperty("experimental") != null) {
+            List<List<IMethodInstance>> instances = createInstances(methodInstances);
+            for (List<IMethodInstance> inst : instances) {
+              TestMethodWorker worker = createTestMethodWorker(inst, params, c);
+              result.add(worker);
+            }
+          }
+          else {
+            // Sequential class: all methods in one worker
+            TestMethodWorker worker = createTestMethodWorker(methodInstances, params, c);
+            result.add(worker);
+          }
+        }
+      }
+      else {
+        // Parallel class: each method in its own worker
+        TestMethodWorker worker = createTestMethodWorker(Arrays.asList(im), params, c);
+        result.add(worker);
+      }
+    }
+
+    // Sort by priorities
+    Collections.sort(result);
+    return result;
+  }
+
+
+  /**
+   * Create workers for parallel="instances".
+   */
+  private List<IWorker<ITestNGMethod>>
+      createInstanceBasedParallelWorkers(List<ITestNGMethod> methods) {
+    List<IWorker<ITestNGMethod>> result = Lists.newArrayList();
+    ListMultiMap<Object, ITestNGMethod> lmm = Maps.newListMultiMap();
+    for (ITestNGMethod m : methods) {
+      lmm.put(m.getInstance(), m);
+    }
+    for (Map.Entry<Object, List<ITestNGMethod>> es : lmm.entrySet()) {
+      List<IMethodInstance> methodInstances = Lists.newArrayList();
+      for (ITestNGMethod m : es.getValue()) {
+        methodInstances.add(new MethodInstance(m));
+      }
+      TestMethodWorker tmw = new TestMethodWorker(m_invoker,
+          methodInstances.toArray(new IMethodInstance[methodInstances.size()]),
+          m_xmlTest.getSuite(),
+          m_xmlTest.getAllParameters(),
+          m_groupMethods,
+          m_classMethodMap,
+          this,
+          m_classListeners);
+      result.add(tmw);
+    }
+
+    return result;
+  }
+
+  private List<List<IMethodInstance>> createInstances(List<IMethodInstance> methodInstances) {
+    Map<Object, List<IMethodInstance>> map = Maps.newHashMap();
+//    MapList<IMethodInstance[], Object> map = new MapList<IMethodInstance[], Object>();
+    for (IMethodInstance imi : methodInstances) {
+      for (Object o : imi.getInstances()) {
+        System.out.println(o);
+        List<IMethodInstance> l = map.get(o);
+        if (l == null) {
+          l = Lists.newArrayList();
+          map.put(o, l);
+        }
+        l.add(imi);
+      }
+//      for (Object instance : imi.getInstances()) {
+//        map.put(imi, instance);
+//      }
+    }
+//    return map.getKeys();
+//    System.out.println(map);
+    return new ArrayList<>(map.values());
+  }
+
+  private TestMethodWorker createTestMethodWorker(
+      List<IMethodInstance> methodInstances, Map<String, String> params,
+      Class<?> c) {
+    return new TestMethodWorker(m_invoker,
+        findClasses(methodInstances, c),
+        m_xmlTest.getSuite(),
+        params,
+        m_groupMethods,
+        m_classMethodMap,
+        this,
+        m_classListeners);
+  }
+
+  private IMethodInstance[] findClasses(List<IMethodInstance> methodInstances, Class<?> c) {
+    List<IMethodInstance> result = Lists.newArrayList();
+    for (IMethodInstance mi : methodInstances) {
+      if (mi.getMethod().getTestClass().getRealClass() == c) {
+        result.add(mi);
+      }
+    }
+    return result.toArray(new IMethodInstance[result.size()]);
+  }
+
+  /**
+   * @@@ remove this
+   */
+  private List<MethodInstance> methodsToMultipleMethodInstances(ITestNGMethod... sl) {
+    List<MethodInstance> vResult = Lists.newArrayList();
+    for (ITestNGMethod m : sl) {
+      vResult.add(new MethodInstance(m));
+    }
+
+    return vResult;
+  }
+
+  private List<IMethodInstance> methodsToMethodInstances(List<ITestNGMethod> sl) {
+    List<IMethodInstance> result = new ArrayList<>();
+      for (ITestNGMethod iTestNGMethod : sl) {
+        result.add(new MethodInstance(iTestNGMethod));
+      }
+    return result;
+  }
+
+  //
+  // Invoke the workers
+  //
+  private static final EnumSet<XmlSuite.ParallelMode> WORKERS_PARALLEL_MODES
+      = EnumSet.of(XmlSuite.ParallelMode.METHODS, XmlSuite.ParallelMode.TRUE,
+                   XmlSuite.ParallelMode.CLASSES);
+  private void runJUnitWorkers(List<? extends IWorker<ITestNGMethod>> workers) {
+      //
+      // Sequential run
+      //
+      for (IWorker<ITestNGMethod> tmw : workers) {
+        tmw.run();
+      }
+  }
+
+  private void afterRun() {
+    // invoke @AfterTest
+    ITestNGMethod[] testConfigurationMethods= getAfterTestConfigurationMethods();
+    if(null != testConfigurationMethods && testConfigurationMethods.length > 0) {
+      m_invoker.invokeConfigurations(null,
+                                     testConfigurationMethods,
+                                     m_xmlTest.getSuite(),
+                                     m_xmlTest.getAllParameters(),
+                                     null, /* no parameter values */
+                                     null /* instance */);
+    }
+
+    //
+    // Log the end date
+    //
+    m_endDate = new Date(System.currentTimeMillis());
+
+    if (getVerbose() >= 3) {
+      dumpInvokedMethods();
+    }
+
+    // Invoke listeners
+    fireEvent(false /*stop*/);
+
+    // Statistics
+//    logResults();
+  }
+
+  private DynamicGraph<ITestNGMethod> createDynamicGraph(ITestNGMethod[] methods) {
+    DynamicGraph<ITestNGMethod> result = new DynamicGraph<>();
+    result.setComparator(new Comparator<ITestNGMethod>() {
+      @Override
+      public int compare(ITestNGMethod o1, ITestNGMethod o2) {
+        return o1.getPriority() - o2.getPriority();
+      }
+    });
+
+    DependencyMap dependencyMap = new DependencyMap(methods);
+
+    // Keep track of whether we have group dependencies. If we do, preserve-order needs
+    // to be ignored since group dependencies create inter-class dependencies which can
+    // end up creating cycles when combined with preserve-order.
+    boolean hasDependencies = false;
+    for (ITestNGMethod m : methods) {
+      result.addNode(m);
+
+      // Dependent methods
+      {
+        String[] dependentMethods = m.getMethodsDependedUpon();
+        if (dependentMethods != null) {
+          for (String d : dependentMethods) {
+            ITestNGMethod dm = dependencyMap.getMethodDependingOn(d, m);
+            if (m != dm){
+            	result.addEdge(m, dm);
+            }
+          }
+        }
+      }
+
+      // Dependent groups
+      {
+        String[] dependentGroups = m.getGroupsDependedUpon();
+        for (String d : dependentGroups) {
+          hasDependencies = true;
+          List<ITestNGMethod> dg = dependencyMap.getMethodsThatBelongTo(d, m);
+          if (dg == null) {
+            throw new TestNGException("Method \"" + m
+                + "\" depends on nonexistent group \"" + d + "\"");
+          }
+          for (ITestNGMethod ddm : dg) {
+            result.addEdge(m, ddm);
+          }
+        }
+      }
+    }
+
+    // Preserve order
+    // Don't preserve the ordering if we're running in parallel, otherwise the suite will
+    // create multiple threads but these threads will be created one after the other,
+    // giving the impression of parallelism (multiple thread id's) while still running
+    // sequentially.
+    if (! hasDependencies
+        && getCurrentXmlTest().getParallel() == XmlSuite.ParallelMode.FALSE
+        && "true".equalsIgnoreCase(getCurrentXmlTest().getPreserveOrder())) {
+      // If preserve-order was specified and the class order is A, B
+      // create a new set of dependencies where each method of B depends
+      // on all the methods of A
+      ListMultiMap<ITestNGMethod, ITestNGMethod> classDependencies
+          = createClassDependencies(methods, getCurrentXmlTest());
+
+      for (Map.Entry<ITestNGMethod, List<ITestNGMethod>> es : classDependencies.entrySet()) {
+        for (ITestNGMethod dm : es.getValue()) {
+          result.addEdge(dm, es.getKey());
+        }
+      }
+    }
+
+    // Group by instances
+    if (getCurrentXmlTest().getGroupByInstances()) {
+      ListMultiMap<ITestNGMethod, ITestNGMethod> instanceDependencies
+          = createInstanceDependencies(methods, getCurrentXmlTest());
+
+      for (Map.Entry<ITestNGMethod, List<ITestNGMethod>> es : instanceDependencies.entrySet()) {
+        for (ITestNGMethod dm : es.getValue()) {
+          result.addEdge(dm, es.getKey());
+        }
+      }
+
+    }
+
+    return result;
+  }
+
+  private ListMultiMap<ITestNGMethod, ITestNGMethod> createInstanceDependencies(
+      ITestNGMethod[] methods, XmlTest currentXmlTest)
+  {
+    ListMultiMap<Object, ITestNGMethod> instanceMap = Maps.newListMultiMap();
+    for (ITestNGMethod m : methods) {
+      instanceMap.put(m.getInstance(), m);
+    }
+
+    ListMultiMap<ITestNGMethod, ITestNGMethod> result = Maps.newListMultiMap();
+    Object previousInstance = null;
+    for (Map.Entry<Object, List<ITestNGMethod>> es : instanceMap.entrySet()) {
+      if (previousInstance == null) {
+        previousInstance = es.getKey();
+      } else {
+        List<ITestNGMethod> previousMethods = instanceMap.get(previousInstance);
+        Object currentInstance = es.getKey();
+        List<ITestNGMethod> currentMethods = instanceMap.get(currentInstance);
+        // Make all the methods from the current instance depend on the methods of
+        // the previous instance
+        for (ITestNGMethod cm : currentMethods) {
+          for (ITestNGMethod pm : previousMethods) {
+            result.put(cm, pm);
+          }
+        }
+        previousInstance = currentInstance;
+      }
+    }
+
+    return result;
+  }
+
+  private ListMultiMap<ITestNGMethod, ITestNGMethod> createClassDependencies(
+      ITestNGMethod[] methods, XmlTest test)
+  {
+    Map<String, List<ITestNGMethod>> classes = Maps.newHashMap();
+    // Note: use a List here to preserve the ordering but make sure
+    // we don't add the same class twice
+    List<XmlClass> sortedClasses = Lists.newArrayList();
+
+    for (XmlClass c : test.getXmlClasses()) {
+      classes.put(c.getName(), new ArrayList<ITestNGMethod>());
+      if (! sortedClasses.contains(c)) sortedClasses.add(c);
+    }
+
+    // Sort the classes based on their order of appearance in the XML
+    Collections.sort(sortedClasses, new Comparator<XmlClass>() {
+      @Override
+      public int compare(XmlClass arg0, XmlClass arg1) {
+        return arg0.getIndex() - arg1.getIndex();
+      }
+    });
+
+    Map<String, Integer> indexedClasses1 = Maps.newHashMap();
+    Map<Integer, String> indexedClasses2 = Maps.newHashMap();
+    int i = 0;
+    for (XmlClass c : sortedClasses) {
+      indexedClasses1.put(c.getName(), i);
+      indexedClasses2.put(i, c.getName());
+      i++;
+    }
+
+    ListMultiMap<String, ITestNGMethod> methodsFromClass = Maps.newListMultiMap();
+    for (ITestNGMethod m : methods) {
+      methodsFromClass.put(m.getTestClass().getName(), m);
+    }
+
+    ListMultiMap<ITestNGMethod, ITestNGMethod> result = Maps.newListMultiMap();
+    for (ITestNGMethod m : methods) {
+      String name = m.getTestClass().getName();
+      Integer index = indexedClasses1.get(name);
+      // The index could be null if the classes listed in the XML are different
+      // from the methods being run (e.g. the .xml only contains a factory that
+      // instantiates methods from a different class). In this case, we cannot
+      // perform any ordering.
+      if (index != null && index > 0) {
+        // Make this method depend on all the methods of the class in the previous
+        // index
+        String classDependedUpon = indexedClasses2.get(index - 1);
+        List<ITestNGMethod> methodsDependedUpon = methodsFromClass.get(classDependedUpon);
+        if (methodsDependedUpon != null) {
+          for (ITestNGMethod mdu : methodsDependedUpon) {
+            result.put(mdu, m);
+          }
+        }
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Logs the beginning of the {@link #beforeRun()} .
+   */
+  private void logStart() {
+    log(3,
+        "Running test " + m_testName + " on " + m_classMap.size() + " " + " classes, "
+        + " included groups:[" + mapToString(m_xmlMethodSelector.getIncludedGroups())
+        + "] excluded groups:[" + mapToString(m_xmlMethodSelector.getExcludedGroups()) + "]");
+
+    if (getVerbose() >= 3) {
+      for (ITestClass tc : m_classMap.values()) {
+        ((TestClass) tc).dump();
+      }
+    }
+  }
+
+  /**
+   * Trigger the start/finish event.
+   *
+   * @param isStart <tt>true</tt> if the event is for start, <tt>false</tt> if the
+   *                event is for finish
+   */
+  private void fireEvent(boolean isStart) {
+    for (ITestListener itl : m_testListeners) {
+      if (isStart) {
+        itl.onStart(this);
+      }
+      else {
+        itl.onFinish(this);
+      }
+    }
+  }
+
+  /////
+  // ITestContext
+  //
+  @Override
+  public String getName() {
+    return m_testName;
+  }
+
+  /**
+   * @return Returns the startDate.
+   */
+  @Override
+  public Date getStartDate() {
+    return m_startDate;
+  }
+
+  /**
+   * @return Returns the endDate.
+   */
+  @Override
+  public Date getEndDate() {
+    return m_endDate;
+  }
+
+  @Override
+  public IResultMap getPassedTests() {
+    return m_passedTests;
+  }
+
+  @Override
+  public IResultMap getSkippedTests() {
+    return m_skippedTests;
+  }
+
+  @Override
+  public IResultMap getFailedTests() {
+    return m_failedTests;
+  }
+
+  @Override
+  public IResultMap getFailedButWithinSuccessPercentageTests() {
+    return m_failedButWithinSuccessPercentageTests;
+  }
+
+  @Override
+  public String[] getIncludedGroups() {
+    Map<String, String> ig= m_xmlMethodSelector.getIncludedGroups();
+    String[] result= ig.values().toArray((new String[ig.size()]));
+
+    return result;
+  }
+
+  @Override
+  public String[] getExcludedGroups() {
+    Map<String, String> eg= m_xmlMethodSelector.getExcludedGroups();
+    String[] result= eg.values().toArray((new String[eg.size()]));
+
+    return result;
+  }
+
+  @Override
+  public String getOutputDirectory() {
+    return m_outputDirectory;
+  }
+
+  /**
+   * @return Returns the suite.
+   */
+  @Override
+  public ISuite getSuite() {
+    return m_suite;
+  }
+
+  @Override
+  public ITestNGMethod[] getAllTestMethods() {
+    return m_allTestMethods;
+  }
+
+
+  @Override
+  public String getHost() {
+    return m_host;
+  }
+
+  @Override
+  public Collection<ITestNGMethod> getExcludedMethods() {
+    Map<ITestNGMethod, ITestNGMethod> vResult = Maps.newHashMap();
+
+    for (ITestNGMethod m : m_excludedMethods) {
+      vResult.put(m, m);
+    }
+
+    return vResult.keySet();
+  }
+
+  /**
+   * @see org.testng.ITestContext#getFailedConfigurations()
+   */
+  @Override
+  public IResultMap getFailedConfigurations() {
+    return m_failedConfigurations;
+  }
+
+  /**
+   * @see org.testng.ITestContext#getPassedConfigurations()
+   */
+  @Override
+  public IResultMap getPassedConfigurations() {
+    return m_passedConfigurations;
+  }
+
+  /**
+   * @see org.testng.ITestContext#getSkippedConfigurations()
+   */
+  @Override
+  public IResultMap getSkippedConfigurations() {
+    return m_skippedConfigurations;
+  }
+
+  //
+  // ITestContext
+  /////
+
+  /////
+  // ITestResultNotifier
+  //
+
+  @Override
+  public void addPassedTest(ITestNGMethod tm, ITestResult tr) {
+    m_passedTests.addResult(tr, tm);
+  }
+
+  @Override
+  public Set<ITestResult> getPassedTests(ITestNGMethod tm) {
+    return m_passedTests.getResults(tm);
+  }
+
+  @Override
+  public Set<ITestResult> getFailedTests(ITestNGMethod tm) {
+    return m_failedTests.getResults(tm);
+  }
+
+  @Override
+  public Set<ITestResult> getSkippedTests(ITestNGMethod tm) {
+    return m_skippedTests.getResults(tm);
+  }
+
+  @Override
+  public void addSkippedTest(ITestNGMethod tm, ITestResult tr) {
+    m_skippedTests.addResult(tr, tm);
+  }
+
+  @Override
+  public void addInvokedMethod(InvokedMethod im) {
+    synchronized(m_invokedMethods) {
+      m_invokedMethods.add(im);
+    }
+  }
+
+  @Override
+  public void addFailedTest(ITestNGMethod testMethod, ITestResult result) {
+    logFailedTest(testMethod, result, false /* withinSuccessPercentage */);
+  }
+
+  @Override
+  public void addFailedButWithinSuccessPercentageTest(ITestNGMethod testMethod,
+                                                      ITestResult result) {
+    logFailedTest(testMethod, result, true /* withinSuccessPercentage */);
+  }
+
+  @Override
+  public XmlTest getTest() {
+    return m_xmlTest;
+  }
+
+  @Override
+  public List<ITestListener> getTestListeners() {
+    return m_testListeners;
+  }
+
+  @Override
+  public List<IConfigurationListener> getConfigurationListeners() {
+    return Lists.<IConfigurationListener>newArrayList(m_configurationListeners);
+  }
+  //
+  // ITestResultNotifier
+  /////
+
+  private void logFailedTest(ITestNGMethod method,
+                             ITestResult tr,
+                             boolean withinSuccessPercentage) {
+    /*
+     * We should not remove a passed method from m_passedTests so that we can
+     * account for the passed instances of this test method.
+     */
+    //m_passedTests.removeResult(method);
+    if (withinSuccessPercentage) {
+      m_failedButWithinSuccessPercentageTests.addResult(tr, method);
+    }
+    else {
+      m_failedTests.addResult(tr, method);
+    }
+  }
+
+  private String mapToString(Map<?, ?> m) {
+    StringBuffer result= new StringBuffer();
+    for (Object o : m.values()) {
+      result.append(o.toString()).append(" ");
+    }
+
+    return result.toString();
+  }
+
+  private void log(int level, String s) {
+    Utils.log("TestRunner", level, s);
+  }
+
+  public static int getVerbose() {
+    return m_verbose;
+  }
+
+  public void setVerbose(int n) {
+    m_verbose = n;
+  }
+
+  private void log(String s) {
+    Utils.log("TestRunner", 2, s);
+  }
+
+  /////
+  // Listeners
+  //
+  public void addListener(Object listener) {
+    if(listener instanceof ITestListener) {
+      addTestListener((ITestListener) listener);
+    }
+    if(listener instanceof IConfigurationListener) {
+      addConfigurationListener((IConfigurationListener) listener);
+    }
+    if(listener instanceof IClassListener) {
+      addClassListener((IClassListener) listener);
+    }
+  }
+
+  public void addTestListener(ITestListener il) {
+    m_testListeners.add(il);
+  }
+
+  public void addClassListener(IClassListener cl) {
+    m_classListeners.add(cl);
+  }
+
+  void addConfigurationListener(IConfigurationListener icl) {
+    m_configurationListeners.add(icl);
+  }
+  //
+  // Listeners
+  /////
+
+  private final List<InvokedMethod> m_invokedMethods = Lists.newArrayList();
+
+  private void dumpInvokedMethods() {
+    System.out.println("===== Invoked methods");
+    for (IInvokedMethod im : m_invokedMethods) {
+      if (im.isTestMethod()) {
+        System.out.print("    ");
+      }
+      else if (im.isConfigurationMethod()) {
+        System.out.print("  ");
+      }
+      else {
+        continue;
+      }
+      System.out.println("" + im);
+    }
+    System.out.println("=====");
+  }
+
+  public List<ITestNGMethod> getInvokedMethods() {
+    List<ITestNGMethod> result= Lists.newArrayList();
+    synchronized(m_invokedMethods) {
+      for (IInvokedMethod im : m_invokedMethods) {
+        ITestNGMethod tm= im.getTestMethod();
+        tm.setDate(im.getDate());
+        result.add(tm);
+      }
+    }
+
+    return result;
+  }
+
+  private IResultMap m_passedConfigurations= new ResultMap();
+  private IResultMap m_skippedConfigurations= new ResultMap();
+  private IResultMap m_failedConfigurations= new ResultMap();
+
+  private class ConfigurationListener implements IConfigurationListener2 {
+    @Override
+    public void beforeConfiguration(ITestResult tr) {
+    }
+
+    @Override
+    public void onConfigurationFailure(ITestResult itr) {
+      m_failedConfigurations.addResult(itr, itr.getMethod());
+    }
+
+    @Override
+    public void onConfigurationSkip(ITestResult itr) {
+      m_skippedConfigurations.addResult(itr, itr.getMethod());
+    }
+
+    @Override
+    public void onConfigurationSuccess(ITestResult itr) {
+      m_passedConfigurations.addResult(itr, itr.getMethod());
+    }
+  }
+
+  @Deprecated
+  public void setMethodInterceptor(IMethodInterceptor methodInterceptor){
+    m_methodInterceptors.add(methodInterceptor);
+  }
+
+  public void addMethodInterceptor(IMethodInterceptor methodInterceptor){
+    m_methodInterceptors.add(methodInterceptor);
+  }
+
+  @Override
+  public XmlTest getCurrentXmlTest() {
+    return m_xmlTest;
+  }
+
+  private IAttributes m_attributes = new Attributes();
+
+  @Override
+  public Object getAttribute(String name) {
+    return m_attributes.getAttribute(name);
+  }
+
+  @Override
+  public void setAttribute(String name, Object value) {
+    m_attributes.setAttribute(name, value);
+  }
+
+  @Override
+  public Set<String> getAttributeNames() {
+    return m_attributes.getAttributeNames();
+  }
+
+  @Override
+  public Object removeAttribute(String name) {
+    return m_attributes.removeAttribute(name);
+  }
+
+  private ListMultiMap<Class<? extends Module>, Module> m_guiceModules = Maps.newListMultiMap();
+
+  @Override
+  public List<Module> getGuiceModules(Class<? extends Module> cls) {
+    List<Module> result = m_guiceModules.get(cls);
+    return result;
+  }
+
+  private void addGuiceModule(Class<? extends Module> cls, Module module) {
+    m_guiceModules.put(cls, module);
+  }
+
+  private Map<List<Module>, Injector> m_injectors = Maps.newHashMap();
+
+  @Override
+  public Injector getInjector(List<Module> moduleInstances) {
+    return m_injectors .get(moduleInstances);
+  }
+
+  @Override
+  public Injector getInjector(IClass iClass) {
+    Annotation annotation = AnnotationHelper.findAnnotationSuperClasses(Guice.class, iClass.getRealClass());
+    if (annotation == null) return null;
+    if (iClass instanceof TestClass) {
+      iClass = ((TestClass)iClass).getIClass();
+    }
+    if (!(iClass instanceof ClassImpl)) return null;
+    Injector parentInjector = ((ClassImpl)iClass).getParentInjector();
+
+    Guice guice = (Guice) annotation;
+    List<Module> moduleInstances = Lists.newArrayList(getModules(guice, parentInjector, iClass.getRealClass()));
+
+    // Reuse the previous injector, if any
+    Injector injector = getInjector(moduleInstances);
+    if (injector == null) {
+      injector = parentInjector.createChildInjector(moduleInstances);
+      addInjector(moduleInstances, injector);
+    }
+    return injector;
+  }
+
+  private Module[] getModules(Guice guice, Injector parentInjector, Class<?> testClass) {
+    List<Module> result = Lists.newArrayList();
+    for (Class<? extends Module> moduleClass : guice.modules()) {
+      List<Module> modules = getGuiceModules(moduleClass);
+      if (modules != null && modules.size() > 0) {
+        result.addAll(modules);
+      } else {
+        Module instance = parentInjector.getInstance(moduleClass);
+        result.add(instance);
+        addGuiceModule(moduleClass, instance);
+      }
+    }
+    Class<? extends IModuleFactory> factory = guice.moduleFactory();
+    if (factory != IModuleFactory.class) {
+      IModuleFactory factoryInstance = parentInjector.getInstance(factory);
+      Module moduleClass = factoryInstance.createModule(this, testClass);
+      if (moduleClass != null) {
+        result.add(moduleClass);
+      }
+    }
+
+    return result.toArray(new Module[result.size()]);
+  }
+
+  @Override
+  public void addInjector(List<Module> moduleInstances, Injector injector) {
+    m_injectors.put(moduleInstances, injector);
+  }
+
+} // TestRunner
diff --git a/src/main/java/org/testng/TimeBombSkipException.java b/src/main/java/org/testng/TimeBombSkipException.java
new file mode 100755
index 0000000..1777233
--- /dev/null
+++ b/src/main/java/org/testng/TimeBombSkipException.java
@@ -0,0 +1,235 @@
+package org.testng;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * A {@link SkipException} extension that transforms a skipped method
+ * into a failed method based on a time trigger.
+ * <p/>
+ * By default the time format is yyyy/MM/dd (according to {@code SimpleDateFormat}).
+ * You can customize this by using the specialized constructors. Suppported date
+ * formats are according to the {@code SimpleDateFormat}.
+ *
+ * @author <a href='mailto:the[dot]mindstorm[at]gmail[dot]com'>Alex Popescu</a>
+ * @since 5.6
+ */
+public class TimeBombSkipException extends SkipException {
+  private static final long serialVersionUID = -8599821478834048537L;
+  private static final SimpleDateFormat SDF= new SimpleDateFormat("yyyy/MM/dd");
+  private Calendar m_expireDate;
+  private DateFormat m_inFormat= SDF;
+  private DateFormat m_outFormat= SDF;
+
+  /**
+   * Creates a {@code TimeBombedSkipException} using the <tt>expirationDate</tt>.
+   * The format used for date comparison is <tt>yyyy/MM/dd</tt>
+   * @param msg exception message
+   * @param expirationDate time limit after which the SKIP becomes a FAILURE
+   */
+  public TimeBombSkipException(String msg, Date expirationDate) {
+    super(msg);
+    initExpireDate(expirationDate);
+  }
+
+  /**
+   * Creates a {@code TimeBombedSkipException} using the <tt>expirationDate</tt>.
+   * The <tt>format</tt> parameter wiil be used for performing the time comparison.
+   * @param msg exception message
+   * @param expirationDate time limit after which the SKIP becomes a FAILURE
+   * @param format format for the time comparison
+   */
+  public TimeBombSkipException(String msg, Date expirationDate, String format) {
+    super(msg);
+    m_inFormat= new SimpleDateFormat(format);
+    m_outFormat= new SimpleDateFormat(format);
+    initExpireDate(expirationDate);
+  }
+
+  /**
+   * Creates a {@code TimeBombedSkipException} using the <tt>date</tt>
+   * in the format <tt>yyyy/MM/dd</tt>.
+   * @param msg exception message
+   * @param date time limit after which the SKIP becomes a FAILURE
+   */
+  public TimeBombSkipException(String msg, String date) {
+    super(msg);
+    initExpireDate(date);
+  }
+
+  /**
+   * Creates a {@code TimeBombedSkipException} using the <tt>date</tt>
+   * in the specified format <tt>format</tt>. The same format is used
+   * when performing the time comparison.
+   * @param msg exception message
+   * @param date time limit after which the SKIP becomes a FAILURE
+   * @param format format of the passed in <tt>date</tt> and of the time comparison
+   */
+  public TimeBombSkipException(String msg, String date, String format) {
+    this(msg, date, format, format);
+  }
+
+  /**
+   * Creates a {@code TimeBombedSkipException} using the <tt>date</tt>
+   * in the specified format <tt>inFormat</tt>. The <tt>outFormat</tt> will be
+   * used to perform the time comparison and display.
+   * @param msg exception message
+   * @param date time limit after which the SKIP becomes a FAILURE
+   * @param inFormat format of the passed in <tt>date</tt>
+   * @param outFormat format of the time comparison
+   */
+  public TimeBombSkipException(String msg, String date, String inFormat, String outFormat) {
+    super(msg);
+    m_inFormat= new SimpleDateFormat(inFormat);
+    m_outFormat= new SimpleDateFormat(outFormat);
+    initExpireDate(date);
+  }
+
+  /**
+   * Creates a {@code TimeBombedSkipException} using the <tt>expirationDate</tt>.
+   * The format used for date comparison is <tt>yyyy/MM/dd</tt>
+   * @param msg exception message
+   * @param expirationDate time limit after which the SKIP becomes a FAILURE
+   * @param cause the cause (which is saved for later retrieval by the
+   *         {@link #getCause()} method).  (A <tt>null</tt> value is
+   *         permitted, and indicates that the cause is nonexistent or
+   *         unknown.)
+   */
+  public TimeBombSkipException(String msg, Date expirationDate, Throwable cause) {
+    super(msg, cause);
+    initExpireDate(expirationDate);
+  }
+
+  /**
+   * Creates a {@code TimeBombedSkipException} using the <tt>expirationDate</tt>.
+   * The <tt>format</tt> parameter wiil be used for performing the time comparison.
+   * @param msg exception message
+   * @param expirationDate time limit after which the SKIP becomes a FAILURE
+   * @param format format for the time comparison
+   * @param cause the cause (which is saved for later retrieval by the
+   *         {@link #getCause()} method).  (A <tt>null</tt> value is
+   *         permitted, and indicates that the cause is nonexistent or
+   *         unknown.)
+   */
+  public TimeBombSkipException(String msg, Date expirationDate, String format, Throwable cause) {
+    super(msg, cause);
+    m_inFormat= new SimpleDateFormat(format);
+    m_outFormat= new SimpleDateFormat(format);
+    initExpireDate(expirationDate);
+  }
+
+  /**
+   * Creates a {@code TimeBombedSkipException} using the <tt>date</tt>
+   * in the format <tt>yyyy/MM/dd</tt>.
+   * @param msg exception message
+   * @param date time limit after which the SKIP becomes a FAILURE
+   * @param cause the cause (which is saved for later retrieval by the
+   *         {@link #getCause()} method).  (A <tt>null</tt> value is
+   *         permitted, and indicates that the cause is nonexistent or
+   *         unknown.)
+   */
+  public TimeBombSkipException(String msg, String date, Throwable cause) {
+    super(msg, cause);
+    initExpireDate(date);
+  }
+
+  /**
+   * Creates a {@code TimeBombedSkipException} using the <tt>date</tt>
+   * in the specified format <tt>format</tt>. The same format is used
+   * when performing the time comparison.
+   * @param msg exception message
+   * @param date time limit after which the SKIP becomes a FAILURE
+   * @param format format of the passed in <tt>date</tt> and of the time comparison
+   * @param cause the cause (which is saved for later retrieval by the
+   *         {@link #getCause()} method).  (A <tt>null</tt> value is
+   *         permitted, and indicates that the cause is nonexistent or
+   *         unknown.)
+   */
+  public TimeBombSkipException(String msg, String date, String format, Throwable cause) {
+    this(msg, date, format, format, cause);
+  }
+
+  /**
+   * Creates a {@code TimeBombedSkipException} using the <tt>date</tt>
+   * in the specified format <tt>inFormat</tt>. The <tt>outFormat</tt> will be
+   * used to perform the time comparison and display.
+   * @param msg exception message
+   * @param date time limit after which the SKIP becomes a FAILURE
+   * @param inFormat format of the passed in <tt>date</tt>
+   * @param outFormat format of the time comparison
+   * @param cause the cause (which is saved for later retrieval by the
+   *         {@link #getCause()} method).  (A <tt>null</tt> value is
+   *         permitted, and indicates that the cause is nonexistent or
+   *         unknown.)
+   */
+  public TimeBombSkipException(String msg, String date, String inFormat, String outFormat, Throwable cause) {
+    super(msg, cause);
+    m_inFormat= new SimpleDateFormat(inFormat);
+    m_outFormat= new SimpleDateFormat(outFormat);
+    initExpireDate(date);
+  }
+
+  private void initExpireDate(Date expireDate) {
+    m_expireDate= Calendar.getInstance();
+    m_expireDate.setTime(expireDate);
+  }
+
+  private void initExpireDate(String date) {
+    try {
+      // SimpleDateFormat is not thread-safe, and m_inFormat 
+      // is, by default, connected to the static SDF variable
+      synchronized( m_inFormat ){
+        Date d = m_inFormat.parse(date);
+        initExpireDate(d);
+      }
+    }
+    catch(ParseException pex) {
+      throw new TestNGException("Cannot parse date:" + date + " using pattern: " + m_inFormat, pex);
+    }
+  }
+
+  @Override
+  public boolean isSkip() {
+    if (null == m_expireDate) {
+      return false;
+    }
+
+    try {
+      Calendar now= Calendar.getInstance();
+      Date nowDate= m_inFormat.parse(m_inFormat.format(now.getTime()));
+      now.setTime(nowDate);
+
+      return !now.after(m_expireDate);
+    }
+    catch(ParseException pex) {
+      throw new TestNGException("Cannot compare dates.");
+    }
+  }
+
+  @Override
+  public String getMessage() {
+    if(isSkip()) {
+      return super.getMessage();
+    }
+    else {
+      return super.getMessage() + "; Test must have been enabled by: " + m_outFormat.format(m_expireDate.getTime());
+    }
+  }
+
+  @Override
+  public void printStackTrace(PrintStream s) {
+    reduceStackTrace();
+    super.printStackTrace(s);
+  }
+
+  @Override
+  public void printStackTrace(PrintWriter s) {
+    reduceStackTrace();
+    super.printStackTrace(s);
+  }
+}
diff --git a/src/main/java/org/testng/annotations/AfterClass.java b/src/main/java/org/testng/annotations/AfterClass.java
new file mode 100755
index 0000000..e75ae75
--- /dev/null
+++ b/src/main/java/org/testng/annotations/AfterClass.java
@@ -0,0 +1,72 @@
+package org.testng.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target(java.lang.annotation.ElementType.METHOD)
+public @interface AfterClass {
+  /**
+   * Whether methods on this class/method are enabled.
+   */
+  public boolean enabled() default true;
+
+  /**
+   * The list of groups this class/method belongs to.
+   */
+  public String[] groups() default {};
+
+  /**
+   * The list of groups this method depends on.  Every method
+   * member of one of these groups is guaranteed to have been
+   * invoked before this method.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   */
+  public String[] dependsOnGroups() default {};
+
+  /**
+   * The list of methods this method depends on.  There is no guarantee
+   * on the order on which the methods depended upon will be run, but you
+   * are guaranteed that all these methods will be run before the test method
+   * that contains this annotation is run.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   *
+   *  If some of these methods have been overloaded, all the overloaded
+   *  versions will be run.
+   */
+  public String[] dependsOnMethods() default {};
+
+  /**
+   *  For before methods (beforeSuite, beforeTest, beforeTestClass and
+   *  beforeTestMethod, but not beforeGroups):
+   *  If set to true, this configuration method will be run
+   *  regardless of what groups it belongs to.
+   *  <br>
+   * For after methods (afterSuite, afterClass, ...):
+   *  If set to true, this configuration method will be run
+   *  even if one or more methods invoked previously failed or
+   *  was skipped.
+   */
+  public boolean alwaysRun() default false;
+
+  /**
+   * If true, this &#64;Configuration method will belong to groups specified in the
+   * &#64;Test annotation on the class (if any).
+   */
+  public boolean inheritGroups() default true;
+
+  /**
+   * The description for this method.  The string used will appear in the
+   * HTML report and also on standard output if verbose >= 2.
+   */
+  public String description() default "";
+
+  /**
+   * The maximum number of milliseconds this method should take.
+   * If it hasn't returned after this time, this method will fail and
+   * it will cause test methods depending on it to be skipped.
+   */
+  public long timeOut() default 0;
+}
diff --git a/src/main/java/org/testng/annotations/AfterGroups.java b/src/main/java/org/testng/annotations/AfterGroups.java
new file mode 100755
index 0000000..c40164c
--- /dev/null
+++ b/src/main/java/org/testng/annotations/AfterGroups.java
@@ -0,0 +1,82 @@
+package org.testng.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target(java.lang.annotation.ElementType.METHOD)
+public @interface AfterGroups {
+  /**
+   * The list of groups that this configuration method will run after. If
+   * specified it overrides the list of groups provided through
+   * {@link #groups()} attribute. This method is guaranteed to run shortly
+   * after the last test method that belongs to any of these groups is
+   * invoked.
+   */
+  public String[] value() default {};
+
+  /**
+   * Whether methods on this class/method are enabled.
+   */
+  public boolean enabled() default true;
+
+  /**
+   * The list of groups this class/method belongs to. The list also describes the groups
+   * that this configuration method will be run after (if no {@link #value()} attribute is defined).
+   */
+  public String[] groups() default {};
+
+  /**
+   * The list of groups this method depends on.  Every method
+   * member of one of these groups is guaranteed to have been
+   * invoked before this method.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   */
+  public String[] dependsOnGroups() default {};
+
+  /**
+   * The list of methods this method depends on.  There is no guarantee
+   * on the order on which the methods depended upon will be run, but you
+   * are guaranteed that all these methods will be run before the test method
+   * that contains this annotation is run.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   *
+   *  If some of these methods have been overloaded, all the overloaded
+   *  versions will be run.
+   */
+  public String[] dependsOnMethods() default {};
+
+  /**
+   *  For before methods (beforeSuite, beforeTest, beforeTestClass and
+   *  beforeTestMethod, but not beforeGroups):
+   *  If set to true, this configuration method will be run
+   *  regardless of what groups it belongs to.
+   *  <br>
+   * For after methods (afterSuite, afterClass, ...):
+   *  If set to true, this configuration method will be run
+   *  even if one or more methods invoked previously failed or
+   *  was skipped.
+   */
+  public boolean alwaysRun() default false;
+
+  /**
+   * If true, this &#64;Configuration method will belong to groups specified in the
+   * &#64;Test annotation on the class (if any).
+   */
+  public boolean inheritGroups() default true;
+
+  /**
+   * The description for this method.  The string used will appear in the
+   * HTML report and also on standard output if verbose >= 2.
+   */
+  public String description() default "";
+
+  /**
+   * The maximum number of milliseconds this method should take.
+   * If it hasn't returned after this time, this method will fail and
+   * it will cause test methods depending on it to be skipped.
+   */
+  public long timeOut() default 0;
+}
diff --git a/src/main/java/org/testng/annotations/AfterMethod.java b/src/main/java/org/testng/annotations/AfterMethod.java
new file mode 100755
index 0000000..13c6a18
--- /dev/null
+++ b/src/main/java/org/testng/annotations/AfterMethod.java
@@ -0,0 +1,78 @@
+package org.testng.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target(java.lang.annotation.ElementType.METHOD)
+public @interface AfterMethod {
+  /**
+   * Whether methods on this class/method are enabled.
+   */
+  public boolean enabled() default true;
+
+  /**
+   * The list of groups this class/method belongs to.
+   */
+  public String[] groups() default {};
+
+  /**
+   * The list of groups this method depends on.  Every method
+   * member of one of these groups is guaranteed to have been
+   * invoked before this method.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   */
+  public String[] dependsOnGroups() default {};
+
+  /**
+   * The list of methods this method depends on.  There is no guarantee
+   * on the order on which the methods depended upon will be run, but you
+   * are guaranteed that all these methods will be run before the test method
+   * that contains this annotation is run.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   *
+   *  If some of these methods have been overloaded, all the overloaded
+   *  versions will be run.
+   */
+  public String[] dependsOnMethods() default {};
+
+  /**
+   *  For before methods (beforeSuite, beforeTest, beforeTestClass and
+   *  beforeTestMethod, but not beforeGroups):
+   *  If set to true, this configuration method will be run
+   *  regardless of what groups it belongs to.
+   *  <br>
+   * For after methods (afterSuite, afterClass, ...):
+   *  If set to true, this configuration method will be run
+   *  even if one or more test methods invoked previously failed or
+   *  was skipped.
+   */
+  public boolean alwaysRun() default false;
+
+  /**
+   * If true, this &#64;Configuration method will belong to groups specified in the
+   * &#64;Test annotation on the class (if any).
+   */
+  public boolean inheritGroups() default true;
+
+  /**
+   * The description for this method.  The string used will appear in the
+   * HTML report and also on standard output if verbose >= 2.
+   */
+  public String description() default "";
+
+  /**
+   * If true and the @Test method that was just run has an invocationCount > 1, this
+   * AfterMethod will only be invoked once (after the last test invocation).
+   */
+  public boolean lastTimeOnly() default false;
+
+  /**
+   * The maximum number of milliseconds this method should take.
+   * If it hasn't returned after this time, this method will fail and
+   * it will cause test methods depending on it to be skipped.
+   */
+  public long timeOut() default 0;
+}
diff --git a/src/main/java/org/testng/annotations/AfterSuite.java b/src/main/java/org/testng/annotations/AfterSuite.java
new file mode 100755
index 0000000..9c86289
--- /dev/null
+++ b/src/main/java/org/testng/annotations/AfterSuite.java
@@ -0,0 +1,72 @@
+package org.testng.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target(java.lang.annotation.ElementType.METHOD)
+public @interface AfterSuite {
+  /**
+   * Whether methods on this class/method are enabled.
+   */
+  public boolean enabled() default true;
+
+  /**
+   * The list of groups this class/method belongs to.
+   */
+  public String[] groups() default {};
+
+  /**
+   * The list of groups this method depends on.  Every method
+   * member of one of these groups is guaranteed to have been
+   * invoked before this method.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   */
+  public String[] dependsOnGroups() default {};
+
+  /**
+   * The list of methods this method depends on.  There is no guarantee
+   * on the order on which the methods depended upon will be run, but you
+   * are guaranteed that all these methods will be run before the test method
+   * that contains this annotation is run.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   *
+   *  If some of these methods have been overloaded, all the overloaded
+   *  versions will be run.
+   */
+  public String[] dependsOnMethods() default {};
+
+  /**
+   *  For before methods (beforeSuite, beforeTest, beforeTestClass and
+   *  beforeTestMethod, but not beforeGroups):
+   *  If set to true, this configuration method will be run
+   *  regardless of what groups it belongs to.
+   *  <br>
+   * For after methods (afterSuite, afterClass, ...):
+   *  If set to true, this configuration method will be run
+   *  even if one or more methods invoked previously failed or
+   *  was skipped.
+   */
+  public boolean alwaysRun() default false;
+
+  /**
+   * If true, this &#64;Configuration method will belong to groups specified in the
+   * &#64;Test annotation on the class (if any).
+   */
+  public boolean inheritGroups() default true;
+
+  /**
+   * The description for this method.  The string used will appear in the
+   * HTML report and also on standard output if verbose >= 2.
+   */
+  public String description() default "";
+
+  /**
+   * The maximum number of milliseconds this method should take.
+   * If it hasn't returned after this time, this method will fail and
+   * it will cause test methods depending on it to be skipped.
+   */
+  public long timeOut() default 0;
+}
diff --git a/src/main/java/org/testng/annotations/AfterTest.java b/src/main/java/org/testng/annotations/AfterTest.java
new file mode 100755
index 0000000..d05ed98
--- /dev/null
+++ b/src/main/java/org/testng/annotations/AfterTest.java
@@ -0,0 +1,72 @@
+package org.testng.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target(java.lang.annotation.ElementType.METHOD)
+public @interface AfterTest {
+  /**
+   * Whether methods on this class/method are enabled.
+   */
+  public boolean enabled() default true;
+
+  /**
+   * The list of groups this class/method belongs to.
+   */
+  public String[] groups() default {};
+
+  /**
+   * The list of groups this method depends on.  Every method
+   * member of one of these groups is guaranteed to have been
+   * invoked before this method.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   */
+  public String[] dependsOnGroups() default {};
+
+  /**
+   * The list of methods this method depends on.  There is no guarantee
+   * on the order on which the methods depended upon will be run, but you
+   * are guaranteed that all these methods will be run before the test method
+   * that contains this annotation is run.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   *
+   *  If some of these methods have been overloaded, all the overloaded
+   *  versions will be run.
+   */
+  public String[] dependsOnMethods() default {};
+
+  /**
+   *  For before methods (beforeSuite, beforeTest, beforeTestClass and
+   *  beforeTestMethod, but not beforeGroups):
+   *  If set to true, this configuration method will be run
+   *  regardless of what groups it belongs to.
+   *  <br>
+   * For after methods (afterSuite, afterClass, ...):
+   *  If set to true, this configuration method will be run
+   *  even if one or more methods invoked previously failed or
+   *  was skipped.
+   */
+  public boolean alwaysRun() default false;
+
+  /**
+   * If true, this &#64;Configuration method will belong to groups specified in the
+   * &#64;Test annotation on the class (if any).
+   */
+  public boolean inheritGroups() default true;
+
+  /**
+   * The description for this method.  The string used will appear in the
+   * HTML report and also on standard output if verbose >= 2.
+   */
+  public String description() default "";
+
+  /**
+   * The maximum number of milliseconds this method should take.
+   * If it hasn't returned after this time, this method will fail and
+   * it will cause test methods depending on it to be skipped.
+   */
+  public long timeOut() default 0;
+}
diff --git a/src/main/java/org/testng/annotations/BeforeClass.java b/src/main/java/org/testng/annotations/BeforeClass.java
new file mode 100755
index 0000000..d1b11c0
--- /dev/null
+++ b/src/main/java/org/testng/annotations/BeforeClass.java
@@ -0,0 +1,72 @@
+package org.testng.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target(java.lang.annotation.ElementType.METHOD)
+public @interface BeforeClass {
+  /**
+   * Whether methods on this class/method are enabled.
+   */
+  public boolean enabled() default true;
+
+  /**
+   * The list of groups this class/method belongs to.
+   */
+  public String[] groups() default {};
+
+  /**
+   * The list of groups this method depends on.  Every method
+   * member of one of these groups is guaranteed to have been
+   * invoked before this method.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   */
+  public String[] dependsOnGroups() default {};
+
+  /**
+   * The list of methods this method depends on.  There is no guarantee
+   * on the order on which the methods depended upon will be run, but you
+   * are guaranteed that all these methods will be run before the test method
+   * that contains this annotation is run.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   *
+   *  If some of these methods have been overloaded, all the overloaded
+   *  versions will be run.
+   */
+  public String[] dependsOnMethods() default {};
+
+  /**
+   *  For before methods (beforeSuite, beforeTest, beforeTestClass and
+   *  beforeTestMethod, but not beforeGroups):
+   *  If set to true, this configuration method will be run
+   *  regardless of what groups it belongs to.
+   *  <br>
+   * For after methods (afterSuite, afterClass, ...):
+   *  If set to true, this configuration method will be run
+   *  even if one or more methods invoked previously failed or
+   *  was skipped.
+   */
+  public boolean alwaysRun() default false;
+
+  /**
+   * If true, this &#64;Configuration method will belong to groups specified in the
+   * &#64;Test annotation on the class (if any).
+   */
+  public boolean inheritGroups() default true;
+
+  /**
+   * The description for this method.  The string used will appear in the
+   * HTML report and also on standard output if verbose >= 2.
+   */
+  public String description() default "";
+
+  /**
+   * The maximum number of milliseconds this method should take.
+   * If it hasn't returned after this time, this method will fail and
+   * it will cause test methods depending on it to be skipped.
+   */
+  public long timeOut() default 0;
+}
diff --git a/src/main/java/org/testng/annotations/BeforeGroups.java b/src/main/java/org/testng/annotations/BeforeGroups.java
new file mode 100755
index 0000000..20896da
--- /dev/null
+++ b/src/main/java/org/testng/annotations/BeforeGroups.java
@@ -0,0 +1,81 @@
+package org.testng.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target(java.lang.annotation.ElementType.METHOD)
+public @interface BeforeGroups {
+  /**
+   * The list of groups that this configuration method will run before. If specified it overrides the
+   * list of groups provided through {@link #groups()} attribute.
+   * This method is guaranteed to run shortly before the first test method that
+   * belongs to any of these groups is invoked.
+   */
+  public String[] value() default {};
+
+  /**
+   * Whether methods on this class/method are enabled.
+   */
+  public boolean enabled() default true;
+
+  /**
+   * The list of groups this class/method belongs to. This list also describes the groups
+   * that this configuration method will run before (if no {@link #value()} attribute is defined).
+   */
+  public String[] groups() default {};
+
+  /**
+   * The list of groups this method depends on.  Every method
+   * member of one of these groups is guaranteed to have been
+   * invoked before this method.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   */
+  public String[] dependsOnGroups() default {};
+
+  /**
+   * The list of methods this method depends on.  There is no guarantee
+   * on the order on which the methods depended upon will be run, but you
+   * are guaranteed that all these methods will be run before the test method
+   * that contains this annotation is run.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   *
+   *  If some of these methods have been overloaded, all the overloaded
+   *  versions will be run.
+   */
+  public String[] dependsOnMethods() default {};
+
+  /**
+   *  For before methods (beforeSuite, beforeTest, beforeTestClass and
+   *  beforeTestMethod, but not beforeGroups):
+   *  If set to true, this configuration method will be run
+   *  regardless of what groups it belongs to.
+   *  <br>
+   * For after methods (afterSuite, afterClass, ...):
+   *  If set to true, this configuration method will be run
+   *  even if one or more methods invoked previously failed or
+   *  was skipped.
+   */
+  public boolean alwaysRun() default false;
+
+  /**
+   * If true, this &#64;Configuration method will belong to groups specified in the
+   * &#64;Test annotation on the class (if any).
+   */
+  public boolean inheritGroups() default true;
+
+  /**
+   * The description for this method.  The string used will appear in the
+   * HTML report and also on standard output if verbose >= 2.
+   */
+  public String description() default "";
+
+  /**
+   * The maximum number of milliseconds this method should take.
+   * If it hasn't returned after this time, this method will fail and
+   * it will cause test methods depending on it to be skipped.
+   */
+  public long timeOut() default 0;
+}
diff --git a/src/main/java/org/testng/annotations/BeforeMethod.java b/src/main/java/org/testng/annotations/BeforeMethod.java
new file mode 100755
index 0000000..a7356f5
--- /dev/null
+++ b/src/main/java/org/testng/annotations/BeforeMethod.java
@@ -0,0 +1,78 @@
+package org.testng.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target(java.lang.annotation.ElementType.METHOD)
+public @interface BeforeMethod {
+  /**
+   * Whether methods on this class/method are enabled.
+   */
+  public boolean enabled() default true;
+
+  /**
+   * The list of groups this class/method belongs to.
+   */
+  public String[] groups() default {};
+
+  /**
+   * The list of groups this method depends on.  Every method
+   * member of one of these groups is guaranteed to have been
+   * invoked before this method.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   */
+  public String[] dependsOnGroups() default {};
+
+  /**
+   * The list of methods this method depends on.  There is no guarantee
+   * on the order on which the methods depended upon will be run, but you
+   * are guaranteed that all these methods will be run before the test method
+   * that contains this annotation is run.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   *
+   *  If some of these methods have been overloaded, all the overloaded
+   *  versions will be run.
+   */
+  public String[] dependsOnMethods() default {};
+
+  /**
+   *  For before methods (beforeSuite, beforeTest, beforeTestClass and
+   *  beforeTestMethod, but not beforeGroups):
+   *  If set to true, this configuration method will be run
+   *  regardless of what groups it belongs to.
+   *  <br>
+   * For after methods (afterSuite, afterClass, ...):
+   *  If set to true, this configuration method will be run
+   *  even if one or more methods invoked previously failed or
+   *  was skipped.
+   */
+  public boolean alwaysRun() default false;
+
+  /**
+   * If true, this &#64;Configuration method will belong to groups specified in the
+   * &#64;Test annotation on the class (if any).
+   */
+  public boolean inheritGroups() default true;
+
+  /**
+   * The description for this method.  The string used will appear in the
+   * HTML report and also on standard output if verbose &gt;= 2.
+   */
+  public String description() default "";
+
+  /**
+   * If true and the @Test method about to be run has an invocationCount &gt; 1, this
+   * BeforeMethod will only be invoked once (before the first test invocation).
+   */
+  public boolean firstTimeOnly() default false;
+
+  /**
+   * The maximum number of milliseconds this method should take.
+   * If it hasn't returned after this time, this method will fail and
+   * it will cause test methods depending on it to be skipped.
+   */
+  public long timeOut() default 0;
+}
diff --git a/src/main/java/org/testng/annotations/BeforeSuite.java b/src/main/java/org/testng/annotations/BeforeSuite.java
new file mode 100755
index 0000000..e811096
--- /dev/null
+++ b/src/main/java/org/testng/annotations/BeforeSuite.java
@@ -0,0 +1,72 @@
+package org.testng.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target(java.lang.annotation.ElementType.METHOD)
+public @interface BeforeSuite {
+  /**
+   * Whether methods on this class/method are enabled.
+   */
+  public boolean enabled() default true;
+
+  /**
+   * The list of groups this class/method belongs to.
+   */
+  public String[] groups() default {};
+
+  /**
+   * The list of groups this method depends on.  Every method
+   * member of one of these groups is guaranteed to have been
+   * invoked before this method.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   */
+  public String[] dependsOnGroups() default {};
+
+  /**
+   * The list of methods this method depends on.  There is no guarantee
+   * on the order on which the methods depended upon will be run, but you
+   * are guaranteed that all these methods will be run before the test method
+   * that contains this annotation is run.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   *
+   *  If some of these methods have been overloaded, all the overloaded
+   *  versions will be run.
+   */
+  public String[] dependsOnMethods() default {};
+
+  /**
+   *  For before methods (beforeSuite, beforeTest, beforeTestClass and
+   *  beforeTestMethod, but not beforeGroups):
+   *  If set to true, this configuration method will be run
+   *  regardless of what groups it belongs to.
+   *  <br>
+   * For after methods (afterSuite, afterClass, ...):
+   *  If set to true, this configuration method will be run
+   *  even if one or more methods invoked previously failed or
+   *  was skipped.
+   */
+  public boolean alwaysRun() default false;
+
+  /**
+   * If true, this &#64;Configuration method will belong to groups specified in the
+   * &#64;Test annotation on the class (if any).
+   */
+  public boolean inheritGroups() default true;
+
+  /**
+   * The description for this method.  The string used will appear in the
+   * HTML report and also on standard output if verbose &gt;= 2.
+   */
+  public String description() default "";
+
+  /**
+   * The maximum number of milliseconds this method should take.
+   * If it hasn't returned after this time, this method will fail and
+   * it will cause test methods depending on it to be skipped.
+   */
+  public long timeOut() default 0;
+}
diff --git a/src/main/java/org/testng/annotations/BeforeTest.java b/src/main/java/org/testng/annotations/BeforeTest.java
new file mode 100755
index 0000000..5066123
--- /dev/null
+++ b/src/main/java/org/testng/annotations/BeforeTest.java
@@ -0,0 +1,72 @@
+package org.testng.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target(java.lang.annotation.ElementType.METHOD)
+public @interface BeforeTest {
+  /**
+   * Whether methods on this class/method are enabled.
+   */
+  public boolean enabled() default true;
+
+  /**
+   * The list of groups this class/method belongs to.
+   */
+  public String[] groups() default {};
+
+  /**
+   * The list of groups this method depends on.  Every method
+   * member of one of these groups is guaranteed to have been
+   * invoked before this method.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   */
+  public String[] dependsOnGroups() default {};
+
+  /**
+   * The list of methods this method depends on.  There is no guarantee
+   * on the order on which the methods depended upon will be run, but you
+   * are guaranteed that all these methods will be run before the test method
+   * that contains this annotation is run.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   *
+   *  If some of these methods have been overloaded, all the overloaded
+   *  versions will be run.
+   */
+  public String[] dependsOnMethods() default {};
+
+  /**
+   *  For before methods (beforeSuite, beforeTest, beforeTestClass and
+   *  beforeTestMethod, but not beforeGroups):
+   *  If set to true, this configuration method will be run
+   *  regardless of what groups it belongs to.
+   *  <br>
+   * For after methods (afterSuite, afterClass, ...):
+   *  If set to true, this configuration method will be run
+   *  even if one or more methods invoked previously failed or
+   *  was skipped.
+   */
+  public boolean alwaysRun() default false;
+
+  /**
+   * If true, this &#64;Configuration method will belong to groups specified in the
+   * &#64;Test annotation on the class (if any).
+   */
+  public boolean inheritGroups() default true;
+
+  /**
+   * The description for this method.  The string used will appear in the
+   * HTML report and also on standard output if verbose >= 2.
+   */
+  public String description() default "";
+
+  /**
+   * The maximum number of milliseconds this method should take.
+   * If it hasn't returned after this time, this method will fail and
+   * it will cause test methods depending on it to be skipped.
+   */
+  public long timeOut() default 0;
+}
diff --git a/src/main/java/org/testng/annotations/Configuration.java b/src/main/java/org/testng/annotations/Configuration.java
new file mode 100755
index 0000000..4c806d4
--- /dev/null
+++ b/src/main/java/org/testng/annotations/Configuration.java
@@ -0,0 +1,142 @@
+package org.testng.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Configuration information for a TestNG class.
+ *
+ * @deprecated Use @BeforeSuite, @AfterSuite, @BeforeTest, @AfterTest,
+ * \@BeforeGroups, @AfterGroups, @BeforeClass, @AfterClass, @BeforeMethod,
+ * \@AfterMethod
+ *
+ * @author Cedric Beust, Apr 26, 2004
+ *
+ */
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target(java.lang.annotation.ElementType.METHOD)
+public @interface Configuration {
+
+  /**
+   * If true, the annotated method will be run after the test class is instantiated
+   * and before the test method is invoked.
+   */
+  public boolean beforeTestClass() default false;
+
+  /**
+   * If true, the annotated method will be run after all the tests in the test
+   * class have been run.
+   */
+  public boolean afterTestClass() default false;
+
+  /**
+   * If true, the annotated method will be run before any test method is invoked.
+   */
+  public boolean beforeTestMethod() default false;
+
+  /**
+   * If true, the annotated method will be run after any test method is invoked.
+   */
+  public boolean afterTestMethod() default false;
+
+  /**
+   * If true, the annotated method will be run before this suite starts.
+   */
+  public boolean beforeSuite() default false;
+
+  /**
+   * If true, the annotated method will be run after all tests in this suite
+   * have run.
+   */
+  public boolean afterSuite() default false;
+
+  /**
+   * If true, the annotated method will be run before every test.
+   */
+  public boolean beforeTest() default false;
+
+  /**
+   * If true, the annotated method will be run after all every test.
+   */
+  public boolean afterTest() default false;
+
+  /**
+   * The list of groups that this configuration method will run before.
+   * This method is guaranteed to run shortly before the first test method that
+   * belongs to any of these groups is invoked.
+   */
+  public String[] beforeGroups() default {};
+
+  /**
+   * The list of groups that this configuration method will run after.
+   * This method is guaranteed to run shortly after the last test method that
+   * belongs to any of these groups is invoked.
+   */
+  public String[] afterGroups() default {};
+
+  /**
+   * The list of variables used to fill the parameters of this method.
+   * These variables must be defined in the property file.
+   *
+   * @deprecated Use @Parameters
+   */
+  @Deprecated
+  public String[] parameters() default {};
+
+  /**
+   * Whether methods on this class/method are enabled.
+   */
+  public boolean enabled() default true;
+
+  /**
+   * The list of groups this class/method belongs to.
+   */
+  public String[] groups() default {};
+
+  /**
+   * The list of groups this method depends on.  Every method
+   * member of one of these groups is guaranteed to have been
+   * invoked before this method.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   */
+  public String[] dependsOnGroups() default {};
+
+  /**
+   * The list of methods this method depends on.  There is no guarantee
+   * on the order on which the methods depended upon will be run, but you
+   * are guaranteed that all these methods will be run before the test method
+   * that contains this annotation is run.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   *
+   *  If some of these methods have been overloaded, all the overloaded
+   *  versions will be run.
+   */
+  public String[] dependsOnMethods() default {};
+
+  /**
+   *  For before methods (beforeSuite, beforeTest, beforeTestClass and
+   *  beforeTestMethod, but not beforeGroups):
+   *  If set to true, this configuration method will be run
+   *  regardless of what groups it belongs to.
+   *  <br>
+   * For after methods (afterSuite, afterClass, ...):
+   *  If set to true, this configuration method will be run
+   *  even if one or more methods invoked previously failed or
+   *  was skipped.
+   */
+  public boolean alwaysRun() default false;
+
+  /**
+   * If true, this &#64;Configuration method will belong to groups specified in the
+   * &#64;Test annotation on the class (if any).
+   */
+  public boolean inheritGroups() default true;
+
+  /**
+   * The description for this method.  The string used will appear in the
+   * HTML report and also on standard output if verbose &gt;= 2.
+   */
+  public String description() default "";
+}
diff --git a/src/main/java/org/testng/annotations/DataProvider.java b/src/main/java/org/testng/annotations/DataProvider.java
new file mode 100755
index 0000000..8ae73fa
--- /dev/null
+++ b/src/main/java/org/testng/annotations/DataProvider.java
@@ -0,0 +1,36 @@
+package org.testng.annotations;
+
+import static java.lang.annotation.ElementType.METHOD;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Mark a method as supplying data for a test method. The data provider name
+ * defaults to method name.
+ * The annotated method must return an Object[][] where each
+ * Object[] can be assigned the parameter list of the test method.
+ * The @Test method that wants to receive data from this DataProvider
+ * needs to use a dataProvider name equals to the name of this annotation.
+ *
+ * @author cbeust
+ */
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target({METHOD})
+public @interface DataProvider {
+
+  /**
+   * The name of this DataProvider.
+   */
+  public String name() default "";
+
+  /**
+   * Whether this data provider should be run in parallel.
+   */
+  boolean parallel() default false;
+
+  /**
+   * Which indices to run from this data provider, default: all.
+   */
+  int[] indices() default {};
+}
diff --git a/src/main/java/org/testng/annotations/ExpectedExceptions.java b/src/main/java/org/testng/annotations/ExpectedExceptions.java
new file mode 100755
index 0000000..604ed0d
--- /dev/null
+++ b/src/main/java/org/testng/annotations/ExpectedExceptions.java
@@ -0,0 +1,22 @@
+package org.testng.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * List of exceptions that a test method is expected to throw.
+ *
+ * @deprecated Use @Test(expectedExceptions = "...")
+ *
+ * @author Cedric Beust, Apr 26, 2004
+ *
+ */
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target(java.lang.annotation.ElementType.METHOD)
+public @interface ExpectedExceptions {
+
+  /**
+   * The list of exceptions expected to be thrown by this method.
+   */
+  public Class[] value();
+}
diff --git a/src/main/java/org/testng/annotations/Factory.java b/src/main/java/org/testng/annotations/Factory.java
new file mode 100755
index 0000000..2f97cb1
--- /dev/null
+++ b/src/main/java/org/testng/annotations/Factory.java
@@ -0,0 +1,44 @@
+package org.testng.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a method as a factory that returns objects that will be used by TestNG
+ * as Test classes.  The method must return Object[].
+ *
+ * @author <a href="mailto:cedric&#64;beust.com">Cedric Beust</a>
+ */
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR})
+public @interface Factory {
+  /**
+   * The list of variables used to fill the parameters of this method.
+   * These variables must be defined in the property file.
+   *
+   * @deprecated Use @Parameters
+   */
+  @Deprecated
+  public String[] parameters() default {};
+
+  /**
+   * The name of the data provider for this test method.
+   * @see org.testng.annotations.DataProvider
+   */
+  public String dataProvider() default "";
+
+  /**
+   * The class where to look for the data provider.  If not
+   * specified, the dataprovider will be looked on the class
+   * of the current test method or one of its super classes.
+   * If this attribute is specified, the data provider method
+   * needs to be static on the specified class.
+   */
+  public Class<?> dataProviderClass() default Object.class;
+
+  /**
+   * Whether this factory is enabled.
+   */
+  public boolean enabled() default true;
+}
diff --git a/src/main/java/org/testng/annotations/Guice.java b/src/main/java/org/testng/annotations/Guice.java
new file mode 100644
index 0000000..0ac3cf8
--- /dev/null
+++ b/src/main/java/org/testng/annotations/Guice.java
@@ -0,0 +1,27 @@
+package org.testng.annotations;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+import com.google.inject.Module;
+
+import org.testng.IModuleFactory;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation specifies what Guice modules should be used to instantiate
+ * this test class.
+ * 
+ * @author Cedric Beust <cedric@beust.com>
+ */
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target(TYPE)
+public @interface Guice {
+  /**
+   * @return the list of modules to query when trying to create an instance of this test class.
+   */
+  Class<? extends Module>[] modules() default {};
+
+  Class<? extends IModuleFactory> moduleFactory() default IModuleFactory.class;
+}
diff --git a/src/main/java/org/testng/annotations/IAnnotation.java b/src/main/java/org/testng/annotations/IAnnotation.java
new file mode 100755
index 0000000..fcc886a
--- /dev/null
+++ b/src/main/java/org/testng/annotations/IAnnotation.java
@@ -0,0 +1,11 @@
+package org.testng.annotations;
+
+/**
+ * The parent interface for all the annotations.
+ *
+ * Created on Dec 20, 2005
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public interface IAnnotation {
+}
diff --git a/src/main/java/org/testng/annotations/IConfigurationAnnotation.java b/src/main/java/org/testng/annotations/IConfigurationAnnotation.java
new file mode 100755
index 0000000..1d76cf2
--- /dev/null
+++ b/src/main/java/org/testng/annotations/IConfigurationAnnotation.java
@@ -0,0 +1,83 @@
+package org.testng.annotations;
+
+/**
+ * Encapsulate the @Configuration / @testng.configuration annotation
+ *
+ * Created on Dec 20, 2005
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public interface IConfigurationAnnotation extends ITestOrConfiguration {
+  /**
+   * If true, the annotated method will be run after the test class is instantiated
+   * and before the test method is invoked.
+   */
+  public boolean getBeforeTestClass();
+
+  /**
+   * If true, the annotated method will be run after all the tests in the test
+   * class have been run.
+   */
+  public boolean getAfterTestClass();
+
+  /**
+   * If true, the annotated method will be run before any test method is invoked.
+   */
+  public boolean getBeforeTestMethod();
+
+  /**
+   * If true, the annotated method will be run after any test method is invoked.
+   */
+  public boolean getAfterTestMethod();
+
+  /**
+   * If true, the annotated method will be run before this suite starts.
+   */
+  public boolean getBeforeSuite();
+
+  /**
+   * If true, the annotated method will be run after all tests in this suite
+   * have run.
+   */
+  public boolean getAfterSuite();
+
+  /**
+   * If true, the annotated method will be run before every test
+   */
+  public boolean getBeforeTest();
+
+  /**
+   * If true, the annotated method will be run after all every test.
+   */
+  public boolean getAfterTest();
+
+  /**
+   * Used only for after type of configuration methods. If set to true than
+   * the configuration method will be run whatever the status of before
+   * configuration methods was.
+   */
+  public boolean getAlwaysRun();
+
+  /**
+   * If true, this @Configuration method will belong to groups specified in the
+   * \@Test annotation on the class (if any).
+   */
+  public boolean getInheritGroups();
+
+  /**
+   * The list of groups that this configuration method will run before.
+   */
+  public String[] getBeforeGroups();
+
+  /**
+   * The list of groups that this configuration method will run after.
+   */
+  public String[] getAfterGroups();
+
+  /**
+   * Internal use only.
+   * @return true if this configuration annotation is not a "true" configuration
+   * annotation but a @BeforeSuite or similar that is represented as a configuration
+   * annotation.
+   */
+  public boolean isFakeConfiguration();
+}
diff --git a/src/main/java/org/testng/annotations/IDataProviderAnnotation.java b/src/main/java/org/testng/annotations/IDataProviderAnnotation.java
new file mode 100755
index 0000000..164fefc
--- /dev/null
+++ b/src/main/java/org/testng/annotations/IDataProviderAnnotation.java
@@ -0,0 +1,21 @@
+package org.testng.annotations;
+
+/**
+ * Encapsulate the @DataProvider / @testng.data-provider annotation
+ *
+ * Created on Dec 20, 2005
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public interface IDataProviderAnnotation extends IAnnotation {
+  /**
+   * The name of this DataProvider.
+   */
+  public String getName();
+  public void setName(String name);
+
+  /**
+   * Whether this data provider should be used in parallel.
+   */
+  boolean isParallel();
+  void setParallel(boolean parallel);
+}
diff --git a/src/main/java/org/testng/annotations/IExpectedExceptionsAnnotation.java b/src/main/java/org/testng/annotations/IExpectedExceptionsAnnotation.java
new file mode 100755
index 0000000..f634430
--- /dev/null
+++ b/src/main/java/org/testng/annotations/IExpectedExceptionsAnnotation.java
@@ -0,0 +1,14 @@
+package org.testng.annotations;
+
+/**
+ * Encapsulate the @ExpectedExceptions / @testng.expected-exceptions annotation
+ * Created on Dec 20, 2005
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public interface IExpectedExceptionsAnnotation extends IAnnotation {
+  /**
+   * The list of exceptions expected to be thrown by this method.
+   */
+  public Class[] getValue();
+
+}
diff --git a/src/main/java/org/testng/annotations/IFactoryAnnotation.java b/src/main/java/org/testng/annotations/IFactoryAnnotation.java
new file mode 100755
index 0000000..ff58219
--- /dev/null
+++ b/src/main/java/org/testng/annotations/IFactoryAnnotation.java
@@ -0,0 +1,12 @@
+package org.testng.annotations;
+
+import org.testng.internal.annotations.IDataProvidable;
+
+/**
+ * Encapsulate the @Factory / @testng.factory annotation
+ *
+ * Created on Dec 20, 2005
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public interface IFactoryAnnotation extends IParameterizable, IDataProvidable {
+}
diff --git a/src/main/java/org/testng/annotations/IListenersAnnotation.java b/src/main/java/org/testng/annotations/IListenersAnnotation.java
new file mode 100644
index 0000000..db1f387
--- /dev/null
+++ b/src/main/java/org/testng/annotations/IListenersAnnotation.java
@@ -0,0 +1,10 @@
+package org.testng.annotations;
+
+import org.testng.ITestNGListener;
+
+public interface IListenersAnnotation extends IAnnotation {
+
+  Class<? extends ITestNGListener>[] getValue();
+
+  void setValue(Class<? extends ITestNGListener>[] value);
+}
diff --git a/src/main/java/org/testng/annotations/IObjectFactoryAnnotation.java b/src/main/java/org/testng/annotations/IObjectFactoryAnnotation.java
new file mode 100755
index 0000000..21d9984
--- /dev/null
+++ b/src/main/java/org/testng/annotations/IObjectFactoryAnnotation.java
@@ -0,0 +1,10 @@
+package org.testng.annotations;
+
+/**
+ * @author Hani Suleiman
+ *         Date: Mar 6, 2007
+ *         Time: 2:16:13 PM
+ */
+public interface IObjectFactoryAnnotation extends IAnnotation
+{
+}
diff --git a/src/main/java/org/testng/annotations/IParameterizable.java b/src/main/java/org/testng/annotations/IParameterizable.java
new file mode 100755
index 0000000..2cbcaf3
--- /dev/null
+++ b/src/main/java/org/testng/annotations/IParameterizable.java
@@ -0,0 +1,24 @@
+package org.testng.annotations;
+
+/**
+ * Parent interface for annotations that can receive parameters.
+ *
+ * Created on Dec 20, 2005
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public interface IParameterizable extends IAnnotation {
+  /**
+   * The list of variables used to fill the parameters of this method.
+   * These variables must be defined in the property file.
+   *
+   * @deprecated Use @Parameters
+   */
+  @Deprecated
+  public String[] getParameters();
+
+  /**
+   * Whether this annotation is enabled.
+   */
+  public boolean getEnabled();
+  public void setEnabled(boolean enabled);
+}
diff --git a/src/main/java/org/testng/annotations/IParametersAnnotation.java b/src/main/java/org/testng/annotations/IParametersAnnotation.java
new file mode 100755
index 0000000..079a055
--- /dev/null
+++ b/src/main/java/org/testng/annotations/IParametersAnnotation.java
@@ -0,0 +1,27 @@
+package org.testng.annotations;
+
+/**
+ * Encapsulate the @Parameters / @testng.parameters annotation
+ *
+ * Created on Dec 20, 2005
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public interface IParametersAnnotation extends IAnnotation {
+  /**
+   * The list of variables used to fill the parameters of this method.
+   * These variables must be defined in your testng.xml file.
+   * For example
+   * <p>
+   * <code>
+   * &#064;Parameters({ "xmlPath" })<br>
+   * &#064;Test<br>
+   * public void verifyXmlFile(String path) { ... }<br>
+   * </code>
+   * <p>and in <tt>testng.xml</tt>:<p>
+   * <code>
+   * &lt;parameter name="xmlPath" value="account.xml" /&gt;<br>
+   * </code>
+   */
+  public String[] getValue();
+
+}
diff --git a/src/main/java/org/testng/annotations/ITestAnnotation.java b/src/main/java/org/testng/annotations/ITestAnnotation.java
new file mode 100755
index 0000000..2c63493
--- /dev/null
+++ b/src/main/java/org/testng/annotations/ITestAnnotation.java
@@ -0,0 +1,83 @@
+package org.testng.annotations;
+
+import org.testng.IRetryAnalyzer;
+import org.testng.internal.annotations.IDataProvidable;
+
+/**
+ * Encapsulate the &#64;Test / &#64;testng.test annotation.
+ *
+ * Created on Dec 20, 2005
+ * @author <a href = "mailto:cedric&#64;beust.com">Cedric Beust</a>
+ */
+public interface ITestAnnotation extends ITestOrConfiguration, IDataProvidable {
+  /**
+   * Returns the number of times this method should be invoked.
+   * @return the number of times this method should be invoked.
+   */
+  public int getInvocationCount();
+  public void setInvocationCount(int l);
+
+  /**
+   * The size of the thread pool for this method.  The method will be invoked
+   * from multiple threads as specified by invocationCount.
+   * Note:  this attribute is ignored if invocationCount is not specified
+   */
+  public int getThreadPoolSize();
+  public void setThreadPoolSize(int n);
+
+  /**
+   * The percentage of success expected from this method.
+   */
+  public int getSuccessPercentage();
+  public void setSuccessPercentage(int s);
+
+  /**
+   * If set to true, this test method will always be run even if it depends
+   * on a method that failed.  This attribute will be ignored if this test
+   * doesn't depend on any method or group.
+   */
+  public boolean getAlwaysRun();
+  public void setAlwaysRun(boolean f);
+
+  public Class<?>[] getExpectedExceptions();
+  public void setExpectedExceptions(Class<?>[] e);
+
+  public String getExpectedExceptionsMessageRegExp();
+  public void setExpectedExceptionsMessageRegExp(String e);
+
+  public String getSuiteName();
+  public void setSuiteName(String s);
+
+  public String getTestName();
+  public void setTestName(String s);
+
+  public boolean getSequential();
+  public void setSequential(boolean f);
+
+  public boolean getSingleThreaded();
+  public void setSingleThreaded(boolean f);
+
+  public String getDataProvider();
+  public void setDataProvider(String v);
+
+  public Class<?> getDataProviderClass();
+  public void setDataProviderClass(Class<?> v);
+
+  public IRetryAnalyzer getRetryAnalyzer();
+  public void setRetryAnalyzer(Class<?> c);
+
+  public boolean skipFailedInvocations();
+  public void setSkipFailedInvocations(boolean skip);
+
+  public long invocationTimeOut();
+  public void setInvocationTimeOut(long timeOut);
+
+  public boolean ignoreMissingDependencies();
+  public void setIgnoreMissingDependencies(boolean ignore);
+
+  /**
+   * The scheduling priority. Lower priorities get scheduled first.
+   */
+  public int getPriority();
+  public void setPriority(int priority);
+}
diff --git a/src/main/java/org/testng/annotations/ITestOrConfiguration.java b/src/main/java/org/testng/annotations/ITestOrConfiguration.java
new file mode 100755
index 0000000..516c862
--- /dev/null
+++ b/src/main/java/org/testng/annotations/ITestOrConfiguration.java
@@ -0,0 +1,54 @@
+package org.testng.annotations;
+
+/**
+ * This interface captures methods common to @Test and @Configuration
+ *
+ * Created on Dec 20, 2005
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public interface ITestOrConfiguration extends IParameterizable {
+  /**
+   * Returns the maximum number of milliseconds this test should take.
+   * If it hasn't returned after this time, it will be marked as a FAIL.
+   * @return the maximum number of milliseconds this test should take.
+   */
+  public long getTimeOut();
+  public void setTimeOut(long l);
+
+  /**
+   * The list of groups this class/method belongs to.
+   */
+  public String[] getGroups();
+  public void setGroups(String[] groups);
+
+  /**
+   * The list of groups this method depends on.  Every method
+   * member of one of these groups is guaranteed to have been
+   * invoked before this method.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   */
+  public String[] getDependsOnGroups();
+  public void setDependsOnGroups(String[] groups);
+
+  /**
+   * The list of methods this method depends on.  There is no guarantee
+   * on the order on which the methods depended upon will be run, but you
+   * are guaranteed that all these methods will be run before the test method
+   * that contains this annotation is run.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   *
+   *  If some of these methods have been overloaded, all the overloaded
+   *  versions will be run.
+   */
+  public String[] getDependsOnMethods();
+  public void setDependsOnMethods(String[] dependsOnMethods);
+
+  /**
+   * The description for this method, which will be shown in the reports.
+   */
+  public String getDescription();
+  public void setDescription(String description);
+
+}
diff --git a/src/main/java/org/testng/annotations/Listeners.java b/src/main/java/org/testng/annotations/Listeners.java
new file mode 100644
index 0000000..91b59d6
--- /dev/null
+++ b/src/main/java/org/testng/annotations/Listeners.java
@@ -0,0 +1,30 @@
+package org.testng.annotations;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+import org.testng.IAnnotationTransformer;
+import org.testng.IAnnotationTransformer2;
+import org.testng.ITestNGListener;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation lets you define listeners directly on a test class
+ * instead of doing so in your testng.xml.  Any class that implements
+ * the interface {@link org.testng.ITestNGListener} is allowed,
+ * except {@link IAnnotationTransformer} and {@link IAnnotationTransformer2},
+ * which need to be defined in XML since they have to be known before we even
+ * start looking for annotations.
+ *
+ * Note that listeners specified this way are global to your entire suite, just
+ * like listeners specified in testng.xml.
+ *
+ * @author Cedric Beust, Mar 26, 2010
+ *
+ */
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target({TYPE})
+public @interface Listeners {
+  Class<? extends ITestNGListener>[] value() default {};
+}
diff --git a/src/main/java/org/testng/annotations/NoInjection.java b/src/main/java/org/testng/annotations/NoInjection.java
new file mode 100644
index 0000000..e5578dd
--- /dev/null
+++ b/src/main/java/org/testng/annotations/NoInjection.java
@@ -0,0 +1,17 @@
+package org.testng.annotations;
+
+import static java.lang.annotation.ElementType.PARAMETER;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Turn off TestNG injection for a parameter.
+ *
+ * @author Cedric Beust, July 9th, 2010
+ */
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target({PARAMETER})
+public @interface NoInjection {
+}
+
diff --git a/src/main/java/org/testng/annotations/ObjectFactory.java b/src/main/java/org/testng/annotations/ObjectFactory.java
new file mode 100755
index 0000000..327aeab
--- /dev/null
+++ b/src/main/java/org/testng/annotations/ObjectFactory.java
@@ -0,0 +1,18 @@
+package org.testng.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a method as the object factory to use for creating all test instances.
+ * The test classes can only contain one method marked with this annotation,
+ * and the method must return an instance of {@link org.testng.ITestObjectFactory}.
+ *
+ * @author Hani Suleiman
+ * @since 5.6
+ */
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target(java.lang.annotation.ElementType.METHOD)
+public @interface ObjectFactory
+{
+}
diff --git a/src/main/java/org/testng/annotations/Optional.java b/src/main/java/org/testng/annotations/Optional.java
new file mode 100755
index 0000000..0d63576
--- /dev/null
+++ b/src/main/java/org/testng/annotations/Optional.java
@@ -0,0 +1,28 @@
+package org.testng.annotations;
+
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import org.testng.internal.Parameters;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies that the current parameter is optional.  TestNG will pass
+ * in a specified default value, or <code>null</code> if none is specified.
+ */
+@Retention(RUNTIME)
+@Target({PARAMETER})
+public @interface Optional {
+  /** The default value to pass to this parameter.  <p>The default deserves
+   * a bit of explanation.  JSR-175 (which defines annotations) says that
+   * Java annotation parameters can only be ConstantExpressions, which
+   * can be primitive/string literals, but not <code>null</code>.</p>
+   * <p>In this case, we use this string as a substitute
+   * for <code>null</code>; in practice, TestNG will pass <code>null</code>
+   * to your code, and not the string "null", if you do not specify
+   * a default value here in this parameter.
+   */
+  public String value() default Parameters.NULL_VALUE;
+}
diff --git a/src/main/java/org/testng/annotations/Parameters.java b/src/main/java/org/testng/annotations/Parameters.java
new file mode 100755
index 0000000..d503de9
--- /dev/null
+++ b/src/main/java/org/testng/annotations/Parameters.java
@@ -0,0 +1,35 @@
+package org.testng.annotations;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Describes how to pass parameters to a &#64;Test method.
+ *
+ * @author <a href="mailto:cedric&#64;beust.com">Cedric Beust</a>
+ */
+@Retention(RUNTIME)
+@Target({METHOD, CONSTRUCTOR, TYPE })
+public @interface Parameters {
+  /**
+   * The list of variables used to fill the parameters of this method.
+   * These variables must be defined in your testng.xml file.
+   * For example
+   * <p>
+   * <code>
+   * &#064;Parameters({ "xmlPath" })<br>
+   * &#064;Test<br>
+   * public void verifyXmlFile(String path) { ... }<br>
+   * </code>
+   * <p>and in <tt>testng.xml</tt>:<p>
+   * <code>
+   * &lt;parameter name="xmlPath" value="account.xml" /&gt;<br>
+   * </code>
+   */
+  public String[] value() default {};
+}
diff --git a/src/main/java/org/testng/annotations/Test.java b/src/main/java/org/testng/annotations/Test.java
new file mode 100755
index 0000000..8c7f812
--- /dev/null
+++ b/src/main/java/org/testng/annotations/Test.java
@@ -0,0 +1,183 @@
+package org.testng.annotations;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Mark a class or a method as part of the test.
+ *
+ * @author Cedric Beust, Apr 26, 2004
+ */
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target({METHOD, TYPE, CONSTRUCTOR})
+public @interface Test {
+  /**
+   * The list of groups this class/method belongs to.
+   */
+  public String[] groups() default {};
+
+  /**
+   * Whether methods on this class/method are enabled.
+   */
+  public boolean enabled() default true;
+
+  /**
+   * The list of variables used to fill the parameters of this method.
+   * These variables must be defined in the property file.
+   *
+   * @deprecated Use @Parameters
+   */
+  @Deprecated
+  public String[] parameters() default {};
+
+  /**
+   * The list of groups this method depends on.  Every method
+   * member of one of these groups is guaranteed to have been
+   * invoked before this method.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   */
+  public String[] dependsOnGroups() default {};
+
+  /**
+   * The list of methods this method depends on.  There is no guarantee
+   * on the order on which the methods depended upon will be run, but you
+   * are guaranteed that all these methods will be run before the test method
+   * that contains this annotation is run.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   *
+   * If some of these methods have been overloaded, all the overloaded
+   * versions will be run.
+   */
+  public String[] dependsOnMethods() default {};
+
+  /**
+   * The maximum number of milliseconds this test should take.
+   * If it hasn't returned after this time, it will be marked as a FAIL.
+   */
+  public long timeOut() default 0;
+
+  /**
+   * The maximum number of milliseconds that the total number of invocations on this test
+   * method should take.  This annotation will be ignored if the attribute invocationCount
+   * is not specified on this method.
+   * If it hasn't returned after this time, it will be marked as a FAIL.
+   */
+  public long invocationTimeOut() default 0;
+
+  /**
+   * The number of times this method should be invoked.
+   */
+  public int invocationCount() default 1;
+
+  /**
+   * The size of the thread pool for this method.  The method will be invoked
+   * from multiple threads as specified by invocationCount.
+   * Note:  this attribute is ignored if invocationCount is not specified
+   */
+  public int threadPoolSize() default 0;
+
+  /**
+   * The percentage of success expected from this method.
+   */
+  public int successPercentage() default 100;
+
+  /**
+   * The name of the data provider for this test method.
+   * @see org.testng.annotations.DataProvider
+   */
+  public String dataProvider() default "";
+
+  /**
+   * The class where to look for the data provider.  If not
+   * specified, the dataprovider will be looked on the class
+   * of the current test method or one of its super classes.
+   * If this attribute is specified, the data provider method
+   * needs to be static on the specified class.
+   */
+  public Class<?> dataProviderClass() default Object.class;
+
+  /**
+   * If set to true, this test method will always be run even if it depends
+   * on a method that failed.  This attribute will be ignored if this test
+   * doesn't depend on any method or group.
+   */
+  public boolean alwaysRun() default false;
+
+  /**
+   * The description for this method.  The string used will appear in the
+   * HTML report and also on standard output if verbose >= 2.
+   */
+  public String description() default "";
+
+  /**
+   * The list of exceptions that a test method is expected to throw.  If no
+   * exception or a different than one on this list is thrown, this test will be
+   * marked a failure.
+   */
+  public Class[] expectedExceptions() default {};
+
+  /**
+   * If expectedExceptions was specified, its message must match the regular expression
+   * specified in this attribute.
+   */
+  public String expectedExceptionsMessageRegExp() default ".*";
+
+  /**
+   * The name of the suite this test class should be placed in.  This
+   * attribute is ignore if @Test is not at the class level.
+   */
+  public String suiteName() default "";
+
+  /**
+   * The name of the test  this test class should be placed in.  This
+   * attribute is ignore if @Test is not at the class level.
+   */
+  public String testName() default "";
+
+  /**
+   * @deprecated Use singleThreaded
+   */
+  public boolean sequential() default false;
+
+  /**
+   * If set to true, all the methods on this test class are guaranteed to run
+   * in the same thread, even if the tests are currently being run with parallel="true".
+   *
+   * This attribute can only be used at the class level and will be ignored
+   * if used at the method level.
+   */
+  public boolean singleThreaded() default false;
+
+  /**
+   * The name of the class that should be called to test if the test
+   * should be retried.
+   * @return String The name of the class that will test if a test method
+   * should be retried.
+   */
+  public Class retryAnalyzer() default Class.class;
+
+  /**
+   * If true and invocationCount is specified with a value > 1,
+   * then all invocations after a failure will be marked as a SKIP
+   * instead of a FAIL.
+   */
+  public boolean skipFailedInvocations() default false;
+
+  /**
+   * If set to true, this test will run even if the methods
+   * it depends on are missing or excluded.
+   */
+  public boolean ignoreMissingDependencies() default false;
+
+  /**
+   * The scheduling priority. Lower priorities will be scheduled first.
+   */
+  int priority() default 0;
+
+}
diff --git a/src/main/java/org/testng/annotations/TestInstance.java b/src/main/java/org/testng/annotations/TestInstance.java
new file mode 100755
index 0000000..52a4087
--- /dev/null
+++ b/src/main/java/org/testng/annotations/TestInstance.java
@@ -0,0 +1,19 @@
+package org.testng.annotations;
+
+import static java.lang.annotation.ElementType.PARAMETER;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * If this annotation is used on a parameter of a data provider, that parameter is the instance
+ * of the test method which is going to be fed by this data provider.
+ *
+ * This annotation is ignored everywhere else.
+ *
+ * @author cbeust
+ */
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target({PARAMETER})
+public @interface TestInstance {
+}
diff --git a/src/main/java/org/testng/asserts/Assertion.java b/src/main/java/org/testng/asserts/Assertion.java
new file mode 100644
index 0000000..200df71
--- /dev/null
+++ b/src/main/java/org/testng/asserts/Assertion.java
@@ -0,0 +1,708 @@
+package org.testng.asserts;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An assert class with various hooks allowing its behavior to be modified
+ * by subclasses.
+ */
+public class Assertion implements IAssertLifecycle {
+  protected void doAssert(IAssert<?> assertCommand) {
+    onBeforeAssert(assertCommand);
+    try {
+      executeAssert(assertCommand);
+      onAssertSuccess(assertCommand);
+    } catch(AssertionError ex) {
+      onAssertFailure(assertCommand, ex);
+      throw ex;
+    } finally {
+      onAfterAssert(assertCommand);
+    }
+  }
+
+  /**
+   * Run the assert command in parameter. Meant to be overridden by subclasses.
+   */
+  @Override
+  public void executeAssert(IAssert<?> assertCommand) {
+    assertCommand.doAssert();
+  }
+
+  /**
+   * Invoked when an assert succeeds. Meant to be overridden by subclasses.
+   */
+  @Override
+  public void onAssertSuccess(IAssert<?> assertCommand) {
+  }
+
+  /**
+   * Invoked when an assert fails. Meant to be overridden by subclasses.
+   * 
+   * @deprecated use onAssertFailure(IAssert assertCommand, AssertionError ex) instead of.
+   */
+  @Deprecated
+  @Override
+  public void onAssertFailure(IAssert<?> assertCommand) {
+  }
+  
+  @Override
+  public void onAssertFailure(IAssert<?> assertCommand, AssertionError ex) {
+      onAssertFailure(assertCommand);
+  }
+
+  /**
+   * Invoked before an assert is run. Meant to be overridden by subclasses.
+   */
+  @Override
+  public void onBeforeAssert(IAssert<?> assertCommand) {
+  }
+
+  /**
+   * Invoked after an assert is run. Meant to be overridden by subclasses.
+   */
+  @Override
+  public void onAfterAssert(IAssert<?> assertCommand) {
+  }
+
+  abstract private static class SimpleAssert<T> implements IAssert<T> {
+    private final T actual;
+    private final T expected;
+    private final String m_message;
+
+    public SimpleAssert(String message) {
+      this(null, null, message);
+    }
+
+    public SimpleAssert(T actual, T expected) {
+      this(actual, expected, null);
+    }
+
+    public SimpleAssert(T actual, T expected, String message) {
+      this.actual = actual;
+      this.expected = expected;
+      m_message = message;
+    }
+
+    @Override
+    public String getMessage() {
+      return m_message;
+    }
+
+    @Override
+    public T getActual() {
+        return actual;
+    }
+
+    @Override
+    public T getExpected() {
+        return expected;
+    }
+
+    @Override
+    abstract public void doAssert();
+  }
+
+
+  public void assertTrue(final boolean condition, final String message) {
+    doAssert(new SimpleAssert<Boolean>(condition, Boolean.TRUE, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertTrue(condition, message);
+      }
+    });
+  }
+  
+	public void assertTrue(final boolean condition) {
+		doAssert(new SimpleAssert<Boolean>(condition, Boolean.TRUE) {
+			@Override
+			public void doAssert() {
+				org.testng.Assert.assertTrue(condition);
+			}
+		});
+	}
+
+  public void assertFalse(final boolean condition, final String message) {
+    doAssert(new SimpleAssert<Boolean>(condition, Boolean.FALSE, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertFalse(condition, message);
+      }
+    });
+  }
+
+  public void assertFalse(final boolean condition) {
+    doAssert(new SimpleAssert<Boolean>(condition, Boolean.FALSE) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertFalse(condition);
+      }
+    });
+  }
+
+  public void fail(final String message, final Throwable realCause) {
+    doAssert(new SimpleAssert<Object>(message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.fail(message, realCause);
+      }
+    });
+  }
+
+  public void fail(final String message) {
+    doAssert(new SimpleAssert<Object>(message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.fail(message);
+      }
+    });
+  }
+
+  public void fail() {
+    doAssert(new SimpleAssert<Object>(null) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.fail();
+      }
+    });
+  }
+
+  public <T> void assertEquals(final T actual, final T expected, final String message) {
+    doAssert(new SimpleAssert<T>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected, message);
+      }
+    });
+  }
+
+  public <T> void assertEquals(final T actual, final T expected) {
+    doAssert(new SimpleAssert<T>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected);
+      }
+    });
+  }
+
+  public void assertEquals(final String actual, final String expected, final String message) {
+    doAssert(new SimpleAssert<String>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected, message);
+      }
+    });
+  }
+  public void assertEquals(final String actual, final String expected) {
+    doAssert(new SimpleAssert<String>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected);
+      }
+    });
+  }
+
+  public void assertEquals(final double actual, final double expected, final double delta,
+      final String message) {
+    doAssert(new SimpleAssert<Double>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected, delta, message);
+      }
+    });
+  }
+
+  public void assertEquals(final double actual, final double expected, final double delta) {
+    doAssert(new SimpleAssert<Double>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected, delta);
+      }
+    });
+  }
+
+  public void assertEquals(final float actual, final float expected, final float delta,
+      final String message) {
+    doAssert(new SimpleAssert<Float>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected, delta, message);
+      }
+    });
+  }
+
+  public void assertEquals(final float actual, final float expected, final float delta) {
+    doAssert(new SimpleAssert<Float>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected, delta);
+      }
+    });
+  }
+
+  public void assertEquals(final long actual, final long expected, final String message) {
+    doAssert(new SimpleAssert<Long>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected, message);
+      }
+    });
+  }
+
+  public void assertEquals(final long actual, final long expected) {
+    doAssert(new SimpleAssert<Long>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected);
+      }
+    });
+  }
+
+  public void assertEquals(final boolean actual, final boolean expected, final String message) {
+    doAssert(new SimpleAssert<Boolean>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected, message);
+      }
+    });
+  }
+
+  public void assertEquals(final boolean actual, final boolean expected) {
+    doAssert(new SimpleAssert<Boolean>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected);
+      }
+    });
+  }
+
+  public void assertEquals(final byte actual, final byte expected, final String message) {
+    doAssert(new SimpleAssert<Byte>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected, message);
+      }
+    });
+  }
+
+  public void assertEquals(final byte actual, final byte expected) {
+    doAssert(new SimpleAssert<Byte>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected);
+      }
+    });
+  }
+
+  public void assertEquals(final char actual, final char expected, final String message) {
+    doAssert(new SimpleAssert<Character>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected, message);
+      }
+    });
+  }
+
+  public void assertEquals(final char actual, final char expected) {
+    doAssert(new SimpleAssert<Character>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected);
+      }
+    });
+  }
+
+  public void assertEquals(final short actual, final short expected, final String message) {
+    doAssert(new SimpleAssert<Short>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected, message);
+      }
+    });
+  }
+
+  public void assertEquals(final short actual, final short expected) {
+    doAssert(new SimpleAssert<Short>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected);
+      }
+    });
+  }
+
+  public void assertEquals(final int actual, final  int expected, final String message) {
+    doAssert(new SimpleAssert<Integer>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected, message);
+      }
+    });
+  }
+
+  public void assertEquals(final int actual, final int expected) {
+    doAssert(new SimpleAssert<Integer>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected);
+      }
+    });
+  }
+
+  public void assertNotNull(final Object object) {
+    doAssert(new SimpleAssert<Object>(object, null) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotNull(object);
+      }
+    });
+  }
+
+  public void assertNotNull(final Object object, final String message) {
+    doAssert(new SimpleAssert<Object>(object, null, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotNull(object, message);
+      }
+    });
+  }
+
+  public void assertNull(final Object object) {
+    doAssert(new SimpleAssert<Object>(object, null) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNull(object);
+      }
+    });
+  }
+
+  public void assertNull(final Object object, final String message) {
+    doAssert(new SimpleAssert<Object>(object, null, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNull(object, message);
+      }
+    });
+  }
+
+  public void assertSame(final Object actual, final Object expected, final String message) {
+    doAssert(new SimpleAssert<Object>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertSame(actual, expected, message);
+      }
+    });
+  }
+
+  public void assertSame(final Object actual, final Object expected) {
+    doAssert(new SimpleAssert<Object>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertSame(actual, expected);
+      }
+    });
+  }
+
+  public void assertNotSame(final Object actual, final Object expected, final String message) {
+    doAssert(new SimpleAssert<Object>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotSame(actual, expected, message);
+      }
+    });
+  }
+
+  public void assertNotSame(final Object actual, final Object expected) {
+    doAssert(new SimpleAssert<Object>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotSame(actual, expected);
+      }
+    });
+  }
+
+  public void assertEquals(final Collection<?> actual, final Collection<?> expected) {
+    doAssert(new SimpleAssert<Collection<?>>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected);
+      }
+    });
+  }
+
+  public void assertEquals(final Collection<?> actual, final Collection<?> expected,
+      final String message) {
+    doAssert(new SimpleAssert<Collection<?>>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected, message);
+      }
+    });
+  }
+
+  public void assertEquals(final Object[] actual, final Object[] expected, final String message) {
+    doAssert(new SimpleAssert<Object[]>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected, message);
+      }
+    });
+  }
+
+  public void assertEqualsNoOrder(final Object[] actual, final Object[] expected,
+      final String message) {
+    doAssert(new SimpleAssert<Object[]>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEqualsNoOrder(actual, expected, message);
+      }
+    });
+  }
+
+  public void assertEquals(final Object[] actual, final Object[] expected) {
+    doAssert(new SimpleAssert<Object[]>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected);
+      }
+    });
+  }
+
+  public void assertEqualsNoOrder(final Object[] actual, final Object[] expected) {
+    doAssert(new SimpleAssert<Object[]>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEqualsNoOrder(actual, expected);
+      }
+    });
+  }
+
+  public void assertEquals(final byte[] actual, final byte[] expected) {
+    doAssert(new SimpleAssert<byte[]>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected);
+      }
+    });
+  }
+
+  public void assertEquals(final byte[] actual, final byte[] expected,
+      final String message) {
+    doAssert(new SimpleAssert<byte[]>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected, message);
+      }
+    });
+  }
+
+  public void assertEquals(final Set<?> actual, final Set<?> expected) {
+    doAssert(new SimpleAssert<Set<?>>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected);
+      }
+    });
+  }
+
+  public void assertEquals(final Set<?> actual, final Set<?> expected, final String message) {
+    doAssert(new SimpleAssert<Set<?>>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected, message);
+      }
+    });
+  }
+
+  public void assertEquals(final Map<?, ?> actual, final Map<?, ?> expected) {
+    doAssert(new SimpleAssert<Map<?, ?>>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertEquals(actual, expected);
+      }
+    });
+  }
+
+  public  void assertNotEquals(final Object actual, final Object expected, final String message) {
+    doAssert(new SimpleAssert<Object>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected, message);
+      }
+    });
+  }
+
+  public void assertNotEquals(final Object actual, final Object expected) {
+    doAssert(new SimpleAssert<Object>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected);
+      }
+    });
+  }
+
+  void assertNotEquals(final String actual, final String expected, final String message) {
+    doAssert(new SimpleAssert<String>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected, message);
+      }
+    });
+  }
+
+  void assertNotEquals(final String actual, final String expected) {
+    doAssert(new SimpleAssert<String>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected);
+      }
+    });
+  }
+
+  void assertNotEquals(final long actual, final long expected, final String message) {
+    doAssert(new SimpleAssert<Long>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected, message);
+      }
+    });
+  }
+
+  void assertNotEquals(final long actual, final long expected) {
+    doAssert(new SimpleAssert<Long>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected);
+      }
+    });
+  }
+
+  void assertNotEquals(final boolean actual, final boolean expected, final String message) {
+    doAssert(new SimpleAssert<Boolean>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected, message);
+      }
+    });
+  }
+
+  void assertNotEquals(final boolean actual, final boolean expected) {
+    doAssert(new SimpleAssert<Boolean>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected);
+      }
+    });
+  }
+
+  void assertNotEquals(final byte actual, final byte expected, final String message) {
+    doAssert(new SimpleAssert<Byte>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected, message);
+      }
+    });
+  }
+
+  void assertNotEquals(final byte actual, final byte expected) {
+    doAssert(new SimpleAssert<Byte>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected);
+      }
+    });
+  }
+
+  void assertNotEquals(final char actual, final char expected, final String message) {
+    doAssert(new SimpleAssert<Character>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected, message);
+      }
+    });
+  }
+
+  void assertNotEquals(final char actual, final char expected) {
+    doAssert(new SimpleAssert<Character>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected);
+      }
+    });
+  }
+
+  void assertNotEquals(final short actual, final short expected, final String message) {
+    doAssert(new SimpleAssert<Short>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected, message);
+      }
+    });
+  }
+
+  void assertNotEquals(final short actual, final short expected) {
+    doAssert(new SimpleAssert<Short>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected);
+      }
+    });
+  }
+
+  void assertNotEquals(final int actual, final int expected, final String message) {
+    doAssert(new SimpleAssert<Integer>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected, message);
+      }
+    });
+  }
+
+  void assertNotEquals(final int actual, final int expected) {
+    doAssert(new SimpleAssert<Integer>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected);
+      }
+    });
+  }
+
+  public void assertNotEquals(final float actual, final float expected, final float delta,
+      final String message) {
+    doAssert(new SimpleAssert<Float>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected, delta, message);
+      }
+   });
+  }
+
+  public void assertNotEquals(final float actual, final float expected, final float delta) {
+    doAssert(new SimpleAssert<Float>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected, delta);
+      }
+    });
+  }
+
+  public void assertNotEquals(final double actual, final double expected, final double delta,
+      final String message) {
+    doAssert(new SimpleAssert<Double>(actual, expected, message) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected, delta, message);
+      }
+    });
+  }
+
+  public void assertNotEquals(final double actual, final double expected, final double delta) {
+    doAssert(new SimpleAssert<Double>(actual, expected) {
+      @Override
+      public void doAssert() {
+        org.testng.Assert.assertNotEquals(actual, expected, delta);
+      }
+    });
+  }
+
+}
diff --git a/src/main/java/org/testng/asserts/IAssert.java b/src/main/java/org/testng/asserts/IAssert.java
new file mode 100644
index 0000000..b372454
--- /dev/null
+++ b/src/main/java/org/testng/asserts/IAssert.java
@@ -0,0 +1,8 @@
+package org.testng.asserts;
+
+public interface IAssert<T> {
+  String getMessage();
+  void doAssert();
+  T getActual();
+  T getExpected();
+}
diff --git a/src/main/java/org/testng/asserts/IAssertLifecycle.java b/src/main/java/org/testng/asserts/IAssertLifecycle.java
new file mode 100644
index 0000000..095b0ee
--- /dev/null
+++ b/src/main/java/org/testng/asserts/IAssertLifecycle.java
@@ -0,0 +1,39 @@
+package org.testng.asserts;
+
+/**
+ * Life cycle methods for the assertion class.
+ */
+public interface IAssertLifecycle {
+  /**
+   * Run the assert command in parameter.
+   */
+  void executeAssert(IAssert<?> assertCommand);
+
+  /**
+   * Invoked when an assert succeeds.
+   */
+  void onAssertSuccess(IAssert<?> assertCommand);
+
+  /**
+   * Invoked when an assert fails.
+   * 
+   * @deprecated use onAssertFailure(IAssert assertCommand, AssertionError ex) instead of.
+   */
+  void onAssertFailure(IAssert<?> assertCommand);
+  
+  /**
+   * Invoked when an assert fails.
+   * 
+   */
+  void onAssertFailure(IAssert<?> assertCommand, AssertionError ex);
+
+  /**
+   * Invoked before an assert is run.
+   */
+  void onBeforeAssert(IAssert<?> assertCommand);
+
+  /**
+   * Invoked after an assert is run.
+   */
+  void onAfterAssert(IAssert<?> assertCommand);
+}
diff --git a/src/main/java/org/testng/asserts/LoggingAssert.java b/src/main/java/org/testng/asserts/LoggingAssert.java
new file mode 100644
index 0000000..6869b78
--- /dev/null
+++ b/src/main/java/org/testng/asserts/LoggingAssert.java
@@ -0,0 +1,22 @@
+package org.testng.asserts;
+
+import org.testng.collections.Lists;
+
+import java.util.List;
+
+/**
+ * Log the messages of all the assertions that get run.
+ */
+public class LoggingAssert extends Assertion {
+
+  private List<String> m_messages = Lists.newArrayList();
+
+  @Override
+  public void onBeforeAssert(IAssert<?> a) {
+    m_messages.add("Test:" + a.getMessage());
+  }
+
+  public List<String> getMessages() {
+    return m_messages;
+  }
+}
diff --git a/src/main/java/org/testng/asserts/SoftAssert.java b/src/main/java/org/testng/asserts/SoftAssert.java
new file mode 100644
index 0000000..845bdea
--- /dev/null
+++ b/src/main/java/org/testng/asserts/SoftAssert.java
@@ -0,0 +1,46 @@
+package org.testng.asserts;
+
+import java.util.Map;
+
+import org.testng.collections.Maps;
+
+/**
+ * When an assertion fails, don't throw an exception but record the failure.
+ * Calling {@code assertAll()} will cause an exception to be thrown if at
+ * least one assertion failed.
+ */
+public class SoftAssert extends Assertion {
+  // LinkedHashMap to preserve the order
+  private final Map<AssertionError, IAssert<?>> m_errors = Maps.newLinkedHashMap();
+
+  @Override
+  protected void doAssert(IAssert<?> a) {
+    onBeforeAssert(a);
+    try {
+      a.doAssert();
+      onAssertSuccess(a);
+    } catch (AssertionError ex) {
+      onAssertFailure(a, ex);
+      m_errors.put(ex, a);
+    } finally {
+      onAfterAssert(a);
+    }
+  }
+
+  public void assertAll() {
+    if (!m_errors.isEmpty()) {
+      StringBuilder sb = new StringBuilder("The following asserts failed:");
+      boolean first = true;
+      for (Map.Entry<AssertionError, IAssert<?>> ae : m_errors.entrySet()) {
+        if (first) {
+          first = false;
+        } else {
+          sb.append(",");
+        }
+        sb.append("\n\t");
+        sb.append(ae.getKey().getMessage());
+      }
+      throw new AssertionError(sb.toString());
+    }
+  }
+}
diff --git a/src/main/java/org/testng/collections/CollectionUtils.java b/src/main/java/org/testng/collections/CollectionUtils.java
new file mode 100644
index 0000000..cdf2bce
--- /dev/null
+++ b/src/main/java/org/testng/collections/CollectionUtils.java
@@ -0,0 +1,18 @@
+package org.testng.collections;
+
+import java.util.Collection;
+import java.util.Map;
+
+public final class CollectionUtils {
+
+  private CollectionUtils() {}
+
+  public static boolean hasElements(Collection<?> c) {
+    return c != null && ! c.isEmpty();
+  }
+
+  public static boolean hasElements(Map<?, ?> c) {
+    return c != null && ! c.isEmpty();
+  }
+
+}
diff --git a/src/main/java/org/testng/collections/ListMultiMap.java b/src/main/java/org/testng/collections/ListMultiMap.java
new file mode 100644
index 0000000..5750c84
--- /dev/null
+++ b/src/main/java/org/testng/collections/ListMultiMap.java
@@ -0,0 +1,20 @@
+package org.testng.collections;
+
+
+import java.util.List;
+
+/**
+ * A container to hold lists indexed by a key.
+ */
+public class ListMultiMap<K, V> extends MultiMap<K, V, List<V>> {
+
+  @Override
+  protected List<V> createValue() {
+    return Lists.newArrayList();
+  }
+
+  @Deprecated
+  public static <K, V> ListMultiMap<K, V> create() {
+    return Maps.newListMultiMap();
+  }
+}
diff --git a/src/main/java/org/testng/collections/Lists.java b/src/main/java/org/testng/collections/Lists.java
new file mode 100755
index 0000000..90e83df
--- /dev/null
+++ b/src/main/java/org/testng/collections/Lists.java
@@ -0,0 +1,29 @@
+package org.testng.collections;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public final class Lists {
+
+  private Lists() {}
+
+  public static <K> List<K> newArrayList() {
+    return new ArrayList<>();
+  }
+
+  public static <K> List<K> newArrayList(Collection<K> c) {
+    return new ArrayList<>(c);
+  }
+
+  public static <K> List<K> newArrayList(K... elements) {
+    List<K> result = new ArrayList<>();
+    Collections.addAll(result, elements);
+    return result;
+  }
+
+  public static <K> List<K> newArrayList(int size) {
+    return new ArrayList<>(size);
+  }
+}
diff --git a/src/main/java/org/testng/collections/Maps.java b/src/main/java/org/testng/collections/Maps.java
new file mode 100755
index 0000000..692cfce
--- /dev/null
+++ b/src/main/java/org/testng/collections/Maps.java
@@ -0,0 +1,34 @@
+package org.testng.collections;
+
+
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class Maps {
+
+  public static <K, V> Map<K,V> newHashMap() {
+    return new HashMap<>();
+  }
+
+  public static <K, V> Map<K,V> newHashtable() {
+    return new Hashtable<>();
+  }
+
+  public static <K, V> ListMultiMap<K, V> newListMultiMap() {
+    return new ListMultiMap<>();
+  }
+
+  public static <K, V> SetMultiMap<K, V> newSetMultiMap() {
+    return new SetMultiMap<>();
+  }
+
+  public static <K, V> Map<K, V> newLinkedHashMap() {
+    return new LinkedHashMap<>();
+  }
+
+  public static <K, V> Map<K, V> newHashMap(Map<K, V> parameters) {
+    return new HashMap<>(parameters);
+  }
+}
diff --git a/src/main/java/org/testng/collections/MultiMap.java b/src/main/java/org/testng/collections/MultiMap.java
new file mode 100644
index 0000000..c606645
--- /dev/null
+++ b/src/main/java/org/testng/collections/MultiMap.java
@@ -0,0 +1,111 @@
+package org.testng.collections;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public abstract class MultiMap<K, V, C extends Collection<V>> {
+  protected final Map<K, C> m_objects = Maps.newHashMap();
+
+  protected abstract C createValue();
+
+  public boolean put(K key, V method) {
+    boolean setExists = true;
+    C l = m_objects.get(key);
+    if (l == null) {
+      setExists = false;
+      l = createValue();
+      m_objects.put(key, l);
+    }
+    return l.add(method) && setExists;
+  }
+
+  public C get(K key) {
+    return m_objects.get(key);
+  }
+
+  @Deprecated
+  public List<K> getKeys() {
+    return new ArrayList<>(keySet());
+  }
+
+  public Set<K> keySet() {
+    return new HashSet(m_objects.keySet());
+  }
+
+  public boolean containsKey(K k) {
+    return m_objects.containsKey(k);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder result = new StringBuilder();
+    Set<K> indices = keySet();
+    for (K i : indices) {
+      result.append("\n    ").append(i).append(" <-- ");
+      for (Object o : m_objects.get(i)) {
+        result.append(o).append(" ");
+      }
+    }
+    return result.toString();
+  }
+
+  public boolean isEmpty() {
+    return m_objects.size() == 0;
+  }
+
+  @Deprecated
+  public int getSize() {
+    return size();
+  }
+
+  public int size() {
+    return m_objects.size();
+  }
+
+  @Deprecated
+  public C remove(K key) {
+    return removeAll(key);
+  }
+
+  public boolean remove(K key, V value) {
+    C values = get(key);
+    if (values == null) {
+      return false;
+    }
+    return values.remove(value);
+  }
+
+  public C removeAll(K key) {
+    return m_objects.remove(key);
+  }
+
+  @Deprecated
+  public Set<Map.Entry<K, C>> getEntrySet() {
+    return entrySet();
+  }
+
+  public Set<Map.Entry<K, C>> entrySet() {
+    return m_objects.entrySet();
+  }
+
+  @Deprecated
+  public Collection<C> getValues() {
+    return values();
+  }
+
+  public Collection<C> values() {
+    return m_objects.values();
+  }
+
+  public boolean putAll(K k, Collection<? extends V> values) {
+    boolean result = false;
+    for (V v : values) {
+      result = put(k, v) || result;
+    }
+    return result;
+  }
+}
diff --git a/src/main/java/org/testng/collections/Objects.java b/src/main/java/org/testng/collections/Objects.java
new file mode 100644
index 0000000..547a4fa
--- /dev/null
+++ b/src/main/java/org/testng/collections/Objects.java
@@ -0,0 +1,94 @@
+package org.testng.collections;
+
+import org.testng.util.Strings;
+
+import java.util.List;
+
+
+public final class Objects {
+
+  private Objects() {}
+
+  private static class ValueHolder {
+    private String m_name;
+    private String m_value;
+
+    public ValueHolder(String name, String value) {
+      m_name = name;
+      m_value = value;
+    }
+
+    boolean isNull() {
+      return m_value == null;
+    }
+
+    @Override
+    public String toString() {
+      return m_name + "=" + m_value;
+    }
+
+    public boolean isEmptyString() {
+      return Strings.isNullOrEmpty(m_value);
+    }
+  }
+
+  public static class ToStringHelper {
+    private String m_className;
+    private List<ValueHolder> values = Lists.newArrayList();
+    private boolean m_omitNulls = false;
+    private boolean m_omitEmptyStrings = false;
+
+    public ToStringHelper(String className) {
+      m_className = className;
+    }
+
+    public ToStringHelper omitNulls() {
+      m_omitNulls = true;
+      return this;
+    }
+
+    public ToStringHelper omitEmptyStrings() {
+      m_omitEmptyStrings = true;
+      return this;
+    }
+
+    public ToStringHelper add(String name, String value) {
+      values.add(new ValueHolder(name, s(value)));
+      return this;
+    }
+
+    public ToStringHelper add(String name, Object value) {
+      values.add(new ValueHolder(name, s(value)));
+      return this;
+    }
+
+    private String s(Object o) {
+      return o != null
+          ? (o.toString().isEmpty() ? "\"\"" : o.toString())
+          : "{null}";
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder result = new StringBuilder("[" + m_className + " ");
+      for (int i = 0; i < values.size(); i++) {
+        ValueHolder vh = values.get(i);
+        if (m_omitNulls && vh.isNull()) continue;
+        if (m_omitEmptyStrings && vh.isEmptyString()) continue;
+
+        if (i > 0) {
+          result.append(" ");
+        }
+        result.append(vh.toString());
+      }
+      result.append("]");
+
+      return result.toString();
+    }
+  }
+
+  public static ToStringHelper toStringHelper(Class<?> class1) {
+    return new ToStringHelper(class1.getSimpleName());
+  }
+
+}
diff --git a/src/main/java/org/testng/collections/SetMultiMap.java b/src/main/java/org/testng/collections/SetMultiMap.java
new file mode 100644
index 0000000..f85ba25
--- /dev/null
+++ b/src/main/java/org/testng/collections/SetMultiMap.java
@@ -0,0 +1,14 @@
+package org.testng.collections;
+
+import java.util.Set;
+
+/**
+ * A container to hold sets indexed by a key.
+ */
+public class SetMultiMap<K, V> extends MultiMap<K, V, Set<V>> {
+
+  @Override
+  protected Set<V> createValue() {
+    return Sets.newHashSet();
+  }
+}
diff --git a/src/main/java/org/testng/collections/Sets.java b/src/main/java/org/testng/collections/Sets.java
new file mode 100644
index 0000000..a2d3d1b
--- /dev/null
+++ b/src/main/java/org/testng/collections/Sets.java
@@ -0,0 +1,23 @@
+package org.testng.collections;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public final class Sets {
+
+  private Sets() {}
+
+  public static <V> Set<V> newHashSet() {
+    return new HashSet<>();
+  }
+
+  public static <V> Set<V> newHashSet(Collection<V> c) {
+    return new HashSet<>(c);
+  }
+
+  public static <V> Set<V> newLinkedHashSet() {
+    return new LinkedHashSet<>();
+  }
+}
diff --git a/src/main/java/org/testng/internal/Attributes.java b/src/main/java/org/testng/internal/Attributes.java
new file mode 100644
index 0000000..55fd834
--- /dev/null
+++ b/src/main/java/org/testng/internal/Attributes.java
@@ -0,0 +1,40 @@
+package org.testng.internal;
+
+import org.testng.IAttributes;
+import org.testng.collections.Maps;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Simple implementation of IAttributes.
+ *
+ * @author cbeust@google.com (Cedric Beust), March 16th, 2010
+ */
+public class Attributes implements IAttributes {
+  /**
+   *
+   */
+  private static final long serialVersionUID = 6701159979281335152L;
+  private Map<String, Object> m_attributes = Maps.newHashMap();
+
+  @Override
+  public Object getAttribute(String name) {
+    return m_attributes.get(name);
+  }
+
+  @Override
+  public Set<String> getAttributeNames() {
+    return m_attributes.keySet();
+  }
+
+  @Override
+  public void setAttribute(String name, Object value) {
+    m_attributes.put(name, value);
+  }
+
+  @Override
+  public Object removeAttribute(String name) {
+    return m_attributes.remove(name);
+  }
+}
diff --git a/src/main/java/org/testng/internal/BaseClassFinder.java b/src/main/java/org/testng/internal/BaseClassFinder.java
new file mode 100755
index 0000000..3feeb02
--- /dev/null
+++ b/src/main/java/org/testng/internal/BaseClassFinder.java
@@ -0,0 +1,68 @@
+package org.testng.internal;
+
+import org.testng.IClass;
+import org.testng.ITestClassFinder;
+import org.testng.ITestContext;
+import org.testng.ITestObjectFactory;
+import org.testng.collections.Maps;
+import org.testng.internal.annotations.IAnnotationFinder;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlTest;
+
+import java.util.Map;
+
+/**
+ * This class
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+abstract public class BaseClassFinder implements ITestClassFinder {
+  private Map<Class, IClass> m_classes = Maps.newLinkedHashMap();
+
+  @Override
+  public IClass getIClass(Class cls) {
+    return m_classes.get(cls);
+  }
+
+  protected void putIClass(Class cls, IClass iClass) {
+    if (! m_classes.containsKey(cls)) {
+      m_classes.put(cls, iClass);
+    }
+  }
+
+  private void ppp(String s) {
+    System.out.println("[BaseClassFinder] " + s);
+  }
+
+  /**
+   * @param cls
+   * @return An IClass for the given class, or null if we have
+   * already treated this class.
+   */
+  protected IClass findOrCreateIClass(ITestContext context, Class cls, XmlClass xmlClass,
+      Object instance, XmlTest xmlTest, IAnnotationFinder annotationFinder,
+      ITestObjectFactory objectFactory)
+  {
+    IClass result = m_classes.get(cls);
+    if (null == result) {
+      result = new ClassImpl(context, cls, xmlClass, instance, m_classes, xmlTest, annotationFinder,
+          objectFactory);
+      m_classes.put(cls, result);
+    }
+
+    return result;
+  }
+
+  protected Map getExistingClasses() {
+    return m_classes;
+  }
+
+  protected boolean classExists(Class cls) {
+    return m_classes.containsKey(cls);
+  }
+
+  @Override
+  public IClass[] findTestClasses() {
+    return m_classes.values().toArray(new IClass[m_classes.size()]);
+   }
+}
diff --git a/src/main/java/org/testng/internal/BaseTestMethod.java b/src/main/java/org/testng/internal/BaseTestMethod.java
new file mode 100755
index 0000000..b449789
--- /dev/null
+++ b/src/main/java/org/testng/internal/BaseTestMethod.java
@@ -0,0 +1,790 @@
+package org.testng.internal;

+

+import java.lang.reflect.Method;

+import java.util.Arrays;

+import java.util.Collection;

+import java.util.Collections;

+import java.util.Comparator;

+import java.util.List;

+import java.util.Map;

+import java.util.Set;

+import java.util.concurrent.atomic.AtomicInteger;

+import java.util.regex.Pattern;

+

+import org.testng.IClass;

+import org.testng.IRetryAnalyzer;

+import org.testng.ITestClass;

+import org.testng.ITestNGMethod;

+import org.testng.annotations.ITestOrConfiguration;

+import org.testng.collections.Lists;

+import org.testng.collections.Maps;

+import org.testng.collections.Sets;

+import org.testng.internal.annotations.IAnnotationFinder;

+import org.testng.xml.XmlClass;

+import org.testng.xml.XmlInclude;

+import org.testng.xml.XmlTest;

+

+/**

+ * Superclass to represent both &#64;Test and &#64;Configuration methods.

+ */

+public abstract class BaseTestMethod implements ITestNGMethod {

+  private static final long serialVersionUID = -2666032602580652173L;

+  private static final Pattern SPACE_SEPARATOR_PATTERN = Pattern.compile(" +");

+

+  /**

+   * The test class on which the test method was found. Note that this is not

+   * necessarily the declaring class.

+   */

+  protected ITestClass m_testClass;

+

+  protected final transient Class<?> m_methodClass;

+  protected final transient ConstructorOrMethod m_method;

+  private transient String m_signature;

+  protected String m_id = "";

+  protected long m_date = -1;

+  protected final transient IAnnotationFinder m_annotationFinder;

+  protected String[] m_groups = {};

+  protected String[] m_groupsDependedUpon = {};

+  protected String[] m_methodsDependedUpon = {};

+  protected String[] m_beforeGroups = {};

+  protected String[] m_afterGroups = {};

+  private boolean m_isAlwaysRun;

+  private boolean m_enabled;

+

+  private final String m_methodName;

+  // If a depended group is not found

+  private String m_missingGroup;

+  private String m_description = null;

+  protected AtomicInteger m_currentInvocationCount = new AtomicInteger(0);

+  private int m_parameterInvocationCount = 1;

+  private IRetryAnalyzer m_retryAnalyzer = null;

+  private boolean m_skipFailedInvocations = true;

+  private long m_invocationTimeOut = 0L;

+

+  private List<Integer> m_invocationNumbers = Lists.newArrayList();

+  private final List<Integer> m_failedInvocationNumbers = Collections.synchronizedList(Lists.<Integer>newArrayList());

+  private long m_timeOut = 0;

+

+  private boolean m_ignoreMissingDependencies;

+  private int m_priority;

+

+  private XmlTest m_xmlTest;

+  private Object m_instance;

+

+  /**

+   * Constructs a <code>BaseTestMethod</code> TODO cquezel JavaDoc.

+   *

+   * @param method

+   * @param annotationFinder

+   * @param instance 

+   */

+  public BaseTestMethod(String methodName, Method method, IAnnotationFinder annotationFinder, Object instance) {

+    this(methodName, new ConstructorOrMethod(method), annotationFinder, instance);

+  }

+

+  public BaseTestMethod(String methodName, ConstructorOrMethod com, IAnnotationFinder annotationFinder,

+      Object instance) {

+    m_methodClass = com.getDeclaringClass();

+    m_method = com;

+    m_methodName = methodName;

+    m_annotationFinder = annotationFinder;

+    m_instance = instance;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public boolean isAlwaysRun() {

+    return m_isAlwaysRun;

+  }

+

+  /**

+   * TODO cquezel JavaDoc.

+   *

+   * @param alwaysRun

+   */

+  protected void setAlwaysRun(boolean alwaysRun) {

+    m_isAlwaysRun = alwaysRun;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public Class<?> getRealClass() {

+    return m_methodClass;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public ITestClass getTestClass() {

+    return m_testClass;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public void setTestClass(ITestClass tc) {

+    assert null != tc;

+    if (! tc.getRealClass().equals(m_method.getDeclaringClass())) {

+      assert m_method.getDeclaringClass().isAssignableFrom(tc.getRealClass()) :

+        "\nMISMATCH : " + tc.getRealClass() + " " + m_method.getDeclaringClass();

+    }

+    m_testClass = tc;

+  }

+

+  @Override

+  public int compareTo(Object o) {

+    int result = -2;

+    Class<?> thisClass = getRealClass();

+    Class<?> otherClass = ((ITestNGMethod) o).getRealClass();

+    if (this == o) {

+      result = 0;

+    } else if (thisClass.isAssignableFrom(otherClass)) {

+      result = -1;

+    } else if (otherClass.isAssignableFrom(thisClass)) {

+      result = 1;

+    } else if (equals(o)) {

+      result = 0;

+    }

+

+    return result;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public Method getMethod() {

+    return m_method.getMethod();

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public String getMethodName() {

+    return m_methodName;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public Object[] getInstances() {

+    return new Object[] { getInstance() };

+  }

+

+  @Override

+  public Object getInstance() {

+    return m_instance;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public long[] getInstanceHashCodes() {

+    return m_testClass.getInstanceHashCodes();

+  }

+

+  /**

+   * {@inheritDoc}

+   * @return the addition of groups defined on the class and on this method.

+   */

+  @Override

+  public String[] getGroups() {

+    return m_groups;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public String[] getGroupsDependedUpon() {

+    return m_groupsDependedUpon;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public String[] getMethodsDependedUpon() {

+    return m_methodsDependedUpon;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public boolean isTest() {

+    return false;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public boolean isBeforeSuiteConfiguration() {

+    return false;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public boolean isAfterSuiteConfiguration() {

+    return false;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public boolean isBeforeTestConfiguration() {

+    return false;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public boolean isAfterTestConfiguration() {

+    return false;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public boolean isBeforeGroupsConfiguration() {

+    return false;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public boolean isAfterGroupsConfiguration() {

+    return false;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public boolean isBeforeClassConfiguration() {

+    return false;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public boolean isAfterClassConfiguration() {

+    return false;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public boolean isBeforeMethodConfiguration() {

+    return false;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public boolean isAfterMethodConfiguration() {

+    return false;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public long getTimeOut() {

+    long result = m_timeOut != 0 ? m_timeOut : (m_xmlTest != null ? m_xmlTest.getTimeOut(0) : 0);

+    return result;

+  }

+

+  @Override

+  public void setTimeOut(long timeOut) {

+    m_timeOut = timeOut;

+  }

+

+  /**

+   * {@inheritDoc}

+   * @return the number of times this method needs to be invoked.

+   */

+  @Override

+  public int getInvocationCount() {

+    return 1;

+  }

+

+  /**

+   * No-op.

+   */

+  @Override

+  public void setInvocationCount(int counter) {

+  }

+

+  /**

+   * {@inheritDoc}

+   * @return the number of times this method or one of its clones must be invoked.

+   */

+  @Override

+  public int getTotalInvocationCount() {

+    return 1;

+  }

+

+  /**

+   * {@inheritDoc} Default value for successPercentage.

+   */

+  @Override

+  public int getSuccessPercentage() {

+    return 100;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public String getId() {

+    return m_id;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public void setId(String id) {

+    m_id = id;

+  }

+

+

+  /**

+   * {@inheritDoc}

+   * @return Returns the date.

+   */

+  @Override

+  public long getDate() {

+    return m_date;

+  }

+

+  /**

+   * {@inheritDoc}

+   * @param date The date to set.

+   */

+  @Override

+  public void setDate(long date) {

+    m_date = date;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public boolean canRunFromClass(IClass testClass) {

+    return m_methodClass.isAssignableFrom(testClass.getRealClass());

+  }

+

+  /**

+   * {@inheritDoc} Compares two BaseTestMethod using the test class then the associated

+   * Java Method.

+   */

+  @Override

+  public boolean equals(Object obj) {

+    if (this == obj) {

+      return true;

+    }

+    if (obj == null) {

+      return false;

+    }

+    if (getClass() != obj.getClass()) {

+      return false;

+    }

+

+    BaseTestMethod other = (BaseTestMethod) obj;

+

+    boolean isEqual = m_testClass == null ? other.m_testClass == null

+        : other.m_testClass != null &&

+          m_testClass.getRealClass().equals(other.m_testClass.getRealClass())

+          && m_instance == other.getInstance();

+

+    return isEqual && getConstructorOrMethod().equals(other.getConstructorOrMethod());

+  }

+

+  /**

+   * {@inheritDoc} This implementation returns the associated Java Method's hash code.

+   * @return the associated Java Method's hash code.

+   */

+  @Override

+  public int hashCode() {

+    return m_method.hashCode();

+  }

+

+  protected void initGroups(Class<? extends ITestOrConfiguration> annotationClass) {

+    //

+    // Init groups

+    //

+    {

+      ITestOrConfiguration annotation = getAnnotationFinder().findAnnotation(getMethod(), annotationClass);

+      ITestOrConfiguration classAnnotation = getAnnotationFinder().findAnnotation(getMethod().getDeclaringClass(), annotationClass);

+

+      setGroups(getStringArray(null != annotation ? annotation.getGroups() : null,

+          null != classAnnotation ? classAnnotation.getGroups() : null));

+    }

+

+    //

+    // Init groups depended upon

+    //

+    {

+      ITestOrConfiguration annotation = getAnnotationFinder().findAnnotation(getMethod(), annotationClass);

+      ITestOrConfiguration classAnnotation = getAnnotationFinder().findAnnotation(getMethod().getDeclaringClass(), annotationClass);

+

+      Map<String, Set<String>> xgd = calculateXmlGroupDependencies(m_xmlTest);

+      List<String> xmlGroupDependencies = Lists.newArrayList();

+      for (String g : getGroups()) {

+        Set<String> gdu = xgd.get(g);

+        if (gdu != null) {

+          xmlGroupDependencies.addAll(gdu);

+        }

+      }

+      setGroupsDependedUpon(

+          getStringArray(null != annotation ? annotation.getDependsOnGroups() : null,

+          null != classAnnotation ? classAnnotation.getDependsOnGroups() : null),

+          xmlGroupDependencies);

+

+      String[] methodsDependedUpon =

+        getStringArray(null != annotation ? annotation.getDependsOnMethods() : null,

+        null != classAnnotation ? classAnnotation.getDependsOnMethods() : null);

+      // Qualify these methods if they don't have a package

+      for (int i = 0; i < methodsDependedUpon.length; i++) {

+        String m = methodsDependedUpon[i];

+        if (!m.contains(".")) {

+          m = MethodHelper.calculateMethodCanonicalName(m_methodClass, methodsDependedUpon[i]);

+          methodsDependedUpon[i] = m != null ? m : methodsDependedUpon[i];

+        }

+      }

+      setMethodsDependedUpon(methodsDependedUpon);

+    }

+  }

+

+

+  private static Map<String, Set<String>> calculateXmlGroupDependencies(XmlTest xmlTest) {

+    Map<String, Set<String>> result = Maps.newHashMap();

+    if (xmlTest == null) {

+      return result;

+    }

+

+    for (Map.Entry<String, String> e : xmlTest.getXmlDependencyGroups().entrySet()) {

+      String name = e.getKey();

+      String dependsOn = e.getValue();

+      Set<String> set = result.get(name);

+      if (set == null) {

+        set = Sets.newHashSet();

+        result.put(name, set);

+      }

+      set.addAll(Arrays.asList(SPACE_SEPARATOR_PATTERN.split(dependsOn)));

+    }

+

+    return result;

+  }

+

+  protected IAnnotationFinder getAnnotationFinder() {

+    return m_annotationFinder;

+  }

+

+  protected IClass getIClass() {

+    return m_testClass;

+  }

+

+  private String computeSignature() {

+    String classLong = m_method.getDeclaringClass().getName();

+    String cls = classLong.substring(classLong.lastIndexOf(".") + 1);

+    StringBuilder result = new StringBuilder(cls).append(".").append(m_method.getName()).append("(");

+    int i = 0;

+    for (Class<?> p : m_method.getParameterTypes()) {

+      if (i++ > 0) {

+        result.append(", ");

+      }

+      result.append(p.getName());

+    }

+    result.append(")");

+    result.append("[pri:").append(getPriority()).append(", instance:").append(m_instance).append("]");

+

+    return result.toString();

+  }

+

+  protected String getSignature() {

+    if (m_signature == null) {

+      m_signature = computeSignature();

+    }

+    return m_signature;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public String toString() {

+    return getSignature();

+  }

+

+  protected String[] getStringArray(String[] methodArray, String[] classArray) {

+    final Set<String> vResult = Sets.newHashSet();

+    if (null != methodArray) {

+      Collections.addAll(vResult, methodArray);

+    }

+    if (null != classArray) {

+      Collections.addAll(vResult, classArray);

+    }

+    return vResult.toArray(new String[vResult.size()]);

+  }

+

+  protected void setGroups(String[] groups) {

+    m_groups = groups;

+  }

+

+  protected void setGroupsDependedUpon(String[] groups, Collection<String> xmlGroupDependencies) {

+    List<String> l = Lists.newArrayList();

+    l.addAll(Arrays.asList(groups));

+    l.addAll(xmlGroupDependencies);

+    m_groupsDependedUpon = l.toArray(new String[l.size()]);

+  }

+

+  protected void setMethodsDependedUpon(String[] methods) {

+    m_methodsDependedUpon = methods;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public void addMethodDependedUpon(String method) {

+    String[] newMethods = new String[m_methodsDependedUpon.length + 1];

+    newMethods[0] = method;

+    System.arraycopy(m_methodsDependedUpon, 0, newMethods, 1, m_methodsDependedUpon.length);

+    m_methodsDependedUpon = newMethods;

+  }

+

+  private static void ppp(String s) {

+    System.out.println("[BaseTestMethod] " + s);

+  }

+

+  /** Compares two ITestNGMethod by date. */

+  public static final Comparator<?> DATE_COMPARATOR = new Comparator<Object>() {

+    @Override

+    public int compare(Object o1, Object o2) {

+      try {

+        ITestNGMethod m1 = (ITestNGMethod) o1;

+        ITestNGMethod m2 = (ITestNGMethod) o2;

+        return (int) (m1.getDate() - m2.getDate());

+      }

+      catch(Exception ex) {

+        return 0; // TODO CQ document this logic

+      }

+    }

+  };

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public String getMissingGroup() {

+    return m_missingGroup;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public void setMissingGroup(String group) {

+    m_missingGroup = group;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public int getThreadPoolSize() {

+    return 0;

+  }

+

+  /**

+   * No-op.

+   */

+  @Override

+  public void setThreadPoolSize(int threadPoolSize) {

+  }

+

+  @Override

+  public void setDescription(String description) {

+    m_description = description;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public String getDescription() {

+    return m_description;

+  }

+

+  public void setEnabled(boolean enabled) {

+    m_enabled = enabled;

+  }

+

+  @Override

+  public boolean getEnabled() {

+    return m_enabled;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public String[] getBeforeGroups() {

+    return m_beforeGroups;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public String[] getAfterGroups() {

+    return m_afterGroups;

+  }

+

+  @Override

+  public void incrementCurrentInvocationCount() {

+    m_currentInvocationCount.incrementAndGet();

+  }

+

+  @Override

+  public int getCurrentInvocationCount() {

+    return m_currentInvocationCount.get();

+  }

+

+  @Override

+  public void setParameterInvocationCount(int n) {

+    m_parameterInvocationCount = n;

+  }

+

+  @Override

+  public int getParameterInvocationCount() {

+    return m_parameterInvocationCount;

+  }

+

+  @Override

+  public abstract ITestNGMethod clone();

+

+  @Override

+  public IRetryAnalyzer getRetryAnalyzer() {

+    return m_retryAnalyzer;

+  }

+

+  @Override

+  public void setRetryAnalyzer(IRetryAnalyzer retryAnalyzer) {

+    m_retryAnalyzer = retryAnalyzer;

+  }

+

+  @Override

+  public boolean skipFailedInvocations() {

+    return m_skipFailedInvocations;

+  }

+

+  @Override

+  public void setSkipFailedInvocations(boolean s) {

+    m_skipFailedInvocations = s;

+  }

+

+  public void setInvocationTimeOut(long timeOut) {

+    m_invocationTimeOut = timeOut;

+  }

+

+  @Override

+  public long getInvocationTimeOut() {

+    return m_invocationTimeOut;

+  }

+

+  @Override

+  public boolean ignoreMissingDependencies() {

+    return m_ignoreMissingDependencies;

+  }

+

+  @Override

+  public void setIgnoreMissingDependencies(boolean i) {

+    m_ignoreMissingDependencies = i;

+  }

+

+  @Override

+  public List<Integer> getInvocationNumbers() {

+    return m_invocationNumbers;

+  }

+

+  @Override

+  public void setInvocationNumbers(List<Integer> numbers) {

+    m_invocationNumbers = numbers;

+  }

+

+  @Override

+  public List<Integer> getFailedInvocationNumbers() {

+    return m_failedInvocationNumbers;

+  }

+

+  @Override

+  public void addFailedInvocationNumber(int number) {

+    m_failedInvocationNumbers.add(number);

+  }

+

+  @Override

+  public int getPriority() {

+    return m_priority;

+  }

+

+  @Override

+  public void setPriority(int priority) {

+    m_priority = priority;

+  }

+

+  @Override

+  public XmlTest getXmlTest() {

+    return m_xmlTest;

+  }

+

+  public void setXmlTest(XmlTest xmlTest) {

+    m_xmlTest = xmlTest;

+  }

+

+  @Override

+  public ConstructorOrMethod getConstructorOrMethod() {

+    return m_method;

+  }

+

+  @Override

+  public Map<String, String> findMethodParameters(XmlTest test) {

+    // Get the test+suite parameters

+    Map<String, String> result = test.getAllParameters();

+    for (XmlClass xmlClass: test.getXmlClasses()) {

+      if (xmlClass.getName().equals(getTestClass().getName())) {

+        result.putAll(xmlClass.getLocalParameters());

+        for (XmlInclude include : xmlClass.getIncludedMethods()) {

+          if (include.getName().equals(getMethodName())) {

+            result.putAll(include.getLocalParameters());

+            break;

+          }

+        }

+      }

+    }

+

+    return result;

+  }

+}

diff --git a/src/main/java/org/testng/internal/Bsh.java b/src/main/java/org/testng/internal/Bsh.java
new file mode 100644
index 0000000..35ee76e
--- /dev/null
+++ b/src/main/java/org/testng/internal/Bsh.java
@@ -0,0 +1,72 @@
+package org.testng.internal;
+
+import bsh.EvalError;
+import bsh.Interpreter;
+
+import org.testng.ITestNGMethod;
+import org.testng.TestNGException;
+import org.testng.collections.Maps;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+public class Bsh implements IBsh {
+  private static Interpreter s_interpreter;
+
+  @Override
+  public boolean includeMethodFromExpression(String expression, ITestNGMethod tm) {
+    boolean result = false;
+
+    Interpreter interpreter = getInterpreter();
+    try {
+      Map<String, String> groups = Maps.newHashMap();
+      for (String group : tm.getGroups()) {
+        groups.put(group, group);
+      }
+      setContext(interpreter, tm.getMethod(), groups, tm);
+      Object evalResult = interpreter.eval(expression);
+      result = (Boolean) evalResult;
+    }
+    catch (EvalError evalError) {
+      Utils.log("bsh.Interpreter", 2, "Cannot evaluate expression:"
+          + expression + ":" + evalError.getMessage());
+    }
+    finally {
+      resetContext(interpreter);
+    }
+
+    return result;
+
+  }
+
+  private static Interpreter getInterpreter() {
+    if(null == s_interpreter) {
+      s_interpreter= new Interpreter();
+    }
+
+    return s_interpreter;
+  }
+
+  private void setContext(Interpreter interpreter, Method method, Map<String, String> groups, ITestNGMethod tm) {
+    try {
+      interpreter.set("method", method);
+      interpreter.set("groups", groups);
+      interpreter.set("testngMethod", tm);
+    }
+    catch(EvalError evalError) {
+      throw new TestNGException("Cannot set BSH interpreter", evalError);
+    }
+  }
+
+  private void resetContext(Interpreter interpreter) {
+    try {
+      interpreter.unset("method");
+      interpreter.unset("groups");
+      interpreter.unset("testngMethod");
+    }
+    catch(EvalError evalError) {
+      Utils.log("bsh.Interpreter", 2, "Cannot reset interpreter:" + evalError.getMessage());
+    }
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/BshMock.java b/src/main/java/org/testng/internal/BshMock.java
new file mode 100644
index 0000000..840bf6c
--- /dev/null
+++ b/src/main/java/org/testng/internal/BshMock.java
@@ -0,0 +1,12 @@
+package org.testng.internal;
+
+import org.testng.ITestNGMethod;
+
+public class BshMock implements IBsh {
+
+  @Override
+  public boolean includeMethodFromExpression(String expression, ITestNGMethod tm) {
+    return false;
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/ClassHelper.java b/src/main/java/org/testng/internal/ClassHelper.java
new file mode 100644
index 0000000..cf4aa74
--- /dev/null
+++ b/src/main/java/org/testng/internal/ClassHelper.java
@@ -0,0 +1,599 @@
+package org.testng.internal;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+import org.testng.IClass;
+import org.testng.IMethodSelector;
+import org.testng.IObjectFactory;
+import org.testng.IObjectFactory2;
+import org.testng.ITestObjectFactory;
+import org.testng.TestNGException;
+import org.testng.TestRunner;
+import org.testng.annotations.IAnnotation;
+import org.testng.annotations.IFactoryAnnotation;
+import org.testng.annotations.IParametersAnnotation;
+import org.testng.collections.Sets;
+import org.testng.internal.annotations.IAnnotationFinder;
+import org.testng.junit.IJUnitTestRunner;
+import org.testng.xml.XmlTest;
+
+/**
+ * Utility class for different class manipulations.
+ */
+public final class ClassHelper {
+  private static final String JUNIT_TESTRUNNER= "org.testng.junit.JUnitTestRunner";
+  private static final String JUNIT_4_TESTRUNNER = "org.testng.junit.JUnit4TestRunner";
+
+  /** The additional class loaders to find classes in. */
+  private static final List<ClassLoader> m_classLoaders = new Vector<>();
+
+  /** Add a class loader to the searchable loaders. */
+  public static void addClassLoader(final ClassLoader loader) {
+    m_classLoaders.add(loader);
+  }
+
+  /** Hide constructor. */
+  private ClassHelper() {
+    // Hide Constructor
+  }
+
+  public static <T> T newInstance(Class<T> clazz) {
+    try {
+      T instance = clazz.newInstance();
+
+      return instance;
+    }
+    catch(IllegalAccessException iae) {
+      throw new TestNGException("Class " + clazz.getName()
+          + " does not have a no-args constructor", iae);
+    }
+    catch(InstantiationException ie) {
+      throw new TestNGException("Cannot instantiate class " + clazz.getName(), ie);
+    }
+    catch(ExceptionInInitializerError eiierr) {
+      throw new TestNGException("An exception occurred in static initialization of class "
+          + clazz.getName(), eiierr);
+    }
+    catch(SecurityException se) {
+      throw new TestNGException(se);
+    }
+  }
+
+  public static <T> T newInstance(Constructor<T> constructor, Object... parameters) {
+    try {
+      return constructor.newInstance(parameters);
+    } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
+      throw new TestNGException("Cannot instantiate class " + constructor.getDeclaringClass().getName(), e);
+    }
+  }
+
+  /**
+   * Tries to load the specified class using the context ClassLoader or if none,
+   * than from the default ClassLoader. This method differs from the standard
+   * class loading methods in that it does not throw an exception if the class
+   * is not found but returns null instead.
+   *
+   * @param className the class name to be loaded.
+   *
+   * @return the class or null if the class is not found.
+   */
+  public static Class<?> forName(final String className) {
+    Vector<ClassLoader> allClassLoaders = new Vector<>();
+    ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+    if (contextClassLoader != null) {
+      allClassLoaders.add(contextClassLoader);
+    }
+    if (m_classLoaders != null) {
+      allClassLoaders.addAll(m_classLoaders);
+    }
+
+    for (ClassLoader classLoader : allClassLoaders) {
+      if (null == classLoader) {
+        continue;
+      }
+      try {
+        return classLoader.loadClass(className);
+      }
+      catch(ClassNotFoundException ex) {
+        // With additional class loaders, it is legitimate to ignore ClassNotFoundException
+        if (null == m_classLoaders || m_classLoaders.size() == 0) {
+          logClassNotFoundError(className, ex);
+        }
+      }
+    }
+
+    try {
+      return Class.forName(className);
+    }
+    catch(ClassNotFoundException cnfe) {
+      logClassNotFoundError(className, cnfe);
+      return null;
+    }
+  }
+
+  private static void logClassNotFoundError(String className, Exception ex) {
+    Utils.log("ClassHelper", 2, "Could not instantiate " + className
+        + " : Class doesn't exist (" + ex.getMessage() + ")");
+  }
+
+  /**
+   * For the given class, returns the method annotated with &#64;Factory or null
+   * if none is found. This method does not search up the superclass hierarchy.
+   * If more than one method is @Factory annotated, a TestNGException is thrown.
+   * @param cls The class to search for the @Factory annotation.
+   * @param finder The finder (JDK 1.4 or JDK 5.0+) use to search for the annotation.
+   *
+   * @return the @Factory <CODE>method</CODE> or null
+   */
+  public static ConstructorOrMethod findDeclaredFactoryMethod(Class<?> cls,
+      IAnnotationFinder finder) {
+    ConstructorOrMethod result = null;
+
+    for (Method method : getAvailableMethods(cls)) {
+      IFactoryAnnotation f = finder.findAnnotation(method, IFactoryAnnotation.class);
+
+      if (null != f) {
+        result = new ConstructorOrMethod(method);
+        result.setEnabled(f.getEnabled());
+        break;
+      }
+    }
+
+    if (result == null) {
+      for (Constructor constructor : cls.getDeclaredConstructors()) {
+        IAnnotation f = finder.findAnnotation(constructor, IFactoryAnnotation.class);
+        if (f != null) {
+          result = new ConstructorOrMethod(constructor);
+        }
+      }
+    }
+    // If we didn't find anything, look for nested classes
+//    if (null == result) {
+//      Class[] subClasses = cls.getClasses();
+//      for (Class subClass : subClasses) {
+//        result = findFactoryMethod(subClass, finder);
+//        if (null != result) {
+//          break;
+//        }
+//      }
+//    }
+
+    return result;
+  }
+
+  /**
+   * Extract all callable methods of a class and all its super (keeping in mind
+   * the Java access rules).
+   */
+  public static Set<Method> getAvailableMethods(Class<?> clazz) {
+    Set<Method> methods = Sets.newHashSet();
+    methods.addAll(Arrays.asList(clazz.getDeclaredMethods()));
+
+    Class<?> parent = clazz.getSuperclass();
+    while (null != parent) {
+      methods.addAll(extractMethods(clazz, parent, methods));
+      parent = parent.getSuperclass();
+    }
+
+    return methods;
+  }
+
+  public static IJUnitTestRunner createTestRunner(TestRunner runner) {
+      try {
+          //try to get runner for JUnit 4 first
+          Class.forName("org.junit.Test");
+          IJUnitTestRunner tr = (IJUnitTestRunner) ClassHelper.forName(JUNIT_4_TESTRUNNER).newInstance();
+          tr.setTestResultNotifier(runner);
+          return tr;
+      } catch (Throwable t) {
+          Utils.log("ClassHelper", 2, "JUnit 4 was not found on the classpath");
+          try {
+              //fallback to JUnit 3
+              Class.forName("junit.framework.Test");
+              IJUnitTestRunner tr = (IJUnitTestRunner) ClassHelper.forName(JUNIT_TESTRUNNER).newInstance();
+              tr.setTestResultNotifier(runner);
+
+              return tr;
+          } catch (Exception ex) {
+              Utils.log("ClassHelper", 2, "JUnit 3 was not found on the classpath");
+              //there's no JUnit on the classpath
+              throw new TestNGException("Cannot create JUnit runner", ex);
+          }
+      }
+  }
+
+  private static Set<Method> extractMethods(Class<?> childClass, Class<?> clazz,
+      Set<Method> collected) {
+    Set<Method> methods = Sets.newHashSet();
+
+    Method[] declaredMethods = clazz.getDeclaredMethods();
+
+    Package childPackage = childClass.getPackage();
+    Package classPackage = clazz.getPackage();
+    boolean isSamePackage = false;
+
+    if ((null == childPackage) && (null == classPackage)) {
+      isSamePackage = true;
+    }
+    if ((null != childPackage) && (null != classPackage)) {
+      isSamePackage = childPackage.getName().equals(classPackage.getName());
+    }
+
+    for (Method method : declaredMethods) {
+      int methodModifiers = method.getModifiers();
+      if ((Modifier.isPublic(methodModifiers) || Modifier.isProtected(methodModifiers))
+        || (isSamePackage && !Modifier.isPrivate(methodModifiers))) {
+        if (!isOverridden(method, collected) && !Modifier.isAbstract(methodModifiers)) {
+          methods.add(method);
+        }
+      }
+    }
+
+    return methods;
+  }
+
+  private static boolean isOverridden(Method method, Set<Method> collectedMethods) {
+    Class<?> methodClass = method.getDeclaringClass();
+    Class<?>[] methodParams = method.getParameterTypes();
+
+    for (Method m: collectedMethods) {
+      Class<?>[] paramTypes = m.getParameterTypes();
+      if (method.getName().equals(m.getName())
+         && methodClass.isAssignableFrom(m.getDeclaringClass())
+         && methodParams.length == paramTypes.length) {
+
+        boolean sameParameters = true;
+        for (int i= 0; i < methodParams.length; i++) {
+          if (!methodParams[i].equals(paramTypes[i])) {
+            sameParameters = false;
+            break;
+          }
+        }
+
+        if (sameParameters) {
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+
+  public static IMethodSelector createSelector(org.testng.xml.XmlMethodSelector selector) {
+    try {
+      Class<?> cls = Class.forName(selector.getClassName());
+      return (IMethodSelector) cls.newInstance();
+    }
+    catch(Exception ex) {
+      throw new TestNGException("Couldn't find method selector : " + selector.getClassName(), ex);
+    }
+  }
+
+  /**
+   * Create an instance for the given class.
+   */
+  public static Object createInstance(Class<?> declaringClass,
+      Map<Class, IClass> classes,
+      XmlTest xmlTest,
+      IAnnotationFinder finder,
+      ITestObjectFactory objectFactory)
+  {
+    if (objectFactory instanceof IObjectFactory) {
+      return createInstance1(declaringClass, classes, xmlTest, finder,
+          (IObjectFactory) objectFactory);
+    } else if (objectFactory instanceof IObjectFactory2) {
+      return createInstance2(declaringClass, (IObjectFactory2) objectFactory);
+    } else {
+      throw new AssertionError("Unknown object factory type:" + objectFactory);
+    }
+  }
+
+  private static Object createInstance2(Class<?> declaringClass, IObjectFactory2 objectFactory) {
+    return objectFactory.newInstance(declaringClass);
+  }
+
+  public static Object createInstance1(Class<?> declaringClass,
+                                      Map<Class, IClass> classes,
+                                      XmlTest xmlTest,
+                                      IAnnotationFinder finder,
+                                      IObjectFactory objectFactory) {
+    Object result = null;
+
+    try {
+
+      //
+      // Any annotated constructor?
+      //
+      Constructor<?> constructor = findAnnotatedConstructor(finder, declaringClass);
+      if (null != constructor) {
+        IParametersAnnotation annotation = finder.findAnnotation(constructor, IParametersAnnotation.class);
+
+        String[] parameterNames = annotation.getValue();
+        Object[] parameters = Parameters.createInstantiationParameters(constructor,
+                                                          "@Parameters",
+                                                          finder,
+                                                          parameterNames,
+                                                          xmlTest.getAllParameters(),
+                                                          xmlTest.getSuite());
+        result = objectFactory.newInstance(constructor, parameters);
+      }
+
+      //
+      // No, just try to instantiate the parameterless constructor (or the one
+      // with a String)
+      //
+      else {
+
+        // If this class is a (non-static) nested class, the constructor contains a hidden
+        // parameter of the type of the enclosing class
+        Class<?>[] parameterTypes = new Class[0];
+        Object[] parameters = new Object[0];
+        Class<?> ec = getEnclosingClass(declaringClass);
+        boolean isStatic = 0 != (declaringClass.getModifiers() & Modifier.STATIC);
+
+        // Only add the extra parameter if the nested class is not static
+        if ((null != ec) && !isStatic) {
+          parameterTypes = new Class[] { ec };
+
+          // Create an instance of the enclosing class so we can instantiate
+          // the nested class (actually, we reuse the existing instance).
+          IClass enclosingIClass = classes.get(ec);
+          Object[] enclosingInstances;
+          if (null != enclosingIClass) {
+            enclosingInstances = enclosingIClass.getInstances(false);
+            if ((null == enclosingInstances) || (enclosingInstances.length == 0)) {
+              Object o = objectFactory.newInstance(ec.getConstructor(parameterTypes));
+              enclosingIClass.addInstance(o);
+              enclosingInstances = new Object[] { o };
+            }
+          }
+          else {
+            enclosingInstances = new Object[] { ec.newInstance() };
+          }
+          Object enclosingClassInstance = enclosingInstances[0];
+
+          // Utils.createInstance(ec, classes, xmlTest, finder);
+          parameters = new Object[] { enclosingClassInstance };
+        } // isStatic
+
+        Constructor<?> ct;
+        try {
+          ct = declaringClass.getDeclaredConstructor(parameterTypes);
+        }
+        catch (NoSuchMethodException ex) {
+          ct = declaringClass.getDeclaredConstructor(String.class);
+          parameters = new Object[] { "Default test name" };
+          // If ct == null here, we'll pass a null
+          // constructor to the factory and hope it can deal with it
+        }
+        result = objectFactory.newInstance(ct, parameters);
+      }
+    }
+    catch (TestNGException ex) {
+      throw ex;
+//      throw new TestNGException("Couldn't instantiate class:" + declaringClass);
+    }
+    catch (NoSuchMethodException ex) {
+    }
+    catch (Throwable cause) {
+      // Something else went wrong when running the constructor
+      throw new TestNGException("An error occurred while instantiating class "
+          + declaringClass.getName() + ": " + cause.getMessage(), cause);
+    }
+
+    if (result == null) {
+      if (! Modifier.isPublic(declaringClass.getModifiers())) {
+        //result should not be null
+        throw new TestNGException("An error occurred while instantiating class "
+            + declaringClass.getName() + ". Check to make sure it can be accessed/instantiated.");
+//      } else {
+//        Utils.log(ClassHelper.class.getName(), 2, "Couldn't instantiate class " + declaringClass);
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Class.getEnclosingClass() only exists on JDK5, so reimplementing it
+   * here.
+   */
+  private static Class<?> getEnclosingClass(Class<?> declaringClass) {
+    Class<?> result = null;
+
+    String className = declaringClass.getName();
+    int index = className.indexOf("$");
+    if (index != -1) {
+      String ecn = className.substring(0, index);
+      try {
+        result = Class.forName(ecn);
+      }
+      catch (ClassNotFoundException e) {
+        e.printStackTrace();
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Find the best constructor given the parameters found on the annotation
+   */
+  private static Constructor<?> findAnnotatedConstructor(IAnnotationFinder finder,
+                                                      Class<?> declaringClass) {
+    Constructor<?>[] constructors = declaringClass.getDeclaredConstructors();
+
+    for (Constructor<?> result : constructors) {
+      IParametersAnnotation annotation = finder.findAnnotation(result, IParametersAnnotation.class);
+
+      if (null != annotation) {
+        String[] parameters = annotation.getValue();
+        Class<?>[] parameterTypes = result.getParameterTypes();
+        if (parameters.length != parameterTypes.length) {
+          throw new TestNGException("Parameter count mismatch:  " + result + "\naccepts "
+                                    + parameterTypes.length
+                                    + " parameters but the @Test annotation declares "
+                                    + parameters.length);
+        }
+        else {
+          return result;
+        }
+      }
+    }
+
+    return null;
+  }
+
+  public static <T> T tryOtherConstructor(Class<T> declaringClass) {
+    T result;
+    try {
+      // Special case for inner classes
+      if (declaringClass.getModifiers() == 0) {
+        return null;
+      }
+
+      Constructor<T> ctor = declaringClass.getConstructor(String.class);
+      result = ctor.newInstance("Default test name");
+    }
+    catch (Exception e) {
+      String message = e.getMessage();
+      if ((message == null) && (e.getCause() != null)) {
+        message = e.getCause().getMessage();
+      }
+      String error = "Could not create an instance of class " + declaringClass
+      + ((message != null) ? (": " + message) : "")
+        + ".\nPlease make sure it has a constructor that accepts either a String or no parameter.";
+      throw new TestNGException(error);
+    }
+
+    return result;
+  }
+
+  /**
+   * When given a file name to form a class name, the file name is parsed and divided
+   * into segments. For example, "c:/java/classes/com/foo/A.class" would be divided
+   * into 6 segments {"C:" "java", "classes", "com", "foo", "A"}. The first segment
+   * actually making up the class name is [3]. This value is saved in m_lastGoodRootIndex
+   * so that when we parse the next file name, we will try 3 right away. If 3 fails we
+   * will take the long approach. This is just a optimization cache value.
+   */
+  private static int m_lastGoodRootIndex = -1;
+
+  /**
+   * Returns the Class object corresponding to the given name. The name may be
+   * of the following form:
+   * <ul>
+   * <li>A class name: "org.testng.TestNG"</li>
+   * <li>A class file name: "/testng/src/org/testng/TestNG.class"</li>
+   * <li>A class source name: "d:\testng\src\org\testng\TestNG.java"</li>
+   * </ul>
+   *
+   * @param file
+   *          the class name.
+   * @return the class corresponding to the name specified.
+   */
+  public static Class<?> fileToClass(String file) {
+    Class<?> result = null;
+
+    if(!file.endsWith(".class") && !file.endsWith(".java")) {
+      // Doesn't end in .java or .class, assume it's a class name
+
+      if (file.startsWith("class ")) {
+        file = file.substring("class ".length());
+      }
+
+      result = ClassHelper.forName(file);
+
+      if (null == result) {
+        throw new TestNGException("Cannot load class from file: " + file);
+      }
+
+      return result;
+    }
+
+    int classIndex = file.lastIndexOf(".class");
+    if (-1 == classIndex) {
+      classIndex = file.lastIndexOf(".java");
+//
+//      if(-1 == classIndex) {
+//        result = ClassHelper.forName(file);
+//
+//        if (null == result) {
+//          throw new TestNGException("Cannot load class from file: " + file);
+//        }
+//
+//        return result;
+//      }
+//
+    }
+
+    // Transforms the file name into a class name.
+
+    // Remove the ".class" or ".java" extension.
+    String shortFileName = file.substring(0, classIndex);
+
+    // Split file name into segments. For example "c:/java/classes/com/foo/A"
+    // becomes {"c:", "java", "classes", "com", "foo", "A"}
+    String[] segments = shortFileName.split("[/\\\\]", -1);
+
+    //
+    // Check if the last good root index works for this one. For example, if the previous
+    // name was "c:/java/classes/com/foo/A.class" then m_lastGoodRootIndex is 3 and we
+    // try to make a class name ignoring the first m_lastGoodRootIndex segments (3). This
+    // will succeed rapidly if the path is the same as the one from the previous name.
+    //
+    if (-1 != m_lastGoodRootIndex) {
+
+      StringBuilder className = new StringBuilder(segments[m_lastGoodRootIndex]);
+      for (int i = m_lastGoodRootIndex + 1; i < segments.length; i++) {
+        className.append(".").append(segments[i]);
+      }
+
+      result = ClassHelper.forName(className.toString());
+
+      if (null != result) {
+        return result;
+      }
+    }
+
+    //
+    // We haven't found a good root yet, start by resolving the class from the end segment
+    // and work our way up.  For example, if we start with "c:/java/classes/com/foo/A"
+    // we'll start by resolving "A", then "foo.A", then "com.foo.A" until something
+    // resolves.  When it does, we remember the path we are at as "lastGoodRoodIndex".
+    //
+
+    // TODO CQ use a StringBuffer here
+    String className = null;
+    for (int i = segments.length - 1; i >= 0; i--) {
+      if (null == className) {
+        className = segments[i];
+      }
+      else {
+        className = segments[i] + "." + className;
+      }
+
+      result = ClassHelper.forName(className);
+
+      if (null != result) {
+        m_lastGoodRootIndex = i;
+        break;
+      }
+    }
+
+    if (null == result) {
+      throw new TestNGException("Cannot load class from file: " + file);
+    }
+
+    return result;
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/ClassImpl.java b/src/main/java/org/testng/internal/ClassImpl.java
new file mode 100755
index 0000000..60a8c4f
--- /dev/null
+++ b/src/main/java/org/testng/internal/ClassImpl.java
@@ -0,0 +1,216 @@
+package org.testng.internal;
+
+import static org.testng.internal.Utils.isStringNotEmpty;
+
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import com.google.inject.Stage;
+
+import org.testng.IClass;
+import org.testng.IModuleFactory;
+import org.testng.ISuite;
+import org.testng.ITest;
+import org.testng.ITestContext;
+import org.testng.ITestObjectFactory;
+import org.testng.TestNGException;
+import org.testng.annotations.Guice;
+import org.testng.annotations.ITestAnnotation;
+import org.testng.collections.Lists;
+import org.testng.collections.Objects;
+import org.testng.internal.annotations.AnnotationHelper;
+import org.testng.internal.annotations.IAnnotationFinder;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlTest;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of an IClass.
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public class ClassImpl implements IClass {
+  private static final long serialVersionUID = 1118178273317520344L;
+  transient private Class m_class = null;
+  transient private Object m_defaultInstance = null;
+  private XmlTest m_xmlTest = null;
+  transient private IAnnotationFinder m_annotationFinder = null;
+  transient private List<Object> m_instances = Lists.newArrayList();
+  transient private Map<Class, IClass> m_classes = null;
+  private int m_instanceCount;
+  private long[] m_instanceHashCodes;
+  private transient Object m_instance;
+  private ITestObjectFactory m_objectFactory;
+  private String m_testName = null;
+  private XmlClass m_xmlClass;
+  private ITestContext m_testContext;
+  private final boolean m_hasParentModule;
+
+  public ClassImpl(ITestContext context, Class cls, XmlClass xmlClass, Object instance,
+      Map<Class, IClass> classes, XmlTest xmlTest, IAnnotationFinder annotationFinder,
+      ITestObjectFactory objectFactory) {
+    m_testContext = context;
+    m_class = cls;
+    m_classes = classes;
+    m_xmlClass = xmlClass;
+    m_xmlTest = xmlTest;
+    m_annotationFinder = annotationFinder;
+    m_instance = instance;
+    m_objectFactory = objectFactory;
+    if (instance instanceof ITest) {
+      m_testName = ((ITest) instance).getTestName();
+    }
+    if (m_testName == null) {
+      ITestAnnotation annotation = m_annotationFinder.findAnnotation(cls, ITestAnnotation.class);
+      if (annotation != null) {
+        m_testName = annotation.getTestName();
+      }
+    }
+    m_hasParentModule = isStringNotEmpty(m_testContext.getSuite().getParentModule());
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[ClassImpl] " + s);
+  }
+
+  @Override
+  public String getTestName() {
+    return m_testName;
+  }
+
+  @Override
+  public String getName() {
+    return m_class.getName();
+  }
+
+  @Override
+  public Class getRealClass() {
+    return m_class;
+  }
+
+  @Override
+  public int getInstanceCount() {
+    return m_instanceCount;
+  }
+
+  @Override
+  public long[] getInstanceHashCodes() {
+    return m_instanceHashCodes;
+  }
+
+  @Override
+  public XmlTest getXmlTest() {
+    return m_xmlTest;
+  }
+
+  @Override
+  public XmlClass getXmlClass() {
+    return m_xmlClass;
+  }
+
+  private Object getDefaultInstance() {
+    if (m_defaultInstance == null) {
+      if (m_instance != null) {
+        m_defaultInstance = m_instance;
+      } else {
+        Object instance = getInstanceFromGuice();
+
+        if (instance != null) {
+          m_defaultInstance = instance;
+        } else {
+          m_defaultInstance =
+              ClassHelper.createInstance(m_class, m_classes, m_xmlTest,
+                  m_annotationFinder, m_objectFactory);
+        }
+      }
+    }
+
+    return m_defaultInstance;
+  }
+
+  /**
+   * @return an instance from Guice if @Test(guiceModule) attribute was found, null otherwise
+   */
+  @SuppressWarnings("unchecked")
+  private Object getInstanceFromGuice() {
+    Injector injector = m_testContext.getInjector(this);
+    if (injector == null) return null;
+    return injector.getInstance(m_class);
+  }
+
+  public Injector getParentInjector() {
+    ISuite suite = m_testContext.getSuite();
+    // Reuse the previous parent injector, if any
+    Injector injector = suite.getParentInjector();
+    if (injector == null) {
+      String stageString = suite.getGuiceStage();
+      Stage stage;
+      if (isStringNotEmpty(stageString)) {
+        stage = Stage.valueOf(stageString);
+      } else {
+        stage = Stage.DEVELOPMENT;
+      }
+      if (m_hasParentModule) {
+        Class<Module> parentModule = (Class<Module>) ClassHelper.forName(suite.getParentModule());
+        if (parentModule == null) {
+          throw new TestNGException("Cannot load parent Guice module class: " + parentModule);
+        }
+        Module module = newModule(parentModule);
+        injector = com.google.inject.Guice.createInjector(stage, module);
+      } else {
+        injector = com.google.inject.Guice.createInjector(stage);
+      }
+      suite.setParentInjector(injector);
+    }
+    return injector;
+  }
+
+  private Module newModule(Class<Module> module) {
+    try {
+      Constructor<Module> moduleConstructor = module.getDeclaredConstructor(ITestContext.class);
+      return ClassHelper.newInstance(moduleConstructor, m_testContext);
+    } catch (NoSuchMethodException e) {
+      return ClassHelper.newInstance(module);
+    }
+  }
+
+  @Override
+  public Object[] getInstances(boolean create) {
+    Object[] result = {};
+
+    if (m_xmlTest.isJUnit()) {
+      if (create) {
+        result = new Object[] { ClassHelper.createInstance(m_class, m_classes,
+            m_xmlTest, m_annotationFinder, m_objectFactory) };
+      }
+    } else {
+      result = new Object[] { getDefaultInstance() };
+    }
+    if (m_instances.size() > 0) {
+      result = m_instances.toArray(new Object[m_instances.size()]);
+    }
+
+    m_instanceCount = m_instances.size();
+    m_instanceHashCodes = new long[m_instanceCount];
+    for (int i = 0; i < m_instanceCount; i++) {
+      m_instanceHashCodes[i] = m_instances.get(i).hashCode();
+    }
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return Objects.toStringHelper(getClass())
+        .add("class", m_class.getName())
+        .toString();
+  }
+
+  @Override
+  public void addInstance(Object instance) {
+    m_instances.add(instance);
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/ClassInfoMap.java b/src/main/java/org/testng/internal/ClassInfoMap.java
new file mode 100644
index 0000000..0853bde
--- /dev/null
+++ b/src/main/java/org/testng/internal/ClassInfoMap.java
@@ -0,0 +1,65 @@
+package org.testng.internal;
+
+import org.testng.collections.Maps;
+import org.testng.xml.XmlClass;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class ClassInfoMap {
+  private Map<Class<?>, XmlClass> m_map = Maps.newLinkedHashMap();
+  private boolean includeNestedClasses;
+
+  public ClassInfoMap() {
+  }
+
+  public ClassInfoMap(List<XmlClass> classes) {
+    this(classes, true);
+  }
+
+  public ClassInfoMap(List<XmlClass> classes, boolean includeNested) {
+    includeNestedClasses = includeNested;
+    for (XmlClass xmlClass : classes) {
+      try {
+        Class c = xmlClass.getSupportClass();
+        registerClass(c, xmlClass);
+      } catch (NoClassDefFoundError e) {
+        Utils.log("[ClassInfoMap]", 1, "Unable to open class " + xmlClass.getName()
+            + " - unable to resolve class reference " + e.getMessage());
+        if (xmlClass.loadClasses()) {
+          throw e;
+        }
+      }
+    }
+  }
+
+  private void registerClass(Class cl, XmlClass xmlClass) {
+    m_map.put(cl, xmlClass);
+    if (includeNestedClasses) {
+      for (Class c : cl.getClasses()) {
+        if (! m_map.containsKey(c)) registerClass(c, xmlClass);
+      }
+    }
+  }
+
+  public void addClass(Class<?> cls) {
+    m_map.put(cls, null);
+  }
+
+  public XmlClass getXmlClass(Class<?> cls) {
+    return m_map.get(cls);
+  }
+
+  public void put(Class<?> cls, XmlClass xmlClass) {
+    m_map.put(cls, xmlClass);
+  }
+
+  public Set<Class<?>> getClasses() {
+    return m_map.keySet();
+  }
+
+  public int getSize() {
+    return m_map.size();
+  }
+}
diff --git a/src/main/java/org/testng/internal/ClonedMethod.java b/src/main/java/org/testng/internal/ClonedMethod.java
new file mode 100755
index 0000000..1a8ff08
--- /dev/null
+++ b/src/main/java/org/testng/internal/ClonedMethod.java
@@ -0,0 +1,381 @@
+package org.testng.internal;
+
+import org.testng.IClass;
+import org.testng.IRetryAnalyzer;
+import org.testng.ITestClass;
+import org.testng.ITestNGMethod;
+import org.testng.collections.Lists;
+import org.testng.xml.XmlTest;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class ClonedMethod implements ITestNGMethod {
+  private static final long serialVersionUID = 1L;
+
+  private ITestNGMethod m_method;
+  transient private Method m_javaMethod;
+  private String m_id;
+  private int m_currentInvocationCount;
+  private long m_date;
+
+  private List<Integer> m_invocationNumbers = Lists.newArrayList();
+  private List<Integer> m_failedInvocationNumbers = Lists.newArrayList();
+
+  public ClonedMethod(ITestNGMethod method, Method javaMethod) {
+    m_method = method;
+    m_javaMethod = javaMethod;
+  }
+
+  @Override
+  public void addMethodDependedUpon(String methodName) {
+    // nop
+  }
+
+  @Override
+  public boolean canRunFromClass(IClass testClass) {
+    return m_method.canRunFromClass(testClass);
+  }
+
+  @Override
+  public String[] getAfterGroups() {
+    return m_method.getAfterGroups();
+  }
+
+  @Override
+  public String[] getBeforeGroups() {
+    return m_method.getBeforeGroups();
+  }
+
+  @Override
+  public int getCurrentInvocationCount() {
+    return m_currentInvocationCount;
+  }
+
+  @Override
+  public long getDate() {
+    return m_method.getDate();
+  }
+
+  @Override
+  public String getDescription() {
+    return "";
+  }
+
+  @Override
+  public void setDescription(String description) {
+    m_method.setDescription(description);
+  }
+
+  @Override
+  public boolean getEnabled() {
+    return true;
+  }
+
+  @Override
+  public String[] getGroups() {
+    return m_method.getGroups();
+  }
+
+  @Override
+  public String[] getGroupsDependedUpon() {
+    return new String[0];
+  }
+
+  @Override
+  public String getId() {
+    return m_id;
+  }
+
+  @Override
+  public long[] getInstanceHashCodes() {
+    return m_method.getInstanceHashCodes();
+  }
+
+  @Override
+  public Object[] getInstances() {
+    return m_method.getInstances();
+  }
+
+  @Override
+  public Object getInstance() {
+    return m_method.getInstance();
+  }
+
+  @Override
+  public int getInvocationCount() {
+    return 1;
+  }
+
+  @Override
+  public int getTotalInvocationCount() {
+    return 1;
+  }
+
+  @Override
+  public long getInvocationTimeOut() {
+    return m_method.getInvocationTimeOut();
+  }
+
+  @Override
+  public Method getMethod() {
+    return m_javaMethod;
+  }
+
+  @Override
+  public String getMethodName() {
+    return m_javaMethod.getName();
+  }
+
+  @Override
+  public String[] getMethodsDependedUpon() {
+    return new String[0];
+  }
+
+  @Override
+  public String getMissingGroup() {
+    return null;
+  }
+
+  @Override
+  public int getParameterInvocationCount() {
+    return 1;
+  }
+
+  @Override
+  public Class getRealClass() {
+    return m_javaMethod.getClass();
+  }
+
+  @Override
+  public IRetryAnalyzer getRetryAnalyzer() {
+    return m_method.getRetryAnalyzer();
+  }
+
+  @Override
+  public int getSuccessPercentage() {
+    return 100;
+  }
+
+  @Override
+  public ITestClass getTestClass() {
+    return m_method.getTestClass();
+  }
+
+  @Override
+  public int getThreadPoolSize() {
+    return m_method.getThreadPoolSize();
+  }
+
+  @Override
+  public long getTimeOut() {
+    return m_method.getTimeOut();
+  }
+
+  @Override
+  public void setTimeOut(long timeOut) {
+    m_method.setTimeOut(timeOut);
+  }
+
+  @Override
+  public boolean ignoreMissingDependencies() {
+    return false;
+  }
+
+  @Override
+  public void incrementCurrentInvocationCount() {
+    m_currentInvocationCount++;
+  }
+
+  @Override
+  public boolean isAfterClassConfiguration() {
+    return false;
+  }
+
+  @Override
+  public boolean isAfterGroupsConfiguration() {
+    return false;
+  }
+
+  @Override
+  public boolean isAfterMethodConfiguration() {
+    return false;
+  }
+
+  @Override
+  public boolean isAfterSuiteConfiguration() {
+    return false;
+  }
+
+  @Override
+  public boolean isAfterTestConfiguration() {
+    return false;
+  }
+
+  @Override
+  public boolean isAlwaysRun() {
+    return false;
+  }
+
+  @Override
+  public boolean isBeforeClassConfiguration() {
+    return false;
+  }
+
+  @Override
+  public boolean isBeforeGroupsConfiguration() {
+    return false;
+  }
+
+  @Override
+  public boolean isBeforeMethodConfiguration() {
+    return false;
+  }
+
+  @Override
+  public boolean isBeforeSuiteConfiguration() {
+    return false;
+  }
+
+  @Override
+  public boolean isBeforeTestConfiguration() {
+    return false;
+  }
+
+  @Override
+  public boolean isTest() {
+    return true;
+  }
+
+  @Override
+  public void setDate(long date) {
+    m_date = date;
+  }
+
+  @Override
+  public void setId(String id) {
+    m_id = id;
+  }
+
+  @Override
+  public void setIgnoreMissingDependencies(boolean ignore) {
+  }
+
+  @Override
+  public void setInvocationCount(int count) {
+  }
+
+  @Override
+  public void setMissingGroup(String group) {
+  }
+
+  @Override
+  public void setParameterInvocationCount(int n) {
+  }
+
+  @Override
+  public void setRetryAnalyzer(IRetryAnalyzer retryAnalyzer) {
+  }
+
+  @Override
+  public void setSkipFailedInvocations(boolean skip) {
+  }
+
+  @Override
+  public void setTestClass(ITestClass cls) {
+  }
+
+  @Override
+  public void setThreadPoolSize(int threadPoolSize) {
+  }
+
+  @Override
+  public boolean skipFailedInvocations() {
+    return false;
+  }
+
+  @Override
+  public int compareTo(Object o) {
+    int result = -2;
+    Class<?> thisClass = getRealClass();
+    Class<?> otherClass = ((ITestNGMethod) o).getRealClass();
+    if (thisClass.isAssignableFrom(otherClass)) {
+      result = -1;
+    } else if (otherClass.isAssignableFrom(thisClass)) {
+      result = 1;
+    } else if (equals(o)) {
+      result = 0;
+    }
+
+    return result;
+  }
+
+  @Override
+  public ClonedMethod clone() {
+    return new ClonedMethod(m_method, m_javaMethod);
+  }
+
+  @Override
+  public String toString() {
+    Method m = getMethod();
+    String cls = m.getDeclaringClass().getName();
+    StringBuffer result = new StringBuffer(cls + "." + m.getName() + "(");
+    int i = 0;
+    for (Class<?> p : m.getParameterTypes()) {
+      if (i++ > 0) {
+        result.append(", ");
+      }
+      result.append(p.getName());
+    }
+    result.append(")");
+
+    return result.toString();
+  }
+
+  @Override
+  public List<Integer> getInvocationNumbers() {
+    return m_invocationNumbers;
+  }
+
+  @Override
+  public void setInvocationNumbers(List<Integer> count) {
+    m_invocationNumbers = count;
+  }
+
+  @Override
+  public List<Integer> getFailedInvocationNumbers() {
+    return m_failedInvocationNumbers;
+  }
+
+  @Override
+  public void addFailedInvocationNumber(int number) {
+    m_failedInvocationNumbers.add(number);
+  }
+
+  @Override
+  public int getPriority() {
+    return m_method.getPriority();
+  }
+
+  @Override
+  public void setPriority(int priority) {
+    // ignored
+  }
+
+  @Override
+  public XmlTest getXmlTest() {
+    return m_method.getXmlTest();
+  }
+
+  @Override
+  public ConstructorOrMethod getConstructorOrMethod() {
+    return null;
+  }
+
+  @Override
+  public Map<String, String> findMethodParameters(XmlTest test) {
+    return Collections.emptyMap();
+  }
+}
diff --git a/src/main/java/org/testng/internal/Configuration.java b/src/main/java/org/testng/internal/Configuration.java
new file mode 100644
index 0000000..e0fe87d
--- /dev/null
+++ b/src/main/java/org/testng/internal/Configuration.java
@@ -0,0 +1,100 @@
+package org.testng.internal;
+
+import org.testng.*;
+import org.testng.collections.Lists;
+import org.testng.internal.annotations.DefaultAnnotationTransformer;
+import org.testng.internal.annotations.IAnnotationFinder;
+import org.testng.internal.annotations.JDK15AnnotationFinder;
+
+import java.util.List;
+
+public class Configuration implements IConfiguration {
+
+  IAnnotationFinder m_annotationFinder;
+  ITestObjectFactory m_objectFactory;
+  IHookable m_hookable;
+  IConfigurable m_configurable;
+  List<IExecutionListener> m_executionListeners = Lists.newArrayList();
+  List<IAlterSuiteListener> m_alterSuiteListeners = Lists.newArrayList();
+  private List<IConfigurationListener> m_configurationListeners = Lists.newArrayList();
+
+  public Configuration() {
+    init(new JDK15AnnotationFinder(new DefaultAnnotationTransformer()));
+  }
+
+  public Configuration(IAnnotationFinder finder) {
+    init(finder);
+  }
+
+  private void init(IAnnotationFinder finder) {
+    m_annotationFinder = finder;
+  }
+
+  @Override
+  public IAnnotationFinder getAnnotationFinder() {
+    return m_annotationFinder;
+  }
+
+  @Override
+  public void setAnnotationFinder(IAnnotationFinder finder) {
+    m_annotationFinder = finder;
+  }
+
+  @Override
+  public ITestObjectFactory getObjectFactory() {
+    return m_objectFactory;
+  }
+
+  @Override
+  public void setObjectFactory(ITestObjectFactory factory) {
+    m_objectFactory = factory;
+  }
+
+  @Override
+  public IHookable getHookable() {
+    return m_hookable;
+  }
+
+  @Override
+  public void setHookable(IHookable h) {
+    m_hookable = h;
+  }
+
+  @Override
+  public IConfigurable getConfigurable() {
+    return m_configurable;
+  }
+
+  @Override
+  public void setConfigurable(IConfigurable c) {
+    m_configurable = c;
+  }
+
+  @Override
+  public List<IExecutionListener> getExecutionListeners() {
+    return m_executionListeners;
+  }
+
+  @Override
+  public void addExecutionListener(IExecutionListener l) {
+    m_executionListeners.add(l);
+  }
+
+  @Override
+  public List<IConfigurationListener> getConfigurationListeners() {
+    return Lists.newArrayList(m_configurationListeners);
+  }
+
+  @Override
+  public void addConfigurationListener(IConfigurationListener cl) {
+    if (! m_configurationListeners.contains(cl)) {
+      m_configurationListeners.add(cl);
+    }
+  }
+
+  @Override
+  public List<IAlterSuiteListener> getAlterSuiteListeners() {
+    return m_alterSuiteListeners;
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/ConfigurationGroupMethods.java b/src/main/java/org/testng/internal/ConfigurationGroupMethods.java
new file mode 100755
index 0000000..d04f33d
--- /dev/null
+++ b/src/main/java/org/testng/internal/ConfigurationGroupMethods.java
@@ -0,0 +1,143 @@
+package org.testng.internal;

+

+

+import org.testng.ITestNGMethod;

+import org.testng.collections.Lists;

+import org.testng.collections.Maps;

+

+import java.io.Serializable;

+import java.util.Collection;

+import java.util.List;

+import java.util.Map;

+

+/**

+ * This class wraps access to beforeGroups and afterGroups methods,

+ * since they are passed around the various invokers and potentially

+ * modified in different threads.

+ *

+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>

+ * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>

+ * @since 5.3 (Mar 2, 2006)

+ */

+public class ConfigurationGroupMethods implements Serializable {

+  /** Use serialVersionUID for interoperability. */

+  private final static long serialVersionUID= 1660798519864898480L;

+

+  /** The list of beforeGroups methods keyed by the name of the group */

+  private final Map<String, List<ITestNGMethod>> m_beforeGroupsMethods;

+

+  /** The list of afterGroups methods keyed by the name of the group */

+  private final Map<String, List<ITestNGMethod>> m_afterGroupsMethods;

+

+  /** The list of all test methods */

+  private final ITestNGMethod[] m_allMethods;

+

+  /**A map that returns the last method belonging to the given group */

+  private Map<String, List<ITestNGMethod>> m_afterGroupsMap= null;

+

+  public ConfigurationGroupMethods(ITestNGMethod[] allMethods,

+                                   Map<String, List<ITestNGMethod>> beforeGroupsMethods,

+                                   Map<String, List<ITestNGMethod>> afterGroupsMethods)

+  {

+    m_allMethods= allMethods;

+    m_beforeGroupsMethods= beforeGroupsMethods;

+    m_afterGroupsMethods= afterGroupsMethods;

+  }

+

+  public Map<String, List<ITestNGMethod>> getBeforeGroupsMethods() {

+    return m_beforeGroupsMethods;

+  }

+

+  public Map<String, List<ITestNGMethod>> getAfterGroupsMethods() {

+    return m_afterGroupsMethods;

+  }

+

+  /**

+   * @return true if the passed method is the last to run for the group.

+   * This method is used to figure out when is the right time to invoke

+   * afterGroups methods.

+   */

+  public synchronized boolean isLastMethodForGroup(String group, ITestNGMethod method) {

+

+    // If we have more invocation to do, this is not the last one yet

+    int invocationCount= method.getCurrentInvocationCount();

+    if(invocationCount < (method.getInvocationCount() * method.getParameterInvocationCount())) {

+      return false;

+    }

+

+    // Lazy initialization since we might never be called

+    if(m_afterGroupsMap == null) {

+      m_afterGroupsMap= initializeAfterGroupsMap();

+    }

+

+    List<ITestNGMethod> methodsInGroup= m_afterGroupsMap.get(group);

+

+    if(null == methodsInGroup || methodsInGroup.isEmpty()) {

+      return false;

+    }

+

+    methodsInGroup.remove(method);

+

+    // Note:  == is not good enough here as we may work with ITestNGMethod clones

+    return methodsInGroup.isEmpty();

+

+  }

+

+  private synchronized Map<String, List<ITestNGMethod>> initializeAfterGroupsMap() {

+    Map<String, List<ITestNGMethod>> result= Maps.newHashMap();

+    for(ITestNGMethod m : m_allMethods) {

+      String[] groups= m.getGroups();

+      for(String g : groups) {

+        List<ITestNGMethod> methodsInGroup= result.get(g);

+        if(null == methodsInGroup) {

+          methodsInGroup= Lists.newArrayList();

+          result.put(g, methodsInGroup);

+        }

+        methodsInGroup.add(m);

+      }

+    }

+

+    return result;

+  }

+

+  public synchronized void removeBeforeMethod(String group, ITestNGMethod method) {

+    List<ITestNGMethod> methods= m_beforeGroupsMethods.get(group);

+    if(methods != null) {

+      Object success= methods.remove(method);

+      if(success == null) {

+        log("Couldn't remove beforeGroups method " + method + " for group " + group);

+      }

+    }

+    else {

+      log("Couldn't find any beforeGroups method for group " + group);

+    }

+  }

+

+  private void log(String string) {

+    Utils.log("ConfigurationGroupMethods", 2, string);

+  }

+

+  synchronized public Map<String, List<ITestNGMethod>> getBeforeGroupsMap() {

+    return m_beforeGroupsMethods;

+  }

+

+  synchronized public Map<String, List<ITestNGMethod>> getAfterGroupsMap() {

+    return m_afterGroupsMethods;

+  }

+

+  synchronized public void removeBeforeGroups(String[] groups) {

+    for(String group : groups) {

+//      log("Removing before group " + group);

+      m_beforeGroupsMethods.remove(group);

+    }

+  }

+

+  synchronized public void removeAfterGroups(Collection<String> groups) {

+    for(String group : groups) {

+//      log("Removing before group " + group);

+      m_afterGroupsMethods.remove(group);

+    }

+

+  }

+

+}

diff --git a/src/main/java/org/testng/internal/ConfigurationMethod.java b/src/main/java/org/testng/internal/ConfigurationMethod.java
new file mode 100755
index 0000000..1bd5c7e
--- /dev/null
+++ b/src/main/java/org/testng/internal/ConfigurationMethod.java
@@ -0,0 +1,462 @@
+package org.testng.internal;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.testng.ITestNGMethod;
+import org.testng.annotations.IAnnotation;
+import org.testng.annotations.IConfigurationAnnotation;
+import org.testng.annotations.ITestAnnotation;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.internal.annotations.AnnotationHelper;
+import org.testng.internal.annotations.ConfigurationAnnotation;
+import org.testng.internal.annotations.IAfterClass;
+import org.testng.internal.annotations.IAfterGroups;
+import org.testng.internal.annotations.IAfterMethod;
+import org.testng.internal.annotations.IAfterSuite;
+import org.testng.internal.annotations.IAfterTest;
+import org.testng.internal.annotations.IAnnotationFinder;
+import org.testng.internal.annotations.IBeforeClass;
+import org.testng.internal.annotations.IBeforeGroups;
+import org.testng.internal.annotations.IBeforeMethod;
+import org.testng.internal.annotations.IBeforeSuite;
+import org.testng.internal.annotations.IBeforeTest;
+
+public class ConfigurationMethod extends BaseTestMethod {
+  /**
+   *
+   */
+  private static final long serialVersionUID = -6537771498553619645L;
+  private final boolean m_isBeforeSuiteConfiguration;
+  private final boolean m_isAfterSuiteConfiguration;
+
+  private final boolean m_isBeforeTestConfiguration;
+  private final boolean m_isAfterTestConfiguration;
+
+  private final boolean m_isBeforeClassConfiguration;
+  private final boolean m_isAfterClassConfiguration;
+
+  private final boolean m_isBeforeMethodConfiguration;
+  private final boolean m_isAfterMethodConfiguration;
+
+  private boolean m_inheritGroupsFromTestClass = false;
+
+  private ConfigurationMethod(ConstructorOrMethod com,
+                              IAnnotationFinder annotationFinder,
+                              boolean isBeforeSuite,
+                              boolean isAfterSuite,
+                              boolean isBeforeTest,
+                              boolean isAfterTest,
+                              boolean isBeforeClass,
+                              boolean isAfterClass,
+                              boolean isBeforeMethod,
+                              boolean isAfterMethod,
+                              String[] beforeGroups,
+                              String[] afterGroups,
+                              boolean initialize,
+                              Object instance)
+  {
+    super(com.getName(), com, annotationFinder, instance);
+    if(initialize) {
+      init();
+    }
+
+    m_isBeforeSuiteConfiguration = isBeforeSuite;
+    m_isAfterSuiteConfiguration = isAfterSuite;
+
+    m_isBeforeTestConfiguration = isBeforeTest;
+    m_isAfterTestConfiguration = isAfterTest;
+
+    m_isBeforeClassConfiguration = isBeforeClass;
+    m_isAfterClassConfiguration = isAfterClass;
+
+    m_isBeforeMethodConfiguration = isBeforeMethod;
+    m_isAfterMethodConfiguration = isAfterMethod;
+
+    m_beforeGroups = beforeGroups;
+    m_afterGroups = afterGroups;
+
+  }
+
+  /**
+   * @deprecated use #ConfigurationMethod(ConstructorOrMethod,...) instead.
+   */
+  @Deprecated
+  public ConfigurationMethod(Method method,
+                             IAnnotationFinder annotationFinder,
+                             boolean isBeforeSuite,
+                             boolean isAfterSuite,
+                             boolean isBeforeTest,
+                             boolean isAfterTest,
+                             boolean isBeforeClass,
+                             boolean isAfterClass,
+                             boolean isBeforeMethod,
+                             boolean isAfterMethod,
+                             String[] beforeGroups,
+                             String[] afterGroups,
+                             Object instance)
+  {
+    this(new ConstructorOrMethod(method), annotationFinder, isBeforeSuite, isAfterSuite, isBeforeTest, isAfterTest,
+        isBeforeClass, isAfterClass, isBeforeMethod, isAfterMethod, beforeGroups, afterGroups, instance);
+  }
+
+  public ConfigurationMethod(ConstructorOrMethod com,
+                             IAnnotationFinder annotationFinder,
+                             boolean isBeforeSuite,
+                             boolean isAfterSuite,
+                             boolean isBeforeTest,
+                             boolean isAfterTest,
+                             boolean isBeforeClass,
+                             boolean isAfterClass,
+                             boolean isBeforeMethod,
+                             boolean isAfterMethod,
+                             String[] beforeGroups,
+                             String[] afterGroups,
+                             Object instance) {
+    this(com, annotationFinder, isBeforeSuite, isAfterSuite, isBeforeTest, isAfterTest,
+        isBeforeClass, isAfterClass, isBeforeMethod, isAfterMethod, beforeGroups, afterGroups,
+        true, instance);
+  }
+
+  private static ITestNGMethod[] createMethods(ITestNGMethod[] methods, IAnnotationFinder finder,
+      boolean isBeforeSuite,
+      boolean isAfterSuite,
+      boolean isBeforeTest,
+      boolean isAfterTest,
+      boolean isBeforeClass,
+      boolean isAfterClass,
+      boolean isBeforeMethod,
+      boolean isAfterMethod,
+      String[] beforeGroups,
+      String[] afterGroups,
+      Object instance)
+  {
+    List<ITestNGMethod> result = Lists.newArrayList();
+      for (ITestNGMethod method : methods) {
+          result.add(new ConfigurationMethod(method.getConstructorOrMethod(),
+                  finder,
+                  isBeforeSuite,
+                  isAfterSuite,
+                  isBeforeTest,
+                  isAfterTest,
+                  isBeforeClass,
+                  isAfterClass,
+                  isBeforeMethod,
+                  isAfterMethod,
+                  new String[0],
+                  new String[0],
+                  instance));
+      }
+
+    return result.toArray(new ITestNGMethod[result.size()]);
+  }
+
+
+  public static ITestNGMethod[] createSuiteConfigurationMethods(ITestNGMethod[] methods,
+      IAnnotationFinder annotationFinder, boolean isBefore, Object instance) {
+    return createMethods(methods, annotationFinder,
+        isBefore,
+        !isBefore,
+        false,
+        false,
+        false,
+        false,
+        false,
+        false,
+        new String[0],
+        new String[0],
+        instance);
+  }
+
+  public static ITestNGMethod[] createTestConfigurationMethods(ITestNGMethod[] methods,
+      IAnnotationFinder annotationFinder, boolean isBefore, Object instance) {
+    return createMethods(methods, annotationFinder,
+        false,
+        false,
+        isBefore,
+        !isBefore,
+        false,
+        false,
+        false,
+        false,
+        new String[0],
+        new String[0],
+        instance);
+  }
+
+  public static ITestNGMethod[] createClassConfigurationMethods(ITestNGMethod[] methods,
+      IAnnotationFinder annotationFinder, boolean isBefore, Object instance)  {
+    return createMethods(methods, annotationFinder,
+        false,
+        false,
+        false,
+        false,
+        isBefore,
+        !isBefore,
+        false,
+        false,
+        new String[0],
+        new String[0],
+        instance);
+  }
+
+  public static ITestNGMethod[] createBeforeConfigurationMethods(ITestNGMethod[] methods,
+      IAnnotationFinder annotationFinder, boolean isBefore, Object instance)
+  {
+    ITestNGMethod[] result = new ITestNGMethod[methods.length];
+    for(int i = 0; i < methods.length; i++) {
+      result[i] = new ConfigurationMethod(methods[i].getConstructorOrMethod(),
+          annotationFinder,
+          false,
+          false,
+          false,
+          false,
+          false,
+          false,
+          false,
+          false,
+          isBefore ? methods[i].getBeforeGroups() : new String[0],
+          new String[0],
+          instance);
+      }
+
+    return result;
+  }
+
+  public static ITestNGMethod[] createAfterConfigurationMethods(ITestNGMethod[] methods,
+      IAnnotationFinder annotationFinder, boolean isBefore, Object instance)
+  {
+    ITestNGMethod[] result = new ITestNGMethod[methods.length];
+    for(int i = 0; i < methods.length; i++) {
+      result[i] = new ConfigurationMethod(methods[i].getConstructorOrMethod(),
+          annotationFinder,
+          false,
+          false,
+          false,
+          false,
+          false,
+          false,
+          false,
+          false,
+          new String[0],
+          isBefore ? new String[0] : methods[i].getAfterGroups(),
+          instance);
+      }
+
+    return result;
+  }
+
+  public static ITestNGMethod[] createTestMethodConfigurationMethods(ITestNGMethod[] methods,
+      IAnnotationFinder annotationFinder, boolean isBefore, Object instance) {
+    return createMethods(methods, annotationFinder,
+        false,
+        false,
+        false,
+        false,
+        false,
+        false,
+        isBefore,
+        !isBefore,
+        new String[0],
+        new String[0],
+        instance);
+  }
+
+  /**
+   * @return Returns the isAfterClassConfiguration.
+   */
+  @Override
+  public boolean isAfterClassConfiguration() {
+    return m_isAfterClassConfiguration;
+  }
+  /**
+   * @return Returns the isAfterMethodConfiguration.
+   */
+  @Override
+  public boolean isAfterMethodConfiguration() {
+    return m_isAfterMethodConfiguration;
+  }
+  /**
+   * @return Returns the isBeforeClassConfiguration.
+   */
+  @Override
+  public boolean isBeforeClassConfiguration() {
+    return m_isBeforeClassConfiguration;
+  }
+  /**
+   * @return Returns the isBeforeMethodConfiguration.
+   */
+  @Override
+  public boolean isBeforeMethodConfiguration() {
+    return m_isBeforeMethodConfiguration;
+  }
+
+
+  /**
+   * @return Returns the isAfterSuiteConfiguration.
+   */
+  @Override
+  public boolean isAfterSuiteConfiguration() {
+    return m_isAfterSuiteConfiguration;
+  }
+
+  /**
+   * @return Returns the isBeforeSuiteConfiguration.
+   */
+  @Override
+  public boolean isBeforeSuiteConfiguration() {
+    return m_isBeforeSuiteConfiguration;
+  }
+
+  @Override
+  public boolean isBeforeTestConfiguration() {
+    return m_isBeforeTestConfiguration;
+  }
+
+  @Override
+  public boolean isAfterTestConfiguration() {
+    return m_isAfterTestConfiguration;
+  }
+
+  @Override
+  public boolean isBeforeGroupsConfiguration() {
+    return m_beforeGroups != null && m_beforeGroups.length > 0;
+  }
+
+  @Override
+  public boolean isAfterGroupsConfiguration() {
+    return m_afterGroups != null && m_afterGroups.length > 0;
+  }
+
+  private boolean inheritGroupsFromTestClass() {
+    return m_inheritGroupsFromTestClass;
+  }
+
+  private void init() {
+    IAnnotation a = AnnotationHelper.findConfiguration(m_annotationFinder, m_method.getMethod());
+    IConfigurationAnnotation annotation = (IConfigurationAnnotation) a;
+    if (a != null) {
+      m_inheritGroupsFromTestClass = annotation.getInheritGroups();
+      setEnabled(annotation.getEnabled());
+      setDescription(annotation.getDescription());
+    }
+
+    if (annotation != null && annotation.isFakeConfiguration()) {
+     if (annotation.getBeforeSuite()) {
+      initGroups(IBeforeSuite.class);
+    }
+     if (annotation.getAfterSuite()) {
+      initGroups(IAfterSuite.class);
+    }
+     if (annotation.getBeforeTest()) {
+      initGroups(IBeforeTest.class);
+    }
+     if (annotation.getAfterTest()) {
+      initGroups(IAfterTest.class);
+    }
+     if (annotation.getBeforeGroups().length != 0) {
+      initGroups(IBeforeGroups.class);
+    }
+     if (annotation.getAfterGroups().length != 0) {
+      initGroups(IAfterGroups.class);
+    }
+     if (annotation.getBeforeTestClass()) {
+      initGroups(IBeforeClass.class);
+    }
+     if (annotation.getAfterTestClass()) {
+      initGroups(IAfterClass.class);
+    }
+     if (annotation.getBeforeTestMethod()) {
+      initGroups(IBeforeMethod.class);
+    }
+     if (annotation.getAfterTestMethod()) {
+      initGroups(IAfterMethod.class);
+    }
+    }
+    else {
+      initGroups(IConfigurationAnnotation.class);
+    }
+
+    // If this configuration method has inherit-groups=true, add the groups
+    // defined in the @Test class
+    if (inheritGroupsFromTestClass()) {
+      ITestAnnotation classAnnotation = m_annotationFinder.findAnnotation(m_methodClass, ITestAnnotation.class);
+      if (classAnnotation != null) {
+        String[] groups = classAnnotation.getGroups();
+        Map<String, String> newGroups = Maps.newHashMap();
+        for (String g : getGroups()) {
+          newGroups.put(g, g);
+        }
+        if (groups != null) {
+          for (String g : groups) {
+            newGroups.put(g, g);
+          }
+          setGroups(newGroups.values().toArray(new String[newGroups.size()]));
+        }
+      }
+    }
+
+    if (annotation != null) {
+      setTimeOut(annotation.getTimeOut());
+    }
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[ConfigurationMethod] " + s);
+  }
+
+  @Override
+  public ConfigurationMethod clone() {
+    ConfigurationMethod clone= new ConfigurationMethod(getConstructorOrMethod(),
+        getAnnotationFinder(),
+        isBeforeSuiteConfiguration(),
+        isAfterSuiteConfiguration(),
+        isBeforeTestConfiguration(),
+        isAfterTestConfiguration(),
+        isBeforeClassConfiguration(),
+        isAfterClassConfiguration(),
+        isBeforeMethodConfiguration(),
+        isAfterMethodConfiguration(),
+        getBeforeGroups(),
+        getAfterGroups(),
+        false /* do not call init() */,
+        getInstance()
+        );
+    clone.m_testClass= getTestClass();
+    clone.setDate(getDate());
+    clone.setGroups(getGroups());
+    clone.setGroupsDependedUpon(getGroupsDependedUpon(), Collections.<String>emptyList());
+    clone.setMethodsDependedUpon(getMethodsDependedUpon());
+    clone.setAlwaysRun(isAlwaysRun());
+    clone.setMissingGroup(getMissingGroup());
+    clone.setDescription(getDescription());
+    clone.setEnabled(getEnabled());
+    clone.setParameterInvocationCount(getParameterInvocationCount());
+    clone.m_inheritGroupsFromTestClass= inheritGroupsFromTestClass();
+
+    return clone;
+  }
+
+  public boolean isFirstTimeOnly() {
+    boolean result = false;
+    IAnnotation before = m_annotationFinder.findAnnotation(getMethod(), IBeforeMethod.class);
+    if (before != null) {
+      result = ((ConfigurationAnnotation) before).isFirstTimeOnly();
+    }
+    return result;
+  }
+
+  public boolean isLastTimeOnly() {
+    boolean result = false;
+    IAnnotation before = m_annotationFinder.findAnnotation(getMethod(), IAfterMethod.class);
+    if (before != null) {
+      result = ((ConfigurationAnnotation) before).isLastTimeOnly();
+    }
+    return result;
+  }
+
+}
+
diff --git a/src/main/java/org/testng/internal/Constants.java b/src/main/java/org/testng/internal/Constants.java
new file mode 100755
index 0000000..aef2c1f
--- /dev/null
+++ b/src/main/java/org/testng/internal/Constants.java
@@ -0,0 +1,97 @@
+package org.testng.internal;
+
+import java.util.Map;
+import java.util.Properties;
+
+import org.testng.ITestResult;
+import org.testng.collections.Maps;
+
+
+
+/**
+ * Constants used by TestNG
+ *
+ * @author Cedric Beust, May 2, 2004
+ *
+ */
+public class Constants {
+  private static final String NAMESPACE = "testng";
+
+////////  //
+  // Properties
+  //
+  public static final String PROP_OUTPUT_DIR = NAMESPACE + "." + "outputDir";
+//  public static final String PROP_NAME = NAMESPACE + "." + "name";
+//  public static final String PROP_INCLUDED_GROUPS = NAMESPACE + "." + "includedGroups";
+//  public static final String PROP_EXCLUDED_GROUPS = NAMESPACE + "." + "excludedGroups";
+//  public static final String PROP_CLASS_NAMES = NAMESPACE + "." + "classNames";
+//  public static final String PROP_VERBOSE = NAMESPACE + "." + "verbose";
+//  public static final String PROP_JUNIT= NAMESPACE + "." + "junit";
+//  public static final String PROP_QUIET= NAMESPACE + "." + "quiet";
+//  public static final String PROP_GROUP= NAMESPACE + "." + "group";
+
+  private static final TestNGProperty[] COMMAND_LINE_PARAMETERS = {
+    new TestNGProperty("-d", PROP_OUTPUT_DIR, "Directory where the result files will be created.", "test-output"),
+  };
+
+  private static final Map<String, TestNGProperty> m_propertiesByName = Maps.newHashMap();
+
+  static {
+//    for (int i = 0; i < PROPERTIES.length; i++) {
+//      m_propertiesByName.put(PROPERTIES[i].getName(), PROPERTIES[i]);
+//    }
+    for (TestNGProperty element : COMMAND_LINE_PARAMETERS) {
+      m_propertiesByName.put(element.getName(), element);
+    }
+  }
+
+  private static TestNGProperty getProperty(String propertyName) {
+    TestNGProperty result = m_propertiesByName.get(propertyName);
+    assert null != result : "Unknown property : " + propertyName;
+
+    return result;
+  }
+
+  public static String getPropertyValue(Properties p, String propertyName) {
+    TestNGProperty r= getProperty(propertyName);
+    assert null != r : "Unknown property : " + propertyName;
+
+    String result = p.getProperty(r.getName());
+
+    return result;
+  }
+
+  public static boolean getBooleanPropertyValue(Properties properties, String propertyName) {
+    TestNGProperty p = getProperty(propertyName);
+    String r = properties.getProperty(propertyName, p.getDefault());
+    boolean result = "true".equalsIgnoreCase(r);
+
+    return Boolean.valueOf(result);
+  }
+
+  public static int getIntegerPropertyValue(Properties properties, String propertyName) {
+    TestNGProperty p = getProperty(propertyName);
+    String r = properties.getProperty(propertyName, p.getDefault());
+    int result = Integer.parseInt(r);
+
+    return result;
+  }
+
+  public static String getDefaultValueFor(String propertyName) {
+    TestNGProperty p = getProperty(propertyName);
+    return p.getDefault();
+  }
+
+  public static String displayStatus(int status) {
+    if (ITestResult.SKIP == status) {
+      return "SKIP";
+    } else if (ITestResult.SUCCESS == status) {
+      return "SUCCESS";
+    } else if (ITestResult.FAILURE == status) {
+      return "FAILURE";
+    } else {
+      return "UNKNOWN_STATUS";
+    }
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/ConstructorOrMethod.java b/src/main/java/org/testng/internal/ConstructorOrMethod.java
new file mode 100644
index 0000000..07e75ce
--- /dev/null
+++ b/src/main/java/org/testng/internal/ConstructorOrMethod.java
@@ -0,0 +1,89 @@
+package org.testng.internal;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/**
+ * Encapsulation of either a method or a constructor.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public class ConstructorOrMethod {
+
+  private Method m_method;
+  private Constructor m_constructor;
+  private boolean m_enabled = true;
+
+  public ConstructorOrMethod(Method m) {
+    m_method = m;
+  }
+
+  public ConstructorOrMethod(Constructor c) {
+    m_constructor = c;
+  }
+
+  public Class<?> getDeclaringClass() {
+    return getMethod() != null ? getMethod().getDeclaringClass() : getConstructor().getDeclaringClass();
+  }
+
+  public String getName() {
+    return getMethod() != null ? getMethod().getName() : getConstructor().getName();
+  }
+
+  public Class[] getParameterTypes() {
+    return getMethod() != null ? getMethod().getParameterTypes() : getConstructor().getParameterTypes();
+  }
+
+  public Method getMethod() {
+    return m_method;
+  }
+
+  public Constructor getConstructor() {
+    return m_constructor;
+  }
+
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((getConstructor() == null) ? 0 : getConstructor().hashCode());
+    result = prime * result + ((getMethod() == null) ? 0 : getMethod().hashCode());
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj)
+      return true;
+    if (obj == null)
+      return false;
+    if (getClass() != obj.getClass())
+      return false;
+    ConstructorOrMethod other = (ConstructorOrMethod) obj;
+    if (getConstructor() == null) {
+      if (other.getConstructor() != null)
+        return false;
+    } else if (!getConstructor().equals(other.getConstructor()))
+      return false;
+    if (getMethod() == null) {
+      if (other.getMethod() != null)
+        return false;
+    } else if (!getMethod().equals(other.getMethod()))
+      return false;
+    return true;
+  }
+
+  public void setEnabled(boolean enabled) {
+    m_enabled = enabled;
+  }
+
+  public boolean getEnabled() {
+    return m_enabled;
+  }
+
+  @Override
+  public String toString() {
+    if (m_method != null) return m_method.toString();
+    else return m_constructor.toString();
+  }
+}
diff --git a/src/main/java/org/testng/internal/DataProviderHolder.java b/src/main/java/org/testng/internal/DataProviderHolder.java
new file mode 100755
index 0000000..47162cc
--- /dev/null
+++ b/src/main/java/org/testng/internal/DataProviderHolder.java
@@ -0,0 +1,20 @@
+package org.testng.internal;
+
+import org.testng.annotations.IDataProviderAnnotation;
+
+import java.lang.reflect.Method;
+
+/**
+ * A holder for a pair of Method and IDataProviderAnnotation
+ */
+public class DataProviderHolder {
+  Object instance;
+  Method method;
+  IDataProviderAnnotation annotation;
+
+  public DataProviderHolder(IDataProviderAnnotation annotation, Method method, Object instance) {
+    this.annotation = annotation;
+    this.method = method;
+    this.instance = instance;
+  }
+}
diff --git a/src/main/java/org/testng/internal/DefaultMethodSelectorContext.java b/src/main/java/org/testng/internal/DefaultMethodSelectorContext.java
new file mode 100755
index 0000000..5df938d
--- /dev/null
+++ b/src/main/java/org/testng/internal/DefaultMethodSelectorContext.java
@@ -0,0 +1,33 @@
+package org.testng.internal;

+

+import org.testng.IMethodSelectorContext;

+import org.testng.collections.Maps;

+

+import java.util.Map;

+

+/**

+ * Simple implementation of IMethodSelectorContext

+ *

+ * Created on Jan 3, 2007

+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>

+ */

+public class DefaultMethodSelectorContext implements IMethodSelectorContext {

+  private Map<Object, Object> m_userData = Maps.newHashMap();

+  private boolean m_isStopped = false;

+

+  @Override

+  public Map<Object, Object> getUserData() {

+    return m_userData;

+  }

+

+  @Override

+  public boolean isStopped() {

+    return m_isStopped;

+  }

+

+  @Override

+  public void setStopped(boolean stopped) {

+    m_isStopped = stopped;

+  }

+

+}

diff --git a/src/main/java/org/testng/internal/Dynamic.java b/src/main/java/org/testng/internal/Dynamic.java
new file mode 100644
index 0000000..3a73535
--- /dev/null
+++ b/src/main/java/org/testng/internal/Dynamic.java
@@ -0,0 +1,16 @@
+package org.testng.internal;
+
+/**
+ * Determine the availability of certain jar files at runtime.
+ */
+public class Dynamic {
+
+  public static boolean hasBsh() {
+    try {
+      Class.forName("bsh.Interpreter");
+      return true;
+    } catch (ClassNotFoundException e) {
+      return false;
+    }
+  }
+}
diff --git a/src/main/java/org/testng/internal/DynamicGraph.java b/src/main/java/org/testng/internal/DynamicGraph.java
new file mode 100644
index 0000000..3597995
--- /dev/null
+++ b/src/main/java/org/testng/internal/DynamicGraph.java
@@ -0,0 +1,215 @@
+package org.testng.internal;
+
+import org.testng.collections.ListMultiMap;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.collections.Sets;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Representation of the graph of methods.
+ */
+public class DynamicGraph<T> {
+  private static final boolean DEBUG = false;
+
+  private List<T> m_nodesReady = Lists.newArrayList();
+  private List<T> m_nodesRunning = Lists.newArrayList();
+  private List<T> m_nodesFinished = Lists.newArrayList();
+
+  private Comparator<? super T> m_nodeComparator = null;
+
+  private ListMultiMap<T, T> m_dependedUpon = Maps.newListMultiMap();
+  private ListMultiMap<T, T> m_dependingOn = Maps.newListMultiMap();
+
+  public static enum Status {
+    READY, RUNNING, FINISHED
+  }
+
+  /**
+   * Define a comparator for the nodes of this graph, which will be used
+   * to order the free nodes when they are asked.
+   */
+  public void setComparator(Comparator<? super T> c) {
+    m_nodeComparator = c;
+  }
+
+  /**
+   * Add a node to the graph.
+   */
+  public void addNode(T node) {
+    m_nodesReady.add(node);
+  }
+
+  /**
+   * Add an edge between two nodes.
+   */
+  public void addEdge(T from, T to) {
+    m_dependingOn.put(to, from);
+    m_dependedUpon.put(from, to);
+  }
+
+  /**
+   * @return a set of all the nodes that don't depend on any other nodes.
+   */
+  public List<T> getFreeNodes() {
+    List<T> result = Lists.newArrayList();
+    for (T m : m_nodesReady) {
+      // A node is free if...
+
+      List<T> du = m_dependedUpon.get(m);
+      // - no other nodes depend on it
+      if (!m_dependedUpon.containsKey(m)) {
+        result.add(m);
+      } else if (getUnfinishedNodes(du).size() == 0) {
+        result.add(m);
+      }
+    }
+
+    // Sort the free nodes if requested (e.g. priorities)
+    if (result != null && ! result.isEmpty()) {
+      if (m_nodeComparator != null) {
+        Collections.sort(result, m_nodeComparator);
+        ppp("Nodes after sorting:" + result.get(0));
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * @return a list of all the nodes that have a status other than FINISHED.
+   */
+  private Collection<? extends T> getUnfinishedNodes(List<T> nodes) {
+    Set<T> result = Sets.newHashSet();
+    for (T node : nodes) {
+      if (m_nodesReady.contains(node) || m_nodesRunning.contains(node)) {
+        result.add(node);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Set the status for a set of nodes.
+   */
+  public void setStatus(Collection<T> nodes, Status status) {
+    for (T n : nodes) {
+      setStatus(n, status);
+    }
+  }
+
+  /**
+   * Set the status for a node.
+   */
+  public void setStatus(T node, Status status) {
+    removeNode(node);
+    switch(status) {
+      case READY: m_nodesReady.add(node); break;
+      case RUNNING: m_nodesRunning.add(node); break;
+      case FINISHED: m_nodesFinished.add(node); break;
+      default: throw new IllegalArgumentException();
+    }
+  }
+
+  private void removeNode(T node) {
+    if (!m_nodesReady.remove(node)) {
+      if (!m_nodesRunning.remove(node)) {
+        m_nodesFinished.remove(node);
+      }
+    }
+  }
+
+  /**
+   * @return the number of nodes in this graph.
+   */
+  public int getNodeCount() {
+    int result = m_nodesReady.size() + m_nodesRunning.size() + m_nodesFinished.size();
+    return result;
+  }
+
+  public int getNodeCountWithStatus(Status status) {
+    switch(status) {
+      case READY: return m_nodesReady.size();
+      case RUNNING: return m_nodesRunning.size();
+      case FINISHED: return m_nodesFinished.size();
+      default: throw new IllegalArgumentException();
+    }
+  }
+
+  private static void ppp(String string) {
+    if (DEBUG) {
+      System.out.println("   [GroupThreadPoolExecutor] " + Thread.currentThread().getId() + " "
+          + string);
+    }
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder result = new StringBuilder("[DynamicGraph ");
+    result.append("\n  Ready:" + m_nodesReady);
+    result.append("\n  Running:" + m_nodesRunning);
+    result.append("\n  Finished:" + m_nodesFinished);
+    result.append("\n  Edges:\n");
+    for (Map.Entry<T, List<T>> es : m_dependingOn.entrySet()) {
+      result.append("     " + es.getKey() + "\n");
+      for (T t : es.getValue()) {
+        result.append("        " + t + "\n");
+      }
+    }
+    result.append("]");
+    return result.toString();
+  }
+
+  private String getName(T t) {
+    String s = t.toString();
+    int n1 = s.lastIndexOf('.') + 1;
+    int n2 = s.indexOf('(');
+    return s.substring(n1, n2);
+  }
+
+  /**
+   * @return a .dot file (GraphViz) version of this graph.
+   */
+  public String toDot() {
+    String FREE = "[style=filled color=yellow]";
+    String RUNNING = "[style=filled color=green]";
+    String FINISHED = "[style=filled color=grey]";
+    StringBuilder result = new StringBuilder("digraph g {\n");
+    List<T> freeNodes = getFreeNodes();
+    String color;
+    for (T n : m_nodesReady) {
+      color = freeNodes.contains(n) ? FREE : "";
+      result.append("  " + getName(n) + color + "\n");
+    }
+    for (T n : m_nodesRunning) {
+      color = freeNodes.contains(n) ? FREE : RUNNING;
+      result.append("  " + getName(n) + color + "\n");
+    }
+    for (T n : m_nodesFinished) {
+      result.append("  " + getName(n) + FINISHED+ "\n");
+    }
+    result.append("\n");
+
+    for (T k : m_dependingOn.keySet()) {
+      List<T> nodes = m_dependingOn.get(k);
+      for (T n : nodes) {
+        String dotted = m_nodesFinished.contains(k) ? "style=dotted" : "";
+        result.append("  " + getName(k) + " -> " + getName(n) + " [dir=back " + dotted + "]\n");
+      }
+    }
+    result.append("}\n");
+
+    return result.toString();
+  }
+
+  public ListMultiMap<T, T> getEdges() {
+    return m_dependingOn;
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/EclipseInterface.java b/src/main/java/org/testng/internal/EclipseInterface.java
new file mode 100644
index 0000000..c9a1892
--- /dev/null
+++ b/src/main/java/org/testng/internal/EclipseInterface.java
@@ -0,0 +1,18 @@
+package org.testng.internal;
+
+/**
+ * Symbols in this class are used by the Eclipse plug-in, do not modify them
+ * without updating the plug-in as well.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ * @since Aug 25, 2012
+ */
+public class EclipseInterface {
+  public static final Character OPENING_CHARACTER = '[';
+  public static final Character CLOSING_CHARACTER = ']';
+
+  public static final String ASSERT_LEFT = "expected " + OPENING_CHARACTER;
+  public static final String ASSERT_LEFT2 = "expected not same " + OPENING_CHARACTER;
+  public static final String ASSERT_MIDDLE = CLOSING_CHARACTER + " but found " + OPENING_CHARACTER;
+  public static final String ASSERT_RIGHT = Character.toString(CLOSING_CHARACTER);
+}
diff --git a/src/main/java/org/testng/internal/ExpectedExceptionsHolder.java b/src/main/java/org/testng/internal/ExpectedExceptionsHolder.java
new file mode 100644
index 0000000..2e08ae7
--- /dev/null
+++ b/src/main/java/org/testng/internal/ExpectedExceptionsHolder.java
@@ -0,0 +1,107 @@
+package org.testng.internal;
+
+import org.testng.IExpectedExceptionsHolder;
+import org.testng.ITestNGMethod;
+import org.testng.TestException;
+import org.testng.annotations.IExpectedExceptionsAnnotation;
+import org.testng.annotations.ITestAnnotation;
+import org.testng.internal.annotations.IAnnotationFinder;
+
+import java.util.Arrays;
+
+public class ExpectedExceptionsHolder {
+
+  protected final IAnnotationFinder finder;
+  protected final ITestNGMethod method;
+  private final Class<?>[] expectedClasses;
+  private final IExpectedExceptionsHolder holder;
+
+  protected ExpectedExceptionsHolder(IAnnotationFinder finder, ITestNGMethod method, IExpectedExceptionsHolder holder) {
+    this.finder = finder;
+    this.method = method;
+    expectedClasses = findExpectedClasses(finder, method);
+    this.holder = holder;
+  }
+
+  private static Class<?>[] findExpectedClasses(IAnnotationFinder finder, ITestNGMethod method) {
+    IExpectedExceptionsAnnotation expectedExceptions =
+        finder.findAnnotation(method, IExpectedExceptionsAnnotation.class);
+    // Old syntax
+    if (expectedExceptions != null) {
+      return expectedExceptions.getValue();
+    }
+
+    // New syntax
+    ITestAnnotation testAnnotation = finder.findAnnotation(method, ITestAnnotation.class);
+    if (testAnnotation != null) {
+      return testAnnotation.getExpectedExceptions();
+    }
+
+    return new Class<?>[0];
+  }
+
+  /**
+   * @param ite The exception that was just thrown
+   * @return true if the exception that was just thrown is part of the
+   * expected exceptions
+   */
+  public boolean isExpectedException(Throwable ite) {
+    if (!hasExpectedClasses()) {
+      return false;
+    }
+
+    // TestException is the wrapper exception that TestNG will be throwing when an exception was
+    // expected but not thrown
+    if (ite.getClass() == TestException.class) {
+      return false;
+    }
+
+    Class<?> realExceptionClass= ite.getClass();
+
+    for (Class<?> exception : expectedClasses) {
+      if (exception.isAssignableFrom(realExceptionClass) && holder.isThrowableMatching(ite)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  public Throwable wrongException(Throwable ite) {
+    if (!hasExpectedClasses()) {
+      return ite;
+    }
+
+    if (holder.isThrowableMatching(ite)) {
+      return new TestException("Expected exception of " +
+                               getExpectedExceptionsPluralize()
+                               + " but got " + ite, ite);
+    } else {
+      return new TestException(holder.getWrongExceptionMessage(ite), ite);
+    }
+  }
+
+  public TestException noException(ITestNGMethod testMethod) {
+    if (!hasExpectedClasses()) {
+      return null;
+    }
+    return new TestException("Method " + testMethod + " should have thrown an exception of "
+                             + getExpectedExceptionsPluralize());
+  }
+
+  private boolean hasExpectedClasses() {
+    return expectedClasses != null && expectedClasses.length > 0;
+  }
+
+  private String getExpectedExceptionsPluralize() {
+    StringBuilder sb = new StringBuilder();
+    if (expectedClasses.length > 1) {
+      sb.append("any of types ");
+      sb.append(Arrays.toString(expectedClasses));
+    } else {
+      sb.append("type ");
+      sb.append(expectedClasses[0]);
+    }
+    return sb.toString();
+  }
+}
diff --git a/src/main/java/org/testng/internal/ExtraOutput.java b/src/main/java/org/testng/internal/ExtraOutput.java
new file mode 100755
index 0000000..e23089c
--- /dev/null
+++ b/src/main/java/org/testng/internal/ExtraOutput.java
@@ -0,0 +1,28 @@
+package org.testng.internal;
+
+import org.testng.IExtraOutput;
+import org.testng.collections.Lists;
+
+import java.util.List;
+
+/**
+ * This class is used by Reporter to store the extra output to be later
+ * included in the HTML report:
+ * - User-generated report
+ * - Parameter info
+ *
+ * Created on Feb 16, 2006
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public class ExtraOutput implements IExtraOutput {
+  /**
+   *
+   */
+  private static final long serialVersionUID = 8195388419611912192L;
+  private List<String> m_parameterOutput = Lists.newArrayList();
+
+  @Override
+  public List<String> getParameterOutput() {
+    return m_parameterOutput;
+  }
+}
diff --git a/src/main/java/org/testng/internal/FactoryMethod.java b/src/main/java/org/testng/internal/FactoryMethod.java
new file mode 100644
index 0000000..4ad59c0
--- /dev/null
+++ b/src/main/java/org/testng/internal/FactoryMethod.java
@@ -0,0 +1,114 @@
+package org.testng.internal;
+
+import java.lang.reflect.Modifier;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.testng.IInstanceInfo;
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+import org.testng.TestNGException;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.internal.annotations.IAnnotationFinder;
+import org.testng.xml.XmlTest;
+
+/**
+ * This class represents a method annotated with @Factory
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public class FactoryMethod extends BaseTestMethod {
+  /**
+   *
+   */
+  private static final long serialVersionUID = -7329918821346197099L;
+  private Object m_instance = null;
+  private XmlTest m_xmlTest = null;
+  private ITestContext m_testContext = null;
+
+  public FactoryMethod(ConstructorOrMethod com,
+                       Object instance,
+                       XmlTest xmlTest,
+                       IAnnotationFinder annotationFinder,
+                       ITestContext testContext)
+  {
+    super(com.getName(), com, annotationFinder, instance);
+    Utils.checkInstanceOrStatic(instance, com.getMethod());
+    Utils.checkReturnType(com.getMethod(), Object[].class, IInstanceInfo[].class);
+    Class<?> declaringClass = com.getDeclaringClass();
+    if (instance != null && ! declaringClass.isAssignableFrom(instance.getClass())) {
+      throw new TestNGException("Mismatch between instance/method classes:"
+          + instance.getClass() + " " + declaringClass);
+    }
+    if (instance == null && com.getMethod() != null && !Modifier.isStatic(com.getMethod().getModifiers())) {
+      throw new TestNGException("An inner factory method MUST be static. But '" + com.getMethod().getName() + "' from '" + declaringClass.getName() + "' is not.");
+    }
+    if (com.getMethod() != null && !Modifier.isPublic(com.getMethod().getModifiers())) {
+      try {
+        com.getMethod().setAccessible(true);
+      } catch (SecurityException e) {
+        throw new TestNGException(e);
+      }
+    }
+
+    m_instance = instance;
+    m_xmlTest = xmlTest;
+    m_testContext = testContext;
+    NoOpTestClass tc = new NoOpTestClass();
+    tc.setTestClass(declaringClass);
+    m_testClass = tc;
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[FactoryMethod] " + s);
+  }
+
+  public Object[] invoke() {
+    List<Object> result = Lists.newArrayList();
+
+    Map<String, String> allParameterNames = Maps.newHashMap();
+    Iterator<Object[]> parameterIterator =
+      Parameters.handleParameters(this,
+          allParameterNames,
+          m_instance,
+          new Parameters.MethodParameters(m_xmlTest.getAllParameters(),
+              findMethodParameters(m_xmlTest),
+              null, null, m_testContext,
+              null /* testResult */),
+          m_xmlTest.getSuite(),
+          m_annotationFinder,
+          null /* fedInstance */).parameters;
+
+    try {
+      while (parameterIterator.hasNext()) {
+        Object[] parameters = parameterIterator.next();
+        Object[] testInstances;
+        ConstructorOrMethod com = getConstructorOrMethod();
+        if (com.getMethod() != null) {
+          testInstances = (Object[]) getMethod().invoke(m_instance, parameters);
+          for (Object testInstance : testInstances) {
+            result.add(testInstance);
+          }
+        } else {
+          Object instance = com.getConstructor().newInstance(parameters);
+          result.add(instance);
+        }
+      }
+    }
+    catch (Throwable t) {
+      ConstructorOrMethod com = getConstructorOrMethod();
+      throw new TestNGException("The factory method "
+          + com.getDeclaringClass() + "." + com.getName()
+          + "() threw an exception", t);
+    }
+
+    return result.toArray(new Object[result.size()]);
+  }
+
+  @Override
+  public ITestNGMethod clone() {
+    throw new IllegalStateException("clone is not supported for FactoryMethod");
+  }
+}
diff --git a/src/main/java/org/testng/internal/Graph.java b/src/main/java/org/testng/internal/Graph.java
new file mode 100644
index 0000000..d833610
--- /dev/null
+++ b/src/main/java/org/testng/internal/Graph.java
@@ -0,0 +1,343 @@
+package org.testng.internal;
+
+import org.testng.TestNGException;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+/**
+ * Simple graph class to implement topological sort (used to sort methods based on what groups
+ * they depend on).
+ *
+ * @author Cedric Beust, Aug 19, 2004
+ */
+public class Graph<T> {
+  private static boolean m_verbose = false;
+  private Map<T, Node<T>> m_nodes = Maps.newLinkedHashMap();
+  private List<T> m_strictlySortedNodes = null;
+
+  //  A map of nodes that are not the predecessors of any node
+  // (not needed for the algorithm but convenient to calculate
+  // the parallel/sequential lists in TestNG).
+  private Map<T, Node<T>> m_independentNodes = null;
+
+  public void addNode(T tm) {
+    ppp("ADDING NODE " + tm + " " + tm.hashCode());
+    m_nodes.put(tm, new Node<>(tm));
+    // Initially, all the nodes are put in the independent list as well
+  }
+
+  public Set<T> getPredecessors(T node) {
+    return findNode(node).getPredecessors().keySet();
+  }
+
+  public boolean isIndependent(T object) {
+    return m_independentNodes.containsKey(object);
+  }
+
+  private Node<T> findNode(T object) {
+    return m_nodes.get(object);
+  }
+
+  public void addPredecessor(T tm, T predecessor) {
+    Node<T> node = findNode(tm);
+    if (null == node) {
+      throw new TestNGException("Non-existing node: " + tm);
+    }
+    else {
+      node.addPredecessor(predecessor);
+      addNeighbor(tm, predecessor);
+      // Remove these two nodes from the independent list
+      initializeIndependentNodes();
+      m_independentNodes.remove(predecessor);
+      m_independentNodes.remove(tm);
+      ppp("  REMOVED " + predecessor + " FROM INDEPENDENT OBJECTS");
+    }
+  }
+
+  private void addNeighbor(T tm, T predecessor) {
+    findNode(tm).addNeighbor(findNode(predecessor));
+  }
+
+  public Set<T> getNeighbors(T t) {
+    Set<T> result = new HashSet<>();
+    for (Node<T> n : findNode(t).getNeighbors()) {
+      result.add(n.getObject());
+    }
+
+    return result;
+  }
+
+  private Collection<Node<T>> getNodes() {
+    return m_nodes.values();
+  }
+
+  public Collection<T> getNodeValues() {
+    return m_nodes.keySet();
+  }
+
+  /**
+   * @return All the nodes that don't have any order with each other.
+   */
+  public Set<T> getIndependentNodes() {
+    return m_independentNodes.keySet();
+  }
+
+  /**
+   * @return All the nodes that have an order with each other, sorted
+   * in one of the valid sorts.
+   */
+  public List<T> getStrictlySortedNodes() {
+    return m_strictlySortedNodes;
+  }
+
+  public void topologicalSort() {
+    ppp("================ SORTING");
+    m_strictlySortedNodes = Lists.newArrayList();
+    initializeIndependentNodes();
+
+    //
+    // Clone the list of nodes but only keep those that are
+    // not independent.
+    //
+    List<Node<T>> nodes2 = Lists.newArrayList();
+    for (Node<T> n : getNodes()) {
+      if (! isIndependent(n.getObject())) {
+        ppp("ADDING FOR SORT: " + n.getObject());
+        nodes2.add(n.clone());
+      }
+      else {
+        ppp("SKIPPING INDEPENDENT NODE " + n);
+      }
+    }
+
+    //
+    // Sort the nodes alphabetically to make sure that methods of the same class
+    // get run close to each other as much as possible
+    //
+    Collections.sort(nodes2);
+
+    //
+    // Sort
+    //
+    while (! nodes2.isEmpty()) {
+
+      //
+      // Find all the nodes that don't have any predecessors, add
+      // them to the result and mark them for removal
+      //
+      Node<T> node = findNodeWithNoPredecessors(nodes2);
+      if (null == node) {
+        List<T> cycle = new Tarjan<>(this, nodes2.get(0).getObject()).getCycle();
+        StringBuilder sb = new StringBuilder();
+        sb.append("The following methods have cyclic dependencies:\n");
+        for (T m : cycle) {
+          sb.append(m).append("\n");
+        }
+        throw new TestNGException(sb.toString());
+      }
+      else {
+        m_strictlySortedNodes.add(node.getObject());
+        removeFromNodes(nodes2, node);
+      }
+    }
+
+    ppp("=============== DONE SORTING");
+    if (m_verbose) {
+      dumpSortedNodes();
+    }
+  }
+
+  private void initializeIndependentNodes() {
+    if (null == m_independentNodes) {
+      List<Node<T>> list = Lists.newArrayList(m_nodes.values());
+      // Ideally, we should not have to sort this. However, due to a bug where it treats all the methods as
+      // dependent nodes. Therefore, all the nodes were mostly sorted alphabetically. So we need to keep
+      // the behavior for now.
+      Collections.sort(list);
+      m_independentNodes = Maps.newLinkedHashMap();
+      for (Node<T> node : list) {
+        m_independentNodes.put(node.getObject(), node);
+      }
+    }
+  }
+
+  private void dumpSortedNodes() {
+    System.out.println("====== SORTED NODES");
+    for (T n : m_strictlySortedNodes) {
+      System.out.println("              " + n);
+    }
+    System.out.println("====== END SORTED NODES");
+  }
+
+  /**
+   * Remove a node from a list of nodes and update the list of predecessors
+   * for all the remaining nodes.
+   */
+  private void removeFromNodes(List<Node<T>> nodes, Node<T> node) {
+    nodes.remove(node);
+    for (Node<T> n : nodes) {
+      n.removePredecessor(node.getObject());
+    }
+  }
+
+  private static void ppp(String s) {
+    if (m_verbose) {
+      System.out.println("[Graph] " + s);
+    }
+  }
+
+  private Node<T> findNodeWithNoPredecessors(List<Node<T>> nodes) {
+    for (Node<T> n : nodes) {
+      if (! n.hasPredecessors()) {
+        return n;
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * @param o
+   * @return A list of all the predecessors for o
+   */
+  public List<T> findPredecessors(T o) {
+    // Locate the node
+    Node<T> node = findNode(o);
+    if (null == node) {
+      // This can happen if an interceptor returned new methods
+      return Lists.newArrayList();
+    }
+
+    // If we found the node, use breadth first search to find all
+    // all of the predecessors of o.  "result" is the growing list
+    // of all predecessors.  "visited" is the set of items we've
+    // already encountered.  "queue" is the queue of items whose
+    // predecessors we haven't yet explored.
+
+    LinkedList<T> result = new LinkedList<>();
+    Set<T> visited = new HashSet<>();
+    LinkedList<T> queue = new LinkedList<>();
+    visited.add(o);
+    queue.addLast(o);
+
+    while (! queue.isEmpty()) {
+      for (T obj : getPredecessors(queue.removeFirst())) {
+        if (! visited.contains(obj)) {
+          visited.add(obj);
+          queue.addLast(obj);
+          result.addFirst(obj);
+        }
+      }
+    }
+
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder result = new StringBuilder("[Graph ");
+    for (T node : m_nodes.keySet()) {
+      result.append(findNode(node)).append(" ");
+    }
+    result.append("]");
+
+    return result.toString();
+  }
+
+
+  /////
+  // class Node
+  //
+  public static class Node<T> implements Comparable<Node<T>> {
+    private T m_object = null;
+    private Map<T, T> m_predecessors = Maps.newHashMap();
+
+    public Node(T tm) {
+      m_object = tm;
+    }
+
+    private Set<Node<T>> m_neighbors = new HashSet<>();
+    public void addNeighbor(Node<T> neighbor) {
+      m_neighbors.add(neighbor);
+    }
+
+    public Set<Node<T>> getNeighbors() {
+      return m_neighbors;
+    }
+
+    @Override
+    public Node<T> clone() {
+      Node<T> result = new Node<>(m_object);
+      for (T pred : m_predecessors.values()) {
+        result.addPredecessor(pred);
+      }
+      return result;
+    }
+
+    public T getObject() {
+      return m_object;
+    }
+
+    public Map<T, T> getPredecessors() {
+      return m_predecessors;
+    }
+
+    /**
+     *
+     * @return true if this predecessor was found and removed
+     */
+    public boolean removePredecessor(T o) {
+      boolean result = false;
+
+      T pred = m_predecessors.get(o);
+      if (null != pred) {
+        result = null != m_predecessors.remove(o);
+        if (result) {
+          ppp("  REMOVED PRED " + o + " FROM NODE " + m_object);
+        }
+        else {
+          ppp("  FAILED TO REMOVE PRED " + o + " FROM NODE " + m_object);
+        }
+      }
+
+      return result;
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder sb = new StringBuilder("[Node:" + m_object);
+      sb.append("  pred:");
+      for (T o : m_predecessors.values()) {
+        sb.append(" ").append(o);
+      }
+      sb.append("]");
+      String result = sb.toString();
+      return result;
+    }
+
+    public void addPredecessor(T tm) {
+      ppp("  ADDING PREDECESSOR FOR " + m_object + " ==> " + tm);
+      m_predecessors.put(tm, tm);
+    }
+
+    public boolean hasPredecessors() {
+      return m_predecessors.size() > 0;
+    }
+
+    public boolean hasPredecessor(T m) {
+      return m_predecessors.containsKey(m);
+    }
+
+    @Override
+    public int compareTo(Node<T> o) {
+      return getObject().toString().compareTo(o.getObject().toString());
+    }
+  }
+}
diff --git a/src/main/java/org/testng/internal/IBsh.java b/src/main/java/org/testng/internal/IBsh.java
new file mode 100644
index 0000000..96d7bab
--- /dev/null
+++ b/src/main/java/org/testng/internal/IBsh.java
@@ -0,0 +1,7 @@
+package org.testng.internal;
+
+import org.testng.ITestNGMethod;
+
+public interface IBsh {
+  boolean includeMethodFromExpression(String expression, ITestNGMethod tm);
+}
diff --git a/src/main/java/org/testng/internal/IConfiguration.java b/src/main/java/org/testng/internal/IConfiguration.java
new file mode 100644
index 0000000..3d227b9
--- /dev/null
+++ b/src/main/java/org/testng/internal/IConfiguration.java
@@ -0,0 +1,28 @@
+package org.testng.internal;
+
+import org.testng.*;
+import org.testng.internal.annotations.IAnnotationFinder;
+
+import java.util.List;
+
+public interface IConfiguration {
+  IAnnotationFinder getAnnotationFinder();
+  void setAnnotationFinder(IAnnotationFinder finder);
+
+  ITestObjectFactory getObjectFactory();
+  void setObjectFactory(ITestObjectFactory m_objectFactory);
+
+  IHookable getHookable();
+  void setHookable(IHookable h);
+
+  IConfigurable getConfigurable();
+  void setConfigurable(IConfigurable c);
+
+  List<IExecutionListener> getExecutionListeners();
+  void addExecutionListener(IExecutionListener l);
+
+  List<IConfigurationListener> getConfigurationListeners();
+  void addConfigurationListener(IConfigurationListener cl);
+
+  List<IAlterSuiteListener> getAlterSuiteListeners();
+}
diff --git a/src/main/java/org/testng/internal/IInvoker.java b/src/main/java/org/testng/internal/IInvoker.java
new file mode 100755
index 0000000..98cda2e
--- /dev/null
+++ b/src/main/java/org/testng/internal/IInvoker.java
@@ -0,0 +1,53 @@
+package org.testng.internal;
+
+import org.testng.IClass;
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+import org.testng.xml.XmlSuite;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class defines an invoker.
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public interface IInvoker {
+
+  /**
+   * Invoke configuration methods if they belong to the same TestClass
+   * passed in parameter..
+   *
+   * TODO:  Calculate ahead of time which methods should be
+   * invoked for each class.   Might speed things up for users who invoke the same
+   * test class with different parameters in the same suite run.
+   *
+   * @param testClass the class whose configuration methods must be run
+   */
+  public  void invokeConfigurations(IClass testClass,
+                                    ITestNGMethod[] allMethods,
+                                    XmlSuite suite,
+                                    Map<String, String> parameters,
+                                    Object[] parameterValues,
+                                    Object instance);
+
+  /**
+   * Invoke the given method
+   *
+   * @param testMethod
+   * @param suite
+   * @param parameters
+   * @param groupMethods
+   *
+   * @return a list containing the results of the test methods invocations
+   */
+  public List<ITestResult> invokeTestMethods(ITestNGMethod testMethod,
+                                             XmlSuite suite,
+                                             Map<String, String> parameters,
+                                             ConfigurationGroupMethods groupMethods,
+                                             Object instance,
+                                             ITestContext testContext);
+
+}
diff --git a/src/main/java/org/testng/internal/IResultListener.java b/src/main/java/org/testng/internal/IResultListener.java
new file mode 100755
index 0000000..29ef001
--- /dev/null
+++ b/src/main/java/org/testng/internal/IResultListener.java
@@ -0,0 +1,13 @@
+package org.testng.internal;

+

+import org.testng.IConfigurationListener;

+import org.testng.ITestListener;

+

+

+/**

+ * A convenient interface to use when implementing listeners.

+ * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>

+ */

+public interface IResultListener extends ITestListener, IConfigurationListener {

+

+}

diff --git a/src/main/java/org/testng/internal/IResultListener2.java b/src/main/java/org/testng/internal/IResultListener2.java
new file mode 100644
index 0000000..6759283
--- /dev/null
+++ b/src/main/java/org/testng/internal/IResultListener2.java
@@ -0,0 +1,7 @@
+package org.testng.internal;
+
+import org.testng.IConfigurationListener2;
+
+public interface IResultListener2 extends IResultListener, IConfigurationListener2 {
+
+}
diff --git a/src/main/java/org/testng/internal/ITestResultNotifier.java b/src/main/java/org/testng/internal/ITestResultNotifier.java
new file mode 100755
index 0000000..30ebd35
--- /dev/null
+++ b/src/main/java/org/testng/internal/ITestResultNotifier.java
@@ -0,0 +1,42 @@
+package org.testng.internal;
+
+import java.util.List;
+import java.util.Set;
+
+import org.testng.IConfigurationListener;
+import org.testng.ITestListener;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+import org.testng.xml.XmlTest;
+
+/**
+ * An interface defining the notification for @Test results and also
+ * \@Configuration results.
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
+ */
+public interface ITestResultNotifier {
+
+  Set<ITestResult> getPassedTests(ITestNGMethod tm);
+
+  Set<ITestResult> getFailedTests(ITestNGMethod tm);
+
+  Set<ITestResult> getSkippedTests(ITestNGMethod tm);
+
+  void addPassedTest(ITestNGMethod tm, ITestResult tr);
+
+  void addSkippedTest(ITestNGMethod tm, ITestResult tr);
+
+  void addFailedTest(ITestNGMethod tm, ITestResult tr);
+
+  void addFailedButWithinSuccessPercentageTest(ITestNGMethod tm, ITestResult tr);
+
+  void addInvokedMethod(InvokedMethod im);
+
+  XmlTest getTest();
+
+  List<ITestListener> getTestListeners();
+
+  List<IConfigurationListener> getConfigurationListeners();
+}
diff --git a/src/main/java/org/testng/internal/InstanceInfo.java b/src/main/java/org/testng/internal/InstanceInfo.java
new file mode 100755
index 0000000..574e2ad
--- /dev/null
+++ b/src/main/java/org/testng/internal/InstanceInfo.java
@@ -0,0 +1,24 @@
+package org.testng.internal;
+
+import org.testng.IInstanceInfo;
+
+public class InstanceInfo implements IInstanceInfo {
+  private Class m_instanceClass = null;
+  private Object m_instance = null;
+
+  public InstanceInfo(Class cls, Object instance) {
+    m_instanceClass = cls;
+    m_instance = instance;
+  }
+
+  @Override
+  public Object getInstance() {
+    return m_instance;
+  }
+
+  @Override
+  public Class getInstanceClass() {
+    return m_instanceClass;
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/InvokeMethodRunnable.java b/src/main/java/org/testng/internal/InvokeMethodRunnable.java
new file mode 100644
index 0000000..1e3f1ee
--- /dev/null
+++ b/src/main/java/org/testng/internal/InvokeMethodRunnable.java
@@ -0,0 +1,83 @@
+package org.testng.internal;
+
+import org.testng.IHookable;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+
+import java.lang.reflect.Method;
+
+/**
+ * A Runnable Method invoker.
+ *
+ * @author <a href="mailto:the_mindstorm@evolva.ro>the_mindstorm</a>
+ */
+public class InvokeMethodRunnable implements Runnable {
+  private ITestNGMethod m_method = null;
+  private Object m_instance = null;
+  private Object[] m_parameters = null;
+  private final IHookable m_hookable;
+  private final ITestResult m_testResult;
+
+  public InvokeMethodRunnable(ITestNGMethod thisMethod,
+                              Object instance,
+                              Object[] parameters,
+                              IHookable hookable,
+                              ITestResult testResult)
+  {
+    m_method = thisMethod;
+    m_instance = instance;
+    m_parameters = parameters;
+    m_hookable = hookable;
+    m_testResult = testResult;
+  }
+
+  @Override
+  public void run() throws TestNGRuntimeException {
+    // If there is an invocation time out, all the invocations need to be done within this
+    // Runnable
+    if (m_method.getInvocationTimeOut() > 0) {
+      for (int i = 0; i < m_method.getInvocationCount(); i++) {
+        runOne();
+      }
+    }
+    else {
+      runOne();
+    }
+  }
+
+  private void runOne() {
+    try {
+      RuntimeException t = null;
+      try {
+        Method m = m_method.getMethod();
+        if (m_hookable == null) {
+          MethodInvocationHelper.invokeMethod(m, m_instance, m_parameters);
+        } else {
+          MethodInvocationHelper.invokeHookable(m_instance, m_parameters, m_hookable, m,
+                                                m_testResult);
+        }
+      }
+      catch(Throwable e) {
+        t = new TestNGRuntimeException(e.getCause());
+      }
+      if(null != t) {
+        Thread.currentThread().interrupt();
+        throw t;
+      }
+    }
+    finally {
+      m_method.incrementCurrentInvocationCount();
+    }
+  }
+
+  public static class TestNGRuntimeException extends RuntimeException {
+    /**
+     *
+     */
+    private static final long serialVersionUID = -8619899270785596231L;
+
+    public TestNGRuntimeException(Throwable rootCause) {
+      super(rootCause);
+    }
+  }
+}
diff --git a/src/main/java/org/testng/internal/InvokedMethod.java b/src/main/java/org/testng/internal/InvokedMethod.java
new file mode 100755
index 0000000..eb0a8cd
--- /dev/null
+++ b/src/main/java/org/testng/internal/InvokedMethod.java
@@ -0,0 +1,83 @@
+package org.testng.internal;
+
+import java.io.Serializable;
+
+import org.testng.IInvokedMethod;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+
+public class InvokedMethod implements Serializable, IInvokedMethod {
+  private static final long serialVersionUID = 2126127194102819222L;
+  transient private Object m_instance;
+  private ITestNGMethod m_testMethod;
+  private Object[] m_parameters;
+  private long m_date = System.currentTimeMillis();
+  private ITestResult m_testResult;
+
+  public InvokedMethod(Object instance,
+                       ITestNGMethod method,
+                       Object[] parameters,
+                       long date,
+                       ITestResult testResult) {
+    m_instance = instance;
+    m_testMethod = method;
+    m_parameters = parameters;
+    m_date = date;
+    m_testResult = testResult;
+  }
+
+  /* (non-Javadoc)
+   * @see org.testng.internal.IInvokedMethod#isTestMethod()
+   */
+  @Override
+  public boolean isTestMethod() {
+    return m_testMethod.isTest();
+  }
+
+  @Override
+  public String toString() {
+    StringBuffer result = new StringBuffer(m_testMethod.toString());
+    for (Object p : m_parameters) {
+      result.append(p).append(" ");
+    }
+    result.append(" ").append(m_instance != null ? m_instance.hashCode() : " <static>");
+
+    return result.toString();
+  }
+
+  /* (non-Javadoc)
+   * @see org.testng.internal.IInvokedMethod#isConfigurationMethod()
+   */
+  @Override
+  public boolean isConfigurationMethod() {
+    return m_testMethod.isBeforeMethodConfiguration() ||
+           m_testMethod.isAfterMethodConfiguration() ||
+           m_testMethod.isBeforeTestConfiguration() ||
+           m_testMethod.isAfterTestConfiguration() ||
+           m_testMethod.isBeforeClassConfiguration() ||
+           m_testMethod.isAfterClassConfiguration() ||
+           m_testMethod.isBeforeSuiteConfiguration() ||
+           m_testMethod.isAfterSuiteConfiguration();
+  }
+
+  /* (non-Javadoc)
+   * @see org.testng.internal.IInvokedMethod#getTestMethod()
+   */
+  @Override
+  public ITestNGMethod getTestMethod() {
+    return m_testMethod;
+  }
+
+  /* (non-Javadoc)
+   * @see org.testng.internal.IInvokedMethod#getDate()
+   */
+  @Override
+  public long getDate() {
+    return m_date;
+  }
+
+  @Override
+  public ITestResult getTestResult() {
+    return m_testResult;
+  }
+}
diff --git a/src/main/java/org/testng/internal/Invoker.java b/src/main/java/org/testng/internal/Invoker.java
new file mode 100644
index 0000000..94073b7
--- /dev/null
+++ b/src/main/java/org/testng/internal/Invoker.java
@@ -0,0 +1,1738 @@
+package org.testng.internal;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.testng.IClass;
+import org.testng.IClassListener;
+import org.testng.IConfigurable;
+import org.testng.IConfigurationListener;
+import org.testng.IConfigurationListener2;
+import org.testng.IHookable;
+import org.testng.IInvokedMethod;
+import org.testng.IInvokedMethodListener;
+import org.testng.IRetryAnalyzer;
+import org.testng.ITestClass;
+import org.testng.ITestContext;
+import org.testng.ITestListener;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+import org.testng.Reporter;
+import org.testng.SkipException;
+import org.testng.SuiteRunState;
+import org.testng.TestException;
+import org.testng.TestNGException;
+import org.testng.annotations.IConfigurationAnnotation;
+import org.testng.annotations.NoInjection;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.collections.Sets;
+import org.testng.internal.InvokeMethodRunnable.TestNGRuntimeException;
+import org.testng.internal.ParameterHolder.ParameterOrigin;
+import org.testng.internal.annotations.AnnotationHelper;
+import org.testng.internal.annotations.IAnnotationFinder;
+import org.testng.internal.invokers.InvokedMethodListenerInvoker;
+import org.testng.internal.invokers.InvokedMethodListenerMethod;
+import org.testng.internal.thread.ThreadExecutionException;
+import org.testng.internal.thread.ThreadUtil;
+import org.testng.internal.thread.graph.IWorker;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import static org.testng.internal.invokers.InvokedMethodListenerMethod.AFTER_INVOCATION;
+import static org.testng.internal.invokers.InvokedMethodListenerMethod.BEFORE_INVOCATION;
+
+/**
+ * This class is responsible for invoking methods:
+ * - test methods
+ * - configuration methods
+ * - possibly in a separate thread
+ * and then for notifying the result listeners.
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
+ */
+public class Invoker implements IInvoker {
+  private final ITestContext m_testContext;
+  private final ITestResultNotifier m_notifier;
+  private final IAnnotationFinder m_annotationFinder;
+  private final SuiteRunState m_suiteState;
+  private final boolean m_skipFailedInvocationCounts;
+  private final Collection<IInvokedMethodListener> m_invokedMethodListeners;
+  private final boolean m_continueOnFailedConfiguration;
+  private final List<IClassListener> m_classListeners;
+
+  /** Group failures must be synced as the Invoker is accessed concurrently */
+  private Map<String, Boolean> m_beforegroupsFailures = Maps.newHashtable();
+
+  /** Class failures must be synced as the Invoker is accessed concurrently */
+  private Map<Class<?>, Set<Object>> m_classInvocationResults = Maps.newHashtable();
+
+  /** Test methods whose configuration methods have failed. */
+  private Map<ITestNGMethod, Set<Object>> m_methodInvocationResults = Maps.newHashtable();
+  private IConfiguration m_configuration;
+
+  /** Predicate to filter methods */
+  private static Predicate<ITestNGMethod, IClass> CAN_RUN_FROM_CLASS = new CanRunFromClassPredicate();
+  /** Predicate to filter methods */
+  private static final Predicate<ITestNGMethod, IClass> SAME_CLASS = new SameClassNamePredicate();
+
+  private void setClassInvocationFailure(Class<?> clazz, Object instance) {
+    Set<Object> instances = m_classInvocationResults.get( clazz );
+    if (instances == null) {
+      instances = Sets.newHashSet();
+      m_classInvocationResults.put(clazz, instances);
+    }
+    instances.add(instance);
+  }
+
+  private void setMethodInvocationFailure(ITestNGMethod method, Object instance) {
+    Set<Object> instances = m_methodInvocationResults.get(method);
+    if (instances == null) {
+      instances = Sets.newHashSet();
+      m_methodInvocationResults.put(method, instances);
+    }
+    instances.add(getMethodInvocationToken(method, instance));
+  }
+
+  public Invoker(IConfiguration configuration,
+                 ITestContext testContext,
+                 ITestResultNotifier notifier,
+                 SuiteRunState state,
+                 boolean skipFailedInvocationCounts,
+                 Collection<IInvokedMethodListener> invokedMethodListeners,
+                 List<IClassListener> classListeners) {
+    m_configuration = configuration;
+    m_testContext= testContext;
+    m_suiteState= state;
+    m_notifier= notifier;
+    m_annotationFinder= configuration.getAnnotationFinder();
+    m_skipFailedInvocationCounts = skipFailedInvocationCounts;
+    m_invokedMethodListeners = invokedMethodListeners;
+    m_continueOnFailedConfiguration = XmlSuite.CONTINUE.equals(testContext.getSuite().getXmlSuite().getConfigFailurePolicy());
+    m_classListeners = classListeners;
+  }
+
+  /**
+   * Invoke configuration methods if they belong to the same TestClass passed
+   * in parameter.. <p/>TODO: Calculate ahead of time which methods should be
+   * invoked for each class. Might speed things up for users who invoke the
+   * same test class with different parameters in the same suite run.
+   *
+   * If instance is non-null, the configuration will be run on it.  If it is null,
+   * the configuration methods will be run on all the instances retrieved
+   * from the ITestClass.
+   */
+  @Override
+  public void invokeConfigurations(IClass testClass,
+                                   ITestNGMethod[] allMethods,
+                                   XmlSuite suite,
+                                   Map<String, String> params,
+                                   Object[] parameterValues,
+                                   Object instance)
+  {
+    invokeConfigurations(testClass, null, allMethods, suite, params, parameterValues, instance,
+        null);
+  }
+
+  private void invokeConfigurations(IClass testClass,
+                                   ITestNGMethod currentTestMethod,
+                                   ITestNGMethod[] allMethods,
+                                   XmlSuite suite,
+                                   Map<String, String> params,
+                                   Object[] parameterValues,
+                                   Object instance,
+                                   ITestResult testMethodResult)
+  {
+    if(null == allMethods) {
+      log(5, "No configuration methods found");
+
+      return;
+    }
+
+    ITestNGMethod[] methods= filterMethods(testClass, allMethods, SAME_CLASS);
+
+    for(ITestNGMethod tm : methods) {
+      if(null == testClass) {
+        testClass= tm.getTestClass();
+      }
+
+      ITestResult testResult= new TestResult(testClass,
+                                             instance,
+                                             tm,
+                                             null,
+                                             System.currentTimeMillis(),
+                                             System.currentTimeMillis(),
+                                             m_testContext);
+
+      IConfigurationAnnotation configurationAnnotation= null;
+      try {
+        Object inst = tm.getInstance();
+        if (inst == null) {
+          inst = instance;
+        }
+        Class<?> objectClass= inst.getClass();
+        Method method= tm.getMethod();
+
+        // Only run the configuration if
+        // - the test is enabled and
+        // - the Configuration method belongs to the same class or a parent
+        configurationAnnotation = AnnotationHelper.findConfiguration(m_annotationFinder, method);
+        boolean alwaysRun= isAlwaysRun(configurationAnnotation);
+        if(MethodHelper.isEnabled(objectClass, m_annotationFinder) || alwaysRun) {
+
+          if (MethodHelper.isEnabled(configurationAnnotation)) {
+
+            if (!confInvocationPassed(tm, currentTestMethod, testClass, instance) && !alwaysRun) {
+              handleConfigurationSkip(tm, testResult, configurationAnnotation, currentTestMethod, instance, suite);
+              continue;
+            }
+
+            log(3, "Invoking " + Utils.detailedMethodName(tm, true));
+
+            Object[] parameters = Parameters.createConfigurationParameters(tm.getMethod(),
+                params,
+                parameterValues,
+                currentTestMethod,
+                m_annotationFinder,
+                suite,
+                m_testContext,
+                testMethodResult);
+            testResult.setParameters(parameters);
+
+            Object newInstance = null != instance ? instance: inst;
+
+            runConfigurationListeners(testResult, true /* before */);
+
+            invokeConfigurationMethod(newInstance, tm,
+              parameters, testResult);
+
+            // TODO: probably we should trigger the event for each instance???
+            testResult.setEndMillis(System.currentTimeMillis());
+            runConfigurationListeners(testResult, false /* after */);
+          }
+          else {
+            log(3,
+                "Skipping "
+                + Utils.detailedMethodName(tm, true)
+                + " because it is not enabled");
+          }
+        } // if is enabled
+        else {
+          log(3,
+              "Skipping "
+              + Utils.detailedMethodName(tm, true)
+              + " because "
+              + objectClass.getName()
+              + " is not enabled");
+        }
+      }
+      catch(InvocationTargetException ex) {
+        handleConfigurationFailure(ex, tm, testResult, configurationAnnotation, currentTestMethod, instance, suite);
+      } catch(Throwable ex) { // covers the non-wrapper exceptions
+        handleConfigurationFailure(ex, tm, testResult, configurationAnnotation, currentTestMethod, instance, suite);
+      }
+    } // for methods
+  }
+
+  /**
+   * Marks the current <code>TestResult</code> as skipped and invokes the listeners.
+   */
+  private void handleConfigurationSkip(ITestNGMethod tm,
+                                       ITestResult testResult,
+                                       IConfigurationAnnotation annotation,
+                                       ITestNGMethod currentTestMethod,
+                                       Object instance,
+                                       XmlSuite suite) {
+    recordConfigurationInvocationFailed(tm, testResult.getTestClass(), annotation, currentTestMethod, instance, suite);
+    testResult.setStatus(ITestResult.SKIP);
+    runConfigurationListeners(testResult, false /* after */);
+  }
+
+  /**
+   * Is the <code>IConfiguration</code> marked as alwaysRun.
+   */
+  private boolean isAlwaysRun(IConfigurationAnnotation configurationAnnotation) {
+    if(null == configurationAnnotation) {
+      return false;
+    }
+
+    boolean alwaysRun= false;
+    if ((configurationAnnotation.getAfterSuite()
+        || configurationAnnotation.getAfterTest()
+        || configurationAnnotation.getAfterTestClass()
+        || configurationAnnotation.getAfterTestMethod()
+        || configurationAnnotation.getBeforeTestMethod()
+        || configurationAnnotation.getBeforeTestClass()
+        || configurationAnnotation.getBeforeTest()
+        || configurationAnnotation.getBeforeSuite())
+        && configurationAnnotation.getAlwaysRun())
+    {
+        alwaysRun= true;
+    }
+
+    return alwaysRun;
+  }
+
+  private void handleConfigurationFailure(Throwable ite,
+                                          ITestNGMethod tm,
+                                          ITestResult testResult,
+                                          IConfigurationAnnotation annotation,
+                                          ITestNGMethod currentTestMethod,
+                                          Object instance,
+                                          XmlSuite suite)
+  {
+    Throwable cause= ite.getCause() != null ? ite.getCause() : ite;
+
+    if(isSkipExceptionAndSkip(cause)) {
+      testResult.setThrowable(cause);
+      handleConfigurationSkip(tm, testResult, annotation, currentTestMethod, instance, suite);
+      return;
+    }
+    Utils.log("", 3, "Failed to invoke configuration method "
+        + tm.getRealClass().getName() + "." + tm.getMethodName() + ":" + cause.getMessage());
+    handleException(cause, tm, testResult, 1);
+    runConfigurationListeners(testResult, false /* after */);
+
+    //
+    // If in TestNG mode, need to take a look at the annotation to figure out
+    // what kind of @Configuration method we're dealing with
+    //
+    if (null != annotation) {
+      recordConfigurationInvocationFailed(tm, testResult.getTestClass(), annotation, currentTestMethod, instance, suite);
+    }
+  }
+
+  /**
+   * @return All the classes that belong to the same <test> tag as @param cls
+   */
+  private XmlClass[] findClassesInSameTest(Class<?> cls, XmlSuite suite) {
+    Map<String, XmlClass> vResult= Maps.newHashMap();
+    String className= cls.getName();
+    for(XmlTest test : suite.getTests()) {
+      for(XmlClass testClass : test.getXmlClasses()) {
+        if(testClass.getName().equals(className)) {
+
+          // Found it, add all the classes in this test in the result
+          for(XmlClass thisClass : test.getXmlClasses()) {
+            vResult.put(thisClass.getName(), thisClass);
+          }
+          // Note:  we need to iterate through the entire suite since the same
+          // class might appear in several <test> tags
+        }
+      }
+    }
+
+    XmlClass[] result= vResult.values().toArray(new XmlClass[vResult.size()]);
+
+    return result;
+  }
+
+  /**
+   * Record internally the failure of a Configuration, so that we can determine
+   * later if @Test should be skipped.
+   */
+  private void recordConfigurationInvocationFailed(ITestNGMethod tm,
+                                                   IClass testClass,
+                                                   IConfigurationAnnotation annotation,
+                                                   ITestNGMethod currentTestMethod,
+                                                   Object instance,
+                                                   XmlSuite suite) {
+    // If beforeTestClass or afterTestClass failed, mark either the config method's
+    // entire class as failed, or the class under tests as failed, depending on
+    // the configuration failure policy
+    if (annotation.getBeforeTestClass() || annotation.getAfterTestClass()) {
+      // tm is the configuration method, and currentTestMethod is null for BeforeClass
+      // methods, so we need testClass
+      if (m_continueOnFailedConfiguration) {
+        setClassInvocationFailure(testClass.getRealClass(), instance);
+      } else {
+        setClassInvocationFailure(tm.getRealClass(), instance);
+      }
+    }
+
+    // If before/afterTestMethod failed, mark either the config method's entire
+    // class as failed, or just the current test method as failed, depending on
+    // the configuration failure policy
+    else if (annotation.getBeforeTestMethod() || annotation.getAfterTestMethod()) {
+      if (m_continueOnFailedConfiguration) {
+        setMethodInvocationFailure(currentTestMethod, instance);
+      } else {
+        setClassInvocationFailure(tm.getRealClass(), instance);
+      }
+    }
+
+    // If beforeSuite or afterSuite failed, mark *all* the classes as failed
+    // for configurations.  At this point, the entire Suite is screwed
+    else if (annotation.getBeforeSuite() || annotation.getAfterSuite()) {
+      m_suiteState.failed();
+    }
+
+    // beforeTest or afterTest:  mark all the classes in the same
+    // <test> stanza as failed for configuration
+    else if (annotation.getBeforeTest() || annotation.getAfterTest()) {
+      setClassInvocationFailure(tm.getRealClass(), instance);
+      XmlClass[] classes= findClassesInSameTest(tm.getRealClass(), suite);
+      for(XmlClass xmlClass : classes) {
+        setClassInvocationFailure(xmlClass.getSupportClass(), instance);
+      }
+    }
+    String[] beforeGroups= annotation.getBeforeGroups();
+    if(null != beforeGroups && beforeGroups.length > 0) {
+      for(String group: beforeGroups) {
+        m_beforegroupsFailures.put(group, Boolean.FALSE);
+      }
+    }
+  }
+
+  /**
+   * @return true if this class or a parent class failed to initialize.
+   */
+  private boolean classConfigurationFailed(Class<?> cls) {
+    for (Class<?> c : m_classInvocationResults.keySet()) {
+      if (c == cls || c.isAssignableFrom(cls)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * @return true if this class has successfully run all its @Configuration
+   * method or false if at least one of these methods failed.
+   */
+  private boolean confInvocationPassed(ITestNGMethod method, ITestNGMethod currentTestMethod,
+      IClass testClass, Object instance) {
+    boolean result= true;
+
+    Class<?> cls = testClass.getRealClass();
+
+    if(m_suiteState.isFailed()) {
+      result= false;
+    }
+    else {
+      if (classConfigurationFailed(cls)) {
+        if (! m_continueOnFailedConfiguration) {
+          result = !classConfigurationFailed(cls);
+        } else {
+          result = !m_classInvocationResults.get(cls).contains(instance);
+        }
+      }
+      // if method is BeforeClass, currentTestMethod will be null
+      else if (m_continueOnFailedConfiguration &&
+              currentTestMethod != null &&
+              m_methodInvocationResults.containsKey(currentTestMethod)) {
+        result = !m_methodInvocationResults.get(currentTestMethod).contains(getMethodInvocationToken(currentTestMethod, instance));
+      }
+      else if (! m_continueOnFailedConfiguration) {
+        for(Class<?> clazz: m_classInvocationResults.keySet()) {
+//          if (clazz == cls) {
+          if(clazz.isAssignableFrom(cls)) {
+            result= false;
+            break;
+          }
+        }
+      }
+    }
+
+    // check if there are failed @BeforeGroups
+    String[] groups= method.getGroups();
+    if(null != groups && groups.length > 0) {
+      for(String group: groups) {
+        if(m_beforegroupsFailures.containsKey(group)) {
+          result= false;
+          break;
+        }
+      }
+    }
+    return result;
+  }
+
+   // Creates a token for tracking a unique invocation of a method on an instance.
+   // Is used when configFailurePolicy=continue.
+  private Object getMethodInvocationToken(ITestNGMethod method, Object instance) {
+    return String.format("%s+%d", instance.toString(), method.getCurrentInvocationCount());
+  }
+
+  /**
+   * Effectively invokes a configuration method on all passed in instances.
+   * TODO: Should change this method to be more like invokeMethod() so that we can
+   * handle calls to {@code IInvokedMethodListener} better.
+   *
+   * @param targetInstance the instance to invoke the configuration method on
+   * @param tm the configuration method
+   * @param params the parameters needed for method invocation
+   * @param testResult
+   * @throws InvocationTargetException
+   * @throws IllegalAccessException
+   */
+  private void invokeConfigurationMethod(Object targetInstance,
+                                         ITestNGMethod tm,
+                                         Object[] params,
+                                         ITestResult testResult)
+    throws InvocationTargetException, IllegalAccessException
+  {
+    // Mark this method with the current thread id
+    tm.setId(ThreadUtil.currentThreadInfo());
+
+    {
+      InvokedMethod invokedMethod= new InvokedMethod(targetInstance,
+                                          tm,
+                                          params,
+                                          System.currentTimeMillis(),
+                                          testResult);
+
+      runInvokedMethodListeners(BEFORE_INVOCATION, invokedMethod, testResult);
+      m_notifier.addInvokedMethod(invokedMethod);
+      try {
+        Reporter.setCurrentTestResult(testResult);
+        Method method = tm.getMethod();
+
+        //
+        // If this method is a IConfigurable, invoke its run() method
+        //
+        IConfigurable configurableInstance =
+          IConfigurable.class.isAssignableFrom(tm.getMethod().getDeclaringClass()) ?
+          (IConfigurable) targetInstance : m_configuration.getConfigurable();
+        if (configurableInstance != null) {
+          MethodInvocationHelper.invokeConfigurable(targetInstance, params, configurableInstance, method,
+              testResult);
+        }
+        else {
+          //
+          // Not a IConfigurable, invoke directly
+          //
+          if (MethodHelper.calculateTimeOut(tm) <= 0) {
+            MethodInvocationHelper.invokeMethod(method, targetInstance, params);
+          }
+          else {
+            MethodInvocationHelper.invokeWithTimeout(tm, targetInstance, params, testResult);
+            if (!testResult.isSuccess()) {
+              // A time out happened
+              throwConfigurationFailure(testResult, testResult.getThrowable());
+              throw testResult.getThrowable();
+            }
+          }
+        }
+      }
+      catch (InvocationTargetException | IllegalAccessException ex) {
+       throwConfigurationFailure(testResult, ex);
+       throw ex;
+      } catch (Throwable ex) {
+        throwConfigurationFailure(testResult, ex);
+        throw new TestNGException(ex);
+      }
+      finally {
+        Reporter.setCurrentTestResult(testResult);
+        runInvokedMethodListeners(AFTER_INVOCATION, invokedMethod, testResult);
+        Reporter.setCurrentTestResult(null);
+      }
+    }
+  }
+
+  private void throwConfigurationFailure(ITestResult testResult, Throwable ex)
+  {
+    testResult.setStatus(ITestResult.FAILURE);;
+    testResult.setThrowable(ex.getCause() == null ? ex : ex.getCause());
+  }
+
+  private void runInvokedMethodListeners(InvokedMethodListenerMethod listenerMethod, IInvokedMethod invokedMethod,
+      ITestResult testResult)
+  {
+    if ( noListenersPresent() ) {
+      return;
+    }
+
+    InvokedMethodListenerInvoker invoker = new InvokedMethodListenerInvoker(listenerMethod, testResult, m_testContext);
+    for (IInvokedMethodListener currentListener : m_invokedMethodListeners) {
+      invoker.invokeListener(currentListener, invokedMethod);
+    }
+  }
+
+  private boolean noListenersPresent() {
+    return (m_invokedMethodListeners == null) || (m_invokedMethodListeners.size() == 0);
+  }
+
+  // pass both paramValues and paramIndex to be thread safe in case parallel=true + dataprovider.
+  private ITestResult invokeMethod(Object instance,
+                                   final ITestNGMethod tm,
+                                   Object[] parameterValues,
+                                   int parametersIndex,
+                                   XmlSuite suite,
+                                   Map<String, String> params,
+                                   ITestClass testClass,
+                                   ITestNGMethod[] beforeMethods,
+                                   ITestNGMethod[] afterMethods,
+                                   ConfigurationGroupMethods groupMethods,
+                                   FailureContext failureContext) {
+    TestResult testResult = new TestResult();
+
+    //
+    // Invoke beforeGroups configurations
+    //
+    invokeBeforeGroupsConfigurations(testClass, tm, groupMethods, suite, params,
+        instance);
+
+    //
+    // Invoke beforeMethods only if
+    // - firstTimeOnly is not set
+    // - firstTimeOnly is set, and we are reaching at the first invocationCount
+    //
+    invokeConfigurations(testClass, tm,
+      filterConfigurationMethods(tm, beforeMethods, true /* beforeMethods */),
+      suite, params, parameterValues,
+      instance, testResult);
+
+    //
+    // Create the ExtraOutput for this method
+    //
+    InvokedMethod invokedMethod = null;
+    try {
+      testResult.init(testClass, instance,
+                                 tm,
+                                 null,
+                                 System.currentTimeMillis(),
+                                 0,
+                                 m_testContext);
+      testResult.setParameters(parameterValues);
+      testResult.setHost(m_testContext.getHost());
+      testResult.setStatus(ITestResult.STARTED);
+
+      invokedMethod= new InvokedMethod(instance,
+          tm,
+          parameterValues,
+          System.currentTimeMillis(),
+          testResult);
+
+      // Fix from ansgarkonermann
+      // invokedMethod is used in the finally, which can be invoked if
+      // any of the test listeners throws an exception, therefore,
+      // invokedMethod must have a value before we get here
+      runTestListeners(testResult);
+
+      runInvokedMethodListeners(BEFORE_INVOCATION, invokedMethod, testResult);
+
+      m_notifier.addInvokedMethod(invokedMethod);
+
+      Method thisMethod = tm.getConstructorOrMethod().getMethod();
+
+      if(confInvocationPassed(tm, tm, testClass, instance)) {
+        log(3, "Invoking " + tm.getRealClass().getName() + "." + tm.getMethodName());
+
+        Reporter.setCurrentTestResult(testResult);
+
+        // If this method is a IHookable, invoke its run() method
+        IHookable hookableInstance =
+            IHookable.class.isAssignableFrom(tm.getRealClass()) ?
+            (IHookable) instance : m_configuration.getHookable();
+
+        if (MethodHelper.calculateTimeOut(tm) <= 0) {
+          if (hookableInstance != null) {
+            MethodInvocationHelper.invokeHookable(instance,
+                parameterValues, hookableInstance, thisMethod, testResult);
+          } else {
+            // Not a IHookable, invoke directly
+            MethodInvocationHelper.invokeMethod(thisMethod, instance,
+                parameterValues);
+          }
+          testResult.setStatus(ITestResult.SUCCESS);
+        } else {
+          // Method with a timeout
+          MethodInvocationHelper.invokeWithTimeout(tm, instance, parameterValues, testResult, hookableInstance);
+        }
+      }
+      else {
+        testResult.setStatus(ITestResult.SKIP);
+      }
+    }
+    catch(InvocationTargetException ite) {
+      testResult.setThrowable(ite.getCause());
+      testResult.setStatus(ITestResult.FAILURE);
+    }
+    catch(ThreadExecutionException tee) { // wrapper for TestNGRuntimeException
+      Throwable cause= tee.getCause();
+      if(TestNGRuntimeException.class.equals(cause.getClass())) {
+        testResult.setThrowable(cause.getCause());
+      }
+      else {
+        testResult.setThrowable(cause);
+      }
+      testResult.setStatus(ITestResult.FAILURE);
+    }
+    catch(Throwable thr) { // covers the non-wrapper exceptions
+      testResult.setThrowable(thr);
+      testResult.setStatus(ITestResult.FAILURE);
+    }
+    finally {
+      // Set end time ASAP
+      testResult.setEndMillis(System.currentTimeMillis());
+
+      ExpectedExceptionsHolder expectedExceptionClasses
+          = new ExpectedExceptionsHolder(m_annotationFinder, tm, new RegexpExpectedExceptionsHolder(m_annotationFinder, tm));
+      List<ITestResult> results = Lists.<ITestResult>newArrayList(testResult);
+      handleInvocationResults(tm, results, expectedExceptionClasses, failureContext);
+
+      // If this method has a data provider and just failed, memorize the number
+      // at which it failed.
+      // Note: we're not exactly testing that this method has a data provider, just
+      // that it has parameters, so might have to revisit this if bugs get reported
+      // for the case where this method has parameters that don't come from a data
+      // provider
+      if (testResult.getThrowable() != null && parameterValues.length > 0) {
+        tm.addFailedInvocationNumber(parametersIndex);
+      }
+
+      //
+      // Increment the invocation count for this method
+      //
+      tm.incrementCurrentInvocationCount();
+
+      // Run invokedMethodListeners after updating TestResult
+      runInvokedMethodListeners(AFTER_INVOCATION, invokedMethod, testResult);
+      runTestListeners(testResult);
+
+      //
+      // Invoke afterMethods only if
+      // - lastTimeOnly is not set
+      // - lastTimeOnly is set, and we are reaching the last invocationCount
+      //
+      invokeConfigurations(testClass, tm,
+          filterConfigurationMethods(tm, afterMethods, false /* beforeMethods */),
+          suite, params, parameterValues,
+          instance,
+          testResult);
+
+      //
+      // Invoke afterGroups configurations
+      //
+      invokeAfterGroupsConfigurations(testClass, tm, groupMethods, suite,
+          params, instance);
+
+      // Reset the test result last. If we do this too early, Reporter.log()
+      // invocations from listeners will be discarded
+      Reporter.setCurrentTestResult(null);
+    }
+
+    return testResult;
+  }
+
+  void collectResults(ITestNGMethod testMethod, Collection<ITestResult> results) {
+    for (ITestResult result : results) {
+      // Collect the results
+      final int status = result.getStatus();
+      if(ITestResult.SUCCESS == status) {
+        m_notifier.addPassedTest(testMethod, result);
+      }
+      else if(ITestResult.SKIP == status) {
+        m_notifier.addSkippedTest(testMethod, result);
+      }
+      else if(ITestResult.FAILURE == status) {
+        m_notifier.addFailedTest(testMethod, result);
+      }
+      else if(ITestResult.SUCCESS_PERCENTAGE_FAILURE == status) {
+        m_notifier.addFailedButWithinSuccessPercentageTest(testMethod, result);
+      }
+      else {
+        assert false : "UNKNOWN STATUS:" + status;
+      }
+    }
+  }
+
+  /**
+   * The array of methods contains @BeforeMethods if isBefore if true, @AfterMethods
+   * otherwise.  This function removes all the methods that should not be run at this
+   * point because they are either firstTimeOnly or lastTimeOnly and we haven't reached
+   * the current invocationCount yet
+   */
+  private ITestNGMethod[] filterConfigurationMethods(ITestNGMethod tm,
+      ITestNGMethod[] methods, boolean isBefore)
+  {
+    List<ITestNGMethod> result = Lists.newArrayList();
+    for (ITestNGMethod m : methods) {
+      ConfigurationMethod cm = (ConfigurationMethod) m;
+      if (isBefore) {
+        if (! cm.isFirstTimeOnly() ||
+            (cm.isFirstTimeOnly() && tm.getCurrentInvocationCount() == 0))
+        {
+          result.add(m);
+        }
+      }
+      else {
+        int current = tm.getCurrentInvocationCount();
+        boolean isLast = false;
+        // If we have parameters, set the boolean if we are about to run
+        // the last invocation
+        if (tm.getParameterInvocationCount() > 0) {
+          isLast = current == tm.getParameterInvocationCount() * tm.getTotalInvocationCount();
+        }
+        // If we have invocationCount > 1, set the boolean if we are about to
+        // run the last invocation
+        else if (tm.getTotalInvocationCount() > 1) {
+          isLast = current == tm.getTotalInvocationCount();
+        }
+        if (! cm.isLastTimeOnly() || (cm.isLastTimeOnly() && isLast)) {
+          result.add(m);
+        }
+      }
+    }
+
+    return result.toArray(new ITestNGMethod[result.size()]);
+  }
+
+  /**
+   * invokeTestMethods() eventually converge here to invoke a single @Test method.
+   * <p/>
+   * This method is responsible for actually invoking the method. It decides if the invocation
+   * must be done:
+   * <ul>
+   * <li>through an <code>IHookable</code></li>
+   * <li>directly (through reflection)</li>
+   * <li>in a separate thread (in case it needs to timeout)
+   * </ul>
+   *
+   * <p/>
+   * This method is also responsible for invoking @BeforeGroup, @BeforeMethod, @AfterMethod, @AfterGroup
+   * if it is the case for the passed in @Test method.
+   */
+  protected ITestResult invokeTestMethod(Object instance,
+                                             final ITestNGMethod tm,
+                                             Object[] parameterValues,
+                                             int parametersIndex,
+                                             XmlSuite suite,
+                                             Map<String, String> params,
+                                             ITestClass testClass,
+                                             ITestNGMethod[] beforeMethods,
+                                             ITestNGMethod[] afterMethods,
+                                             ConfigurationGroupMethods groupMethods,
+                                             FailureContext failureContext)
+  {
+    // Mark this method with the current thread id
+    tm.setId(ThreadUtil.currentThreadInfo());
+
+    ITestResult result = invokeMethod(instance, tm, parameterValues, parametersIndex, suite, params,
+                                      testClass, beforeMethods, afterMethods, groupMethods,
+                                      failureContext);
+
+    return result;
+  }
+
+  /**
+   * Filter all the beforeGroups methods and invoke only those that apply
+   * to the current test method
+   */
+  private void invokeBeforeGroupsConfigurations(ITestClass testClass,
+                                                ITestNGMethod currentTestMethod,
+                                                ConfigurationGroupMethods groupMethods,
+                                                XmlSuite suite,
+                                                Map<String, String> params,
+                                                Object instance)
+  {
+    synchronized(groupMethods) {
+      List<ITestNGMethod> filteredMethods = Lists.newArrayList();
+      String[] groups = currentTestMethod.getGroups();
+      Map<String, List<ITestNGMethod>> beforeGroupMap = groupMethods.getBeforeGroupsMap();
+
+      for (String group : groups) {
+        List<ITestNGMethod> methods = beforeGroupMap.get(group);
+        if (methods != null) {
+          filteredMethods.addAll(methods);
+        }
+      }
+
+      ITestNGMethod[] beforeMethodsArray = filteredMethods.toArray(new ITestNGMethod[filteredMethods.size()]);
+      //
+      // Invoke the right groups methods
+      //
+      if(beforeMethodsArray.length > 0) {
+        // don't pass the IClass or the instance as the method may be external
+        // the invocation must be similar to @BeforeTest/@BeforeSuite
+        invokeConfigurations(null, beforeMethodsArray, suite, params,
+            null, /* no parameter values */
+            null);
+      }
+
+      //
+      // Remove them so they don't get run again
+      //
+      groupMethods.removeBeforeGroups(groups);
+    }
+  }
+
+  private void invokeAfterGroupsConfigurations(ITestClass testClass,
+                                               ITestNGMethod currentTestMethod,
+                                               ConfigurationGroupMethods groupMethods,
+                                               XmlSuite suite,
+                                               Map<String, String> params,
+                                               Object instance)
+  {
+    // Skip this if the current method doesn't belong to any group
+    // (only a method that belongs to a group can trigger the invocation
+    // of afterGroups methods)
+    if (currentTestMethod.getGroups().length == 0) {
+      return;
+    }
+
+    // See if the currentMethod is the last method in any of the groups
+    // it belongs to
+    Map<String, String> filteredGroups = Maps.newHashMap();
+    String[] groups = currentTestMethod.getGroups();
+    synchronized(groupMethods) {
+      for (String group : groups) {
+        if (groupMethods.isLastMethodForGroup(group, currentTestMethod)) {
+          filteredGroups.put(group, group);
+        }
+      }
+
+      if(filteredGroups.isEmpty()) {
+        return;
+      }
+
+      // The list of afterMethods to run
+      Map<ITestNGMethod, ITestNGMethod> afterMethods = Maps.newHashMap();
+
+      // Now filteredGroups contains all the groups for which we need to run the afterGroups
+      // method.  Find all the methods that correspond to these groups and invoke them.
+      Map<String, List<ITestNGMethod>> map = groupMethods.getAfterGroupsMap();
+      for (String g : filteredGroups.values()) {
+        List<ITestNGMethod> methods = map.get(g);
+        // Note:  should put them in a map if we want to make sure the same afterGroups
+        // doesn't get run twice
+        if (methods != null) {
+          for (ITestNGMethod m : methods) {
+            afterMethods.put(m, m);
+          }
+        }
+      }
+
+      // Got our afterMethods, invoke them
+      ITestNGMethod[] afterMethodsArray = afterMethods.keySet().toArray(new ITestNGMethod[afterMethods.size()]);
+      // don't pass the IClass or the instance as the method may be external
+      // the invocation must be similar to @BeforeTest/@BeforeSuite
+      invokeConfigurations(null, afterMethodsArray, suite, params,
+          null, /* no parameter values */
+          null);
+
+      // Remove the groups so they don't get run again
+      groupMethods.removeAfterGroups(filteredGroups.keySet());
+    }
+  }
+
+  private Object[] getParametersFromIndex(Iterator<Object[]> parametersValues, int index) {
+    while (parametersValues.hasNext()) {
+      Object[] parameters = parametersValues.next();
+
+      if (index == 0) {
+        return parameters;
+      }
+      index--;
+    }
+    return null;
+  }
+
+  int retryFailed(Object instance,
+                           final ITestNGMethod tm,
+                           XmlSuite suite,
+                           ITestClass testClass,
+                           ITestNGMethod[] beforeMethods,
+                           ITestNGMethod[] afterMethods,
+                           ConfigurationGroupMethods groupMethods,
+                           List<ITestResult> result,
+                           int failureCount,
+                           ExpectedExceptionsHolder expectedExceptionHolder,
+                           ITestContext testContext,
+                           Map<String, String> parameters,
+                           int parametersIndex) {
+    final FailureContext failure = new FailureContext();
+    failure.count = failureCount;
+    do {
+      failure.instances = Lists.newArrayList ();
+      Map<String, String> allParameters = Maps.newHashMap();
+      /**
+       * TODO: This recreates all the parameters every time when we only need
+       * one specific set. Should optimize it by only recreating the set needed.
+       */
+      ParameterBag bag = createParameters(tm, parameters,
+          allParameters, suite, testContext, null /* fedInstance */);
+      Object[] parameterValues =
+          getParametersFromIndex(bag.parameterHolder.parameters, parametersIndex);
+
+      result.add(invokeMethod(instance, tm, parameterValues, parametersIndex, suite,
+          allParameters, testClass, beforeMethods, afterMethods, groupMethods, failure));
+    }
+    while (!failure.instances.isEmpty());
+    return failure.count;
+  }
+
+  private ParameterBag createParameters(ITestNGMethod testMethod,
+                                        Map<String, String> parameters,
+                                        Map<String, String> allParameterNames,
+                                        XmlSuite suite,
+                                        ITestContext testContext,
+                                        Object fedInstance)
+  {
+    Object instance;
+    if (fedInstance != null) {
+      instance = fedInstance;
+    }
+    else {
+      instance = testMethod.getInstance();
+    }
+
+    ParameterBag bag = handleParameters(testMethod,
+        instance, allParameterNames, parameters, null, suite, testContext, fedInstance, null);
+
+    return bag;
+  }
+
+  /**
+   * Invoke all the test methods. Note the plural: the method passed in
+   * parameter might be invoked several times if the test class it belongs
+   * to has more than one instance (i.e., if an @Factory method has been
+   * declared somewhere that returns several instances of this TestClass).
+   * If no @Factory method was specified, testMethod will only be invoked
+   * once.
+   * <p/>
+   * Note that this method also takes care of invoking the beforeTestMethod
+   * and afterTestMethod, if any.
+   *
+   * Note (alex): this method can be refactored to use a SingleTestMethodWorker that
+   * directly invokes
+   * {@link #invokeTestMethod(Object, ITestNGMethod, Object[], int, XmlSuite, Map, ITestClass, ITestNGMethod[], ITestNGMethod[], ConfigurationGroupMethods, FailureContext)}
+   * and this would simplify the implementation (see how DataTestMethodWorker is used)
+   */
+  @Override
+  public List<ITestResult> invokeTestMethods(ITestNGMethod testMethod,
+                                             XmlSuite suite,
+                                             Map<String, String> testParameters,
+                                             ConfigurationGroupMethods groupMethods,
+                                             Object instance,
+                                             ITestContext testContext)
+  {
+    // Potential bug here if the test method was declared on a parent class
+    assert null != testMethod.getTestClass()
+        : "COULDN'T FIND TESTCLASS FOR " + testMethod.getRealClass();
+
+    if (!MethodHelper.isEnabled(testMethod.getMethod(), m_annotationFinder)) {
+      // return if the method is not enabled. No need to do any more calculations
+      return Collections.emptyList();
+    }
+
+    // By the time this testMethod to be invoked,
+    // all dependencies should be already run or we need to skip this method,
+    // so invocation count should not affect dependencies check
+    final String okToProceed = checkDependencies(testMethod, testContext.getAllTestMethods());
+
+    if (okToProceed != null) {
+      //
+      // Not okToProceed. Test is being skipped
+      //
+      ITestResult result = registerSkippedTestResult(testMethod, null, System.currentTimeMillis(),
+          new Throwable(okToProceed));
+      m_notifier.addSkippedTest(testMethod, result);
+      return Collections.singletonList(result);
+    }
+
+
+    final Map<String, String> parameters =
+        testMethod.findMethodParameters(testContext.getCurrentXmlTest());
+
+    // For invocationCount > 1 and threadPoolSize > 1 run this method in its own pool thread.
+    if (testMethod.getInvocationCount() > 1 && testMethod.getThreadPoolSize() > 1) {
+      return invokePooledTestMethods(testMethod, suite, parameters, groupMethods, testContext);
+    }
+
+    long timeOutInvocationCount = testMethod.getInvocationTimeOut();
+    //FIXME: Is this correct?
+    boolean onlyOne = testMethod.getThreadPoolSize() > 1 ||
+      timeOutInvocationCount > 0;
+
+    int invocationCount = onlyOne ? 1 : testMethod.getInvocationCount();
+
+    ExpectedExceptionsHolder expectedExceptionHolder =
+        new ExpectedExceptionsHolder(m_annotationFinder, testMethod,
+                                     new RegexpExpectedExceptionsHolder(m_annotationFinder, testMethod));
+    final ITestClass testClass= testMethod.getTestClass();
+    final List<ITestResult> result = Lists.newArrayList();
+    final FailureContext failure = new FailureContext();
+    final ITestNGMethod[] beforeMethods = filterMethods(testClass, testClass.getBeforeTestMethods(), CAN_RUN_FROM_CLASS);
+    final ITestNGMethod[] afterMethods = filterMethods(testClass, testClass.getAfterTestMethods(), CAN_RUN_FROM_CLASS);
+    while(invocationCount-- > 0) {
+      if(false) {
+        // Prevent code formatting
+      }
+      //
+      // No threads, regular invocation
+      //
+      else {
+        // Used in catch statement
+        long start = System.currentTimeMillis();
+
+        Map<String, String> allParameterNames = Maps.newHashMap();
+        ParameterBag bag = createParameters(testMethod,
+            parameters, allParameterNames, suite, testContext, instance);
+
+        if (bag.hasErrors()) {
+          final ITestResult tr = bag.errorResult;
+          tr.setStatus(ITestResult.SKIP);
+          runTestListeners(tr);
+          m_notifier.addSkippedTest(testMethod, tr);
+          result.add(tr);
+          continue;
+        }
+
+        Iterator<Object[]> allParameterValues = bag.parameterHolder.parameters;
+        int parametersIndex = 0;
+
+        try {
+          List<TestMethodWithDataProviderMethodWorker> workers = Lists.newArrayList();
+
+          if (bag.parameterHolder.origin == ParameterOrigin.ORIGIN_DATA_PROVIDER &&
+              bag.parameterHolder.dataProviderHolder.annotation.isParallel()) {
+            while (allParameterValues.hasNext()) {
+              Object[] parameterValues = injectParameters(allParameterValues.next(),
+                  testMethod.getMethod(), testContext, null /* test result */);
+              TestMethodWithDataProviderMethodWorker w =
+                new TestMethodWithDataProviderMethodWorker(this,
+                    testMethod, parametersIndex,
+                    parameterValues, instance, suite, parameters, testClass,
+                    beforeMethods, afterMethods, groupMethods,
+                    expectedExceptionHolder, testContext, m_skipFailedInvocationCounts,
+                    invocationCount, failure.count, m_notifier);
+              workers.add(w);
+              // testng387: increment the param index in the bag.
+              parametersIndex++;
+            }
+            PoolService<List<ITestResult>> ps =
+                    new PoolService<>(suite.getDataProviderThreadCount());
+            List<List<ITestResult>> r = ps.submitTasksAndWait(workers);
+            for (List<ITestResult> l2 : r) {
+              result.addAll(l2);
+            }
+
+          } else {
+            while (allParameterValues.hasNext()) {
+              Object[] parameterValues = injectParameters(allParameterValues.next(),
+                  testMethod.getMethod(), testContext, null /* test result */);
+
+              List<ITestResult> tmpResults = Lists.newArrayList();
+
+              try {
+                tmpResults.add(invokeTestMethod(instance,
+                    testMethod,
+                    parameterValues,
+                    parametersIndex,
+                    suite,
+                    parameters,
+                    testClass,
+                    beforeMethods,
+                    afterMethods,
+                    groupMethods, failure));
+              }
+              finally {
+                if (failure.instances.isEmpty()) {
+                  result.addAll(tmpResults);
+                } else {
+                  for (Object failedInstance : failure.instances) {
+                    List<ITestResult> retryResults = Lists.newArrayList();
+
+                    failure.count = retryFailed(
+                            failedInstance, testMethod, suite, testClass, beforeMethods,
+                     afterMethods, groupMethods, retryResults,
+                     failure.count, expectedExceptionHolder,
+                     testContext, parameters, parametersIndex);
+                  result.addAll(retryResults);
+                  }
+                }
+
+                //
+                // If we have a failure, skip all the
+                // other invocationCounts
+                //
+                if (failure.count > 0
+                      && (m_skipFailedInvocationCounts
+                            || testMethod.skipFailedInvocations())) {
+                  while (invocationCount-- > 0) {
+                    result.add(registerSkippedTestResult(testMethod, instance, System.currentTimeMillis(), null));
+                  }
+                  break;
+                }
+              }// end finally
+              parametersIndex++;
+            }
+          }
+        }
+        catch (Throwable cause) {
+          ITestResult r =
+              new TestResult(testMethod.getTestClass(),
+                instance,
+                testMethod,
+                cause,
+                start,
+                System.currentTimeMillis(),
+                m_testContext);
+            r.setStatus(TestResult.FAILURE);
+            result.add(r);
+            runTestListeners(r);
+            m_notifier.addFailedTest(testMethod, r);
+        } // catch
+      }
+    }
+
+    return result;
+
+  } // invokeTestMethod
+
+  private ITestResult registerSkippedTestResult(ITestNGMethod testMethod, Object instance,
+      long start, Throwable throwable) {
+    ITestResult result =
+      new TestResult(testMethod.getTestClass(),
+        instance,
+        testMethod,
+        throwable,
+        start,
+        System.currentTimeMillis(),
+        m_testContext);
+    result.setStatus(TestResult.SKIP);
+    runTestListeners(result);
+
+    return result;
+  }
+
+  /**
+   * Gets an array of parameter values returned by data provider or the ones that
+   * are injected based on parameter type. The method also checks for {@code NoInjection}
+   * annotation
+   * @param parameterValues parameter values from a data provider
+   * @param method method to be invoked
+   * @param context test context
+   * @param testResult test result
+   */
+  private Object[] injectParameters(Object[] parameterValues, Method method,
+      ITestContext context, ITestResult testResult)
+    throws TestNGException {
+    List<Object> vResult = Lists.newArrayList();
+    int i = 0;
+    int numValues = parameterValues.length;
+    int numParams = method.getParameterTypes().length;
+
+    if (numValues > numParams && ! method.isVarArgs()) {
+      throw new TestNGException("The data provider is trying to pass " + numValues
+          + " parameters but the method "
+          + method.getDeclaringClass().getName() + "#" + method.getName()
+          + " takes " + numParams);
+    }
+
+    // beyond this, numValues <= numParams
+    for (Class<?> cls : method.getParameterTypes()) {
+      Annotation[] annotations = method.getParameterAnnotations()[i];
+      boolean noInjection = false;
+      for (Annotation a : annotations) {
+        if (a instanceof NoInjection) {
+          noInjection = true;
+          break;
+        }
+      }
+      Object injected = Parameters.getInjectedParameter(cls, method, context, testResult);
+      if (injected != null && ! noInjection) {
+        vResult.add(injected);
+      } else {
+        try {
+          if (method.isVarArgs()) vResult.add(parameterValues);
+          else vResult.add(parameterValues[i++]);
+        } catch (ArrayIndexOutOfBoundsException ex) {
+          throw new TestNGException("The data provider is trying to pass " + numValues
+              + " parameters but the method "
+              + method.getDeclaringClass().getName() + "#" + method.getName()
+              + " takes " + numParams
+              + " and TestNG is unable in inject a suitable object", ex);
+        }
+      }
+    }
+    return vResult.toArray(new Object[vResult.size()]);
+  }
+
+  private ParameterBag handleParameters(ITestNGMethod testMethod,
+      Object instance,
+      Map<String, String> allParameterNames,
+      Map<String, String> parameters,
+      Object[] parameterValues,
+      XmlSuite suite,
+      ITestContext testContext,
+      Object fedInstance,
+      ITestResult testResult)
+  {
+    try {
+      return new ParameterBag(
+          Parameters.handleParameters(testMethod,
+            allParameterNames,
+            instance,
+            new Parameters.MethodParameters(parameters,
+                testMethod.findMethodParameters(testContext.getCurrentXmlTest()),
+                parameterValues,
+                testMethod.getMethod(), testContext, testResult),
+            suite,
+            m_annotationFinder,
+            fedInstance));
+    }
+//    catch(TestNGException ex) {
+//      throw ex;
+//    }
+    catch(Throwable cause) {
+      return new ParameterBag(
+          new TestResult(
+              testMethod.getTestClass(),
+              instance,
+              testMethod,
+              cause,
+              System.currentTimeMillis(),
+              System.currentTimeMillis(),
+              m_testContext));
+    }
+  }
+
+  /**
+   * Invokes a method that has a specified threadPoolSize.
+   */
+  private List<ITestResult> invokePooledTestMethods(ITestNGMethod testMethod,
+                                                    XmlSuite suite,
+                                                    Map<String, String> parameters,
+                                                    ConfigurationGroupMethods groupMethods,
+                                                    ITestContext testContext)
+  {
+    //
+    // Create the workers
+    //
+    List<IWorker<ITestNGMethod>> workers = Lists.newArrayList();
+
+    // Create one worker per invocationCount
+    for (int i = 0; i < testMethod.getInvocationCount(); i++) {
+      // we use clones for reporting purposes
+      ITestNGMethod clonedMethod= testMethod.clone();
+      clonedMethod.setInvocationCount(1);
+      clonedMethod.setThreadPoolSize(1);
+
+      MethodInstance mi = new MethodInstance(clonedMethod);
+      workers.add(new SingleTestMethodWorker(this,
+          mi,
+          suite,
+          parameters,
+          testContext,
+          m_classListeners));
+    }
+
+    return runWorkers(testMethod, workers, testMethod.getThreadPoolSize(), groupMethods, suite,
+                      parameters);
+  }
+
+  static class FailureContext {
+    int count = 0;
+    List<Object> instances = Lists.newArrayList();
+  }
+
+  void handleInvocationResults(ITestNGMethod testMethod,
+                               List<ITestResult> result,
+                               ExpectedExceptionsHolder expectedExceptionsHolder,
+                               FailureContext failure)
+  {
+    //
+    // Go through all the results and create a TestResult for each of them
+    //
+    List<ITestResult> resultsToRetry = Lists.newArrayList();
+
+    for (ITestResult testResult : result) {
+      Throwable ite= testResult.getThrowable();
+      int status= testResult.getStatus();
+
+      boolean handled = false;
+
+      // Exception thrown?
+      if (ite != null) {
+
+        //  Invocation caused an exception, see if the method was annotated with @ExpectedException
+        if (expectedExceptionsHolder != null) {
+          if (expectedExceptionsHolder.isExpectedException(ite)) {
+            testResult.setStatus(ITestResult.SUCCESS);
+            status = ITestResult.SUCCESS;
+          } else {
+            if (isSkipExceptionAndSkip(ite)){
+              status = ITestResult.SKIP;
+            } else {
+              testResult.setThrowable(expectedExceptionsHolder.wrongException(ite));
+              status = ITestResult.FAILURE;
+            }
+          }
+        } else {
+          handleException(ite, testMethod, testResult, failure.count++);
+          handled = true;
+          status = testResult.getStatus();
+        }
+      }
+
+      // No exception thrown, make sure we weren't expecting one
+      else if(status != ITestResult.SKIP && expectedExceptionsHolder != null) {
+        TestException exception = expectedExceptionsHolder.noException(testMethod);
+        if (exception != null) {
+          testResult.setThrowable(exception);
+          status= ITestResult.FAILURE;
+        }
+      }
+
+      IRetryAnalyzer retryAnalyzer = testMethod.getRetryAnalyzer();
+      boolean willRetry = retryAnalyzer != null && status == ITestResult.FAILURE && failure.instances != null && retryAnalyzer.retry(testResult);
+
+      if (willRetry) {
+        resultsToRetry.add(testResult);
+        failure.count++;
+        failure.instances.add(testResult.getInstance());
+        testResult.setStatus(ITestResult.SKIP);
+      } else {
+        testResult.setStatus(status);
+        if (status == ITestResult.FAILURE && !handled) {
+          handleException(ite, testMethod, testResult, failure.count++);
+        }
+      }
+      collectResults(testMethod, Collections.singleton(testResult));
+    } // for results
+
+    removeResultsToRetryFromResult(resultsToRetry, result, failure);
+  }
+
+  private boolean isSkipExceptionAndSkip(Throwable ite) {
+    return SkipException.class.isAssignableFrom(ite.getClass()) && ((SkipException) ite).isSkip();
+  }
+
+  private void removeResultsToRetryFromResult(List<ITestResult> resultsToRetry,
+                                              List<ITestResult> result, FailureContext failure) {
+    if (resultsToRetry != null) {
+      for (ITestResult res : resultsToRetry) {
+        result.remove(res);
+        failure.count--;
+      }
+    }
+  }
+
+  /**
+   * To reduce thread contention and also to correctly handle thread-confinement
+   * this method invokes the @BeforeGroups and @AfterGroups corresponding to the current @Test method.
+   */
+  private List<ITestResult> runWorkers(ITestNGMethod testMethod,
+      List<IWorker<ITestNGMethod>> workers,
+      int threadPoolSize,
+      ConfigurationGroupMethods groupMethods,
+      XmlSuite suite,
+      Map<String, String> parameters)
+  {
+    // Invoke @BeforeGroups on the original method (reduce thread contention,
+    // and also solve thread confinement)
+    ITestClass testClass= testMethod.getTestClass();
+    Object[] instances = testClass.getInstances(true);
+    for(Object instance: instances) {
+      invokeBeforeGroupsConfigurations(testClass, testMethod, groupMethods, suite, parameters, instance);
+    }
+
+
+    long maxTimeOut= -1; // 10 seconds
+
+    for(IWorker<ITestNGMethod> tmw : workers) {
+      long mt= tmw.getTimeOut();
+      if(mt > maxTimeOut) {
+        maxTimeOut= mt;
+      }
+    }
+
+    ThreadUtil.execute(workers, threadPoolSize, maxTimeOut, true);
+
+    //
+    // Collect all the TestResults
+    //
+    List<ITestResult> result = Lists.newArrayList();
+    for (IWorker<ITestNGMethod> tmw : workers) {
+      if (tmw instanceof TestMethodWorker) {
+        result.addAll(((TestMethodWorker)tmw).getTestResults());
+      }
+    }
+
+    for(Object instance: instances) {
+      invokeAfterGroupsConfigurations(testClass, testMethod, groupMethods, suite, parameters, instance);
+    }
+
+    return result;
+  }
+
+  /**
+   * Checks to see of the test method has certain dependencies that prevents
+   * TestNG from executing it
+   * @param testMethod test method being checked for
+   * @return error message or null if dependencies have been run successfully
+   */
+  private String checkDependencies(ITestNGMethod testMethod,
+                                   ITestNGMethod[] allTestMethods)
+  {
+    // If this method is marked alwaysRun, no need to check for its dependencies
+    if (testMethod.isAlwaysRun()) {
+      return null;
+    }
+
+    // Any missing group?
+    if (testMethod.getMissingGroup() != null
+        && !testMethod.ignoreMissingDependencies()) {
+      return "Method " + testMethod + " depends on nonexistent group \"" + testMethod.getMissingGroup() + "\"";
+    }
+
+    // If this method depends on groups, collect all the methods that
+    // belong to these groups and make sure they have been run successfully
+    final String[] groups = testMethod.getGroupsDependedUpon();
+    if (null != groups && groups.length > 0) {
+      // Get all the methods that belong to the group depended upon
+      for (String element : groups) {
+        ITestNGMethod[] methods =
+            MethodGroupsHelper.findMethodsThatBelongToGroup(testMethod,
+                m_testContext.getAllTestMethods(),
+                element);
+        if (methods.length == 0 && !testMethod.ignoreMissingDependencies()) {
+          // Group is missing
+          return "Method " + testMethod + " depends on nonexistent group \"" + element + "\"";
+        }
+        if (!haveBeenRunSuccessfully(testMethod, methods)) {
+          return "Method " + testMethod +
+              " depends on not successfully finished methods in group \"" + element + "\"";
+        }
+      }
+    } // depends on groups
+
+    // If this method depends on other methods, make sure all these other
+    // methods have been run successfully
+    if (dependsOnMethods(testMethod)) {
+      ITestNGMethod[] methods =
+          MethodHelper.findDependedUponMethods(testMethod, allTestMethods);
+
+      if (!haveBeenRunSuccessfully(testMethod, methods)) {
+        return "Method " + testMethod + " depends on not successfully finished methods";
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * @return the test results that apply to one of the instances of the testMethod.
+   */
+  private Set<ITestResult> keepSameInstances(ITestNGMethod method, Set<ITestResult> results) {
+    Set<ITestResult> result = Sets.newHashSet();
+    for (ITestResult r : results) {
+      final Object o = method.getInstance();
+        // Keep this instance if 1) It's on a different class or 2) It's on the same class
+        // and on the same instance
+        Object instance = r.getInstance() != null
+            ? r.getInstance() : r.getMethod().getInstance();
+        if (r.getTestClass() != method.getTestClass() || instance == o) result.add(r);
+    }
+    return result;
+  }
+
+  /**
+   * @return true if all the methods have been run successfully
+   */
+  private boolean haveBeenRunSuccessfully(ITestNGMethod testMethod, ITestNGMethod[] methods) {
+    // Make sure the method has been run successfully
+    for (ITestNGMethod method : methods) {
+      Set<ITestResult> results = keepSameInstances(testMethod, m_notifier.getPassedTests(method));
+      Set<ITestResult> failedAndSkippedMethods = Sets.newHashSet();
+      failedAndSkippedMethods.addAll(m_notifier.getFailedTests(method));
+      failedAndSkippedMethods.addAll(m_notifier.getSkippedTests(method));
+      Set<ITestResult> failedresults = keepSameInstances(testMethod, failedAndSkippedMethods);
+
+      // If failed results were returned on the same instance, then these tests didn't pass
+      if (failedresults != null && failedresults.size() > 0) {
+        return false;
+      }
+
+      for (ITestResult result : results) {
+        if(!result.isSuccess()) {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+//  private boolean containsInstance(Set<ITestResult> failedresults, Object[] instances) {
+//    for (ITestResult tr : failedresults) {
+//      for (Object o : instances) {
+//        if (o == tr.getInstance()) {
+//          return true;
+//        }
+//      }
+//    }
+//    return false;
+//  }
+
+  /**
+   * An exception was thrown by the test, determine if this method
+   * should be marked as a failure or as failure_but_within_successPercentage
+   */
+  private void handleException(Throwable throwable,
+                               ITestNGMethod testMethod,
+                               ITestResult testResult,
+                               int failureCount) {
+    if (throwable != null) {
+      testResult.setThrowable(throwable);
+    }
+    int successPercentage= testMethod.getSuccessPercentage();
+    int invocationCount= testMethod.getInvocationCount();
+    float numberOfTestsThatCanFail= ((100 - successPercentage) * invocationCount) / 100f;
+
+    if(failureCount < numberOfTestsThatCanFail) {
+      testResult.setStatus(ITestResult.SUCCESS_PERCENTAGE_FAILURE);
+    }
+    else {
+      testResult.setStatus(ITestResult.FAILURE);
+    }
+
+  }
+
+  static interface Predicate<K, T> {
+    boolean isTrue(K k, T v);
+  }
+
+  static class CanRunFromClassPredicate implements Predicate <ITestNGMethod, IClass> {
+    @Override
+    public boolean isTrue(ITestNGMethod m, IClass v) {
+      return m.canRunFromClass(v);
+    }
+  }
+
+  static class SameClassNamePredicate implements Predicate<ITestNGMethod, IClass> {
+    @Override
+    public boolean isTrue(ITestNGMethod m, IClass c) {
+      return c == null || m.getTestClass().getName().equals(c.getName());
+    }
+  }
+
+  /**
+   * @return Only the ITestNGMethods applicable for this testClass
+   */
+  private ITestNGMethod[] filterMethods(IClass testClass, ITestNGMethod[] methods,
+      Predicate<ITestNGMethod, IClass> predicate) {
+    List<ITestNGMethod> vResult= Lists.newArrayList();
+
+    for(ITestNGMethod tm : methods) {
+      if (predicate.isTrue(tm, testClass)) {
+        log(10, "Keeping method " + tm + " for class " + testClass);
+        vResult.add(tm);
+      } else {
+        log(10, "Filtering out method " + tm + " for class " + testClass);
+      }
+    }
+
+    ITestNGMethod[] result= vResult.toArray(new ITestNGMethod[vResult.size()]);
+
+    return result;
+  }
+
+  /**
+   * @return true if this method depends on certain methods.
+   */
+  private boolean dependsOnMethods(ITestNGMethod tm) {
+    String[] methods = tm.getMethodsDependedUpon();
+    return null != methods && methods.length > 0;
+  }
+
+  private void runConfigurationListeners(ITestResult tr, boolean before) {
+    if (before) {
+      for(IConfigurationListener icl: m_notifier.getConfigurationListeners()) {
+        if (icl instanceof IConfigurationListener2) {
+          ((IConfigurationListener2) icl).beforeConfiguration(tr);
+        }
+      }
+    } else {
+      for(IConfigurationListener icl: m_notifier.getConfigurationListeners()) {
+        switch(tr.getStatus()) {
+          case ITestResult.SKIP:
+            icl.onConfigurationSkip(tr);
+            break;
+          case ITestResult.FAILURE:
+            icl.onConfigurationFailure(tr);
+            break;
+          case ITestResult.SUCCESS:
+            icl.onConfigurationSuccess(tr);
+            break;
+        }
+      }
+    }
+  }
+
+  void runTestListeners(ITestResult tr) {
+    runTestListeners(tr, m_notifier.getTestListeners());
+  }
+
+  // TODO: move this from here as it is directly called from TestNG
+  public static void runTestListeners(ITestResult tr, List<ITestListener> listeners) {
+    for (ITestListener itl : listeners) {
+      switch(tr.getStatus()) {
+        case ITestResult.SKIP: {
+          itl.onTestSkipped(tr);
+          break;
+        }
+        case ITestResult.SUCCESS_PERCENTAGE_FAILURE: {
+          itl.onTestFailedButWithinSuccessPercentage(tr);
+          break;
+        }
+        case ITestResult.FAILURE: {
+          itl.onTestFailure(tr);
+          break;
+        }
+        case ITestResult.SUCCESS: {
+          itl.onTestSuccess(tr);
+          break;
+        }
+
+        case ITestResult.STARTED: {
+          itl.onTestStart(tr);
+          break;
+        }
+
+        default: {
+          assert false : "UNKNOWN STATUS:" + tr;
+        }
+      }
+    }
+  }
+
+  private void log(int level, String s) {
+    Utils.log("Invoker " + Thread.currentThread().hashCode(), level, s);
+  }
+
+  /**
+   * This class holds a {@code ParameterHolder} or in case of an error, a non-null
+   * {@code TestResult} containing the cause
+   */
+  private static class ParameterBag {
+    final ParameterHolder parameterHolder;
+    final ITestResult errorResult;
+
+    public ParameterBag(ParameterHolder parameterHolder) {
+      this.parameterHolder = parameterHolder;
+      this.errorResult = null;
+    }
+
+    public ParameterBag(ITestResult errorResult) {
+      this.parameterHolder = null;
+      this.errorResult = errorResult;
+    }
+
+    public boolean hasErrors() {
+      return errorResult != null;
+    }
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/MethodGroupsHelper.java b/src/main/java/org/testng/internal/MethodGroupsHelper.java
new file mode 100644
index 0000000..aa7a0f4
--- /dev/null
+++ b/src/main/java/org/testng/internal/MethodGroupsHelper.java
@@ -0,0 +1,308 @@
+package org.testng.internal;

+

+import java.lang.reflect.Method;

+import java.util.Collection;

+import java.util.List;

+import java.util.Map;

+import java.util.Set;

+import java.util.concurrent.ConcurrentHashMap;

+import java.util.regex.Pattern;

+

+import org.testng.ITestClass;

+import org.testng.ITestNGMethod;

+import org.testng.annotations.IConfigurationAnnotation;

+import org.testng.annotations.ITestOrConfiguration;

+import org.testng.collections.Lists;

+import org.testng.collections.Maps;

+import org.testng.internal.annotations.AnnotationHelper;

+import org.testng.internal.annotations.IAnnotationFinder;

+import org.testng.internal.collections.Pair;

+

+/**

+ * Collections of helper methods to help deal with test methods

+ *

+ * @author Cedric Beust <cedric@beust.com>

+ * @author nullin <nalin.makar * gmail.com>

+ *

+ */

+public class MethodGroupsHelper {

+

+  private static final Map<String, Pattern> PATTERN_CACHE = new ConcurrentHashMap<>();

+  private static final Map<Pair<String, String>, Boolean> MATCH_CACHE =

+          new ConcurrentHashMap<>();

+

+    /**

+   * Collect all the methods that belong to the included groups and exclude all

+   * the methods that belong to an excluded group.

+   */

+  static void collectMethodsByGroup(ITestNGMethod[] methods,

+      boolean forTests,

+      List<ITestNGMethod> outIncludedMethods,

+      List<ITestNGMethod> outExcludedMethods,

+      RunInfo runInfo,

+      IAnnotationFinder finder, boolean unique)

+  {

+    for (ITestNGMethod tm : methods) {

+      boolean in = false;

+      Method m = tm.getMethod();

+      //

+      // @Test method

+      //

+      if (forTests) {

+        in = MethodGroupsHelper.includeMethod(AnnotationHelper.findTest(finder, m),

+            runInfo, tm, forTests, unique, outIncludedMethods);

+      }

+

+      //

+      // @Configuration method

+      //

+      else {

+        IConfigurationAnnotation annotation = AnnotationHelper.findConfiguration(finder, m);

+        if (annotation.getAlwaysRun()) {

+        	if (!unique || !MethodGroupsHelper.isMethodAlreadyPresent(outIncludedMethods, tm)) {

+        		in = true;

+        	}

+        }

+        else {

+          in = MethodGroupsHelper.includeMethod(AnnotationHelper.findTest(finder, tm),

+              runInfo, tm, forTests, unique, outIncludedMethods);

+        }

+      }

+      if (in) {

+        outIncludedMethods.add(tm);

+      }

+      else {

+        outExcludedMethods.add(tm);

+      }

+    }

+  }

+

+  private static boolean includeMethod(ITestOrConfiguration annotation,

+      RunInfo runInfo, ITestNGMethod tm, boolean forTests, boolean unique, List<ITestNGMethod> outIncludedMethods)

+  {

+    boolean result = false;

+

+    if (MethodHelper.isEnabled(annotation)) {

+      if (runInfo.includeMethod(tm, forTests)) {

+        if (unique) {

+          if (!MethodGroupsHelper.isMethodAlreadyPresent(outIncludedMethods, tm)) {

+            result = true;

+          }

+        }

+        else {

+          result = true;

+        }

+      }

+    }

+

+    return result;

+  }

+

+  /**

+   * @param result

+   * @param tm

+   * @return true if a method by a similar name (and same hierarchy) already

+   *         exists

+   */

+  private static boolean isMethodAlreadyPresent(List<ITestNGMethod> result,

+      ITestNGMethod tm) {

+    for (ITestNGMethod m : result) {

+      Method jm1 = m.getMethod();

+      Method jm2 = tm.getMethod();

+      if (jm1.getName().equals(jm2.getName())) {

+        // Same names, see if they are in the same hierarchy

+        Class<?> c1 = jm1.getDeclaringClass();

+        Class<?> c2 = jm2.getDeclaringClass();

+        if (c1.isAssignableFrom(c2) || c2.isAssignableFrom(c1)) {

+          return true;

+        }

+      }

+    }

+

+    return false;

+  }

+

+  /**

+   * Extracts the map of groups and their corresponding methods from the <code>classes</code>.

+   */

+  public static Map<String, List<ITestNGMethod>> findGroupsMethods(Collection<ITestClass> classes, boolean before) {

+    Map<String, List<ITestNGMethod>> result = Maps.newHashMap();

+    for (ITestClass cls : classes) {

+      ITestNGMethod[] methods = before ? cls.getBeforeGroupsMethods() : cls.getAfterGroupsMethods();

+      for (ITestNGMethod method : methods) {

+        for (String group : before ? method.getBeforeGroups() : method.getAfterGroups()) {

+          List<ITestNGMethod> methodList = result.get(group);

+          if (methodList == null) {

+            methodList = Lists.newArrayList();

+            result.put(group, methodList);

+          }

+          // NOTE(cbeust, 2007/01/23)

+          // BeforeGroups/AfterGroups methods should only be invoked once.

+          // I should probably use a map instead of a list for a contains(), but

+          // this list should usually be fairly short

+          if (! methodList.contains(method)) {

+            methodList.add(method);

+          }

+        }

+      }

+    }

+

+    return result;

+  }

+

+  protected static void findGroupTransitiveClosure(XmlMethodSelector xms,

+      List<ITestNGMethod> includedMethods,

+      List<ITestNGMethod> allMethods,

+      String[] includedGroups,

+      Set<String> outGroups, Set<ITestNGMethod> outMethods)

+  {

+    Map<ITestNGMethod, ITestNGMethod> runningMethods = Maps.newHashMap();

+    for (ITestNGMethod m : includedMethods) {

+      runningMethods.put(m, m);

+    }

+

+    Map<String, String> runningGroups = Maps.newHashMap();

+    for (String thisGroup : includedGroups) {

+      runningGroups.put(thisGroup, thisGroup);

+    }

+

+    boolean keepGoing = true;

+

+    Map<ITestNGMethod, ITestNGMethod> newMethods = Maps.newHashMap();

+    while (keepGoing) {

+      for (ITestNGMethod m : includedMethods) {

+

+        //

+        // Depends on groups?

+        // Adds all included methods to runningMethods

+        //

+        String[] ig = m.getGroupsDependedUpon();

+        for (String g : ig) {

+          if (! runningGroups.containsKey(g)) {

+            // Found a new included group, add all the methods it contains to

+            // our outMethod closure

+            runningGroups.put(g, g);

+            ITestNGMethod[] im =

+              MethodGroupsHelper.findMethodsThatBelongToGroup(m,

+                    allMethods.toArray(new ITestNGMethod[allMethods.size()]), g);

+            for (ITestNGMethod thisMethod : im) {

+              if (! runningMethods.containsKey(thisMethod)) {

+                runningMethods.put(thisMethod, thisMethod);

+                newMethods.put(thisMethod, thisMethod);

+              }

+            }

+          }

+        } // groups

+

+        //

+        // Depends on methods?

+        // Adds all depended methods to runningMethods

+        //

+        String[] mdu = m.getMethodsDependedUpon();

+        for (String tm : mdu) {

+          ITestNGMethod thisMethod = MethodGroupsHelper.findMethodNamed(tm, allMethods);

+          if (thisMethod != null && ! runningMethods.containsKey(thisMethod)) {

+            runningMethods.put(thisMethod, thisMethod);

+            newMethods.put(thisMethod, thisMethod);

+          }

+        }

+

+      } // methods

+

+      //

+      // Only keep going if new methods have been added

+      //

+      keepGoing = newMethods.size() > 0;

+      includedMethods = Lists.newArrayList();

+      includedMethods.addAll(newMethods.keySet());

+      newMethods = Maps.newHashMap();

+    } // while keepGoing

+

+    outMethods.addAll(runningMethods.keySet());

+    outGroups.addAll(runningGroups.keySet());

+  }

+

+  private static ITestNGMethod findMethodNamed(String tm, List<ITestNGMethod> allMethods) {

+    for (ITestNGMethod m : allMethods) {

+      // TODO(cbeust):  account for package

+      String methodName =

+        m.getMethod().getDeclaringClass().getName() + "." + m.getMethodName();

+      if (methodName.equals(tm)) {

+        return m;

+      }

+    }

+

+    return null;

+  }

+

+  /**

+   * Only used if a group is missing to flag an error on that method

+   *

+   * @param method if no group is found, group regex is set as this method's missing group

+   * @param methods list of methods to search

+   * @param groupRegexp regex representing the group

+   *

+   * @return all the methods that belong to the group specified by the regular

+   * expression groupRegExp.  methods[] is the list of all the methods we

+   * are choosing from and method is the method that owns the dependsOnGroups

+   * statement (only used if a group is missing to flag an error on that method).

+   */

+  protected static ITestNGMethod[] findMethodsThatBelongToGroup(

+      ITestNGMethod method,

+      ITestNGMethod[] methods, String groupRegexp)

+  {

+    ITestNGMethod[] found = findMethodsThatBelongToGroup(methods, groupRegexp);

+

+    if (found.length == 0) {

+      method.setMissingGroup(groupRegexp);

+    }

+

+    return found;

+  }

+

+  /**

+   * @param methods list of methods to search

+   * @param groupRegexp regex representing the group

+   *

+   * @return all the methods that belong to the group specified by the regular

+   * expression groupRegExp.  methods[] is the list of all the methods we

+   * are choosing from.

+   */

+  protected static ITestNGMethod[] findMethodsThatBelongToGroup(ITestNGMethod[] methods, String groupRegexp)

+  {

+    List<ITestNGMethod> vResult = Lists.newArrayList();

+    final Pattern pattern = getPattern(groupRegexp);

+    for (ITestNGMethod tm : methods) {

+      String[] groups = tm.getGroups();

+      for (String group : groups) {

+        Boolean match = isMatch(pattern, group);

+        if (match) {

+          vResult.add(tm);

+        }

+      }

+    }

+

+    return vResult.toArray(new ITestNGMethod[vResult.size()]);

+  }

+

+  private static Boolean isMatch(Pattern pattern, String group) {

+    Pair<String, String> cacheKey = Pair.create(pattern.pattern(), group);

+    Boolean match = MATCH_CACHE.get(cacheKey);

+    if (match == null) {

+      match = pattern.matcher(group).matches();

+      MATCH_CACHE.put(cacheKey, match);

+    }

+    return match;

+  }

+

+  private static Pattern getPattern(String groupRegexp) {

+    Pattern groupPattern = PATTERN_CACHE.get(groupRegexp);

+    if (groupPattern == null) {

+      groupPattern = Pattern.compile(groupRegexp);

+      PATTERN_CACHE.put(groupRegexp, groupPattern);

+    }

+    return groupPattern;

+  }

+

+

+}

diff --git a/src/main/java/org/testng/internal/MethodHelper.java b/src/main/java/org/testng/internal/MethodHelper.java
new file mode 100644
index 0000000..88666a8
--- /dev/null
+++ b/src/main/java/org/testng/internal/MethodHelper.java
@@ -0,0 +1,366 @@
+package org.testng.internal;

+

+import java.lang.reflect.Method;

+import java.util.Collection;

+import java.util.Iterator;

+import java.util.List;

+import java.util.Map;

+import java.util.NoSuchElementException;

+import java.util.Set;

+import java.util.concurrent.ConcurrentHashMap;

+import java.util.regex.Pattern;

+

+import org.testng.ITestNGMethod;

+import org.testng.TestNGException;

+import org.testng.annotations.IExpectedExceptionsAnnotation;

+import org.testng.annotations.ITestAnnotation;

+import org.testng.annotations.ITestOrConfiguration;

+import org.testng.collections.Lists;

+import org.testng.collections.Sets;

+import org.testng.internal.annotations.AnnotationHelper;

+import org.testng.internal.annotations.IAnnotationFinder;

+import org.testng.internal.collections.Pair;

+

+/**

+ * Collection of helper methods to help sort and arrange methods.

+ *

+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>

+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>

+ */

+public class MethodHelper {

+  private static final Map<ITestNGMethod[], Graph<ITestNGMethod>> GRAPH_CACHE =

+          new ConcurrentHashMap<>();

+  private static final Map<Method, String> CANONICAL_NAME_CACHE = new ConcurrentHashMap<>();

+  private static final Map<Pair<String, String>, Boolean> MATCH_CACHE =

+          new ConcurrentHashMap<>();

+

+  /**

+   * Collects and orders test or configuration methods

+   * @param methods methods to be worked on

+   * @param forTests true for test methods, false for configuration methods

+   * @param runInfo

+   * @param finder annotation finder

+   * @param unique true for unique methods, false otherwise

+   * @param outExcludedMethods

+   * @return list of ordered methods

+   */

+  public static ITestNGMethod[] collectAndOrderMethods(List<ITestNGMethod> methods,

+      boolean forTests, RunInfo runInfo, IAnnotationFinder finder,

+      boolean unique, List<ITestNGMethod> outExcludedMethods)

+  {

+    List<ITestNGMethod> includedMethods = Lists.newArrayList();

+    MethodGroupsHelper.collectMethodsByGroup(methods.toArray(new ITestNGMethod[methods.size()]),

+        forTests,

+        includedMethods,

+        outExcludedMethods,

+        runInfo,

+        finder,

+        unique);

+

+    return sortMethods(forTests, includedMethods, finder).toArray(new ITestNGMethod[]{});

+  }

+

+  /**

+   * Finds TestNG methods that the specified TestNG method depends upon

+   * @param m TestNG method

+   * @param methods list of methods to search for depended upon methods

+   * @return list of methods that match the criteria

+   */

+  protected static ITestNGMethod[] findDependedUponMethods(ITestNGMethod m,

+      ITestNGMethod[] methods)

+  {

+    String canonicalMethodName = calculateMethodCanonicalName(m);

+    List<ITestNGMethod> vResult = Lists.newArrayList();

+    String regexp = null;

+    for (String fullyQualifiedRegexp : m.getMethodsDependedUpon()) {

+      boolean foundAtLeastAMethod = false;

+

+      if (null != fullyQualifiedRegexp) {

+        // Escapes $ in regexps as it is not meant for end - line matching, but inner class matches.

+        regexp = fullyQualifiedRegexp.replace("$", "\\$");

+        boolean usePackage = regexp.indexOf('.') != -1;

+        Pattern pattern = Pattern.compile(regexp);

+

+        for (ITestNGMethod method : methods) {

+          Method thisMethod = method.getMethod();

+          String thisMethodName = thisMethod.getName();

+          String methodName = usePackage ?

+              calculateMethodCanonicalName(thisMethod)

+              : thisMethodName;

+          Pair<String, String> cacheKey = Pair.create(regexp, methodName);

+          Boolean match = MATCH_CACHE.get(cacheKey);

+          if (match == null) {

+              match = pattern.matcher(methodName).matches();

+              MATCH_CACHE.put(cacheKey, match);

+          }

+          if (match) {

+            vResult.add(method);

+            foundAtLeastAMethod = true;

+          }

+        }

+      }

+

+      if (!foundAtLeastAMethod) {

+        if (m.ignoreMissingDependencies()) {

+          continue;

+        }

+        if (m.isAlwaysRun()) {

+          continue;

+        }

+        Method maybeReferringTo = findMethodByName(m, regexp);

+        if (maybeReferringTo != null) {

+          throw new TestNGException(canonicalMethodName + "() is depending on method "

+              + maybeReferringTo + ", which is not annotated with @Test or not included.");

+        }

+        throw new TestNGException(canonicalMethodName

+            + "() depends on nonexistent method " + regexp);

+      }

+    }//end for

+

+    return vResult.toArray(new ITestNGMethod[vResult.size()]);

+  }

+

+  /**

+   * Finds method based on regex and TestNGMethod. If regex doesn't represent the

+   * class name, uses the TestNG method's class name.

+   * @param testngMethod TestNG method

+   * @param regExp regex representing a method and/or related class name

+   */

+  private static Method findMethodByName(ITestNGMethod testngMethod, String regExp) {

+    if (regExp == null) {

+      return null;

+    }

+    int lastDot = regExp.lastIndexOf('.');

+    String className, methodName;

+    if (lastDot == -1) {

+      className = testngMethod.getMethod().getDeclaringClass().getCanonicalName();

+      methodName = regExp;

+    } else {

+      methodName = regExp.substring(lastDot + 1);

+      className = regExp.substring(0, lastDot);

+    }

+

+    try {

+      Class<?> c = Class.forName(className);

+      for (Method m : c.getDeclaredMethods()) {

+        if (methodName.equals(m.getName())) {

+          return m;

+        }

+      }

+    }

+    catch (Exception e) {

+      //only logging

+      Utils.log("MethodHelper", 3, "Caught exception while searching for methods using regex");

+    }

+    return null;

+  }

+

+  protected static boolean isEnabled(Class<?> objectClass, IAnnotationFinder finder) {

+    ITestAnnotation testClassAnnotation = AnnotationHelper.findTest(finder, objectClass);

+    return isEnabled(testClassAnnotation);

+  }

+

+  protected static boolean isEnabled(Method m, IAnnotationFinder finder) {

+    ITestAnnotation annotation = AnnotationHelper.findTest(finder, m);

+

+    // If no method annotation, look for one on the class

+    if (null == annotation) {

+      annotation = AnnotationHelper.findTest(finder, m.getDeclaringClass());

+    }

+

+    return isEnabled(annotation);

+  }

+

+  protected static boolean isEnabled(ITestOrConfiguration test) {

+    return null == test || test.getEnabled();

+  }

+

+  /**

+   * Extracts the unique list of <code>ITestNGMethod</code>s.

+   */

+  public static List<ITestNGMethod> uniqueMethodList(Collection<List<ITestNGMethod>> methods) {

+    Set<ITestNGMethod> resultSet = Sets.newHashSet();

+

+    for (List<ITestNGMethod> l : methods) {

+      resultSet.addAll(l);

+    }

+

+    return Lists.newArrayList(resultSet);

+  }

+

+  private static Graph<ITestNGMethod> topologicalSort(ITestNGMethod[] methods,

+      List<ITestNGMethod> sequentialList, List<ITestNGMethod> parallelList) {

+    Graph<ITestNGMethod> result = new Graph<>();

+

+    if (methods.length == 0) {

+      return result;

+    }

+

+    //

+    // Create the graph

+    //

+    for (ITestNGMethod m : methods) {

+      result.addNode(m);

+

+      List<ITestNGMethod> predecessors = Lists.newArrayList();

+

+      String[] methodsDependedUpon = m.getMethodsDependedUpon();

+      String[] groupsDependedUpon = m.getGroupsDependedUpon();

+      if (methodsDependedUpon.length > 0) {

+        ITestNGMethod[] methodsNamed =

+          MethodHelper.findDependedUponMethods(m, methods);

+        for (ITestNGMethod pred : methodsNamed) {

+          predecessors.add(pred);

+        }

+      }

+      if (groupsDependedUpon.length > 0) {

+        for (String group : groupsDependedUpon) {

+          ITestNGMethod[] methodsThatBelongToGroup =

+            MethodGroupsHelper.findMethodsThatBelongToGroup(m, methods, group);

+          for (ITestNGMethod pred : methodsThatBelongToGroup) {

+            predecessors.add(pred);

+          }

+        }

+      }

+

+      for (ITestNGMethod predecessor : predecessors) {

+        result.addPredecessor(m, predecessor);

+      }

+    }

+

+    result.topologicalSort();

+    sequentialList.addAll(result.getStrictlySortedNodes());

+    parallelList.addAll(result.getIndependentNodes());

+

+    return result;

+  }

+

+  protected static String calculateMethodCanonicalName(ITestNGMethod m) {

+    return calculateMethodCanonicalName(m.getMethod());

+  }

+

+  private static String calculateMethodCanonicalName(Method m) {

+    String result = CANONICAL_NAME_CACHE.get(m);

+    if (result != null) {

+      return result;

+    }

+

+    String packageName = m.getDeclaringClass().getName() + "." + m.getName();

+

+    // Try to find the method on this class or parents

+    Class<?> cls = m.getDeclaringClass();

+    while (cls != Object.class) {

+      try {

+        if (cls.getDeclaredMethod(m.getName(), m.getParameterTypes()) != null) {

+          packageName = cls.getName();

+          break;

+        }

+      }

+      catch (Exception e) {

+        // ignore

+      }

+      cls = cls.getSuperclass();

+    }

+

+    result = packageName + "." + m.getName();

+    CANONICAL_NAME_CACHE.put(m, result);

+    return result;

+  }

+

+  private static List<ITestNGMethod> sortMethods(boolean forTests,

+      List<ITestNGMethod> allMethods, IAnnotationFinder finder) {

+    List<ITestNGMethod> sl = Lists.newArrayList();

+    List<ITestNGMethod> pl = Lists.newArrayList();

+    ITestNGMethod[] allMethodsArray = allMethods.toArray(new ITestNGMethod[allMethods.size()]);

+

+    // Fix the method inheritance if these are @Configuration methods to make

+    // sure base classes are invoked before child classes if 'before' and the

+    // other way around if they are 'after'

+    if (!forTests && allMethodsArray.length > 0) {

+      ITestNGMethod m = allMethodsArray[0];

+      boolean before = m.isBeforeClassConfiguration()

+          || m.isBeforeMethodConfiguration() || m.isBeforeSuiteConfiguration()

+          || m.isBeforeTestConfiguration();

+      MethodInheritance.fixMethodInheritance(allMethodsArray, before);

+    }

+

+    topologicalSort(allMethodsArray, sl, pl);

+

+    List<ITestNGMethod> result = Lists.newArrayList();

+    result.addAll(sl);

+    result.addAll(pl);

+    return result;

+  }

+

+  /**

+   * @return A sorted array containing all the methods 'method' depends on

+   */

+  public static List<ITestNGMethod> getMethodsDependedUpon(ITestNGMethod method, ITestNGMethod[] methods) {

+    Graph<ITestNGMethod> g = GRAPH_CACHE.get(methods);

+    if (g == null) {

+      List<ITestNGMethod> parallelList = Lists.newArrayList();

+      List<ITestNGMethod> sequentialList = Lists.newArrayList();

+      g = topologicalSort(methods, sequentialList, parallelList);

+      GRAPH_CACHE.put(methods, g);

+    }

+

+    List<ITestNGMethod> result = g.findPredecessors(method);

+    return result;

+  }

+

+  protected static Iterator<Object[]> createArrayIterator(final Object[][] objects) {

+    ArrayIterator result = new ArrayIterator(objects);

+    return result;

+  }

+

+  protected static String calculateMethodCanonicalName(Class<?> methodClass, String methodName) {

+    Set<Method> methods = ClassHelper.getAvailableMethods(methodClass); // TESTNG-139

+    Method result = null;

+    for (Method m : methods) {

+      if (methodName.equals(m.getName())) {

+        result = m;

+        break;

+      }

+    }

+

+    return result != null ? calculateMethodCanonicalName(result) : null;

+  }

+

+  protected static long calculateTimeOut(ITestNGMethod tm) {

+    long result = tm.getTimeOut() > 0 ? tm.getTimeOut() : tm.getInvocationTimeOut();

+    return result;

+  }

+}

+

+/**

+ * Custom iterator class over a 2D array

+ *

+ */

+class ArrayIterator implements Iterator<Object[]> {

+  private Object[][] m_objects;

+  private int m_count;

+

+  public ArrayIterator(Object[][] objects) {

+    m_objects = objects;

+    m_count = 0;

+  }

+

+  @Override

+  public boolean hasNext() {

+    return m_count < m_objects.length;

+  }

+

+  @Override

+  public Object[] next() {

+    if (m_count >= m_objects.length) {

+      throw new NoSuchElementException();

+    }

+    return m_objects[m_count++];

+  }

+

+  @Override

+  public void remove() {

+    throw new UnsupportedOperationException("Remove operation is not supported on this iterator");

+  }

+

+}

diff --git a/src/main/java/org/testng/internal/MethodInheritance.java b/src/main/java/org/testng/internal/MethodInheritance.java
new file mode 100755
index 0000000..fe3d574
--- /dev/null
+++ b/src/main/java/org/testng/internal/MethodInheritance.java
@@ -0,0 +1,161 @@
+package org.testng.internal;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.testng.ITestNGMethod;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+
+public class MethodInheritance {
+  /**
+   * Look in map for a class that is a superclass of methodClass
+   */
+  private static List<ITestNGMethod> findMethodListSuperClass(Map<Class, List<ITestNGMethod>> map,
+      Class< ? extends ITestNGMethod> methodClass)
+  {
+    for (Map.Entry<Class, List<ITestNGMethod>> entry : map.entrySet()) {
+      if (entry.getKey().isAssignableFrom(methodClass)) {
+        return entry.getValue();
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Look in map for a class that is a subclass of methodClass
+   */
+  private static Class findSubClass(Map<Class, List<ITestNGMethod>> map,
+      Class< ? extends ITestNGMethod> methodClass)
+  {
+    for (Class cls : map.keySet()) {
+      if (methodClass.isAssignableFrom(cls)) {
+        return cls;
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Fix the methodsDependedUpon to make sure that @Configuration methods
+   * respect inheritance (before methods are invoked in the order Base first
+   * and after methods are invoked in the order Child first)
+   *
+   * @param methods the list of methods
+   * @param before true if we are handling a before method (meaning, the methods
+   * need to be sorted base class first and subclass last). false otherwise (subclass
+   * methods first, base classes last).
+   */
+  public static void fixMethodInheritance(ITestNGMethod[] methods, boolean before) {
+    // Map of classes -> List of methods that belong to this class or same hierarchy
+    Map<Class, List<ITestNGMethod>> map = Maps.newHashMap();
+
+    //
+    // Put the list of methods in their hierarchy buckets
+    //
+    for (ITestNGMethod method : methods) {
+      Class< ? extends ITestNGMethod> methodClass = method.getRealClass();
+      List<ITestNGMethod> l = findMethodListSuperClass(map, methodClass);
+      if (null != l) {
+        l.add(method);
+      }
+      else {
+        Class subClass = findSubClass(map, methodClass);
+        if (null != subClass) {
+          l = map.get(subClass);
+          l.add(method);
+          map.remove(subClass);
+          map.put(methodClass, l);
+        }
+        else {
+          l = Lists.newArrayList();
+          l.add(method);
+          map.put(methodClass, l);
+        }
+      }
+    }
+
+    //
+    // Each bucket that has a list bigger than one element gets sorted
+    //
+    for (List<ITestNGMethod> l : map.values()) {
+      if (l.size() > 1) {
+        // Sort them
+        sortMethodsByInheritance(l, before);
+
+        /*
+         *  Set methodDependedUpon accordingly
+         *  E.g. Base class can have multiple @BeforeClass methods. Need to ensure
+         *  that @BeforeClass methods in derived class depend on all @BeforeClass methods
+         *  of base class. Vice versa for @AfterXXX methods
+         */
+        for (int i = 0; i < l.size() - 1; i++) {
+          ITestNGMethod m1 = l.get(i);
+          for (int j = i + 1; j < l.size(); j++) {
+            ITestNGMethod m2 = l.get(j);
+            if (!equalsEffectiveClass(m1, m2) && !dependencyExists(m1, m2, methods)) {
+              Utils.log("MethodInheritance", 4, m2 + " DEPENDS ON " + m1);
+              m2.addMethodDependedUpon(MethodHelper.calculateMethodCanonicalName(m1));
+            }
+          }
+        }
+      }
+    }
+  }
+
+  private static boolean dependencyExists(ITestNGMethod m1, ITestNGMethod m2, ITestNGMethod[] methods) {
+    return internalDependencyExists(m1, m2, methods) || internalDependencyExists(m2, m1, methods);
+  }
+
+  private static boolean internalDependencyExists(ITestNGMethod m1, ITestNGMethod m2, ITestNGMethod[] methods) {
+    ITestNGMethod[] methodsNamed =
+      MethodHelper.findDependedUponMethods(m1, methods);
+
+    for (ITestNGMethod method : methodsNamed) {
+      if (method.equals(m2)) {
+        return true;
+      }
+    }
+
+    for (String group : m1.getGroupsDependedUpon()) {
+      ITestNGMethod[] methodsThatBelongToGroup =
+        MethodGroupsHelper.findMethodsThatBelongToGroup(m1, methods, group);
+      for (ITestNGMethod method : methodsThatBelongToGroup) {
+         if (method.equals(m2)) {
+           return true;
+         }
+       }
+    }
+
+    return false;
+  }
+
+  private static boolean equalsEffectiveClass(ITestNGMethod m1, ITestNGMethod m2) {
+    try {
+      Class c1 = m1.getRealClass();
+      Class c2 = m2.getRealClass();
+
+      return c1 == null ? c2 == null : c1.equals(c2);
+    }
+    catch(Exception ex) {
+      return false;
+    }
+  }
+
+
+  /**
+   * Given a list of methods belonging to the same class hierarchy, orders them
+   * from the base class to the child (if true) or from child to base class (if false)
+   * @param methods
+   */
+  private static void sortMethodsByInheritance(List<ITestNGMethod> methods,
+      boolean baseClassToChild)
+  {
+    Collections.sort(methods);
+    if (! baseClassToChild) {
+      Collections.reverse(methods);
+    }
+  }
+}
diff --git a/src/main/java/org/testng/internal/MethodInstance.java b/src/main/java/org/testng/internal/MethodInstance.java
new file mode 100755
index 0000000..5b47885
--- /dev/null
+++ b/src/main/java/org/testng/internal/MethodInstance.java
@@ -0,0 +1,109 @@
+package org.testng.internal;

+

+import org.testng.IMethodInstance;

+import org.testng.ITestNGMethod;

+import org.testng.collections.Objects;

+import org.testng.xml.XmlClass;

+import org.testng.xml.XmlInclude;

+import org.testng.xml.XmlTest;

+

+import java.util.Comparator;

+import java.util.List;

+

+public class MethodInstance implements IMethodInstance {

+  private ITestNGMethod m_method;

+

+  public MethodInstance(ITestNGMethod method) {

+    m_method = method;

+  }

+

+  @Override

+  public ITestNGMethod getMethod() {

+    return m_method;

+  }

+

+  @Override

+  public Object[] getInstances() {

+    return new Object[] { getInstance() };

+  }

+

+  @Override

+  public Object getInstance() {

+    return m_method.getInstance();

+  }

+

+  @Override

+  public String toString() {

+    return Objects.toStringHelper(getClass())

+        .add("method", m_method)

+        .add("instance", getInstance())

+        .toString();

+  }

+

+

+  public static final Comparator<IMethodInstance> SORT_BY_INDEX

+    = new Comparator<IMethodInstance>() {

+    @Override

+    public int compare(IMethodInstance o1, IMethodInstance o2) {

+      // If the two methods are in different <test>

+      XmlTest test1 = o1.getMethod().getTestClass().getXmlTest();

+      XmlTest test2 = o2.getMethod().getTestClass().getXmlTest();

+

+      // If the two methods are not in the same <test>, we can't compare them

+      if (! test1.getName().equals(test2.getName())) {

+        return 0;

+      }

+

+      int result = 0;

+

+      // If the two methods are in the same <class>, compare them by their method

+      // index, otherwise compare them with their class index.

+      XmlClass class1 = o1.getMethod().getTestClass().getXmlClass();

+      XmlClass class2 = o2.getMethod().getTestClass().getXmlClass();

+

+      // This can happen if these classes came from a @Factory, in which case, they

+      // don't have an associated XmlClass

+      if (class1 == null || class2 == null) {

+        if (class1 != null) return -1;

+        if (class2 != null) return 1;

+        return 0;

+      }

+

+      if (! class1.getName().equals(class2.getName())) {

+        int index1 = class1.getIndex();

+        int index2 = class2.getIndex();

+        result = index1 - index2;

+      }

+      else {

+        XmlInclude include1 =

+            findXmlInclude(class1.getIncludedMethods(), o1.getMethod().getMethodName());

+        XmlInclude include2 =

+          findXmlInclude(class2.getIncludedMethods(), o2.getMethod().getMethodName());

+        if (include1 != null && include2 != null) {

+          result = include1.getIndex() - include2.getIndex();

+        }

+      }

+

+      return result;

+    }

+

+    private XmlInclude findXmlInclude(List<XmlInclude> includedMethods, String methodName) {

+      for (XmlInclude xi : includedMethods) {

+        if (xi.getName().equals(methodName)) {

+          return xi;

+        }

+      }

+      return null;

+    }

+  };

+

+//  public static final Comparator<IMethodInstance> SORT_BY_CLASS

+//    = new Comparator<IMethodInstance>() {

+//    public int compare(IMethodInstance o1, IMethodInstance o2) {

+//      int result= o1.getMethod().getTestClass().getName()

+//        .compareTo(o2.getMethod().getTestClass().getName());

+//      return result;

+//    }

+//  };

+

+}

diff --git a/src/main/java/org/testng/internal/MethodInvocationHelper.java b/src/main/java/org/testng/internal/MethodInvocationHelper.java
new file mode 100644
index 0000000..9155f42
--- /dev/null
+++ b/src/main/java/org/testng/internal/MethodInvocationHelper.java
@@ -0,0 +1,311 @@
+package org.testng.internal;

+

+import org.testng.IConfigurable;

+import org.testng.IConfigureCallBack;

+import org.testng.IHookCallBack;

+import org.testng.IHookable;

+import org.testng.ITestContext;

+import org.testng.ITestNGMethod;

+import org.testng.ITestResult;

+import org.testng.TestNGException;

+import org.testng.annotations.DataProvider;

+import org.testng.collections.Lists;

+import org.testng.internal.annotations.IAnnotationFinder;

+import org.testng.internal.collections.Pair;

+import org.testng.internal.thread.IExecutor;

+import org.testng.internal.thread.IFutureResult;

+import org.testng.internal.thread.ThreadExecutionException;

+import org.testng.internal.thread.ThreadTimeoutException;

+import org.testng.internal.thread.ThreadUtil;

+import org.testng.xml.XmlSuite;

+

+import java.lang.reflect.Constructor;

+import java.lang.reflect.InvocationTargetException;

+import java.lang.reflect.Method;

+import java.lang.reflect.Modifier;

+import java.util.ArrayList;

+import java.util.Collection;

+import java.util.Iterator;

+import java.util.List;

+

+/**

+ * Collections of helper methods to help deal with invocation of TestNG methods

+ *

+ * @author Cedric Beust <cedric@beust.com>

+ * @author nullin <nalin.makar * gmail.com>

+ *

+ */

+public class MethodInvocationHelper {

+

+  protected static Object invokeMethod(Method thisMethod, Object instance, Object[] parameters)

+      throws InvocationTargetException, IllegalAccessException {

+    Utils.checkInstanceOrStatic(instance, thisMethod);

+

+    // TESTNG-326, allow IObjectFactory to load from non-standard classloader

+    // If the instance has a different classloader, its class won't match the

+    // method's class

+    if (instance == null || !thisMethod.getDeclaringClass().isAssignableFrom(instance.getClass())) {

+      // for some reason, we can't call this method on this class

+      // is it static?

+      boolean isStatic = Modifier.isStatic(thisMethod.getModifiers());

+      if (!isStatic) {

+        // not static, so grab a method with the same name and signature in this case

+        Class<?> clazz = instance.getClass();

+        try {

+          thisMethod = clazz.getMethod(thisMethod.getName(), thisMethod.getParameterTypes());

+        } catch (Exception e) {

+          // ignore, the method may be private

+          boolean found = false;

+          for (; clazz != null; clazz = clazz.getSuperclass()) {

+            try {

+              thisMethod = clazz.getDeclaredMethod(thisMethod.getName(),

+                  thisMethod.getParameterTypes());

+              found = true;

+              break;

+            } catch (Exception e2) {

+            }

+          }

+          if (!found) {

+            // should we assert here? Or just allow it to fail on invocation?

+            if (thisMethod.getDeclaringClass().getName().equals(instance.getClass().getName())) {

+              throw new RuntimeException("Can't invoke method " + thisMethod

+                  + ", probably due to classloader mismatch");

+            }

+            throw new RuntimeException("Can't invoke method " + thisMethod

+                + " on this instance of " + instance.getClass() + " due to class mismatch");

+          }

+        }

+      }

+    }

+

+    synchronized(thisMethod) {

+      if (! Modifier.isPublic(thisMethod.getModifiers())) {

+        thisMethod.setAccessible(true);

+      }

+    }

+    return thisMethod.invoke(instance, parameters);

+  }

+

+  protected static Iterator<Object[]> invokeDataProvider(Object instance, Method dataProvider,

+      ITestNGMethod method, ITestContext testContext, Object fedInstance,

+      IAnnotationFinder annotationFinder) {

+    Iterator<Object[]> result;

+    final ConstructorOrMethod com = method.getConstructorOrMethod();

+

+    // If it returns an Object[][], convert it to an Iterable<Object[]>

+    try {

+      List<Object> lParameters = Lists.newArrayList();

+

+      // Go through all the parameters declared on this Data Provider and

+      // make sure we have at most one Method and one ITestContext.

+      // Anything else is an error

+      Class<?>[] parameterTypes = dataProvider.getParameterTypes();

+

+      final Collection<Pair<Integer, Class<?>>> unresolved = new ArrayList<>(parameterTypes.length);

+      int i = 0;

+      for (Class<?> cls : parameterTypes) {

+        boolean isTestInstance = annotationFinder.hasTestInstance(dataProvider, i++);

+        if (cls.equals(Method.class)) {

+          lParameters.add(com.getMethod());

+        } else if (cls.equals(Constructor.class)) {

+          lParameters.add(com.getConstructor());

+        } else if (cls.equals(ConstructorOrMethod.class)) {

+          lParameters.add(com);

+        } else if (cls.equals(ITestNGMethod.class)) {

+          lParameters.add(method);

+        } else if (cls.equals(ITestContext.class)) {

+          lParameters.add(testContext);

+        } else if (isTestInstance) {

+          lParameters.add(fedInstance);

+        } else {

+          unresolved.add(new Pair<Integer, Class<?>>(i, cls));

+        }

+      }

+      if (!unresolved.isEmpty()) {

+        final StringBuilder sb = new StringBuilder();

+        sb.append("Some DataProvider ").append(dataProvider).append(" parameters unresolved: ");

+        for (Pair<Integer, Class<?>> pair : unresolved) {

+          sb.append(" at ").append(pair.first()).append(" type ").append(pair.second()).append("\n");

+        }

+        throw new TestNGException(sb.toString());

+      }

+

+      Object[] parameters = lParameters.toArray(new Object[lParameters.size()]);

+

+      Class<?> returnType = dataProvider.getReturnType();

+      if (Object[][].class.isAssignableFrom(returnType)) {

+        Object[][] originalResult = (Object[][]) invokeMethod(dataProvider, instance, parameters);

+

+        // If the data provider is restricting the indices to return, filter them out

+        int[] indices = dataProvider.getAnnotation(DataProvider.class).indices();

+        Object[][] oResult;

+        if (indices.length > 0) {

+          oResult = new Object[indices.length][];

+          for (int j = 0; j < indices.length; j++) {

+            oResult[j] = originalResult[indices[j]];

+          }

+        } else {

+          oResult = originalResult;

+        }

+

+        method.setParameterInvocationCount(oResult.length);

+        result = MethodHelper.createArrayIterator(oResult);

+      } else if (Iterator.class.isAssignableFrom(returnType)) {

+        // Already an Iterator<Object[]>, assign it directly

+        result = (Iterator<Object[]>) invokeMethod(dataProvider, instance, parameters);

+      } else {

+        throw new TestNGException("Data Provider " + dataProvider + " must return"

+            + " either Object[][] or Iterator<Object>[], not " + returnType);

+      }

+    } catch (InvocationTargetException | IllegalAccessException e) {

+      // Don't throw TestNGException here or this test won't be reported as a

+      // skip or failure

+      throw new RuntimeException(e.getCause());

+    }

+

+    return result;

+  }

+

+  /**

+   * Invokes the <code>run</code> method of the <code>IHookable</code>.

+   *

+   * @param testInstance

+   *          the instance to invoke the method in

+   * @param parameters

+   *          the parameters to be passed to <code>IHookCallBack</code>

+   * @param thisMethod

+   *          the method to be invoked through the <code>IHookCallBack</code>

+   * @param testResult

+   *          the current <code>ITestResult</code> passed to

+   *          <code>IHookable.run</code>

+   * @throws NoSuchMethodException

+   * @throws IllegalAccessException

+   * @throws InvocationTargetException

+   * @throws Throwable

+   *           thrown if the reflective call to

+   *           <tt>thisMethod</code> results in an exception

+   */

+  protected static void invokeHookable(final Object testInstance, final Object[] parameters,

+                                       final IHookable hookable, final Method thisMethod,

+                                       final ITestResult testResult) throws Throwable {

+    final Throwable[] error = new Throwable[1];

+

+    IHookCallBack callback = new IHookCallBack() {

+      @Override

+      public void runTestMethod(ITestResult tr) {

+        try {

+          invokeMethod(thisMethod, testInstance, parameters);

+        } catch (Throwable t) {

+          error[0] = t;

+          tr.setThrowable(t); // make Throwable available to IHookable

+        }

+      }

+

+      @Override

+      public Object[] getParameters() {

+        return parameters;

+      }

+    };

+    hookable.run(callback, testResult);

+    if (error[0] != null) {

+      throw error[0];

+    }

+  }

+

+  /**

+   * Invokes a method on a separate thread in order to allow us to timeout the

+   * invocation. It uses as implementation an <code>Executor</code> and a

+   * <code>CountDownLatch</code>.

+   */

+  protected static void invokeWithTimeout(ITestNGMethod tm, Object instance,

+      Object[] parameterValues, ITestResult testResult)

+      throws InterruptedException, ThreadExecutionException {

+    invokeWithTimeout(tm, instance, parameterValues, testResult, null);

+  }

+

+  protected static void invokeWithTimeout(ITestNGMethod tm, Object instance,

+      Object[] parameterValues, ITestResult testResult, IHookable hookable)

+      throws InterruptedException, ThreadExecutionException {

+    if (ThreadUtil.isTestNGThread() && testResult.getTestContext().getCurrentXmlTest().getParallel() != XmlSuite.ParallelMode.TESTS) {

+      // We are already running in our own executor, don't create another one (or we will

+      // lose the time out of the enclosing executor).

+      invokeWithTimeoutWithNoExecutor(tm, instance, parameterValues, testResult, hookable);

+    } else {

+      invokeWithTimeoutWithNewExecutor(tm, instance, parameterValues, testResult, hookable);

+    }

+  }

+

+  private static void invokeWithTimeoutWithNoExecutor(ITestNGMethod tm, Object instance,

+      Object[] parameterValues, ITestResult testResult, IHookable hookable) {

+

+    InvokeMethodRunnable imr = new InvokeMethodRunnable(tm, instance, parameterValues, hookable, testResult);

+    try {

+      imr.run();

+      testResult.setStatus(ITestResult.SUCCESS);

+    } catch (Exception ex) {

+      testResult.setThrowable(ex.getCause());

+      testResult.setStatus(ITestResult.FAILURE);

+    }

+  }

+

+  private static void invokeWithTimeoutWithNewExecutor(ITestNGMethod tm, Object instance,

+      Object[] parameterValues, ITestResult testResult, IHookable hookable)

+      throws InterruptedException, ThreadExecutionException {

+    IExecutor exec = ThreadUtil.createExecutor(1, tm.getMethodName());

+

+    InvokeMethodRunnable imr = new InvokeMethodRunnable(tm, instance, parameterValues, hookable, testResult);

+    IFutureResult future = exec.submitRunnable(imr);

+    exec.shutdown();

+    long realTimeOut = MethodHelper.calculateTimeOut(tm);

+    boolean finished = exec.awaitTermination(realTimeOut);

+

+    if (!finished) {

+      exec.stopNow();

+      ThreadTimeoutException exception = new ThreadTimeoutException("Method "

+          + tm.getClass().getName() + "." + tm.getMethodName() + "()"

+          + " didn't finish within the time-out " + realTimeOut);

+      exception.setStackTrace(exec.getStackTraces()[0]);

+      testResult.setThrowable(exception);

+      testResult.setStatus(ITestResult.FAILURE);

+    } else {

+      Utils.log("Invoker " + Thread.currentThread().hashCode(), 3, "Method " + tm.getMethodName()

+          + " completed within the time-out " + tm.getTimeOut());

+

+      // We don't need the result from the future but invoking get() on it

+      // will trigger the exception that was thrown, if any

+      future.get();

+      // done.await();

+

+      testResult.setStatus(ITestResult.SUCCESS); // if no exception till here

+                                                 // than SUCCESS

+    }

+  }

+

+  protected static void invokeConfigurable(final Object instance, final Object[] parameters,

+                                           final IConfigurable configurableInstance, final Method thisMethod,

+                                           final ITestResult testResult) throws Throwable {

+    final Throwable[] error = new Throwable[1];

+

+    IConfigureCallBack callback = new IConfigureCallBack() {

+      @Override

+      public void runConfigurationMethod(ITestResult tr) {

+        try {

+          invokeMethod(thisMethod, instance, parameters);

+        } catch (Throwable t) {

+          error[0] = t;

+          tr.setThrowable(t); // make Throwable available to IConfigurable

+        }

+      }

+

+      @Override

+      public Object[] getParameters() {

+        return parameters;

+      }

+    };

+    configurableInstance.run(callback, testResult);

+    if (error[0] != null) {

+      throw error[0];

+    }

+  }

+

+}

diff --git a/src/main/java/org/testng/internal/MethodSelectorDescriptor.java b/src/main/java/org/testng/internal/MethodSelectorDescriptor.java
new file mode 100755
index 0000000..1ad2d08
--- /dev/null
+++ b/src/main/java/org/testng/internal/MethodSelectorDescriptor.java
@@ -0,0 +1,53 @@
+package org.testng.internal;
+
+import org.testng.IMethodSelector;
+import org.testng.ITestNGMethod;
+
+import java.util.List;
+
+/**
+ * This class describes a method selector:
+ * - The class that implements it
+ * - Its priority
+ *
+ * Created on Sep 26, 2005
+ * @author cbeust
+ */
+public class MethodSelectorDescriptor implements Comparable<MethodSelectorDescriptor> {
+  private IMethodSelector m_methodSelector;
+  private int m_priority;
+
+  public int getPriority() {
+    return m_priority;
+  }
+
+  public IMethodSelector getMethodSelector() {
+    return m_methodSelector;
+  }
+
+  public MethodSelectorDescriptor(IMethodSelector selector, int priority) {
+    m_methodSelector = selector;
+    m_priority = priority;
+  }
+
+  @Override
+  public int compareTo(MethodSelectorDescriptor other) {
+    int result = 0;
+
+    try {
+      int p1 = getPriority();
+      int p2 = other.getPriority();
+      result = p1 - p2;
+    }
+    catch(Exception ex) {
+      // ignore
+    }
+
+    return result;
+  }
+
+  public void setTestMethods(List<ITestNGMethod> testMethods) {
+    m_methodSelector.setTestMethods(testMethods);
+
+  }
+}
diff --git a/src/main/java/org/testng/internal/NoOpTestClass.java b/src/main/java/org/testng/internal/NoOpTestClass.java
new file mode 100755
index 0000000..043a4cf
--- /dev/null
+++ b/src/main/java/org/testng/internal/NoOpTestClass.java
@@ -0,0 +1,199 @@
+package org.testng.internal;

+

+

+import org.testng.ITestClass;

+import org.testng.ITestNGMethod;

+import org.testng.xml.XmlClass;

+import org.testng.xml.XmlTest;

+

+public class NoOpTestClass implements ITestClass {

+  private static final long serialVersionUID = -4544061405329040593L;

+

+  protected Class m_testClass= null;

+

+  // Test methods

+  protected ITestNGMethod[] m_beforeClassMethods= null;

+  protected ITestNGMethod[] m_beforeTestMethods= null;

+  protected ITestNGMethod[] m_testMethods= null;

+  protected ITestNGMethod[] m_afterClassMethods= null;

+  protected ITestNGMethod[] m_afterTestMethods= null;

+  protected ITestNGMethod[] m_beforeSuiteMethods= null;

+  protected ITestNGMethod[] m_afterSuiteMethods= null;

+  protected ITestNGMethod[] m_beforeTestConfMethods= null;

+  protected ITestNGMethod[] m_afterTestConfMethods= null;

+  protected ITestNGMethod[] m_beforeGroupsMethods= null;

+  protected ITestNGMethod[] m_afterGroupsMethods= null;

+

+  private transient Object[] m_instances;

+  private long[] m_instanceHashes;

+

+  private XmlTest m_xmlTest;

+

+  private XmlClass m_xmlClass;

+

+  protected NoOpTestClass() {

+  }

+

+  public NoOpTestClass(ITestClass testClass) {

+    m_testClass= testClass.getRealClass();

+    m_beforeSuiteMethods= testClass.getBeforeSuiteMethods();

+    m_beforeTestConfMethods= testClass.getBeforeTestConfigurationMethods();

+    m_beforeGroupsMethods= testClass.getBeforeGroupsMethods();

+    m_beforeClassMethods= testClass.getBeforeClassMethods();

+    m_beforeTestMethods= testClass.getBeforeTestMethods();

+    m_afterSuiteMethods= testClass.getAfterSuiteMethods();

+    m_afterTestConfMethods= testClass.getAfterTestConfigurationMethods();

+    m_afterGroupsMethods= testClass.getAfterGroupsMethods();

+    m_afterClassMethods= testClass.getAfterClassMethods();

+    m_afterTestMethods= testClass.getAfterTestMethods();

+    m_instances= testClass.getInstances(true);

+    m_instanceHashes= testClass.getInstanceHashCodes();

+    m_xmlTest = testClass.getXmlTest();

+    m_xmlClass = testClass.getXmlClass();

+  }

+

+  public void setBeforeTestMethods(ITestNGMethod[] beforeTestMethods) {

+    m_beforeTestMethods= beforeTestMethods;

+  }

+

+  public void setAfterTestMethod(ITestNGMethod[] afterTestMethods) {

+    m_afterTestMethods= afterTestMethods;

+  }

+

+  /**

+   * @return Returns the afterClassMethods.

+   */

+  @Override

+  public ITestNGMethod[] getAfterClassMethods() {

+    return m_afterClassMethods;

+  }

+

+  /**

+   * @return Returns the afterTestMethods.

+   */

+  @Override

+  public ITestNGMethod[] getAfterTestMethods() {

+    return m_afterTestMethods;

+  }

+

+  /**

+   * @return Returns the beforeClassMethods.

+   */

+  @Override

+  public ITestNGMethod[] getBeforeClassMethods() {

+    return m_beforeClassMethods;

+  }

+

+  /**

+   * @return Returns the beforeTestMethods.

+   */

+  @Override

+  public ITestNGMethod[] getBeforeTestMethods() {

+    return m_beforeTestMethods;

+  }

+

+  /**

+   * @return Returns the testMethods.

+   */

+  @Override

+  public ITestNGMethod[] getTestMethods() {

+    return m_testMethods;

+  }

+

+  @Override

+  public ITestNGMethod[] getBeforeSuiteMethods() {

+    return m_beforeSuiteMethods;

+  }

+

+  @Override

+  public ITestNGMethod[] getAfterSuiteMethods() {

+    return m_afterSuiteMethods;

+  }

+

+  @Override

+  public ITestNGMethod[] getBeforeTestConfigurationMethods() {

+    return m_beforeTestConfMethods;

+  }

+

+  @Override

+  public ITestNGMethod[] getAfterTestConfigurationMethods() {

+    return m_afterTestConfMethods;

+  }

+

+  /**

+   * @return all @Configuration methods that should be invoked before certain groups

+   */

+  @Override

+  public ITestNGMethod[] getBeforeGroupsMethods() {

+    return m_beforeGroupsMethods;

+  }

+

+  /**

+   * @return all @Configuration methods that should be invoked after certain groups

+   */

+  @Override

+  public ITestNGMethod[] getAfterGroupsMethods() {

+    return m_afterGroupsMethods;

+  }

+

+  /**

+   * @see org.testng.ITestClass#getInstanceCount()

+   */

+  @Override

+  public int getInstanceCount() {

+    return m_instances.length;

+  }

+

+  /**

+   * @see org.testng.ITestClass#getInstanceHashCodes()

+   */

+  @Override

+  public long[] getInstanceHashCodes() {

+    return m_instanceHashes;

+  }

+

+  /**

+   * @see org.testng.ITestClass#getInstances(boolean)

+   */

+  @Override

+  public Object[] getInstances(boolean reuse) {

+    return m_instances;

+  }

+

+  @Override

+  public String getName() {

+    return m_testClass.getName();

+  }

+

+  @Override

+  public Class getRealClass() {

+    return m_testClass;

+  }

+

+  /**

+   * @see org.testng.IClass#addInstance(java.lang.Object)

+   */

+  @Override

+  public void addInstance(Object instance) {

+  }

+

+  public void setTestClass(Class< ? > declaringClass) {

+    m_testClass = declaringClass;

+  }

+

+  @Override

+  public String getTestName() {

+    // TODO Auto-generated method stub

+    return null;

+  }

+

+  @Override

+  public XmlTest getXmlTest() {

+    return m_xmlTest;

+  }

+

+  @Override

+  public XmlClass getXmlClass() {

+    return m_xmlClass;

+  }

+}

diff --git a/src/main/java/org/testng/internal/Nullable.java b/src/main/java/org/testng/internal/Nullable.java
new file mode 100644
index 0000000..1592b8d
--- /dev/null
+++ b/src/main/java/org/testng/internal/Nullable.java
@@ -0,0 +1,12 @@
+package org.testng.internal;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.PARAMETER;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target({FIELD, PARAMETER})
+public @interface Nullable {
+}
diff --git a/src/main/java/org/testng/internal/ObjectFactoryImpl.java b/src/main/java/org/testng/internal/ObjectFactoryImpl.java
new file mode 100755
index 0000000..79c0436
--- /dev/null
+++ b/src/main/java/org/testng/internal/ObjectFactoryImpl.java
@@ -0,0 +1,45 @@
+package org.testng.internal;
+
+import org.testng.IObjectFactory;
+import org.testng.TestNGException;
+
+import java.lang.reflect.Constructor;
+
+/**
+ * Default factory for test creation.
+ * Note that if no constructor is found matching the specified parameters,
+ * this factory will try to invoke a constructor that takes in a string object
+ *
+ * @author Hani Suleiman
+ *         Date: Mar 6, 2007
+ *         Time: 12:00:27 PM
+ * @since 5.6
+ */
+public class ObjectFactoryImpl implements IObjectFactory {
+
+  /**
+   *
+   */
+  private static final long serialVersionUID = -4547389328475540017L;
+
+  @Override
+  public Object newInstance(Constructor constructor, Object... params) {
+    try {
+      constructor.setAccessible(true);
+      return constructor.newInstance(params);
+    }
+    catch (IllegalAccessException ex) {
+      return ClassHelper.tryOtherConstructor(constructor.getDeclaringClass());
+    }
+    catch (InstantiationException ex) {
+      return ClassHelper.tryOtherConstructor(constructor.getDeclaringClass());
+    }
+    catch(Exception ex) {
+      throw new TestNGException("Cannot instantiate class "
+          + (constructor != null
+                ? constructor.getDeclaringClass().getName()
+                : ": couldn't find a suitable constructor"),
+                    ex);
+    }
+  }
+}
diff --git a/src/main/java/org/testng/internal/OverrideProcessor.java b/src/main/java/org/testng/internal/OverrideProcessor.java
new file mode 100644
index 0000000..d7bdd68
--- /dev/null
+++ b/src/main/java/org/testng/internal/OverrideProcessor.java
@@ -0,0 +1,46 @@
+package org.testng.internal;
+
+import org.testng.xml.IPostProcessor;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * Override the groups included in the XML file with groups specified on the command line.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public class OverrideProcessor implements IPostProcessor {
+
+  private String[] m_groups;
+  private String[] m_excludedGroups;
+
+  public OverrideProcessor(String[] groups, String[] excludedGroups) {
+    m_groups = groups;
+    m_excludedGroups = excludedGroups;
+  }
+
+  @Override
+  public Collection<XmlSuite> process(Collection<XmlSuite> suites) {
+    for (XmlSuite s : suites) {
+      if (m_groups != null && m_groups.length > 0) {
+        for (XmlTest t : s.getTests()) {
+          t.getIncludedGroups().clear();
+          t.getIncludedGroups().addAll(Arrays.asList(m_groups));
+        }
+      }
+
+      if (m_excludedGroups != null && m_excludedGroups.length > 0) {
+        for (XmlTest t : s.getTests()) {
+          t.getExcludedGroups().clear();
+          t.getExcludedGroups().addAll(Arrays.asList(m_excludedGroups));
+        }
+      }
+    }
+
+    return suites;
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/PackageUtils.java b/src/main/java/org/testng/internal/PackageUtils.java
new file mode 100644
index 0000000..a76cbcf
--- /dev/null
+++ b/src/main/java/org/testng/internal/PackageUtils.java
@@ -0,0 +1,304 @@
+package org.testng.internal;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Method;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLDecoder;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.regex.Pattern;
+
+import org.testng.TestNG;
+import org.testng.collections.Lists;
+
+/**
+ * Utility class that finds all the classes in a given package.
+ *
+ * Created on Feb 24, 2006
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public class PackageUtils {
+  private static String[] s_testClassPaths;
+
+  /** The additional class loaders to find classes in. */
+  private static final List<ClassLoader> m_classLoaders = new Vector<>();
+
+  /** Add a class loader to the searchable loaders. */
+  public static void addClassLoader(final ClassLoader loader) {
+    m_classLoaders.add(loader);
+  }
+
+  /**
+   *
+   * @param packageName
+   * @return The list of all the classes inside this package
+   * @throws IOException
+   */
+  public static String[] findClassesInPackage(String packageName,
+      List<String> included, List<String> excluded)
+    throws IOException
+  {
+    String packageOnly = packageName;
+    boolean recursive = false;
+    if (packageName.endsWith(".*")) {
+      packageOnly = packageName.substring(0, packageName.lastIndexOf(".*"));
+      recursive = true;
+    }
+
+    List<String> vResult = Lists.newArrayList();
+    String packageDirName = packageOnly.replace('.', '/') + (packageOnly.length() > 0 ? "/" : "");
+
+
+    Vector<URL> dirs = new Vector<>();
+    // go through additional class loaders
+    Vector<ClassLoader> allClassLoaders = new Vector<>();
+    ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+    if (contextClassLoader != null) {
+      allClassLoaders.add(contextClassLoader);
+    }
+    if (m_classLoaders != null) {
+      allClassLoaders.addAll(m_classLoaders);
+    }
+
+    int count = 0;
+    for (ClassLoader classLoader : allClassLoaders) {
+      ++count;
+      if (null == classLoader) {
+        continue;
+      }
+      Enumeration<URL> dirEnumeration = classLoader.getResources(packageDirName);
+      while(dirEnumeration.hasMoreElements()){
+        URL dir = dirEnumeration.nextElement();
+        dirs.add(dir);
+      }
+    }
+
+    Iterator<URL> dirIterator = dirs.iterator();
+    while (dirIterator.hasNext()) {
+      URL url = dirIterator.next();
+      String protocol = url.getProtocol();
+      if(!matchTestClasspath(url, packageDirName, recursive)) {
+        continue;
+      }
+
+      if ("file".equals(protocol)) {
+        findClassesInDirPackage(packageOnly, included, excluded,
+                                URLDecoder.decode(url.getFile(), "UTF-8"),
+                                recursive,
+                                vResult);
+      }
+      else if ("jar".equals(protocol)) {
+        JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
+        Enumeration<JarEntry> entries = jar.entries();
+        while (entries.hasMoreElements()) {
+          JarEntry entry = entries.nextElement();
+          String name = entry.getName();
+          if (name.charAt(0) == '/') {
+            name = name.substring(1);
+          }
+          if (name.startsWith(packageDirName)) {
+            int idx = name.lastIndexOf('/');
+            if (idx != -1) {
+              packageName = name.substring(0, idx).replace('/', '.');
+            }
+
+            if (recursive || packageName.equals(packageOnly)) {
+              //it's not inside a deeper dir
+              Utils.log("PackageUtils", 4, "Package name is " + packageName);
+              if (name.endsWith(".class") && !entry.isDirectory()) {
+                String className = name.substring(packageName.length() + 1, name.length() - 6);
+                Utils.log("PackageUtils", 4, "Found class " + className + ", seeing it if it's included or excluded");
+                includeOrExcludeClass(packageName, className, included, excluded, vResult);
+              }
+            }
+          }
+        }
+      }
+      else if ("bundleresource".equals(protocol)) {
+        try {
+          Class params[] = {};
+          // BundleURLConnection
+          URLConnection connection = url.openConnection();
+          Method thisMethod = url.openConnection().getClass()
+              .getDeclaredMethod("getFileURL", params);
+          Object paramsObj[] = {};
+          URL fileUrl = (URL) thisMethod.invoke(connection, paramsObj);
+          findClassesInDirPackage(packageOnly, included, excluded,
+              URLDecoder.decode(fileUrl.getFile(), "UTF-8"), recursive, vResult);
+        } catch (Exception ex) {
+          // ignore - probably not an Eclipse OSGi bundle
+        }
+      }
+    }
+
+    String[] result = vResult.toArray(new String[vResult.size()]);
+    return result;
+  }
+
+  private static String[] getTestClasspath() {
+    if (null != s_testClassPaths) {
+      return s_testClassPaths;
+    }
+
+    String testClasspath = System.getProperty(TestNG.TEST_CLASSPATH);
+    if (null == testClasspath) {
+      return null;
+    }
+
+    String[] classpathFragments= Utils.split(testClasspath, File.pathSeparator);
+    s_testClassPaths= new String[classpathFragments.length];
+
+    for(int i= 0; i < classpathFragments.length; i++)  {
+      String path= null;
+      if(classpathFragments[i].toLowerCase().endsWith(".jar") || classpathFragments[i].toLowerCase().endsWith(".zip")) {
+        path= classpathFragments[i] + "!/";
+      }
+      else {
+        if(classpathFragments[i].endsWith(File.separator)) {
+          path= classpathFragments[i];
+        }
+        else {
+          path= classpathFragments[i] + "/";
+        }
+      }
+
+      s_testClassPaths[i]= path.replace('\\', '/');
+    }
+
+    return s_testClassPaths;
+  }
+
+  private static boolean matchTestClasspath(URL url, String lastFragment, boolean recursive) {
+    String[] classpathFragments= getTestClasspath();
+    if(null == classpathFragments) {
+      return true;
+    }
+
+    String fileName= null;
+    try {
+      fileName= URLDecoder.decode(url.getFile(), "UTF-8");
+    }
+    catch(UnsupportedEncodingException ueex) {
+      ; // ignore. should never happen
+    }
+
+    for(String classpathFrag: classpathFragments) {
+      String path=  classpathFrag + lastFragment;
+      int idx= fileName.indexOf(path);
+      if((idx == -1) || (idx > 0 && fileName.charAt(idx-1) != '/')) {
+        continue;
+      }
+
+      if(fileName.endsWith(classpathFrag + lastFragment)
+          || (recursive && fileName.charAt(idx + path.length()) == '/')) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  private static void findClassesInDirPackage(String packageName,
+                                              List<String> included,
+                                              List<String> excluded,
+                                              String packagePath,
+                                              final boolean recursive,
+                                              List<String> classes) {
+    File dir = new File(packagePath);
+
+    if (!dir.exists() || !dir.isDirectory()) {
+      return;
+    }
+
+    File[] dirfiles = dir.listFiles(new FileFilter() {
+          @Override
+          public boolean accept(File file) {
+            return (recursive && file.isDirectory())
+              || (file.getName().endsWith(".class"))
+              || (file.getName().endsWith(".groovy"));
+          }
+        });
+
+    Utils.log("PackageUtils", 4, "Looking for test classes in the directory: " + dir);
+    for (File file : dirfiles) {
+      if (file.isDirectory()) {
+        findClassesInDirPackage(makeFullClassName(packageName, file.getName()),
+                                included,
+                                excluded,
+                                file.getAbsolutePath(),
+                                recursive,
+                                classes);
+      }
+      else {
+        String className = file.getName().substring(0, file.getName().lastIndexOf("."));
+        Utils.log("PackageUtils", 4, "Found class " + className
+            + ", seeing it if it's included or excluded");
+        includeOrExcludeClass(packageName, className, included, excluded, classes);
+      }
+    }
+  }
+
+  private static String makeFullClassName(String pkg, String cls) {
+    return pkg.length() > 0 ? pkg + "." + cls : cls;
+  }
+
+  private static void includeOrExcludeClass(String packageName, String className,
+      List<String> included, List<String> excluded, List<String> classes)
+  {
+    if (isIncluded(packageName, included, excluded)) {
+      Utils.log("PackageUtils", 4, "... Including class " + className);
+      classes.add(makeFullClassName(packageName, className));
+    }
+    else {
+      Utils.log("PackageUtils", 4, "... Excluding class " + className);
+    }
+  }
+
+  /**
+   * @return true if name should be included.
+   */
+  private static boolean isIncluded(String name,
+      List<String> included, List<String> excluded)
+  {
+    boolean result = false;
+
+    //
+    // If no includes nor excludes were specified, return true.
+    //
+    if (included.size() == 0 && excluded.size() == 0) {
+      result = true;
+    }
+    else {
+      boolean isIncluded = PackageUtils.find(name, included);
+      boolean isExcluded = PackageUtils.find(name, excluded);
+      if (isIncluded && !isExcluded) {
+        result = true;
+      }
+      else if (isExcluded) {
+        result = false;
+      }
+      else {
+        result = included.size() == 0;
+      }
+    }
+    return result;
+  }
+
+  private static boolean find(String name, List<String> list) {
+    for (String regexpStr : list) {
+      if (Pattern.matches(regexpStr, name)) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
diff --git a/src/main/java/org/testng/internal/ParameterHolder.java b/src/main/java/org/testng/internal/ParameterHolder.java
new file mode 100755
index 0000000..8bd0914
--- /dev/null
+++ b/src/main/java/org/testng/internal/ParameterHolder.java
@@ -0,0 +1,31 @@
+package org.testng.internal;
+
+import java.util.Iterator;
+
+/**
+ * A simple holder for parameters that contains the parameters and where these came from
+ * (data provider or testng.xml)
+ * @author cbeust
+ *
+ */
+public class ParameterHolder {
+  /**
+   * Origin of the parameters.
+   */
+  public enum ParameterOrigin {
+    ORIGIN_DATA_PROVIDER, // A data provider
+    ORIGIN_XML // TestNG XML suite
+  };
+
+  public DataProviderHolder dataProviderHolder;
+  public Iterator<Object[]> parameters;
+  public ParameterOrigin origin;
+
+  public ParameterHolder(Iterator<Object[]> parameters, ParameterOrigin origin, DataProviderHolder dph) {
+    super();
+    this.parameters = parameters;
+    this.origin = origin;
+    this.dataProviderHolder = dph;
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/Parameters.java b/src/main/java/org/testng/internal/Parameters.java
new file mode 100755
index 0000000..cd86eb8
--- /dev/null
+++ b/src/main/java/org/testng/internal/Parameters.java
@@ -0,0 +1,532 @@
+package org.testng.internal;
+
+import com.google.inject.Injector;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.testng.ITestClass;
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+import org.testng.TestNGException;
+import org.testng.annotations.IConfigurationAnnotation;
+import org.testng.annotations.IDataProviderAnnotation;
+import org.testng.annotations.IParameterizable;
+import org.testng.annotations.IParametersAnnotation;
+import org.testng.annotations.ITestAnnotation;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.internal.ParameterHolder.ParameterOrigin;
+import org.testng.internal.annotations.AnnotationHelper;
+import org.testng.internal.annotations.IAnnotationFinder;
+import org.testng.internal.annotations.IDataProvidable;
+import org.testng.util.Strings;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+/**
+ * Methods that bind parameters declared in testng.xml to actual values
+ * used to invoke methods.
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public class Parameters {
+  public static final String NULL_VALUE= "null";
+
+  /**
+   * Creates the parameters needed for constructing a test class instance.
+   * @param finder TODO
+   */
+  public static Object[] createInstantiationParameters(Constructor ctor,
+      String methodAnnotation,
+      IAnnotationFinder finder,
+      String[] parameterNames,
+      Map<String, String> params, XmlSuite xmlSuite)
+  {
+    return createParameters(ctor.toString(), ctor.getParameterTypes(),
+        finder.findOptionalValues(ctor), methodAnnotation, finder, parameterNames,
+            new MethodParameters(params, Collections.<String, String>emptyMap()),
+            xmlSuite);
+  }
+
+  /**
+   * Creates the parameters needed for the specified <tt>@Configuration</tt> <code>Method</code>.
+   *
+   * @param m the configuraton method
+   * @param currentTestMethod the current @Test method or <code>null</code> if no @Test is available (this is not
+   *    only in case the configuration method is a @Before/@AfterMethod
+   * @param finder the annotation finder
+   */
+  public static Object[] createConfigurationParameters(Method m,
+      Map<String, String> params,
+      Object[] parameterValues,
+      @Nullable ITestNGMethod currentTestMethod,
+      IAnnotationFinder finder,
+      XmlSuite xmlSuite,
+      ITestContext ctx,
+      ITestResult testResult)
+  {
+    Method currentTestMeth= currentTestMethod != null ?
+        currentTestMethod.getMethod() : null;
+
+    Map<String, String> methodParams = currentTestMethod != null
+        ? currentTestMethod.findMethodParameters(ctx.getCurrentXmlTest())
+        : Collections.<String, String>emptyMap();
+
+    return createParameters(m,
+        new MethodParameters(params,
+            methodParams,
+            parameterValues,
+            currentTestMeth, ctx, testResult),
+        finder, xmlSuite, IConfigurationAnnotation.class, "@Configuration");
+  }
+
+  ////////////////////////////////////////////////////////
+
+  public static Object getInjectedParameter(Class<?> c, Method method, ITestContext context,
+      ITestResult testResult) {
+    Object result = null;
+    if (Method.class.equals(c)) {
+      result = method;
+    }
+    else if (ITestContext.class.equals(c)) {
+      result = context;
+    }
+    else if (XmlTest.class.equals(c)) {
+      result = context.getCurrentXmlTest();
+    }
+    else if (ITestResult.class.equals(c)) {
+      result = testResult;
+    }
+    return result;
+  }
+
+  /**
+   * @return An array of parameters suitable to invoke this method, possibly
+   * picked from the property file
+   */
+  private static Object[] createParameters(String methodName,
+      Class[] parameterTypes,
+      String[] optionalValues,
+      String methodAnnotation,
+      IAnnotationFinder finder,
+      String[] parameterNames, MethodParameters params, XmlSuite xmlSuite)
+  {
+    Object[] result = new Object[0];
+    if(parameterTypes.length > 0) {
+      List<Object> vResult = Lists.newArrayList();
+
+      checkParameterTypes(methodName, parameterTypes, methodAnnotation, parameterNames);
+
+      for(int i = 0, j = 0; i < parameterTypes.length; i++) {
+        Object inject = getInjectedParameter(parameterTypes[i], params.currentTestMethod,
+            params.context, params.testResult);
+        if (inject != null) {
+          vResult.add(inject);
+        }
+        else {
+          if (j < parameterNames.length) {
+            String p = parameterNames[j];
+            String value = params.xmlParameters.get(p);
+            if(null == value) {
+              // try SysEnv entries
+              value= System.getProperty(p);
+            }
+            if (null == value) {
+              if (optionalValues != null) {
+                value = optionalValues[i];
+              }
+              if (null == value) {
+              throw new TestNGException("Parameter '" + p + "' is required by "
+                  + methodAnnotation
+                  + " on method "
+                  + methodName
+                  + " but has not been marked @Optional or defined\n"
+                  + (xmlSuite.getFileName() != null ? "in "
+                  + xmlSuite.getFileName() : ""));
+              }
+            }
+
+            vResult.add(convertType(parameterTypes[i], value, p));
+            j++;
+          }
+        }
+      }
+
+      result = vResult.toArray(new Object[vResult.size()]);
+    }
+
+    return result;
+  }
+
+  private static void checkParameterTypes(String methodName,
+      Class[] parameterTypes, String methodAnnotation, String[] parameterNames)
+  {
+    int totalLength = parameterTypes.length;
+    Set<Class> injectedTypes = new HashSet<Class>() {
+      private static final long serialVersionUID = -5324894581793435812L;
+
+    {
+      add(ITestContext.class);
+      add(ITestResult.class);
+      add(XmlTest.class);
+      add(Method.class);
+      add(Object[].class);
+    }};
+    for (Class parameterType : parameterTypes) {
+      if (injectedTypes.contains(parameterType)) {
+        totalLength--;
+      }
+    }
+
+    if (parameterNames.length != totalLength) {
+      throw new TestNGException( "Method " + methodName + " requires "
+          + parameterTypes.length + " parameters but "
+          + parameterNames.length
+          + " were supplied in the "
+          + methodAnnotation
+          + " annotation.");
+    }
+  }
+
+  public static Object convertType(Class type, String value, String paramName) {
+    Object result = null;
+
+    if(NULL_VALUE.equals(value.toLowerCase())) {
+      if(type.isPrimitive()) {
+        Utils.log("Parameters", 2, "Attempt to pass null value to primitive type parameter '" + paramName + "'");
+      }
+
+      return null; // null value must be used
+    }
+
+    if(type == String.class) {
+      result = value;
+    }
+    else if(type == int.class || type == Integer.class) {
+      result = Integer.parseInt(value);
+    }
+    else if(type == boolean.class || type == Boolean.class) {
+      result = Boolean.valueOf(value);
+    }
+    else if(type == byte.class || type == Byte.class) {
+      result = Byte.parseByte(value);
+    }
+    else if(type == char.class || type == Character.class) {
+      result = value.charAt(0);
+    }
+    else if(type == double.class || type == Double.class) {
+      result = Double.parseDouble(value);
+    }
+    else if(type == float.class || type == Float.class) {
+      result = Float.parseFloat(value);
+    }
+    else if(type == long.class || type == Long.class) {
+      result = Long.parseLong(value);
+    }
+    else if(type == short.class || type == Short.class) {
+      result = Short.parseShort(value);
+    }
+    else if (type.isEnum()) {
+    	result = Enum.valueOf(type, value);
+    }
+    else {
+      assert false : "Unsupported type parameter : " + type;
+    }
+
+    return result;
+  }
+
+  private static DataProviderHolder findDataProvider(Object instance, ITestClass clazz,
+                                                     ConstructorOrMethod m,
+                                                     IAnnotationFinder finder, ITestContext context) {
+    DataProviderHolder result = null;
+
+    IDataProvidable dp = findDataProviderInfo(clazz, m, finder);
+    if (dp != null) {
+      String dataProviderName = dp.getDataProvider();
+      Class dataProviderClass = dp.getDataProviderClass();
+
+      if (! Utils.isStringEmpty(dataProviderName)) {
+        result = findDataProvider(instance, clazz, finder, dataProviderName, dataProviderClass, context);
+
+        if(null == result) {
+          throw new TestNGException("Method " + m + " requires a @DataProvider named : "
+              + dataProviderName + (dataProviderClass != null ? " in class " + dataProviderClass.getName() : "")
+              );
+        }
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Find the data provider info (data provider name and class) on either @Test(dataProvider),
+   * @Factory(dataProvider) on a method or @Factory(dataProvider) on a constructor.
+   */
+  private static IDataProvidable findDataProviderInfo(ITestClass clazz, ConstructorOrMethod m,
+      IAnnotationFinder finder) {
+    IDataProvidable result;
+
+    if (m.getMethod() != null) {
+      //
+      // @Test(dataProvider) on a method
+      //
+      result = AnnotationHelper.findTest(finder, m.getMethod());
+      if (result == null) {
+        //
+        // @Factory(dataProvider) on a method
+        //
+        result = AnnotationHelper.findFactory(finder, m.getMethod());
+      }
+      if (result == null) {
+        //
+        // @Test(dataProvider) on a class
+        result = AnnotationHelper.findTest(finder, clazz.getRealClass());
+      }
+    } else {
+      //
+      // @Factory(dataProvider) on a constructor
+      //
+      result = AnnotationHelper.findFactory(finder, m.getConstructor());
+    }
+
+    return result;
+  }
+
+  /**
+   * Find a method that has a @DataProvider(name=name)
+   */
+  private static DataProviderHolder findDataProvider(Object instance, ITestClass clazz,
+                                                     IAnnotationFinder finder,
+                                                     String name, Class dataProviderClass,
+                                                     ITestContext context)
+  {
+    DataProviderHolder result = null;
+
+    Class cls = clazz.getRealClass();
+    boolean shouldBeStatic = false;
+    if (dataProviderClass != null) {
+      cls = dataProviderClass;
+      shouldBeStatic = true;
+    }
+
+    for (Method m : ClassHelper.getAvailableMethods(cls)) {
+      IDataProviderAnnotation dp = finder.findAnnotation(m, IDataProviderAnnotation.class);
+      if (null != dp && name.equals(getDataProviderName(dp, m))) {
+        if (shouldBeStatic && (m.getModifiers() & Modifier.STATIC) == 0) {
+          Injector injector = context.getInjector(clazz);
+          if (injector != null) {
+            instance = injector.getInstance(dataProviderClass);
+          }
+        }
+
+        if (result != null) {
+          throw new TestNGException("Found two providers called '" + name + "' on " + cls);
+        }
+        result = new DataProviderHolder(dp, m, instance);
+      }
+    }
+
+    return result;
+  }
+
+  private static String getDataProviderName(IDataProviderAnnotation dp, Method m) {
+	  return Strings.isNullOrEmpty(dp.getName()) ? m.getName() : dp.getName();
+  }
+  
+  @SuppressWarnings({"deprecation"})
+  private static Object[] createParameters(Method m, MethodParameters params,
+      IAnnotationFinder finder, XmlSuite xmlSuite, Class annotationClass, String atName)
+  {
+    List<Object> result = Lists.newArrayList();
+
+    Object[] extraParameters;
+    //
+    // Try to find an @Parameters annotation
+    //
+    IParametersAnnotation annotation = finder.findAnnotation(m, IParametersAnnotation.class);
+    Class<?>[] types = m.getParameterTypes();
+    if(null != annotation) {
+      String[] parameterNames = annotation.getValue();
+      extraParameters = createParameters(m.getName(), types,
+          finder.findOptionalValues(m), atName, finder, parameterNames, params, xmlSuite);
+    }
+
+    //
+    // Else, use the deprecated syntax
+    //
+    else {
+      IParameterizable a = (IParameterizable) finder.findAnnotation(m, annotationClass);
+      if(null != a && a.getParameters().length > 0) {
+        String[] parameterNames = a.getParameters();
+        extraParameters = createParameters(m.getName(), types,
+            finder.findOptionalValues(m), atName, finder, parameterNames, params, xmlSuite);
+      }
+      else {
+        extraParameters = createParameters(m.getName(), types,
+            finder.findOptionalValues(m), atName, finder, new String[0], params, xmlSuite);
+      }
+    }
+
+    //
+    // Add the extra parameters we found
+    //
+    Collections.addAll(result, extraParameters);
+
+    // If the method declared an Object[] parameter and we have parameter values, inject them
+    for (int i = 0; i < types.length; i++) {
+        if (Object[].class.equals(types[i])) {
+            result.add(i, params.parameterValues);
+        }
+    }
+
+
+    return result.toArray(new Object[result.size()]);
+  }
+
+  /**
+   * If the method has parameters, fill them in. Either by using a @DataProvider
+   * if any was provided, or by looking up <parameters> in testng.xml
+   * @return An Iterator over the values for each parameter of this
+   * method.
+   */
+  public static ParameterHolder handleParameters(ITestNGMethod testMethod,
+      Map<String, String> allParameterNames,
+      Object instance,
+      MethodParameters methodParams,
+      XmlSuite xmlSuite,
+      IAnnotationFinder annotationFinder,
+      Object fedInstance)
+  {
+    ParameterHolder result;
+    Iterator<Object[]> parameters;
+
+    /*
+     * Do we have a @DataProvider? If yes, then we have several
+     * sets of parameters for this method
+     */
+    DataProviderHolder dataProviderHolder =
+        findDataProvider(instance, testMethod.getTestClass(),
+            testMethod.getConstructorOrMethod(), annotationFinder, methodParams.context);
+
+    if (null != dataProviderHolder) {
+      int parameterCount = testMethod.getConstructorOrMethod().getParameterTypes().length;
+
+      for (int i = 0; i < parameterCount; i++) {
+        String n = "param" + i;
+        allParameterNames.put(n, n);
+      }
+
+      parameters = MethodInvocationHelper.invokeDataProvider(
+          dataProviderHolder.instance, /* a test instance or null if the dataprovider is static*/
+          dataProviderHolder.method,
+          testMethod,
+          methodParams.context,
+          fedInstance,
+          annotationFinder);
+
+      Iterator<Object[]> filteredParameters = filterParameters(parameters,
+          testMethod.getInvocationNumbers());
+
+      result = new ParameterHolder(filteredParameters, ParameterOrigin.ORIGIN_DATA_PROVIDER,
+          dataProviderHolder);
+    }
+    else {
+      //
+      // Normal case: we have only one set of parameters coming from testng.xml
+      //
+      allParameterNames.putAll(methodParams.xmlParameters);
+      // Create an Object[][] containing just one row of parameters
+      Object[][] allParameterValuesArray = new Object[1][];
+      allParameterValuesArray[0] = createParameters(testMethod.getMethod(),
+          methodParams, annotationFinder, xmlSuite, ITestAnnotation.class, "@Test");
+
+      // Mark that this method needs to have at least a certain
+      // number of invocations (needed later to call AfterGroups
+      // at the right time).
+      testMethod.setParameterInvocationCount(allParameterValuesArray.length);
+      // Turn it into an Iterable
+      parameters = MethodHelper.createArrayIterator(allParameterValuesArray);
+
+      result = new ParameterHolder(parameters, ParameterOrigin.ORIGIN_XML, null);
+    }
+
+    return result;
+  }
+
+  /**
+   * If numbers is empty, return parameters, otherwise, return a subset of parameters
+   * whose ordinal number match these found in numbers.
+   */
+  static private Iterator<Object[]> filterParameters(Iterator<Object[]> parameters,
+      List<Integer> list) {
+    if (list.isEmpty()) {
+      return parameters;
+    } else {
+      List<Object[]> result = Lists.newArrayList();
+      int i = 0;
+      while (parameters.hasNext()) {
+        Object[] next = parameters.next();
+        if (list.contains(i)) {
+          result.add(next);
+        }
+        i++;
+      }
+      return new ArrayIterator(result.toArray(new Object[list.size()][]));
+    }
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[Parameters] " + s);
+  }
+
+  /** A parameter passing helper class. */
+  public static class MethodParameters {
+    private final Map<String, String> xmlParameters;
+    private final Method currentTestMethod;
+    private final ITestContext context;
+    private Object[] parameterValues;
+    public ITestResult testResult;
+
+    public MethodParameters(Map<String, String> params, Map<String, String> methodParams) {
+      this(params, methodParams, null, null, null, null);
+    }
+
+    public MethodParameters(Map<String, String> params, Map<String, String> methodParams,
+        Method m) {
+      this(params, methodParams, null, m, null, null);
+    }
+
+    /**
+     * @param params parameters found in the suite and test tags
+     * @param methodParams parameters found in the include tag
+     * @param pv
+     * @param m
+     * @param ctx
+     * @param tr
+     */
+    public MethodParameters(Map<String, String> params,
+        Map<String, String> methodParams,
+        Object[] pv, Method m, ITestContext ctx,
+        ITestResult tr) {
+      Map<String, String> allParams = Maps.newHashMap();
+      allParams.putAll(params);
+      allParams.putAll(methodParams);
+      xmlParameters = allParams;
+      currentTestMethod = m;
+      context = ctx;
+      parameterValues = pv;
+      testResult = tr;
+    }
+  }
+}
diff --git a/src/main/java/org/testng/internal/PoolService.java b/src/main/java/org/testng/internal/PoolService.java
new file mode 100755
index 0000000..5e65f46
--- /dev/null
+++ b/src/main/java/org/testng/internal/PoolService.java
@@ -0,0 +1,58 @@
+package org.testng.internal;
+
+import org.testng.TestNGException;
+import org.testng.collections.Lists;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorCompletionService;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * Simple wrapper for an ExecutorCompletionService.
+ */
+public class PoolService<FutureType> {
+
+  private ExecutorCompletionService<FutureType> m_completionService;
+  private ThreadFactory m_threadFactory;
+  private ExecutorService m_executor;
+
+  public PoolService(int threadPoolSize) {
+    m_threadFactory = new ThreadFactory() {
+      private int m_threadIndex = 0;
+
+      @Override
+      public Thread newThread(Runnable r) {
+        Thread result = new Thread(r);
+        result.setName("PoolService-" + m_threadIndex);
+        m_threadIndex++;
+        return result;
+      }
+    };
+    m_executor = Executors.newFixedThreadPool(threadPoolSize, m_threadFactory);
+    m_completionService = new ExecutorCompletionService<>(m_executor);
+  }
+
+  public List<FutureType> submitTasksAndWait(List<? extends Callable<FutureType>> tasks) {
+    List<FutureType> result = Lists.newArrayList();
+
+    for (Callable<FutureType> callable : tasks) {
+      m_completionService.submit(callable);
+    }
+    for (int i = 0; i < tasks.size(); i++) {
+      try {
+        Future<FutureType> take = m_completionService.take();
+        result.add(take.get());
+      } catch (InterruptedException | ExecutionException e) {
+        throw new TestNGException(e);
+      }
+    }
+
+    m_executor.shutdown();
+    return result;
+  }
+}
diff --git a/src/main/java/org/testng/internal/PropertiesFile.java b/src/main/java/org/testng/internal/PropertiesFile.java
new file mode 100755
index 0000000..584e4e1
--- /dev/null
+++ b/src/main/java/org/testng/internal/PropertiesFile.java
@@ -0,0 +1,51 @@
+package org.testng.internal;

+

+import java.io.File;

+import java.io.FileInputStream;

+import java.io.IOException;

+import java.util.Properties;

+

+/**

+ * This class loads and abstracts remote.properties

+ *

+ * @author cbeust

+ * @author Guy Korland

+ * @since April 13, 2006

+ */

+public class PropertiesFile {

+

+  private Properties m_properties = new Properties();

+

+  /**

+   * Loads a Properties file.

+   *

+   * @param fileName properties file path

+   * @throws IOException if an error occurred when reading from the Properties file.

+   */

+  public PropertiesFile(String fileName) throws IOException

+  {

+	  FileInputStream fis = null;

+	  //

+	  // Parse the Properties file

+	  //

+	  try {

+		  fis = new FileInputStream(new File(fileName));

+		  m_properties.load(fis);

+	  }

+	  finally

+	  {

+		  if( fis != null) {

+        fis.close();

+      }

+	  }

+  }

+

+  /**

+   * Returns the properties loaded.

+   * @return loaded properties.

+   */

+  public Properties getProperties()

+  {

+	  return m_properties;

+  }

+}

diff --git a/src/main/java/org/testng/internal/PropertyUtils.java b/src/main/java/org/testng/internal/PropertyUtils.java
new file mode 100755
index 0000000..ba69110
--- /dev/null
+++ b/src/main/java/org/testng/internal/PropertyUtils.java
@@ -0,0 +1,87 @@
+package org.testng.internal;
+
+import org.testng.log4testng.Logger;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Utility class for setting JavaBeans-style properties on instances.
+ *
+ * @author Cosmin Marginean, Apr 12, 2007
+ */
+public class PropertyUtils {
+
+  private static final Logger LOGGER = Logger.getLogger(PropertyUtils.class);
+
+  public static void setProperty(Object instance, String name, String value) {
+    if (instance == null) {
+      LOGGER.warn("Cannot set property " + name + " with value " + value + ". The target instance is null");
+      return;
+    }
+
+    Class propClass = getPropertyType(instance.getClass(), name);
+    if (propClass == null) {
+      LOGGER.warn("Cannot set property " + name + " with value " + value + ". Property class could not be found");
+      return;
+    }
+
+    Object realValue = Parameters.convertType(propClass, value, name);
+    //TODO: Here the property desc is serched again
+    setPropertyRealValue(instance, name, realValue);
+  }
+
+  public static Class getPropertyType(Class instanceClass, String propertyName) {
+    if (instanceClass == null) {
+      LOGGER.warn("Cannot retrieve property class for " + propertyName + ". Target instance class is null");
+    }
+    PropertyDescriptor propDesc = getPropertyDescriptor(instanceClass, propertyName);
+    return propDesc.getPropertyType();
+  }
+
+  private static PropertyDescriptor getPropertyDescriptor(Class targetClass, String propertyName) {
+    PropertyDescriptor result = null;
+    if (targetClass == null) {
+      LOGGER.warn("Cannot retrieve property " + propertyName + ". Class is null");
+    } else {
+      try {
+        BeanInfo beanInfo = Introspector.getBeanInfo(targetClass);
+        PropertyDescriptor[] propDescriptors = beanInfo.getPropertyDescriptors();
+        for (PropertyDescriptor propDesc : propDescriptors) {
+          if (propDesc.getName().equals(propertyName)) {
+            result = propDesc;
+            break;
+          }
+        }
+      } catch (IntrospectionException ie) {
+        LOGGER.warn("Cannot retrieve property " + propertyName + ". Cause is: " + ie);
+      }
+    }
+    return result;
+  }
+
+  public static void setPropertyRealValue(Object instance, String name, Object value) {
+    if (instance == null) {
+      LOGGER.warn("Cannot set property " + name + " with value " + value + ". Targe instance is null");
+      return;
+    }
+
+    PropertyDescriptor propDesc = getPropertyDescriptor(instance.getClass(), name);
+    if (propDesc == null) {
+      LOGGER.warn("Cannot set property " + name + " with value " + value + ". Property does not exist");
+      return;
+    }
+
+    Method method = propDesc.getWriteMethod();
+    try {
+      method.invoke(instance, new Object[]{value});
+    } catch (IllegalAccessException | InvocationTargetException iae) {
+      LOGGER.warn("Cannot set property " + name + " with value " + value + ". Cause " + iae);
+    }
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/RegexpExpectedExceptionsHolder.java b/src/main/java/org/testng/internal/RegexpExpectedExceptionsHolder.java
new file mode 100755
index 0000000..f1f1e93
--- /dev/null
+++ b/src/main/java/org/testng/internal/RegexpExpectedExceptionsHolder.java
@@ -0,0 +1,65 @@
+package org.testng.internal;
+
+import org.testng.IExpectedExceptionsHolder;
+import org.testng.ITestNGMethod;
+import org.testng.annotations.IExpectedExceptionsAnnotation;
+import org.testng.annotations.ITestAnnotation;
+import org.testng.internal.annotations.IAnnotationFinder;
+
+import java.util.regex.Pattern;
+
+/**
+ * A class that contains the expected exceptions and the message regular expression.
+ * @author cbeust
+ */
+public class RegexpExpectedExceptionsHolder implements IExpectedExceptionsHolder {
+  public static final String DEFAULT_REGEXP = ".*";
+
+  private final IAnnotationFinder finder;
+  private final ITestNGMethod method;
+
+  public RegexpExpectedExceptionsHolder(IAnnotationFinder finder, ITestNGMethod method) {
+    this.finder = finder;
+    this.method = method;
+  }
+
+  /**
+   *   message / regEx  .*      other
+   *   null             true    false
+   *   non-null         true    match
+   */
+  @Override
+  public boolean isThrowableMatching(Throwable ite) {
+    String messageRegExp = getRegExp();
+
+    if (DEFAULT_REGEXP.equals(messageRegExp)) {
+      return true;
+    }
+
+    final String message = ite.getMessage();
+    return message != null && Pattern.compile(messageRegExp, Pattern.DOTALL).matcher(message).matches();
+  }
+
+  public String getWrongExceptionMessage(Throwable ite) {
+    return "The exception was thrown with the wrong message:" +
+           " expected \"" + getRegExp() + "\"" +
+           " but got \"" + ite.getMessage() + "\"";
+  }
+
+  private String getRegExp() {
+    IExpectedExceptionsAnnotation expectedExceptions =
+        finder.findAnnotation(method, IExpectedExceptionsAnnotation.class);
+    if (expectedExceptions != null) {
+      // Old syntax => default value
+      return DEFAULT_REGEXP;
+    }
+
+    // New syntax
+    ITestAnnotation testAnnotation = finder.findAnnotation(method, ITestAnnotation.class);
+    if (testAnnotation != null) {
+      return testAnnotation.getExpectedExceptionsMessageRegExp();
+    }
+
+    return DEFAULT_REGEXP;
+  }
+}
diff --git a/src/main/java/org/testng/internal/ResultMap.java b/src/main/java/org/testng/internal/ResultMap.java
new file mode 100755
index 0000000..ae37ee4
--- /dev/null
+++ b/src/main/java/org/testng/internal/ResultMap.java
@@ -0,0 +1,77 @@
+package org.testng.internal;
+
+import org.testng.IResultMap;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+import org.testng.collections.Objects;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ResultMap implements IResultMap {
+  /**
+   *
+   */
+  private static final long serialVersionUID = 80134376515999093L;
+  private Map<ITestResult, ITestNGMethod> m_map = new ConcurrentHashMap<>();
+
+  @Override
+  public void addResult(ITestResult result, ITestNGMethod method) {
+    m_map.put(result, method);
+  }
+
+  @Override
+  public Set<ITestResult> getResults(ITestNGMethod method) {
+    Set<ITestResult> result = new HashSet<>();
+
+    for (Map.Entry<ITestResult, ITestNGMethod> entry : m_map.entrySet()) {
+      if (entry.getValue().equals(method)) {
+        result.add(entry.getKey());
+      }
+    }
+
+    return result;
+  }
+
+  @Override
+  public void removeResult(ITestNGMethod m) {
+    for (Entry<ITestResult, ITestNGMethod> entry : m_map.entrySet()) {
+      if (entry.getValue().equals(m)) {
+        m_map.remove(entry.getKey());
+        return;
+      }
+    }
+  }
+
+  @Override
+  public void removeResult(ITestResult r) {
+    m_map.remove(r);
+  }
+
+  @Override
+  public Set<ITestResult> getAllResults() {
+    return m_map.keySet();
+  }
+
+  @Override
+  public int size() {
+    return m_map.size();
+  }
+
+  @Override
+  public Collection<ITestNGMethod> getAllMethods() {
+    return m_map.values();
+  }
+
+  @Override
+  public String toString() {
+    return Objects.toStringHelper(getClass())
+        .add("map", m_map)
+        .toString();
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/RunInfo.java b/src/main/java/org/testng/internal/RunInfo.java
new file mode 100755
index 0000000..4f65d21
--- /dev/null
+++ b/src/main/java/org/testng/internal/RunInfo.java
@@ -0,0 +1,73 @@
+package org.testng.internal;
+
+import org.testng.IMethodSelector;
+import org.testng.IMethodSelectorContext;
+import org.testng.ITestNGMethod;
+import org.testng.collections.Lists;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This class contains all the information needed to determine
+ * what methods should be run.  It gets invoked by the TestRunner
+ * and then goes through its list of method selectors to decide what methods
+ * need to be run.
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public class RunInfo implements Serializable {
+  private static final long serialVersionUID = -9085221672822562888L;
+  transient private List<MethodSelectorDescriptor>
+    m_methodSelectors = Lists.newArrayList();
+
+  public void addMethodSelector(IMethodSelector selector, int priority) {
+    Utils.log("RunInfo", 3, "Adding method selector: " + selector + " priority: " + priority);
+    MethodSelectorDescriptor md = new MethodSelectorDescriptor(selector, priority);
+    m_methodSelectors.add(md);
+  }
+
+  /**
+   * @return true as soon as we fond a Method Selector that returns
+   * true for the method "tm".
+   */
+  public boolean includeMethod(ITestNGMethod tm, boolean isTestMethod) {
+    Collections.sort(m_methodSelectors);
+    boolean foundNegative = false;
+    IMethodSelectorContext context = new DefaultMethodSelectorContext();
+
+    boolean result = false;
+    for (MethodSelectorDescriptor mds : m_methodSelectors) {
+      // If we found any negative priority, we break as soon as we encounter
+      // a selector with a positive priority
+      if (! foundNegative) {
+        foundNegative = mds.getPriority() < 0;
+      }
+      if (foundNegative && mds.getPriority() >= 0) {
+        break;
+      }
+
+      // Proceeed normally
+      IMethodSelector md = mds.getMethodSelector();
+      result = md.includeMethod(context, tm, isTestMethod);
+      if (context.isStopped()) {
+        return result;
+      }
+
+      // This selector returned false, move on to the next
+    }
+
+    return result;
+  }
+
+  public static void ppp(String s) {
+    System.out.println("[RunInfo] " + s);
+  }
+
+  public void setTestMethods(List<ITestNGMethod> testMethods) {
+    for (MethodSelectorDescriptor mds : m_methodSelectors) {
+      mds.setTestMethods(testMethods);
+    }
+  }
+}
diff --git a/src/main/java/org/testng/internal/SuiteRunnerMap.java b/src/main/java/org/testng/internal/SuiteRunnerMap.java
new file mode 100644
index 0000000..197833d
--- /dev/null
+++ b/src/main/java/org/testng/internal/SuiteRunnerMap.java
@@ -0,0 +1,30 @@
+package org.testng.internal;
+
+import org.testng.ISuite;
+import org.testng.TestNGException;
+import org.testng.collections.Maps;
+import org.testng.xml.XmlSuite;
+
+import java.util.Collection;
+import java.util.Map;
+
+public class SuiteRunnerMap {
+
+  private Map<String, ISuite> m_map = Maps.newHashMap();
+
+  public void put(XmlSuite xmlSuite, ISuite suite) {
+    final String name = xmlSuite.getName();
+    if (m_map.containsKey(name)) {
+      throw new TestNGException("SuiteRunnerMap already have runner for suite " + name);
+    }
+    m_map.put(name, suite);
+  }
+
+  public ISuite get(XmlSuite xmlSuite) {
+    return m_map.get(xmlSuite.getName());
+  }
+
+  public Collection<ISuite> values() {
+    return m_map.values();
+  }
+}
diff --git a/src/main/java/org/testng/internal/Tarjan.java b/src/main/java/org/testng/internal/Tarjan.java
new file mode 100755
index 0000000..07ba35d
--- /dev/null
+++ b/src/main/java/org/testng/internal/Tarjan.java
@@ -0,0 +1,81 @@
+package org.testng.internal;
+
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Stack;
+
+/**
+ * Implementation of the Tarjan algorithm to find and display a cycle in a graph.
+ * @author cbeust
+ */
+public class Tarjan<T> {
+  int m_index = 0;
+  private Stack<T> m_s;
+  Map<T, Integer> m_indices = Maps.newHashMap();
+  Map<T, Integer> m_lowlinks = Maps.newHashMap();
+  private List<T> m_cycle;
+
+  public Tarjan(Graph<T> graph, T start) {
+    m_s = new Stack<>();
+    run(graph, start);
+  }
+
+  private void run(Graph<T> graph, T v) {
+    m_indices.put(v, m_index);
+    m_lowlinks.put(v, m_index);
+    m_index++;
+    m_s.push(v);
+
+    for (T vprime : graph.getPredecessors(v)) {
+      if (! m_indices.containsKey(vprime)) {
+        run(graph, vprime);
+        int min = Math.min(m_lowlinks.get(v), m_lowlinks.get(vprime));
+        m_lowlinks.put(v, min);
+      }
+      else if (m_s.contains(vprime)) {
+        m_lowlinks.put(v, Math.min(m_lowlinks.get(v), m_indices.get(vprime)));
+      }
+    }
+
+    if (Objects.equals(m_lowlinks.get(v), m_indices.get(v))) {
+      m_cycle = Lists.newArrayList();
+      T n;
+      do {
+        n = m_s.pop();
+        m_cycle.add(n);
+      } while (! n.equals(v));
+    }
+
+  }
+
+  public static void main(String[] args) {
+    Graph<String> g = new Graph<>();
+    g.addNode("a");
+    g.addNode("b");
+    g.addNode("c");
+    g.addNode("d");
+
+    String[] edges = new String[] {
+        "a", "b",
+        "b", "a",
+        "c", "d",
+        "d", "a",
+        "a", "c",
+    };
+
+    for (int i = 0; i < edges.length; i += 2) {
+      g.addPredecessor(edges[i], edges[i+1]);
+    }
+
+    new Tarjan<>(g, "a");
+  }
+
+  public List<T> getCycle() {
+    return m_cycle;
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/TestMethodWithDataProviderMethodWorker.java b/src/main/java/org/testng/internal/TestMethodWithDataProviderMethodWorker.java
new file mode 100755
index 0000000..96be14a
--- /dev/null
+++ b/src/main/java/org/testng/internal/TestMethodWithDataProviderMethodWorker.java
@@ -0,0 +1,149 @@
+package org.testng.internal;
+
+import org.testng.ITestClass;
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+import org.testng.collections.Lists;
+import org.testng.xml.XmlSuite;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+public class TestMethodWithDataProviderMethodWorker implements Callable<List<ITestResult>> {
+
+  private ITestNGMethod m_testMethod;
+  private Object[] m_parameterValues;
+  private Object m_instance;
+  private XmlSuite m_xmlSuite;
+  private Map<String, String> m_parameters;
+  private ITestClass m_testClass;
+  private ITestNGMethod[] m_beforeMethods;
+  private ITestNGMethod[] m_afterMethods;
+  private ConfigurationGroupMethods m_groupMethods;
+  private Invoker m_invoker;
+  private ExpectedExceptionsHolder m_expectedExceptionHolder;
+  private ITestContext m_testContext;
+  private int m_parameterIndex;
+  private boolean m_skipFailedInvocationCounts;
+  private int m_invocationCount;
+  private ITestResultNotifier m_notifier;
+
+  private List<ITestResult> m_testResults = Lists.newArrayList();
+  private int m_failureCount;
+
+  public TestMethodWithDataProviderMethodWorker(Invoker invoker, ITestNGMethod testMethod,
+      int parameterIndex,
+      Object[] parameterValues, Object instance, XmlSuite suite,
+      Map<String, String> parameters, ITestClass testClass,
+      ITestNGMethod[] beforeMethods, ITestNGMethod[] afterMethods,
+      ConfigurationGroupMethods groupMethods, ExpectedExceptionsHolder expectedExceptionHolder,
+      ITestContext testContext, boolean skipFailedInvocationCounts,
+      int invocationCount, int failureCount, ITestResultNotifier notifier) {
+    m_invoker = invoker;
+    m_testMethod = testMethod;
+    m_parameterIndex = parameterIndex;
+    m_parameterValues = parameterValues;
+    m_instance = instance;
+    m_xmlSuite = suite;
+    m_parameters = parameters;
+    m_testClass = testClass;
+    m_beforeMethods = beforeMethods;
+    m_afterMethods = afterMethods;
+    m_groupMethods = groupMethods;
+    m_expectedExceptionHolder = expectedExceptionHolder;
+    m_skipFailedInvocationCounts = skipFailedInvocationCounts;
+    m_testContext = testContext;
+    m_invocationCount = invocationCount;
+    m_failureCount = failureCount;
+    m_notifier = notifier;
+  }
+
+  public long getMaxTimeOut() {
+    return 500;
+  }
+
+  @Override
+  public List<ITestResult> call() {
+    List<ITestResult> tmpResults = Lists.newArrayList();
+    long start = System.currentTimeMillis();
+
+    final Invoker.FailureContext failure = new Invoker.FailureContext();
+    failure.count = m_failureCount;
+    try {
+      tmpResults.add(m_invoker.invokeTestMethod(m_instance,
+          m_testMethod,
+          m_parameterValues,
+          m_parameterIndex,
+          m_xmlSuite,
+          m_parameters,
+          m_testClass,
+          m_beforeMethods,
+          m_afterMethods,
+          m_groupMethods,
+          failure));
+    }
+    finally {
+      m_failureCount = failure.count;
+      if (failure.instances.isEmpty()) {
+        m_testResults.addAll(tmpResults);
+      } else {
+        for (Object instance : failure.instances) {
+          List<ITestResult> retryResults = Lists.newArrayList();
+
+          m_failureCount =
+             m_invoker.retryFailed(
+                 instance, m_testMethod, m_xmlSuite, m_testClass, m_beforeMethods,
+                 m_afterMethods, m_groupMethods, retryResults,
+                 m_failureCount, m_expectedExceptionHolder,
+                 m_testContext, m_parameters, m_parameterIndex);
+          m_testResults.addAll(retryResults);
+        }
+      }
+
+      //
+      // If we have a failure, skip all the
+      // other invocationCounts
+      //
+
+      // If not specified globally, use the attribute
+      // on the annotation
+      //
+      if (! m_skipFailedInvocationCounts) {
+        m_skipFailedInvocationCounts = m_testMethod.skipFailedInvocations();
+      }
+      if (m_failureCount > 0 && m_skipFailedInvocationCounts) {
+        while (m_invocationCount-- > 0) {
+          ITestResult r =
+            new TestResult(m_testMethod.getTestClass(),
+              m_instance,
+              m_testMethod,
+              null,
+              start,
+              System.currentTimeMillis(),
+              m_testContext);
+          r.setStatus(TestResult.SKIP);
+          m_testResults.add(r);
+          m_invoker.runTestListeners(r);
+          m_notifier.addSkippedTest(m_testMethod, r);
+        }
+      }
+    }
+    m_parameterIndex++;
+
+    return m_testResults;
+  }
+
+  public List<ITestResult> getTestResults() {
+    return m_testResults;
+  }
+
+  public int getInvocationCount() {
+    return m_invocationCount;
+  }
+
+  public int getFailureCount() {
+    return m_failureCount;
+  }
+}
diff --git a/src/main/java/org/testng/internal/TestMethodWorker.java b/src/main/java/org/testng/internal/TestMethodWorker.java
new file mode 100644
index 0000000..a263419
--- /dev/null
+++ b/src/main/java/org/testng/internal/TestMethodWorker.java
@@ -0,0 +1,310 @@
+package org.testng.internal;
+
+import org.testng.ClassMethodMap;
+import org.testng.IClassListener;
+import org.testng.IMethodInstance;
+import org.testng.ITestClass;
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+import org.testng.collections.Lists;
+import org.testng.internal.thread.ThreadUtil;
+import org.testng.internal.thread.graph.IWorker;
+import org.testng.xml.XmlSuite;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * FIXME: reduce contention when this class is used through parallel invocation due to
+ * invocationCount and threadPoolSize by not invoking the @BeforeClass and @AfterClass
+ * which are already invoked on the original method.
+ *
+ * This class implements Runnable and will invoke the ITestMethod passed in its
+ * constructor on its run() method.
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ */
+public class TestMethodWorker implements IWorker<ITestNGMethod> {
+  // Map of the test methods and their associated instances
+  // It has to be a set because the same method can be passed several times
+  // and associated to a different instance
+  private IMethodInstance[] m_methodInstances;
+  private final IInvoker m_invoker;
+  private final Map<String, String> m_parameters;
+  private final XmlSuite m_suite;
+  private List<ITestResult> m_testResults = Lists.newArrayList();
+  private final ConfigurationGroupMethods m_groupMethods;
+  private final ClassMethodMap m_classMethodMap;
+  private final ITestContext m_testContext;
+  private final List<IClassListener> m_listeners;
+
+  public TestMethodWorker(IInvoker invoker,
+                          IMethodInstance[] testMethods,
+                          XmlSuite suite,
+                          Map<String, String> parameters,
+                          ConfigurationGroupMethods groupMethods,
+                          ClassMethodMap classMethodMap,
+                          ITestContext testContext,
+                          List<IClassListener> listeners)
+  {
+    m_invoker = invoker;
+    m_methodInstances = testMethods;
+    m_suite = suite;
+    m_parameters = parameters;
+    m_groupMethods = groupMethods;
+    m_classMethodMap = classMethodMap;
+    m_testContext = testContext;
+    m_listeners = listeners;
+  }
+
+  /**
+   * Retrieves the maximum specified timeout of all ITestNGMethods to
+   * be run.
+   *
+   * @return the max timeout or 0 if no timeout was specified
+   */
+  @Override
+  public long getTimeOut() {
+    long result = 0;
+    for (IMethodInstance mi : m_methodInstances) {
+      ITestNGMethod tm = mi.getMethod();
+      if (tm.getTimeOut() > result) {
+        result = tm.getTimeOut();
+      }
+    }
+
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder result = new StringBuilder("[Worker thread:" + Thread.currentThread().getId()
+        + " priority:" + getPriority() + " ");
+
+    for (IMethodInstance m : m_methodInstances) {
+      result.append(m.getMethod()).append(" ");
+    }
+    result.append("]");
+
+    return result.toString();
+  }
+
+  /**
+   * Run all the ITestNGMethods passed in through the constructor.
+   *
+   * @see java.lang.Runnable#run()
+   */
+  @Override
+  public void run() {
+    for (IMethodInstance testMthdInst : m_methodInstances) {
+      ITestNGMethod testMethod = testMthdInst.getMethod();
+      ITestClass testClass = testMethod.getTestClass();
+
+      invokeBeforeClassMethods(testClass, testMthdInst);
+
+      // Invoke test method
+      try {
+        invokeTestMethods(testMethod, testMthdInst.getInstance(), m_testContext);
+      }
+      finally {
+        invokeAfterClassMethods(testClass, testMthdInst);
+      }
+    }
+  }
+
+  protected void invokeTestMethods(ITestNGMethod tm, Object instance,
+      ITestContext testContext)
+  {
+    // Potential bug here:  we look up the method index of tm among all
+    // the test methods (not very efficient) but if this method appears
+    // several times and these methods are run in parallel, the results
+    // are unpredictable...  Need to think about this more (and make it
+    // more efficient)
+    List<ITestResult> testResults =
+        m_invoker.invokeTestMethods(tm,
+            m_suite,
+            m_parameters,
+            m_groupMethods,
+            instance,
+            testContext);
+
+    if (testResults != null) {
+      m_testResults.addAll(testResults);
+    }
+  }
+
+  /**
+   * Invoke the @BeforeClass methods if not done already
+   * @param testClass
+   * @param mi
+   */
+  protected void invokeBeforeClassMethods(ITestClass testClass, IMethodInstance mi) {
+    for (IClassListener listener : m_listeners) {
+      listener.onBeforeClass(testClass, mi);
+    }
+
+    // if no BeforeClass than return immediately
+    // used for parallel case when BeforeClass were already invoked
+    if( (null == m_classMethodMap) || (null == m_classMethodMap.getInvokedBeforeClassMethods())) {
+      return;
+    }
+    ITestNGMethod[] classMethods= testClass.getBeforeClassMethods();
+    if(null == classMethods || classMethods.length == 0) {
+      return;
+    }
+
+    // the whole invocation must be synchronized as other threads must
+    // get a full initialized test object (not the same for @After)
+    Map<ITestClass, Set<Object>> invokedBeforeClassMethods =
+        m_classMethodMap.getInvokedBeforeClassMethods();
+//    System.out.println("SYNCHRONIZING ON " + testClass
+//        + " thread:" + Thread.currentThread().getId()
+//        + " invokedMap:" + invokedBeforeClassMethods.hashCode() + " "
+//        + invokedBeforeClassMethods);
+    synchronized(testClass) {
+      Set<Object> instances= invokedBeforeClassMethods.get(testClass);
+      if(null == instances) {
+        instances= new HashSet<>();
+        invokedBeforeClassMethods.put(testClass, instances);
+      }
+      for(Object instance: mi.getInstances()) {
+        if (! instances.contains(instance)) {
+          instances.add(instance);
+          m_invoker.invokeConfigurations(testClass,
+                                         testClass.getBeforeClassMethods(),
+                                         m_suite,
+                                         m_parameters,
+                                         null, /* no parameter values */
+                                         instance);
+        }
+      }
+    }
+  }
+
+  /**
+   * Invoke the @AfterClass methods if not done already
+   * @param testClass
+   * @param mi
+   */
+  protected void invokeAfterClassMethods(ITestClass testClass, IMethodInstance mi) {
+    for (IClassListener listener : m_listeners) {
+      listener.onAfterClass(testClass, mi);
+    }
+
+    // if no BeforeClass than return immediately
+    // used for parallel case when BeforeClass were already invoked
+    if( (null == m_classMethodMap) || (null == m_classMethodMap.getInvokedAfterClassMethods()) ) {
+      return;
+    }
+    ITestNGMethod[] afterClassMethods= testClass.getAfterClassMethods();
+
+    if(null == afterClassMethods || afterClassMethods.length == 0) {
+      return;
+    }
+
+    //
+    // Invoke after class methods if this test method is the last one
+    //
+    List<Object> invokeInstances= Lists.newArrayList();
+    ITestNGMethod tm= mi.getMethod();
+    if (m_classMethodMap.removeAndCheckIfLast(tm, mi.getInstance())) {
+      Map<ITestClass, Set<Object>> invokedAfterClassMethods
+          = m_classMethodMap.getInvokedAfterClassMethods();
+      synchronized(invokedAfterClassMethods) {
+        Set<Object> instances = invokedAfterClassMethods.get(testClass);
+        if(null == instances) {
+          instances= new HashSet<>();
+          invokedAfterClassMethods.put(testClass, instances);
+        }
+        for(Object inst: mi.getInstances()) {
+          if(! instances.contains(inst)) {
+            invokeInstances.add(inst);
+          }
+        }
+      }
+
+      for(Object inst: invokeInstances) {
+        m_invoker.invokeConfigurations(testClass,
+                                       afterClassMethods,
+                                       m_suite,
+                                       m_parameters,
+                                       null, /* no parameter values */
+                                       inst);
+      }
+    }
+  }
+
+  protected int indexOf(ITestNGMethod tm, ITestNGMethod[] allTestMethods) {
+    for (int i = 0; i < allTestMethods.length; i++) {
+      if (allTestMethods[i] == tm) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+  public List<ITestResult> getTestResults() {
+    return m_testResults;
+  }
+
+  private void ppp(String s) {
+    Utils.log("TestMethodWorker", 2, ThreadUtil.currentThreadInfo() + ":" + s);
+  }
+
+  @Override
+  public List<ITestNGMethod> getTasks()
+  {
+    List<ITestNGMethod> result = Lists.newArrayList();
+    for (IMethodInstance m : m_methodInstances) {
+      result.add(m.getMethod());
+    }
+    return result;
+  }
+
+  @Override
+  public int compareTo(IWorker<ITestNGMethod> other) {
+    return getPriority() - other.getPriority();
+  }
+
+  /**
+   * The priority of a worker is the priority of the first method it's going to run.
+   */
+  @Override
+  public int getPriority() {
+    return m_methodInstances.length > 0
+        ? m_methodInstances[0].getMethod().getPriority()
+        : 0;
+  }
+}
+
+/**
+ * Extends {@code TestMethodWorker} and is used to work on only a single method
+ * instance
+ */
+class SingleTestMethodWorker extends TestMethodWorker {
+  private static final ConfigurationGroupMethods EMPTY_GROUP_METHODS =
+    new ConfigurationGroupMethods(new ITestNGMethod[0],
+        new HashMap<String, List<ITestNGMethod>>(), new HashMap<String, List<ITestNGMethod>>());
+
+  public SingleTestMethodWorker(IInvoker invoker,
+                                MethodInstance testMethod,
+                                XmlSuite suite,
+                                Map<String, String> parameters,
+                                ITestContext testContext,
+                                List<IClassListener> listeners)
+  {
+    super(invoker,
+          new MethodInstance[] {testMethod},
+          suite,
+          parameters,
+          EMPTY_GROUP_METHODS,
+          null,
+          testContext,
+          listeners);
+  }
+}
diff --git a/src/main/java/org/testng/internal/TestNGClassFinder.java b/src/main/java/org/testng/internal/TestNGClassFinder.java
new file mode 100644
index 0000000..f9a0b5c
--- /dev/null
+++ b/src/main/java/org/testng/internal/TestNGClassFinder.java
@@ -0,0 +1,280 @@
+package org.testng.internal;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.testng.IClass;
+import org.testng.IInstanceInfo;
+import org.testng.ITestContext;
+import org.testng.ITestObjectFactory;
+import org.testng.TestNGException;
+import org.testng.annotations.IAnnotation;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.internal.annotations.AnnotationHelper;
+import org.testng.internal.annotations.IAnnotationFinder;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlTest;
+
+import static org.testng.internal.ClassHelper.getAvailableMethods;
+
+/**
+ * This class creates an ITestClass from a test class.
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public class TestNGClassFinder extends BaseClassFinder {
+  private ITestContext m_testContext = null;
+  private Map<Class, List<Object>> m_instanceMap = Maps.newHashMap();
+
+  public TestNGClassFinder(ClassInfoMap cim,
+                           Map<Class, List<Object>> instanceMap,
+                           XmlTest xmlTest,
+                           IConfiguration configuration,
+                           ITestContext testContext)
+  {
+    m_testContext = testContext;
+
+    if(null == instanceMap) {
+      instanceMap= Maps.newHashMap();
+    }
+
+    IAnnotationFinder annotationFinder = configuration.getAnnotationFinder();
+    ITestObjectFactory objectFactory = configuration.getObjectFactory();
+
+    //
+    // Find all the new classes and their corresponding instances
+    //
+    Set<Class<?>> allClasses= cim.getClasses();
+
+    //very first pass is to find ObjectFactory, can't create anything else until then
+    if(objectFactory == null) {
+      objectFactory = new ObjectFactoryImpl();
+      outer:
+      for (Class cls : allClasses) {
+        try {
+          if (null != cls) {
+            Method[] ms;
+            try {
+              ms = cls.getMethods();
+            } catch (NoClassDefFoundError e) {
+              // https://github.com/cbeust/testng/issues/602
+              ppp("Warning: Can't link and determine methods of " + cls);
+              ms = new Method[0];
+            }
+            for (Method m : ms) {
+              IAnnotation a = annotationFinder.findAnnotation(m,
+                  org.testng.annotations.IObjectFactoryAnnotation.class);
+              if (null != a) {
+                if (!ITestObjectFactory.class.isAssignableFrom(m.getReturnType())) {
+                  throw new TestNGException("Return type of " + m + " is not IObjectFactory");
+                }
+                try {
+                  Object instance = cls.newInstance();
+                  if (m.getParameterTypes().length > 0 && m.getParameterTypes()[0].equals(ITestContext.class)) {
+                    objectFactory = (ITestObjectFactory) m.invoke(instance, testContext);
+                  } else {
+                    objectFactory = (ITestObjectFactory) m.invoke(instance);
+                  }
+                  break outer;
+                }
+                catch (Exception ex) {
+                  throw new TestNGException("Error creating object factory: " + cls,
+                      ex);
+                }
+              }
+            }
+          }
+        } catch (NoClassDefFoundError e) {
+          Utils.log("[TestNGClassFinder]", 1, "Unable to read methods on class " + cls.getName()
+              + " - unable to resolve class reference " + e.getMessage());
+
+          for (XmlClass xmlClass : xmlTest.getXmlClasses()) {
+            if (xmlClass.loadClasses() && xmlClass.getName().equals(cls.getName())) {
+              throw e;
+            }
+          }
+
+        }
+      }
+    }
+
+    for(Class cls : allClasses) {
+      if((null == cls)) {
+        ppp("FOUND NULL CLASS IN FOLLOWING ARRAY:");
+        int i= 0;
+        for(Class c : allClasses) {
+          ppp("  " + i + ": " + c);
+        }
+
+        continue;
+      }
+
+      if(isTestNGClass(cls, annotationFinder)) {
+        List allInstances= instanceMap.get(cls);
+        Object thisInstance= (null != allInstances) ? allInstances.get(0) : null;
+
+        // If annotation class and instances are abstract, skip them
+        if ((null == thisInstance) && Modifier.isAbstract(cls.getModifiers())) {
+          Utils.log("", 5, "[WARN] Found an abstract class with no valid instance attached: " + cls);
+          continue;
+        }
+
+        IClass ic= findOrCreateIClass(m_testContext, cls, cim.getXmlClass(cls), thisInstance,
+            xmlTest, annotationFinder, objectFactory);
+        if(null != ic) {
+          Object[] theseInstances = ic.getInstances(false);
+          if (theseInstances.length == 0) {
+            theseInstances = ic.getInstances(true);
+          }
+          Object instance= theseInstances[0];
+          putIClass(cls, ic);
+
+          ConstructorOrMethod factoryMethod =
+            ClassHelper.findDeclaredFactoryMethod(cls, annotationFinder);
+          if (factoryMethod != null && factoryMethod.getEnabled()) {
+            FactoryMethod fm = new FactoryMethod( /* cls, */
+              factoryMethod,
+              instance,
+              xmlTest,
+              annotationFinder,
+              m_testContext);
+            ClassInfoMap moreClasses = new ClassInfoMap();
+
+            {
+//            ppp("INVOKING FACTORY " + fm + " " + this.hashCode());
+              Object[] instances= fm.invoke();
+
+              //
+              // If the factory returned IInstanceInfo, get the class from it,
+              // otherwise, just call getClass() on the returned instances
+              //
+              if (instances.length > 0) {
+                if (instances[0] != null) {
+                  Class elementClass = instances[0].getClass();
+                  if(IInstanceInfo.class.isAssignableFrom(elementClass)) {
+                    for(Object o : instances) {
+                      IInstanceInfo ii = (IInstanceInfo) o;
+                      addInstance(ii.getInstanceClass(), ii.getInstance());
+                      moreClasses.addClass(ii.getInstanceClass());
+                    }
+                  }
+                  else {
+                    for (int i = 0; i < instances.length; i++) {
+                      Object o = instances[i];
+                      if (o == null) {
+                        throw new TestNGException("The factory " + fm + " returned a null instance" +
+                            "at index " + i);
+                      } else {
+                        addInstance(o.getClass(), o);
+                        if(!classExists(o.getClass())) {
+                          moreClasses.addClass(o.getClass());
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+
+            if(moreClasses.getSize() > 0) {
+              TestNGClassFinder finder=
+                new TestNGClassFinder(moreClasses,
+                    m_instanceMap,
+                    xmlTest,
+                    configuration,
+                    m_testContext);
+
+              IClass[] moreIClasses= finder.findTestClasses();
+              for(IClass ic2 : moreIClasses) {
+                putIClass(ic2.getRealClass(), ic2);
+              }
+            } // if moreClasses.size() > 0
+          }
+        } // null != ic
+      } // if not TestNG class
+      else {
+        Utils.log("TestNGClassFinder", 3, "SKIPPING CLASS " + cls + " no TestNG annotations found");
+      }
+    } // for
+
+    //
+    // Add all the instances we found to their respective IClasses
+    //
+    for(Map.Entry<Class, List<Object>> entry : m_instanceMap.entrySet()) {
+      Class clazz = entry.getKey();
+      for(Object instance : entry.getValue()) {
+        IClass ic= getIClass(clazz);
+        if(null != ic) {
+          ic.addInstance(instance);
+        }
+      }
+    }
+  }
+
+  /**
+   * @return true if this class contains TestNG annotations (either on itself
+   * or on a superclass).
+   */
+  public static boolean isTestNGClass(Class<?> c, IAnnotationFinder annotationFinder) {
+    Class[] allAnnotations= AnnotationHelper.getAllAnnotations();
+    Class<?> cls = c;
+
+    try {
+      for(Class annotation : allAnnotations) {
+
+        for (cls = c; cls != null; cls = cls.getSuperclass()) {
+          // Try on the methods
+          for (Method m : getAvailableMethods(cls)) {
+            IAnnotation ma= annotationFinder.findAnnotation(m, annotation);
+            if(null != ma) {
+              return true;
+            }
+          }
+
+          // Try on the class
+          IAnnotation a= annotationFinder.findAnnotation(cls, annotation);
+          if(null != a) {
+            return true;
+          }
+
+          // Try on the constructors
+          for (Constructor ctor : cls.getConstructors()) {
+            IAnnotation ca= annotationFinder.findAnnotation(ctor, annotation);
+            if(null != ca) {
+              return true;
+            }
+          }
+        }
+      }
+
+      return false;
+
+    } catch (NoClassDefFoundError e) {
+      Utils.log("[TestNGClassFinder]", 1,
+          "Unable to read methods on class " + cls.getName()
+          + " - unable to resolve class reference " + e.getMessage());
+      return false;
+    }
+  }
+
+  private void addInstance(Class clazz, Object o) {
+    List<Object> list= m_instanceMap.get(clazz);
+
+    if(null == list) {
+      list= Lists.newArrayList();
+      m_instanceMap.put(clazz, list);
+    }
+
+    list.add(o);
+  }
+
+  public static void ppp(String s) {
+    System.out.println("[TestNGClassFinder] " + s);
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/TestNGMethod.java b/src/main/java/org/testng/internal/TestNGMethod.java
new file mode 100755
index 0000000..7e86ef9
--- /dev/null
+++ b/src/main/java/org/testng/internal/TestNGMethod.java
@@ -0,0 +1,229 @@
+package org.testng.internal;

+

+import org.testng.ITestClass;

+import org.testng.ITestNGMethod;

+import org.testng.annotations.ITestAnnotation;

+import org.testng.internal.annotations.AnnotationHelper;

+import org.testng.internal.annotations.IAnnotationFinder;

+import org.testng.xml.XmlClass;

+import org.testng.xml.XmlInclude;

+import org.testng.xml.XmlTest;

+

+import java.io.Serializable;

+import java.lang.reflect.Method;

+import java.util.Collections;

+import java.util.Comparator;

+import java.util.List;

+

+

+/**

+ * This class represents a test method.

+ *

+ * @author Cedric Beust, May 3, 2004

+ * @author <a href = "mailto:the_mindstorm&#64;evolva.ro">Alexandru Popescu</a>

+ */

+public class TestNGMethod extends BaseTestMethod implements Serializable {

+  /**

+   *

+   */

+  private static final long serialVersionUID = -1742868891986775307L;

+  private int m_threadPoolSize = 0;

+  private int m_invocationCount = 1;

+  private int m_totalInvocationCount = m_invocationCount;

+  private int m_successPercentage = 100;

+

+  /**

+   * Constructs a <code>TestNGMethod</code>

+   *

+   * @param method

+   * @param finder

+   */

+  public TestNGMethod(Method method, IAnnotationFinder finder, XmlTest xmlTest, Object instance) {

+    this(method, finder, true, xmlTest, instance);

+  }

+

+  private TestNGMethod(Method method, IAnnotationFinder finder, boolean initialize,

+      XmlTest xmlTest, Object instance) {

+    super(method.getName(), method, finder, instance);

+

+    if(initialize) {

+      init(xmlTest);

+    }

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public int getInvocationCount() {

+    return m_invocationCount;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public int getTotalInvocationCount() {

+    return m_totalInvocationCount;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public int getSuccessPercentage() {

+    return m_successPercentage;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public boolean isTest() {

+    return true;

+  }

+

+  private void ppp(String s) {

+    System.out.println("[TestNGMethod] " + s);

+  }

+

+  private void init(XmlTest xmlTest) {

+    setXmlTest(xmlTest);

+    setInvocationNumbers(xmlTest.getInvocationNumbers(

+        m_method.getDeclaringClass().getName() + "." + m_method.getName()));

+    {

+      ITestAnnotation testAnnotation =

+          AnnotationHelper.findTest(getAnnotationFinder(), m_method.getMethod());

+

+      if (testAnnotation == null) {

+        // Try on the class

+        testAnnotation = AnnotationHelper.findTest(getAnnotationFinder(), m_method.getDeclaringClass());

+      }

+

+      if (null != testAnnotation) {

+        setTimeOut(testAnnotation.getTimeOut());

+        m_successPercentage = testAnnotation.getSuccessPercentage();

+

+        setInvocationCount(testAnnotation.getInvocationCount());

+        m_totalInvocationCount = testAnnotation.getInvocationCount();

+        setThreadPoolSize(testAnnotation.getThreadPoolSize());

+        setAlwaysRun(testAnnotation.getAlwaysRun());

+        setDescription(findDescription(testAnnotation, xmlTest));

+        setEnabled(testAnnotation.getEnabled());

+        setRetryAnalyzer(testAnnotation.getRetryAnalyzer());

+        setSkipFailedInvocations(testAnnotation.skipFailedInvocations());

+        setInvocationTimeOut(testAnnotation.invocationTimeOut());

+        setIgnoreMissingDependencies(testAnnotation.ignoreMissingDependencies());

+        setPriority(testAnnotation.getPriority());

+      }

+

+      // Groups

+      {

+        initGroups(ITestAnnotation.class);

+      }

+    }

+  }

+

+  private String findDescription(ITestAnnotation testAnnotation, XmlTest xmlTest) {

+    String result = testAnnotation.getDescription();

+    if (result == null) {

+      List<XmlClass> classes = xmlTest.getXmlClasses();

+      for (XmlClass c : classes) {

+        if (c.getName().equals(m_method.getMethod().getDeclaringClass().getName())) {

+          for (XmlInclude include : c.getIncludedMethods()) {

+            if (include.getName().equals(m_method.getName())) {

+              result = include.getDescription();

+              if (result != null) {

+                break;

+              }

+            }

+          }

+        }

+      }

+    }

+    return result;

+  }

+

+  /**

+   * {@inheritDoc}

+   */

+  @Override

+  public int getThreadPoolSize() {

+    return m_threadPoolSize;

+  }

+

+  /**

+   * Sets the number of threads on which this method should be invoked.

+   */

+  @Override

+  public void setThreadPoolSize(int threadPoolSize) {

+    m_threadPoolSize = threadPoolSize;

+  }

+

+  /**

+   * Sets the number of invocations for this method.

+   */

+  @Override

+  public void setInvocationCount(int counter) {

+    m_invocationCount= counter;

+  }

+

+  /**

+   * Clones the current <code>TestNGMethod</code> and its @BeforeMethod and @AfterMethod methods.

+   * @see org.testng.internal.BaseTestMethod#clone()

+   */

+  @Override

+  public BaseTestMethod clone() {

+    TestNGMethod clone= new TestNGMethod(getMethod(), getAnnotationFinder(), false, getXmlTest(),

+        getInstance());

+    ITestClass tc= getTestClass();

+    NoOpTestClass testClass= new NoOpTestClass(tc);

+    testClass.setBeforeTestMethods(clone(tc.getBeforeTestMethods()));

+    testClass.setAfterTestMethod(clone(tc.getAfterTestMethods()));

+    clone.m_testClass= testClass;

+    clone.setDate(getDate());

+    clone.setGroups(getGroups());

+    clone.setGroupsDependedUpon(getGroupsDependedUpon(), Collections.<String>emptyList());

+    clone.setMethodsDependedUpon(getMethodsDependedUpon());

+    clone.setAlwaysRun(isAlwaysRun());

+    clone.m_beforeGroups= getBeforeGroups();

+    clone.m_afterGroups= getAfterGroups();

+    clone.m_currentInvocationCount= m_currentInvocationCount;

+    clone.setMissingGroup(getMissingGroup());

+    clone.setThreadPoolSize(getThreadPoolSize());

+    clone.setDescription(getDescription());

+    clone.setEnabled(getEnabled());

+    clone.setParameterInvocationCount(getParameterInvocationCount());

+    clone.setInvocationCount(getInvocationCount());

+    clone.m_totalInvocationCount = getTotalInvocationCount();

+    clone.m_successPercentage = getSuccessPercentage();

+    clone.setTimeOut(getTimeOut());

+    clone.setRetryAnalyzer(getRetryAnalyzer());

+    clone.setSkipFailedInvocations(skipFailedInvocations());

+    clone.setInvocationNumbers(getInvocationNumbers());

+    clone.setPriority(getPriority());

+

+    return clone;

+  }

+

+  private ITestNGMethod[] clone(ITestNGMethod[] sources) {

+    ITestNGMethod[] clones= new ITestNGMethod[sources.length];

+    for(int i= 0; i < sources.length; i++) {

+      clones[i]= sources[i].clone();

+    }

+

+    return clones;

+  }

+

+  /** Sorts ITestNGMethod by Class name. */

+  public static final Comparator<ITestNGMethod> SORT_BY_CLASS =

+    new Comparator<ITestNGMethod>() {

+

+    @Override

+    public int compare(ITestNGMethod o1, ITestNGMethod o2) {

+      String c1 = o1.getTestClass().getName();

+      String c2 = o2.getTestClass().getName();

+      return c1.compareTo(c2);

+    }

+  };

+}

diff --git a/src/main/java/org/testng/internal/TestNGMethodFinder.java b/src/main/java/org/testng/internal/TestNGMethodFinder.java
new file mode 100755
index 0000000..50f3b16
--- /dev/null
+++ b/src/main/java/org/testng/internal/TestNGMethodFinder.java
@@ -0,0 +1,233 @@
+package org.testng.internal;
+
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+
+import org.testng.ITestMethodFinder;
+import org.testng.ITestNGMethod;
+import org.testng.annotations.IConfigurationAnnotation;
+import org.testng.annotations.ITestAnnotation;
+import org.testng.collections.Lists;
+import org.testng.internal.annotations.AnnotationHelper;
+import org.testng.internal.annotations.IAnnotationFinder;
+import org.testng.xml.XmlTest;
+
+/**
+ * The default strategy for finding test methods:  look up
+ * annotations @Test in front of methods.
+ *
+ * @author Cedric Beust, May 3, 2004
+ * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
+ */
+public class TestNGMethodFinder implements ITestMethodFinder {
+  private static final int BEFORE_SUITE = 1;
+  private static final int AFTER_SUITE = 2;
+  private static final int BEFORE_TEST = 3;
+  private static final int AFTER_TEST = 4;
+  private static final int BEFORE_CLASS = 5;
+  private static final int AFTER_CLASS = 6;
+  private static final int BEFORE_TEST_METHOD = 7;
+  private static final int AFTER_TEST_METHOD = 8;
+  private static final int BEFORE_GROUPS = 9;
+  private static final int AFTER_GROUPS = 10;
+
+  private RunInfo m_runInfo = null;
+  private IAnnotationFinder m_annotationFinder = null;
+
+  public TestNGMethodFinder(RunInfo runInfo, IAnnotationFinder annotationFinder)
+  {
+    m_runInfo = runInfo;
+    m_annotationFinder = annotationFinder;
+  }
+
+  @Override
+  public ITestNGMethod[] getTestMethods(Class<?> clazz, XmlTest xmlTest) {
+    return AnnotationHelper.findMethodsWithAnnotation(
+        clazz, ITestAnnotation.class, m_annotationFinder, xmlTest);
+  }
+
+  @Override
+  public ITestNGMethod[] getBeforeClassMethods(Class cls) {
+    return findConfiguration(cls, BEFORE_CLASS);
+  }
+
+  @Override
+  public ITestNGMethod[] getAfterClassMethods(Class cls) {
+    return findConfiguration(cls, AFTER_CLASS);
+  }
+
+  @Override
+  public ITestNGMethod[] getBeforeTestMethods(Class cls) {
+    return findConfiguration(cls, BEFORE_TEST_METHOD);
+  }
+
+  @Override
+  public ITestNGMethod[] getAfterTestMethods(Class cls) {
+    return findConfiguration(cls, AFTER_TEST_METHOD);
+  }
+
+  @Override
+  public ITestNGMethod[] getBeforeSuiteMethods(Class cls) {
+    return findConfiguration(cls, BEFORE_SUITE);
+  }
+
+  @Override
+  public ITestNGMethod[] getAfterSuiteMethods(Class cls) {
+    return findConfiguration(cls, AFTER_SUITE);
+  }
+
+  @Override
+  public ITestNGMethod[] getBeforeTestConfigurationMethods(Class clazz) {
+    return findConfiguration(clazz, BEFORE_TEST);
+  }
+
+  @Override
+  public ITestNGMethod[] getAfterTestConfigurationMethods(Class clazz) {
+    return findConfiguration(clazz, AFTER_TEST);
+  }
+
+  @Override
+  public ITestNGMethod[] getBeforeGroupsConfigurationMethods(Class clazz) {
+    return findConfiguration(clazz, BEFORE_GROUPS);
+  }
+
+  @Override
+  public ITestNGMethod[] getAfterGroupsConfigurationMethods(Class clazz) {
+    return findConfiguration(clazz, AFTER_GROUPS);
+  }
+
+  private ITestNGMethod[] findConfiguration(final Class clazz, final int configurationType) {
+    List<ITestNGMethod> vResult = Lists.newArrayList();
+
+    Set<Method> methods = ClassHelper.getAvailableMethods(clazz);
+
+    for (Method m : methods) {
+      IConfigurationAnnotation configuration = AnnotationHelper.findConfiguration(m_annotationFinder, m);
+
+      if (null == configuration) {
+        continue;
+      }
+
+      boolean create = false;
+      boolean isBeforeSuite = false;
+      boolean isAfterSuite = false;
+      boolean isBeforeTest = false;
+      boolean isAfterTest = false;
+      boolean isBeforeClass = false;
+      boolean isAfterClass = false;
+      boolean isBeforeTestMethod = false;
+      boolean isAfterTestMethod = false;
+      String[] beforeGroups = null;
+      String[] afterGroups = null;
+
+      switch(configurationType) {
+        case BEFORE_SUITE:
+          create = configuration.getBeforeSuite();
+          isBeforeSuite = true;
+          break;
+        case AFTER_SUITE:
+          create = configuration.getAfterSuite();
+          isAfterSuite = true;
+          break;
+        case BEFORE_TEST:
+          create = configuration.getBeforeTest();
+          isBeforeTest = true;
+          break;
+        case AFTER_TEST:
+          create = configuration.getAfterTest();
+          isAfterTest = true;
+          break;
+        case BEFORE_CLASS:
+          create = configuration.getBeforeTestClass();
+          isBeforeClass = true;
+          break;
+        case AFTER_CLASS:
+          create = configuration.getAfterTestClass();
+          isAfterClass = true;
+          break;
+        case BEFORE_TEST_METHOD:
+          create = configuration.getBeforeTestMethod();
+          isBeforeTestMethod = true;
+          break;
+        case AFTER_TEST_METHOD:
+          create = configuration.getAfterTestMethod();
+          isAfterTestMethod = true;
+          break;
+        case BEFORE_GROUPS:
+          beforeGroups = configuration.getBeforeGroups();
+          create = beforeGroups.length > 0;
+          isBeforeTestMethod = true;
+          break;
+        case AFTER_GROUPS:
+          afterGroups = configuration.getAfterGroups();
+          create = afterGroups.length > 0;
+          isBeforeTestMethod = true;
+          break;
+      }
+
+      if(create) {
+        addConfigurationMethod(clazz,
+                               vResult,
+                               m,
+                               isBeforeSuite,
+                               isAfterSuite,
+                               isBeforeTest,
+                               isAfterTest,
+                               isBeforeClass,
+                               isAfterClass,
+                               isBeforeTestMethod,
+                               isAfterTestMethod,
+                               beforeGroups,
+                               afterGroups,
+                               null); /* @@@ */
+      }
+    }
+
+    List<ITestNGMethod> excludedMethods = Lists.newArrayList();
+    boolean unique = configurationType == BEFORE_SUITE || configurationType == AFTER_SUITE;
+    ITestNGMethod[] tmResult = MethodHelper.collectAndOrderMethods(Lists.newArrayList(vResult),
+                                              false /* forTests */,
+                                              m_runInfo,
+                                              m_annotationFinder,
+                                              unique,
+                                              excludedMethods);
+
+    return tmResult;
+  }
+
+  private void addConfigurationMethod(Class<?> clazz,
+                                      List<ITestNGMethod> results,
+                                      Method method,
+                                      boolean isBeforeSuite,
+                                      boolean isAfterSuite,
+                                      boolean isBeforeTest,
+                                      boolean isAfterTest,
+                                      boolean isBeforeClass,
+                                      boolean isAfterClass,
+                                      boolean isBeforeTestMethod,
+                                      boolean isAfterTestMethod,
+                                      String[] beforeGroups,
+                                      String[] afterGroups,
+                                      Object instance)
+  {
+    if(method.getDeclaringClass().isAssignableFrom(clazz)) {
+      ITestNGMethod confMethod = new ConfigurationMethod(new ConstructorOrMethod(method),
+                                                         m_annotationFinder,
+                                                         isBeforeSuite,
+                                                         isAfterSuite,
+                                                         isBeforeTest,
+                                                         isAfterTest,
+                                                         isBeforeClass,
+                                                         isAfterClass,
+                                                         isBeforeTestMethod,
+                                                         isAfterTestMethod,
+                                                         beforeGroups,
+                                                         afterGroups,
+                                                         instance);
+      results.add(confMethod);
+    }
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/TestNGProperty.java b/src/main/java/org/testng/internal/TestNGProperty.java
new file mode 100755
index 0000000..227c6d4
--- /dev/null
+++ b/src/main/java/org/testng/internal/TestNGProperty.java
@@ -0,0 +1,53 @@
+package org.testng.internal;
+
+
+/**
+ * Describes a property
+ *
+ * @author Cedric Beust, May 2, 2004
+ *
+ */
+public class TestNGProperty {
+  private String m_commandLineName = null;
+  private String m_name = null;
+  private String m_documentation = null;
+  private String m_default = null;
+
+  public TestNGProperty(String clName, String name, String doc, String def) {
+    init(clName, name, doc, def);
+  }
+
+  public TestNGProperty(String name, String doc, String def) {
+    init(name, name, doc, def);
+  }
+
+  private void init(String clName, String name, String doc, String def) {
+    m_commandLineName = clName;
+    m_name = name;
+    m_documentation = doc;
+    m_default = def;
+  }
+
+  /**
+   * @return Returns the default.
+   */
+  public String getDefault() {
+    return m_default;
+  }
+  /**
+   * @return Returns the documentation.
+   */
+  public String getDocumentation() {
+    return m_documentation;
+  }
+  /**
+   * @return Returns the name.
+   */
+  public String getName() {
+    return m_name;
+  }
+
+  public String getCommandLineName() {
+    return m_commandLineName;
+  }
+}
diff --git a/src/main/java/org/testng/internal/TestResult.java b/src/main/java/org/testng/internal/TestResult.java
new file mode 100644
index 0000000..867bd33
--- /dev/null
+++ b/src/main/java/org/testng/internal/TestResult.java
@@ -0,0 +1,326 @@
+package org.testng.internal;

+

+import org.testng.IAttributes;

+import org.testng.IClass;

+import org.testng.ITest;

+import org.testng.ITestContext;

+import org.testng.ITestNGMethod;

+import org.testng.ITestResult;

+import org.testng.Reporter;

+import org.testng.collections.Objects;

+

+import java.util.List;

+import java.util.Set;

+

+

+/**

+ * This class represents the result of a test.

+ *

+ * @author Cedric Beust, May 2, 2004

+ */

+public class TestResult implements ITestResult {

+

+  private static final long serialVersionUID = 6273017418233324556L;

+  private IClass m_testClass = null;

+  private ITestNGMethod m_method = null;

+  private int m_status = -1;

+  private Throwable m_throwable = null;

+  private long m_startMillis = 0;

+  private long m_endMillis = 0;

+  private String m_name = null;

+  private String m_host;

+  transient private Object[] m_parameters = {};

+  transient private Object m_instance;

+  private String m_instanceName;

+  private ITestContext m_context;

+

+  public TestResult() {

+

+  }

+

+  public TestResult(IClass testClass,

+      Object instance,

+      ITestNGMethod method,

+      Throwable throwable,

+      long start,

+      long end,

+      ITestContext context)

+  {

+    init(testClass, instance, method, throwable, start, end, context);

+  }

+

+  /**

+   *

+   * @param testClass

+   * @param instance

+   * @param method

+   * @param throwable

+   * @param start

+   * @param end

+   */

+  public void init (IClass testClass,

+      Object instance,

+      ITestNGMethod method,

+      Throwable throwable,

+      long start,

+      long end,

+      ITestContext context)

+  {

+    m_testClass = testClass;

+    m_throwable = throwable;

+    m_instanceName = m_testClass.getName();

+    if (null == m_throwable) {

+      m_status = ITestResult.SUCCESS;

+    }

+    m_startMillis = start;

+    m_endMillis = end;

+    m_method = method;

+    m_context = context;

+

+    m_instance = instance;

+

+    // Calculate the name: either the method name, ITest#getTestName or

+    // toString() if it's been overridden.

+    if (m_instance == null) {

+      m_name = m_method.getMethodName();

+    } else {

+      if (m_instance instanceof ITest) {

+        m_name = ((ITest) m_instance).getTestName();

+      }

+      else if (testClass.getTestName() != null) {

+        m_name = testClass.getTestName();

+      }

+      else {

+        String string = m_instance.toString();

+        // Only display toString() if it's been overridden by the user

+        m_name = getMethod().getMethodName();

+        try {

+          if (!Object.class.getMethod("toString")

+              .equals(m_instance.getClass().getMethod("toString"))) {

+            m_instanceName = string.startsWith("class ")

+                ? string.substring("class ".length())

+                : string;

+            m_name = m_name + " on " + m_instanceName;

+          }

+        }

+        catch(NoSuchMethodException ignore) {

+          // ignore

+        }

+      }

+    }

+  }

+

+  private static void ppp(String s) {

+    System.out.println("[TestResult] " + s);

+  }

+

+  @Override

+  public void setEndMillis(long millis) {

+    m_endMillis = millis;

+  }

+

+  /**

+   * If this result's related instance implements ITest or use @Test(testName=...), returns its test name,

+   * otherwise returns null.

+   */

+  @Override

+  public String getTestName() {

+    if (m_instance instanceof ITest) {

+      return ((ITest) m_instance).getTestName();

+    }

+    if (m_testClass.getTestName() != null) {

+      return m_testClass.getTestName();

+    }

+    return null;

+  }

+

+  @Override

+  public String getName() {

+    return m_name;

+  }

+

+  /**

+   * @return Returns the method.

+   */

+  @Override

+  public ITestNGMethod getMethod() {

+    return m_method;

+  }

+

+  /**

+   * @param method The method to set.

+   */

+  public void setMethod(ITestNGMethod method) {

+    m_method = method;

+  }

+

+  /**

+   * @return Returns the status.

+   */

+  @Override

+  public int getStatus() {

+    return m_status;

+  }

+

+  /**

+   * @param status The status to set.

+   */

+  @Override

+  public void setStatus(int status) {

+    m_status = status;

+  }

+

+  @Override

+  public boolean isSuccess() {

+    return ITestResult.SUCCESS == m_status;

+  }

+

+  /**

+   * @return Returns the testClass.

+   */

+  @Override

+  public IClass getTestClass() {

+    return m_testClass;

+  }

+

+  /**

+   * @param testClass The testClass to set.

+   */

+  public void setTestClass(IClass testClass) {

+    m_testClass = testClass;

+  }

+

+  /**

+   * @return Returns the throwable.

+   */

+  @Override

+  public Throwable getThrowable() {

+    return m_throwable;

+  }

+

+  /**

+   * @param throwable The throwable to set.

+   */

+  @Override

+  public void setThrowable(Throwable throwable) {

+    m_throwable = throwable;

+  }

+

+  /**

+   * @return Returns the endMillis.

+   */

+  @Override

+  public long getEndMillis() {

+    return m_endMillis;

+  }

+

+  /**

+   * @return Returns the startMillis.

+   */

+  @Override

+  public long getStartMillis() {

+    return m_startMillis;

+  }

+

+//  public List<String> getOutput() {

+//    return m_output;

+//  }

+

+  @Override

+  public String toString() {

+    List<String> output = Reporter.getOutput(this);

+    String result = Objects.toStringHelper(getClass())

+        .omitNulls()

+        .omitEmptyStrings()

+        .add("name", getName())

+        .add("status", toString(m_status))

+        .add("method", m_method)

+        .add("output", output != null && output.size() > 0 ? output.get(0) : null)

+        .toString();

+

+    return result;

+  }

+

+  private String toString(int status) {

+    switch(status) {

+      case SUCCESS: return "SUCCESS";

+      case FAILURE: return "FAILURE";

+      case SKIP: return "SKIP";

+      case SUCCESS_PERCENTAGE_FAILURE: return "SUCCESS WITHIN PERCENTAGE";

+      case STARTED: return "STARTED";

+      default: throw new RuntimeException();

+    }

+  }

+

+  @Override

+  public String getHost() {

+    return m_host;

+  }

+

+  public void setHost(String host) {

+    m_host = host;

+  }

+

+  @Override

+  public Object[] getParameters() {

+    return m_parameters;

+  }

+

+  @Override

+  public void setParameters(Object[] parameters) {

+    m_parameters = parameters;

+  }

+

+  @Override

+  public Object getInstance() {

+    return m_instance;

+  }

+

+  private IAttributes m_attributes = new Attributes();

+

+  @Override

+  public Object getAttribute(String name) {

+    return m_attributes.getAttribute(name);

+  }

+

+  @Override

+  public void setAttribute(String name, Object value) {

+    m_attributes.setAttribute(name, value);

+  }

+

+  @Override

+  public Set<String> getAttributeNames() {

+    return m_attributes.getAttributeNames();

+  }

+

+  @Override

+  public Object removeAttribute(String name) {

+    return m_attributes.removeAttribute(name);

+  }

+  

+  @Override

+  public ITestContext getTestContext() {

+	  return m_context;

+  }

+  

+  public void setContext(ITestContext context) {

+	  m_context = context;

+  }

+

+  @Override

+  public int compareTo(ITestResult comparison) {

+	  if( getStartMillis() > comparison.getStartMillis() ) {

+		  return 1;

+	  } else if( getStartMillis() < comparison.getStartMillis()) {

+		  return -1;

+	  } else {

+		  return 0;

+	  }

+  }

+

+  @Override

+  public String getInstanceName() {

+    return m_instanceName;

+  }

+}

+

diff --git a/src/main/java/org/testng/internal/Utils.java b/src/main/java/org/testng/internal/Utils.java
new file mode 100644
index 0000000..c65acba
--- /dev/null
+++ b/src/main/java/org/testng/internal/Utils.java
@@ -0,0 +1,862 @@
+package org.testng.internal;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.logging.FileHandler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.testng.ITestNGMethod;
+import org.testng.TestNG;
+import org.testng.TestNGException;
+import org.testng.TestRunner;
+import org.testng.annotations.IConfigurationAnnotation;
+import org.testng.annotations.ITestAnnotation;
+import org.testng.collections.Lists;
+import org.testng.internal.annotations.AnnotationHelper;
+import org.testng.internal.annotations.IAnnotationFinder;
+import org.testng.log.TextFormatter;
+import org.testng.reporters.XMLStringBuffer;
+import org.testng.xml.XmlClass;
+
+/**
+ * Helper methods to parse annotations.
+ *
+ * @author Cedric Beust, Apr 26, 2004
+ */
+public final class Utils {
+  private static final String LINE_SEP = System.getProperty("line.separator");
+
+  public static final char[] SPECIAL_CHARACTERS =
+      {'*','/','\\','?','%',':',';','<','>','&','~','|'};
+  public static final char CHAR_REPLACEMENT = '_';
+  public static final char UNICODE_REPLACEMENT = 0xFFFD;
+
+  /**
+   * Hide constructor for utility class.
+   */
+  private Utils() {
+    // Hide constructor
+  }
+
+  /**
+   * Splits the given String s into tokens where the separator is
+   * either the space character or the comma character. For example,
+   * if s is "a,b, c" this method returns {"a", "b", "c"}
+   *
+   * @param s the string to split
+   * @return the split token
+   */
+  public static String[] stringToArray(String s) {
+    // TODO CQ would s.split() be a better way of doing this?
+    StringTokenizer st = new StringTokenizer(s, " ,");
+    String[] result = new String[st.countTokens()];
+    for (int i = 0; i < result.length; i++) {
+      result[i] = st.nextToken();
+    }
+
+    return result;
+  }
+
+  public static XmlClass[] classesToXmlClasses(Class<?>[] classes) {
+    List<XmlClass> result = Lists.newArrayList();
+
+    for (Class<?> cls : classes) {
+      result.add(new XmlClass(cls, true /* load classes */));
+    }
+
+    return result.toArray(new XmlClass[classes.length]);
+  }
+
+  public static String[] parseMultiLine(String line) {
+    List<String> vResult = Lists.newArrayList();
+    if (isStringNotBlank(line)) {
+      StringTokenizer st = new StringTokenizer(line, " ");
+      while (st.hasMoreTokens()) {
+        vResult.add(st.nextToken());
+      }
+      // Bug in split when passed " " : returns one too many result
+      //      result = line.split(" ");
+    }
+
+    return vResult.toArray(new String[vResult.size()]);
+  }
+
+  public static void writeUtf8File(@Nullable String outputDir, String fileName, XMLStringBuffer xsb, String prefix) {
+    try {
+      final File outDir = (outputDir != null) ? new File(outputDir) : new File("").getAbsoluteFile();
+      if (!outDir.exists()) {
+        outDir.mkdirs();
+      }
+      final File file = new File(outDir, fileName);
+      if (!file.exists()) {
+        file.createNewFile();
+      }
+      final OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
+      if (prefix != null) {
+        w.append(prefix);
+      }
+      xsb.toWriter(w);
+      w.close();
+    } catch(IOException ex) {
+      ex.printStackTrace();
+    }
+  }
+
+  /**
+   * Writes the content of the sb string to the file named filename in outDir encoding the output as UTF-8.
+   * If outDir does not exist, it is created.
+   *
+   * @param outputDir the output directory (may not exist). If <tt>null</tt> then current directory is used.
+   * @param fileName the filename
+   * @param sb the file content
+   */
+  public static void writeUtf8File(@Nullable String outputDir, String fileName, String sb) {
+    final String outDirPath= outputDir != null ? outputDir : "";
+    final File outDir= new File(outDirPath);
+    writeFile(outDir, fileName, escapeUnicode(sb), "UTF-8", false /* don't append */);
+  }
+
+  /**
+   * Writes the content of the sb string to the file named filename in outDir. If
+   * outDir does not exist, it is created.
+   *
+   * @param outputDir the output directory (may not exist). If <tt>null</tt> then current directory is used.
+   * @param fileName the filename
+   * @param sb the file content
+   */
+  public static void writeFile(@Nullable String outputDir, String fileName, String sb) {
+    final String outDirPath= outputDir != null ? outputDir : "";
+    final File outDir= new File(outDirPath);
+    writeFile(outDir, fileName, sb, null, false /* don't append */);
+  }
+
+  /**
+   * Writes the content of the sb string to the file named filename in outDir. If
+   * outDir does not exist, it is created.
+   *
+   * @param outDir the output directory (may not exist). If <tt>null</tt> then current directory is used.
+   * @param fileName the filename
+   * @param sb the file content
+   */
+  private static void writeFile(@Nullable File outDir, String fileName, String sb, @Nullable String encoding, boolean append) {
+    try {
+      if (outDir == null) {
+        outDir = new File("").getAbsoluteFile();
+      }
+      if (!outDir.exists()) {
+        outDir.mkdirs();
+      }
+
+      fileName = replaceSpecialCharacters(fileName);
+      File outputFile = new File(outDir, fileName);
+      if (!append) {
+        outputFile.delete();
+        outputFile.createNewFile();
+      }
+      writeFile(outputFile, sb, encoding, append);
+    }
+    catch (IOException e) {
+      if (TestRunner.getVerbose() > 1) {
+        e.printStackTrace();
+      }
+      else {
+        log("[Utils]", 1, e.getMessage());
+      }
+    }
+  }
+
+  private static void writeFile(File outputFile, String sb, @Nullable String encoding, boolean append) {
+    BufferedWriter fw = null;
+    try {
+      fw = openWriter(outputFile, encoding, append);
+      fw.write(sb);
+
+      Utils.log("", 3, "Creating " + outputFile.getAbsolutePath());
+    }
+    catch(IOException ex) {
+      if (TestRunner.getVerbose() > 1) {
+        System.err.println("ERROR WHILE WRITING TO " + outputFile);
+        ex.printStackTrace();
+      }
+      else {
+        log("[Utils]", 1, "Error while writing to " + outputFile + ": " + ex.getMessage());
+      }
+    }
+    finally {
+      try {
+        if (fw != null) {
+          fw.close();
+        }
+      }
+      catch (IOException e) {
+        ; // ignore
+      }
+    }
+  }
+
+  /**
+   * Open a BufferedWriter for the specified file. If output directory doesn't
+   * exist, it is created. If the output file exists, it is deleted. The output file is
+   * created in any case.
+   * @param outputDir output directory. If <tt>null</tt>, then current directory is used
+   * @param fileName file name
+   * @throws IOException if anything goes wrong while creating files.
+   */
+  public static BufferedWriter openWriter(@Nullable String outputDir, String fileName) throws IOException {
+    String outDirPath= outputDir != null ? outputDir : "";
+    File outDir= new File(outDirPath);
+    if (outDir.exists()) {
+      outDir.mkdirs();
+    }
+    fileName = replaceSpecialCharacters(fileName);
+    File outputFile = new File(outDir, fileName);
+    outputFile.delete();
+    return openWriter(outputFile, null, false);
+  }
+
+  private static BufferedWriter openWriter(File outputFile, @Nullable String encoding, boolean append) throws IOException {
+    if (!outputFile.exists()) {
+      outputFile.createNewFile();
+    }
+    OutputStreamWriter osw= null;
+    if (null != encoding) {
+      osw = new OutputStreamWriter(new FileOutputStream(outputFile, append), encoding);
+    }
+    else {
+      osw = new OutputStreamWriter(new FileOutputStream(outputFile, append));
+    }
+    return new BufferedWriter(osw);
+  }
+
+  private static void ppp(String s) {
+    Utils.log("Utils", 0, s);
+  }
+
+  /**
+   * @param result
+   */
+  public static void dumpMap(Map<?, ?> result) {
+    System.out.println("vvvvv");
+    for (Map.Entry<?, ?> entry : result.entrySet()) {
+      System.out.println(entry.getKey() + " => " + entry.getValue());
+    }
+    System.out.println("^^^^^");
+  }
+
+  /**
+   * @param allMethods
+   */
+  public static void dumpMethods(List<ITestNGMethod> allMethods) {
+    ppp("======== METHODS:");
+    for (ITestNGMethod tm : allMethods) {
+      ppp("  " + tm);
+    }
+  }
+
+  /**
+   * @return The list of dependent groups for this method, including the
+   * class groups
+   */
+  public static String[] dependentGroupsForThisMethodForTest(Method m, IAnnotationFinder finder) {
+    List<String> vResult = Lists.newArrayList();
+    Class<?> cls = m.getDeclaringClass();
+
+    // Collect groups on the class
+    ITestAnnotation tc = AnnotationHelper.findTest(finder, cls);
+    if (null != tc) {
+      for (String group : tc.getDependsOnGroups()) {
+        vResult.add(group);
+      }
+    }
+
+    // Collect groups on the method
+    ITestAnnotation tm = AnnotationHelper.findTest(finder, m);
+    if (null != tm) {
+      String[] groups = tm.getDependsOnGroups();
+
+      //       ppp("Method:" + m + " #Groups:" + groups.length);
+      for (String group : groups) {
+        vResult.add(group);
+      }
+    }
+
+    return vResult.toArray(new String[vResult.size()]);
+  }
+
+  /**
+   * @return The list of groups this method belongs to, including the
+   * class groups
+   */
+  public static String[] groupsForThisMethodForTest(Method m, IAnnotationFinder finder) {
+    List<String> vResult = Lists.newArrayList();
+    Class<?> cls = m.getDeclaringClass();
+
+    // Collect groups on the class
+    ITestAnnotation tc = AnnotationHelper.findTest(finder, cls);
+    if (null != tc) {
+      for (String group : tc.getGroups()) {
+        vResult.add(group);
+      }
+    }
+
+    // Collect groups on the method
+    ITestAnnotation tm = AnnotationHelper.findTest(finder, m);
+    if (null != tm) {
+      String[] groups = tm.getGroups();
+
+      //       ppp("Method:" + m + " #Groups:" + groups.length);
+      for (String group : groups) {
+        vResult.add(group);
+      }
+    }
+
+    return vResult.toArray(new String[vResult.size()]);
+  }
+
+  /**
+   * @return The list of groups this method belongs to, including the
+   * class groups
+   */
+  public static String[] groupsForThisMethodForConfiguration(Method m, IAnnotationFinder finder) {
+    String[] result = {};
+
+    // Collect groups on the method
+    ITestAnnotation tm = AnnotationHelper.findTest(finder, m);
+    if (null != tm) {
+      result = tm.getGroups();
+    }
+
+    return result;
+  }
+
+  /**
+   * @return The list of groups this method depends on, including the
+   * class groups
+   */
+  public static String[] dependentGroupsForThisMethodForConfiguration(Method m,
+                                                                      IAnnotationFinder finder) {
+    String[] result = {};
+
+    // Collect groups on the method
+    IConfigurationAnnotation tm = AnnotationHelper.findConfiguration(finder, m);
+    if (null != tm) {
+      result = tm.getDependsOnGroups();
+    }
+
+    return result;
+  }
+
+  public static void log(String msg) {
+    log("Utils", 2, msg);
+  }
+
+  /**
+   * Logs the the message to System.out if level is greater than
+   * or equal to TestRunner.getVerbose(). The message is logged as:
+   * <pre>
+   *     "[cls] msg"
+   * </pre>
+   *
+   * @param cls the class name to prefix the log message.
+   * @param level the logging level of the message.
+   * @param msg the message to log to System.out.
+   */
+  public static void log(String cls, int level, String msg) {
+    // Why this coupling on a static member of TestRunner.getVerbose()?
+    if (TestRunner.getVerbose() >= level) {
+      if (cls.length() > 0) {
+        System.out.println("[" + cls + "] " + msg);
+      }
+      else {
+        System.out.println(msg);
+      }
+    }
+  }
+
+  public static void error(String errorMessage) {
+    System.err.println("[Error] " + errorMessage);
+  }
+
+  /**
+   * @return The number of methods invoked, taking into account the number
+   * of instances.
+   */
+//  public static int calculateInvokedMethodCount(IResultMap map) {
+//    return calculateInvokedMethodCount(
+//        (ITestNGMethod[]) map.getAllMethods().toArray(new ITestNGMethod[map.size()]));
+//  }
+
+  public static int calculateInvokedMethodCount(ITestNGMethod[] methods) {
+    return methods.length;
+//    int result = 0;
+//
+//    for (ITestNGMethod method : methods) {
+//      int instanceCount = method.getInvocationCount();
+//      result += instanceCount;
+//    }
+//
+//    return result;
+  }
+
+//  public static int calculateInvokedMethodCount(Map<ITestNGMethod, ITestResult> methods) {
+//    return calculateInvokedMethodCount(methods.keySet().toArray(new ITestNGMethod[methods.values()
+//                                                                .size()]));
+//  }
+
+  /**
+   * Tokenize the string using the separator.
+   */
+  public static String[] split(String string, String sep) {
+    if ((string == null) || (string.length() == 0)) {
+      return new String[0];
+    }
+
+    // TODO How different is this from:
+    // return string.split(sep);
+
+    int start = 0;
+    int idx = string.indexOf(sep, start);
+    int len = sep.length();
+    List<String> strings = Lists.newArrayList();
+
+    while (idx != -1) {
+      strings.add(string.substring(start, idx).trim());
+      start = idx + len;
+      idx = string.indexOf(sep, start);
+    }
+
+    strings.add(string.substring(start).trim());
+
+    return strings.toArray(new String[strings.size()]);
+  }
+
+  public static void initLogger(Logger logger, String outputLogPath) {
+    try {
+      logger.setUseParentHandlers(false);
+      FileHandler fh = new FileHandler(outputLogPath);
+      fh.setFormatter(new TextFormatter());
+      fh.setLevel(Level.INFO);
+      logger.addHandler(fh);
+    }
+    catch (SecurityException | IOException se) {
+      se.printStackTrace();
+    }
+  }
+
+  public static void logInvocation(String reason, Method thisMethod, Object[] parameters) {
+    String clsName = thisMethod.getDeclaringClass().getName();
+    int n = clsName.lastIndexOf(".");
+    if (n >= 0) {
+      clsName = clsName.substring(n + 1);
+    }
+    String methodName = clsName + '.' + thisMethod.getName();
+    if (TestRunner.getVerbose() >= 2) {
+      StringBuffer paramString = new StringBuffer();
+      if (parameters != null) {
+        for (Object p : parameters) {
+          paramString.append(p.toString()).append(' ');
+        }
+      }
+      log("", 2, "Invoking " + reason + methodName + '(' + paramString + ')');
+    }
+  }
+
+  public static void writeResourceToFile(File file, String resourceName, Class<?> clasz) throws IOException {
+    InputStream inputStream = clasz.getResourceAsStream("/" + resourceName);
+    if (inputStream == null) {
+      System.err.println("Couldn't find resource on the class path: " + resourceName);
+//      throw new IllegalArgumentException("Resource does not exist: " + resourceName);
+    }
+
+    else {
+
+      try {
+        FileOutputStream outputStream = new FileOutputStream(file);
+        try {
+          int nread;
+          byte[] buffer = new byte[4096];
+          while (0 < (nread = inputStream.read(buffer))) {
+            outputStream.write(buffer, 0, nread);
+          }
+        } finally {
+          outputStream.close();
+        }
+      } finally {
+        inputStream.close();
+      }
+    }
+  }
+
+  public static String defaultIfStringEmpty(String s, String defaultValue) {
+    return isStringEmpty(s) ? defaultValue : s;
+  }
+
+  public static boolean isStringBlank(String s) {
+    return s == null || "".equals(s.trim());
+  }
+
+  public static boolean isStringEmpty(String s) {
+    return s == null || "".equals(s);
+  }
+
+  public static boolean isStringNotBlank(String s) {
+    return !isStringBlank(s);
+  }
+
+  public static boolean isStringNotEmpty(String s) {
+    return !isStringEmpty(s);
+  }
+
+  /**
+   * @return an array of two strings: the short stack trace and the long stack trace.
+   */
+  public static String[] stackTrace(Throwable t, boolean toHtml) {
+    StringWriter sw = new StringWriter();
+    PrintWriter pw = new PrintWriter(sw);
+    t.printStackTrace(pw);
+    pw.flush();
+
+    String fullStackTrace = sw.getBuffer().toString();
+    String shortStackTrace;
+
+    if (Boolean.getBoolean(TestNG.SHOW_TESTNG_STACK_FRAMES) || TestRunner.getVerbose() >= 2) {
+      shortStackTrace = fullStackTrace;
+    } else {
+      shortStackTrace = filterTrace(sw.getBuffer().toString());
+    }
+
+    if (toHtml) {
+      shortStackTrace = escapeHtml(shortStackTrace);
+      fullStackTrace = escapeHtml(fullStackTrace);
+    }
+
+    return new String[] {
+        shortStackTrace, fullStackTrace
+    };
+  }
+
+  private static final Map<Character, String> ESCAPES = new HashMap<Character, String>() {
+    private static final long serialVersionUID = 1285607660247157523L;
+
+  {
+    put('<', "&lt;");
+    put('>', "&gt;");
+    put('\'', "&apos;");
+    put('"', "&quot;");
+    put('&', "&amp;");
+  }};
+
+  public static String escapeHtml(String s) {
+    if (s == null) {
+      return null;
+    }
+
+    StringBuilder result = new StringBuilder();
+
+    for (int i = 0; i < s.length(); i++) {
+      char c = s.charAt(i);
+      String nc = ESCAPES.get(c);
+      if (nc != null) {
+        result.append(nc);
+      } else {
+        result.append(c);
+      }
+    }
+
+    return result.toString();
+  }
+
+  public static String escapeUnicode(String s) {
+    if (s == null) {
+      return null;
+    }
+
+    StringBuilder result = new StringBuilder();
+
+    for (int i = 0; i < s.length(); i++) {
+      char c = s.charAt(i);
+      char ca = (Character.isDefined(c)) ? c: UNICODE_REPLACEMENT;
+      result.append(ca);
+    }
+
+    return result.toString();
+  }
+
+  private static String filterTrace(String trace) {
+    StringReader   stringReader = new StringReader(trace);
+    BufferedReader bufferedReader = new BufferedReader(stringReader);
+    StringBuffer buf = new StringBuffer();
+
+    try {
+      // first line contains the thrown exception
+      String line = bufferedReader.readLine();
+      if(line == null) {
+        return "";
+      }
+      buf.append(line).append(LINE_SEP);
+
+      //
+      // the stack frames of the trace
+      //
+      String[] excludedStrings = new String[] {
+          "org.testng",
+          "reflect",
+          "org.apache.maven.surefire"
+      };
+
+      int excludedCount = 0;
+      while((line = bufferedReader.readLine()) != null) {
+        boolean isExcluded = false;
+        for (String excluded : excludedStrings) {
+          if(line.contains(excluded)) {
+            isExcluded = true;
+            excludedCount++;
+            break;
+           }
+        }
+        if (! isExcluded) {
+          buf.append(line).append(LINE_SEP);
+        }
+      }
+      if (excludedCount > 0) {
+        buf.append("... Removed " + excludedCount + " stack frames");
+      }
+    }
+    catch(IOException ioex) {
+      ; // do nothing
+    }
+
+    return buf.toString();
+  }
+
+  public static String toString(Object object, Class<?> objectClass) {
+    if(null == object) {
+      return "null";
+    }
+    final String toString= object.toString();
+    if(isStringEmpty(toString)) {
+      return "\"\"";
+    }
+    else if (String.class.equals(objectClass)) {
+      return "\"" + toString + '\"';
+    }
+    else {
+      return toString;
+    }
+  }
+
+  public static String detailedMethodName(ITestNGMethod method, boolean fqn) {
+    StringBuffer buf= new StringBuffer();
+    if(method.isBeforeSuiteConfiguration()) {
+      buf.append("@BeforeSuite ");
+    }
+    else if(method.isBeforeTestConfiguration()) {
+      buf.append("@BeforeTest ");
+    }
+    else if(method.isBeforeClassConfiguration()) {
+      buf.append("@BeforeClass ");
+    }
+    else if(method.isBeforeGroupsConfiguration()) {
+      buf.append("@BeforeGroups ");
+    }
+    else if(method.isBeforeMethodConfiguration()) {
+      buf.append("@BeforeMethod ");
+    }
+    else if(method.isAfterMethodConfiguration()) {
+      buf.append("@AfterMethod ");
+    }
+    else if(method.isAfterGroupsConfiguration()) {
+      buf.append("@AfterGroups ");
+    }
+    else if(method.isAfterClassConfiguration()) {
+      buf.append("@AfterClass ");
+    }
+    else if(method.isAfterTestConfiguration()) {
+      buf.append("@AfterTest ");
+    }
+    else if(method.isAfterSuiteConfiguration()) {
+      buf.append("@AfterSuite ");
+    }
+
+    return buf.append(fqn ? method.toString() : method.getMethodName()).toString();
+  }
+
+  public static String arrayToString(String[] strings) {
+    StringBuffer result = new StringBuffer("");
+    if ((strings != null) && (strings.length > 0)) {
+      for (int i = 0; i < strings.length; i++) {
+        result.append(strings[i]);
+        if (i < strings.length - 1) {
+          result.append(", ");
+        }
+      }
+    }
+    return result.toString();
+  }
+
+  /**
+   * If the file name contains special characters like *,/,\ and so on,
+   * exception will be thrown and report file will not be created.<br>
+   * Special characters are platform specific and they are not same for
+   * example on Windows and Macintosh. * is not allowed on Windows, but it is on Macintosh.<br>
+   * In order to have the same behavior of testng on the all platforms, characters like * will
+   * be replaced on all platforms whether they are causing the problem or not.
+   *
+   * @param fileName file name that could contain special characters.
+   * @return fileName with special characters replaced
+   * @author Borojevic
+   */
+  public static String replaceSpecialCharacters(String fileName) {
+   if (fileName == null || fileName.length() == 0) {
+     return fileName;
+   }
+   for (char element : SPECIAL_CHARACTERS) {
+     fileName = fileName.replace(element, CHAR_REPLACEMENT);
+   }
+
+   return fileName;
+  }
+
+  public static <T> String join(List<T> objects, String separator) {
+    StringBuilder result = new StringBuilder();
+    for (int i = 0; i < objects.size(); i++) {
+      if (i > 0) {
+        result.append(separator);
+      }
+      result.append(objects.get(i).toString());
+    }
+    return result.toString();
+  }
+
+  public static void copyFile(File from, File to) {
+    to.getParentFile().mkdirs();
+    try (InputStream in = new FileInputStream(from); OutputStream out = new FileOutputStream(to)) {
+      byte[] buf = new byte[1024];
+      int len;
+      while ((len = in.read(buf)) > 0) {
+        out.write(buf, 0, len);
+      }
+    } catch(IOException e){
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * @return a temporary file with the given content.
+   */
+  public static File createTempFile(String content) {
+    try {
+      // Create temp file.
+      File result = File.createTempFile("testng-tmp", "");
+
+      // Delete temp file when program exits.
+      result.deleteOnExit();
+
+      // Write to temp file
+      BufferedWriter out = new BufferedWriter(new FileWriter(result));
+      out.write(content);
+      out.close();
+
+      return result;
+    } catch (IOException e) {
+      throw new TestNGException(e);
+    }
+  }
+
+  /**
+   * Make sure that either we have an instance or if not, that the method is static
+   */
+  public static void checkInstanceOrStatic(Object instance, Method method) {
+    if (instance == null && method != null && ! Modifier.isStatic(method.getModifiers())) {
+      throw new TestNGException("Can't invoke " + method + ": either make it static or add "
+          + "a no-args constructor to your class");
+    }
+  }
+
+  public static void checkReturnType(Method method, Class<?>... returnTypes) {
+    if (method == null) {
+      return;
+    }
+    for (Class<?> returnType : returnTypes) {
+      if (method.getReturnType() == returnType) {
+        return;
+      }
+    }
+    throw new TestNGException(method.getDeclaringClass().getName() + "."
+              + method.getName() + " MUST return " + toString(returnTypes) + " but returns " + method.getReturnType().getName());
+  }
+
+  private static String toString(Class<?>[] classes) {
+    StringBuilder sb = new StringBuilder("[ ");
+    for (int i=0; i<classes.length;) {
+      Class<?> clazz = classes[i];
+      if (clazz.isArray()) {
+        sb.append(clazz.getComponentType().getName()).append("[]");
+      } else {
+        sb.append(clazz.getName());
+      }
+      if (++i < classes.length) { // increment and compare
+        sb.append(" or ");
+      }
+    }
+    sb.append(" ]");
+    return sb.toString();
+  }
+
+  /**
+   * Returns the string representation of the specified object, transparently
+   * handling null references and arrays.
+   *
+   * @param obj
+   *            the object
+   * @return the string representation
+   */
+  public static String toString(Object obj) {
+    String result;
+    if (obj != null) {
+      if (obj instanceof boolean[]) {
+        result = Arrays.toString((boolean[]) obj);
+      } else if (obj instanceof byte[]) {
+        result = Arrays.toString((byte[]) obj);
+      } else if (obj instanceof char[]) {
+        result = Arrays.toString((char[]) obj);
+      } else if (obj instanceof double[]) {
+        result = Arrays.toString((double[]) obj);
+      } else if (obj instanceof float[]) {
+        result = Arrays.toString((float[]) obj);
+      } else if (obj instanceof int[]) {
+        result = Arrays.toString((int[]) obj);
+      } else if (obj instanceof long[]) {
+        result = Arrays.toString((long[]) obj);
+      } else if (obj instanceof Object[]) {
+        result = Arrays.deepToString((Object[]) obj);
+      } else if (obj instanceof short[]) {
+        result = Arrays.toString((short[]) obj);
+      } else {
+        result = obj.toString();
+      }
+    } else {
+      result = "null";
+    }
+    return result;
+  }
+}
diff --git a/src/main/java/org/testng/internal/XmlMethodSelector.java b/src/main/java/org/testng/internal/XmlMethodSelector.java
new file mode 100755
index 0000000..c3f644f
--- /dev/null
+++ b/src/main/java/org/testng/internal/XmlMethodSelector.java
@@ -0,0 +1,385 @@
+package org.testng.internal;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.testng.IMethodSelector;
+import org.testng.IMethodSelectorContext;
+import org.testng.ITestNGMethod;
+import org.testng.TestNGException;
+import org.testng.collections.ListMultiMap;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlInclude;
+
+/**
+ * This class is the default method selector used by TestNG to determine
+ * which methods need to be included and excluded based on the specification
+ * given in testng.xml.
+ *
+ * Created on Sep 30, 2005
+ * @author cbeust
+ */
+public class XmlMethodSelector implements IMethodSelector {
+  private static final long serialVersionUID = -9030548178025605629L;
+
+  // Groups included and excluded for this run
+  private Map<String, String> m_includedGroups = Maps.newHashMap();
+  private Map<String, String> m_excludedGroups = Maps.newHashMap();
+  private List<XmlClass> m_classes = null;
+  // The BeanShell expression for this test, if any
+  private String m_expression = null;
+  // List of methods included implicitly
+  private ListMultiMap<String, XmlInclude> m_includedMethods = Maps.newListMultiMap();
+  private IBsh m_bsh = Dynamic.hasBsh() ? new Bsh() : new BshMock();
+
+  @Override
+  public boolean includeMethod(IMethodSelectorContext context,
+      ITestNGMethod tm, boolean isTestMethod)
+  {
+//    ppp("XML METHOD SELECTOR " + tm + " " + m_isInitialized);
+
+    if (! m_isInitialized) {
+      m_isInitialized = true;
+      init(context);
+    }
+
+    boolean result = false;
+    if (null != m_expression) {
+      return m_bsh.includeMethodFromExpression(m_expression, tm);
+    }
+    else {
+      result = includeMethodFromIncludeExclude(tm, isTestMethod);
+    }
+
+    return result;
+  }
+
+  private boolean includeMethodFromIncludeExclude(ITestNGMethod tm, boolean isTestMethod) {
+    boolean result = false;
+    Method m = tm.getMethod();
+    String[] groups = tm.getGroups();
+    Map<String, String> includedGroups = m_includedGroups;
+    Map<String, String> excludedGroups = m_excludedGroups;
+    List<XmlInclude> includeList =
+        m_includedMethods.get(MethodHelper.calculateMethodCanonicalName(tm));
+
+    //
+    // No groups were specified:
+    //
+    if (includedGroups.size() == 0 && excludedGroups.size() == 0
+        && ! hasIncludedMethods() && ! hasExcludedMethods())
+    //
+    // If we don't include or exclude any methods, method is in
+    //
+    {
+      result = true;
+    }
+    //
+    // If it's a configuration method and no groups were requested, we want it in
+    //
+    else if (includedGroups.size() == 0 && excludedGroups.size() == 0 && ! isTestMethod)
+    {
+      result = true;
+    }
+
+    //
+    // Is this method included implicitly?
+    //
+    else if (includeList != null) {
+      result = true;
+    }
+
+    //
+    // Include or Exclude groups were specified:
+    //
+    else {
+      //
+      // Only add this method if it belongs to an included group and not
+      // to an excluded group
+      //
+      {
+        boolean isIncludedInGroups = isIncluded(groups, m_includedGroups.values());
+        boolean isExcludedInGroups = isExcluded(groups, m_excludedGroups.values());
+
+        //
+        // Calculate the run methods by groups first
+        //
+        if (isIncludedInGroups && !isExcludedInGroups) {
+          result = true;
+        }
+        else if (isExcludedInGroups) {
+          result = false;
+        }
+      }
+
+      if(isTestMethod) {
+        //
+        // Now filter by method name
+        //
+        Method method = tm.getMethod();
+        Class methodClass = method.getDeclaringClass();
+        String fullMethodName =  methodClass.getName()
+                + "."
+                + method.getName();
+
+        String[] fullyQualifiedMethodName = new String[] { fullMethodName };
+
+        //
+        // Iterate through all the classes so we can gather all the included and
+        // excluded methods
+        //
+        for (XmlClass xmlClass : m_classes) {
+          // Only consider included/excluded methods that belong to the same class
+          // we are looking at
+          Class cls = xmlClass.getSupportClass();
+          if(!assignable(methodClass, cls)) {
+            continue;
+          }
+
+          List<String> includedMethods =
+              createQualifiedMethodNames(xmlClass, toStringList(xmlClass.getIncludedMethods()));
+          boolean isIncludedInMethods = isIncluded(fullyQualifiedMethodName, includedMethods);
+          List<String> excludedMethods = createQualifiedMethodNames(xmlClass,
+              xmlClass.getExcludedMethods());
+          boolean isExcludedInMethods = isExcluded(fullyQualifiedMethodName, excludedMethods);
+          if (result) {
+            // If we're about to include this method by group, make sure
+            // it's included by method and not excluded by method
+            result = isIncludedInMethods && ! isExcludedInMethods;
+          }
+          // otherwise it's already excluded and nothing will bring it back,
+          // since exclusions preempt inclusions
+        }
+      }
+    }
+
+    Package pkg = m.getDeclaringClass().getPackage();
+    String methodName = pkg != null ? pkg.getName() + "." + m.getName() : m.getName();
+
+    logInclusion(result ? "Including" : "Excluding", "method", methodName + "()");
+
+    return result;
+  }
+
+  @SuppressWarnings({"unchecked"})
+  private boolean assignable(Class sourceClass, Class targetClass) {
+    return sourceClass.isAssignableFrom(targetClass) || targetClass.isAssignableFrom(sourceClass);
+  }
+
+  private Map<String, String> m_logged = Maps.newHashMap();
+  private void logInclusion(String including, String type, String name) {
+    if (! m_logged.containsKey(name)) {
+      log(4, including + " " + type + " " + name);
+      m_logged.put(name, name);
+    }
+  }
+
+  private boolean hasIncludedMethods() {
+    for (XmlClass xmlClass : m_classes) {
+      if (xmlClass.getIncludedMethods().size() > 0) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  private boolean hasExcludedMethods() {
+    for (XmlClass xmlClass : m_classes) {
+      if (xmlClass.getExcludedMethods().size() > 0) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  private List<String> toStringList(List<XmlInclude> methods) {
+    List<String> result = Lists.newArrayList();
+    for (XmlInclude m : methods) {
+      result.add(m.getName());
+    }
+    return result;
+  }
+
+  private List<String> createQualifiedMethodNames(XmlClass xmlClass,
+      List<String> methods) {
+    List<String> vResult = Lists.newArrayList();
+    Class cls = xmlClass.getSupportClass();
+
+    while (null != cls) {
+      for (String im : methods) {
+        String methodName = im;
+        Method[] allMethods = cls.getDeclaredMethods();
+        Pattern pattern = Pattern.compile(methodName);
+        for (Method m : allMethods) {
+          if (pattern.matcher(m.getName()).matches()) {
+            vResult.add(makeMethodName(cls.getName(), m.getName()));
+          }
+        }
+      }
+      cls = cls.getSuperclass();
+    }
+
+    return vResult;
+  }
+
+  private String makeMethodName(String className, String methodName) {
+    return className + "." + methodName;
+  }
+
+  private void checkMethod(Class<?> c, String methodName) {
+    Pattern p = Pattern.compile(methodName);
+    for (Method m : c.getMethods()) {
+      if (p.matcher(m.getName()).matches()) {
+        return;
+      }
+    }
+    Utils.log("Warning", 2, "The regular expression \"" + methodName + "\" didn't match any" +
+              " method in class " + c.getName());
+  }
+
+  public void setXmlClasses(List<XmlClass> classes) {
+    m_classes = classes;
+    for (XmlClass c : classes) {
+      for (XmlInclude m : c.getIncludedMethods()) {
+        checkMethod(c.getSupportClass(), m.getName());
+        String methodName = makeMethodName(c.getName(), m.getName());
+        m_includedMethods.put(methodName, m);
+      }
+    }
+  }
+
+  /**
+   * @return Returns the excludedGroups.
+   */
+  public Map<String, String> getExcludedGroups() {
+    return m_excludedGroups;
+  }
+
+  /**
+   * @return Returns the includedGroups.
+   */
+  public Map<String, String> getIncludedGroups() {
+    return m_includedGroups;
+  }
+
+  /**
+   * @param excludedGroups The excludedGroups to set.
+   */
+  public void setExcludedGroups(Map<String, String> excludedGroups) {
+    m_excludedGroups = excludedGroups;
+  }
+
+  /**
+   * @param includedGroups The includedGroups to set.
+   */
+  public void setIncludedGroups(Map<String, String> includedGroups) {
+    m_includedGroups = includedGroups;
+  }
+
+  private static boolean isIncluded(String[] groups, Collection<String> includedGroups) {
+    if (includedGroups.size() == 0) {
+      return true;
+    }
+    else {
+      return isMemberOf(groups, includedGroups);
+    }
+  }
+
+  private static boolean isExcluded(String[] groups, Collection<String> excludedGroups) {
+    return isMemberOf(groups, excludedGroups);
+  }
+
+  /**
+   *
+   * @param groups Array of groups on the method
+   * @param list Map of regexps of groups to be run
+   */
+  private static boolean isMemberOf(String[] groups, Collection<String> list) {
+    for (String group : groups) {
+      for (Object o : list) {
+        String regexpStr = o.toString();
+        boolean match = Pattern.matches(regexpStr, group);
+        if (match) {
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+
+  private static void log(int level, String s) {
+    Utils.log("XmlMethodSelector", level, s);
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[XmlMethodSelector] " + s);
+  }
+
+  public void setExpression(String expression) {
+    m_expression = expression;
+  }
+
+  private boolean m_isInitialized = false;
+  private List<ITestNGMethod> m_testMethods = null;
+
+  @Override
+  public void setTestMethods(List<ITestNGMethod> testMethods) {
+    // Caution: this variable is initialized with an empty list first and then modified
+    // externally by the caller (TestRunner#fixMethodWithClass). Ugly.
+    m_testMethods = testMethods;
+  }
+
+  private void init(IMethodSelectorContext context) {
+    String[] groups = m_includedGroups.keySet().toArray(new String[m_includedGroups.size()]);
+    Set<String> groupClosure = new HashSet<>();
+    Set<ITestNGMethod> methodClosure = new HashSet<>();
+
+    List<ITestNGMethod> includedMethods = Lists.newArrayList();
+    for (ITestNGMethod m : m_testMethods) {
+      if (includeMethod(context, m, true)) {
+        includedMethods.add(m);
+      }
+    }
+    MethodGroupsHelper.findGroupTransitiveClosure(this, includedMethods, m_testMethods,
+        groups, groupClosure, methodClosure);
+
+    // If we are asked to include or exclude specific groups, calculate
+    // the transitive closure of all the included groups.  If no include groups
+    // were specified, don't do anything.
+    // Any group that is part of the transitive closure but not part of
+    // m_includedGroups is being added implicitly by TestNG so that if someone
+    // includes a group z that depends on a, b and c, they don't need to
+    // include a, b and c explicitly.
+    if (m_includedGroups.size() > 0) {
+      // Make the transitive closure our new included groups
+      for (String g : groupClosure) {
+        log(4, "Including group "
+            + (m_includedGroups.containsKey(g) ?
+                ": " : "(implicitly): ") + g);
+        m_includedGroups.put(g, g);
+      }
+
+      // Make the transitive closure our new included methods
+      for (ITestNGMethod m : methodClosure) {
+        String methodName =
+         m.getMethod().getDeclaringClass().getName() + "." + m.getMethodName();
+//        m_includedMethods.add(methodName);
+        List<XmlInclude> includeList = m_includedMethods.get(methodName);
+        XmlInclude xi = new XmlInclude(methodName);
+        // TODO: set the XmlClass on this xi or we won't get inheritance of parameters
+        m_includedMethods.put(methodName, xi);
+        logInclusion("Including", "method ", methodName);
+      }
+    }
+  }
+}
diff --git a/src/main/java/org/testng/internal/Yaml.java b/src/main/java/org/testng/internal/Yaml.java
new file mode 100644
index 0000000..11ecd25
--- /dev/null
+++ b/src/main/java/org/testng/internal/Yaml.java
@@ -0,0 +1,278 @@
+package org.testng.internal;
+
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlInclude;
+import org.testng.xml.XmlPackage;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+import org.yaml.snakeyaml.TypeDescription;
+import org.yaml.snakeyaml.constructor.Constructor;
+import org.yaml.snakeyaml.nodes.Node;
+import org.yaml.snakeyaml.nodes.NodeId;
+import org.yaml.snakeyaml.nodes.ScalarNode;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * YAML support for TestNG.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public class Yaml {
+
+  public static XmlSuite parse(String filePath, InputStream is)
+      throws FileNotFoundException {
+    Constructor constructor = new TestNGConstructor(XmlSuite.class);
+    {
+      TypeDescription suiteDescription = new TypeDescription(XmlSuite.class);
+      suiteDescription.putListPropertyType("packages", XmlPackage.class);
+      suiteDescription.putListPropertyType("listeners", String.class);
+      suiteDescription.putListPropertyType("tests", XmlTest.class);
+      suiteDescription.putListPropertyType("method-selectors", XmlMethodSelector.class);
+      constructor.addTypeDescription(suiteDescription);
+    }
+
+    {
+      TypeDescription testDescription = new TypeDescription(XmlTest.class);
+      testDescription.putListPropertyType("classes", XmlClass.class);
+      testDescription.putMapPropertyType("metaGroups", String.class, List.class);
+      testDescription.putListPropertyType("method-selectors", XmlMethodSelector.class);
+      constructor.addTypeDescription(testDescription);
+    }
+
+    org.yaml.snakeyaml.Yaml y = new org.yaml.snakeyaml.Yaml(constructor);
+    if (is == null) is = new FileInputStream(new File(filePath));
+    XmlSuite result = (XmlSuite) y.load(is);
+
+    result.setFileName(filePath);
+    // DEBUG
+//    System.out.println("[Yaml] " + result.toXml());
+
+    // Adjust XmlTest parents and indices
+    for (XmlTest t : result.getTests()) {
+      t.setSuite(result);
+      int index = 0;
+      for (XmlClass c : t.getClasses()) {
+        c.setIndex(index++);
+      }
+    }
+
+    return result;
+  }
+
+  private static void maybeAdd(StringBuilder sb, String key, Object value, Object def) {
+    maybeAdd(sb, "", key, value, def);
+  }
+
+  private static void maybeAdd(StringBuilder sb, String sp, String key, Object value, Object def) {
+    if (value != null && ! value.equals(def)) {
+      sb.append(sp).append(key).append(": ").append(value.toString()).append("\n");
+    }
+  }
+
+  /**
+   * The main entry point to convert an XmlSuite into YAML. This method is allowed to be used
+   * by external tools (e.g. Eclipse).
+   */
+  public static StringBuilder toYaml(XmlSuite suite) {
+    StringBuilder result = new StringBuilder();
+
+    maybeAdd(result, "name", suite.getName(), null);
+    maybeAdd(result, "junit", suite.isJUnit(), XmlSuite.DEFAULT_JUNIT);
+    maybeAdd(result, "verbose", suite.getVerbose(), XmlSuite.DEFAULT_VERBOSE);
+    maybeAdd(result, "threadCount", suite.getThreadCount(), XmlSuite.DEFAULT_THREAD_COUNT);
+    maybeAdd(result, "dataProviderThreadCount", suite.getDataProviderThreadCount(),
+        XmlSuite.DEFAULT_DATA_PROVIDER_THREAD_COUNT);
+    maybeAdd(result, "timeOut", suite.getTimeOut(), null);
+    maybeAdd(result, "parallel", suite.getParallel(), XmlSuite.DEFAULT_PARALLEL);
+    maybeAdd(result, "skipFailedInvocationCounts", suite.skipFailedInvocationCounts(),
+        XmlSuite.DEFAULT_SKIP_FAILED_INVOCATION_COUNTS);
+
+    toYaml(result, "parameters", "", suite.getParameters());
+    toYaml(result, suite.getPackages());
+
+    if (suite.getListeners().size() > 0) {
+      result.append("listeners:\n");
+      toYaml(result, "  ", suite.getListeners());
+    }
+
+    if (suite.getPackages().size() > 0) {
+      result.append("packages:\n");
+      toYaml(result, suite.getPackages());
+    }
+    if (suite.getTests().size() > 0) {
+      result.append("tests:\n");
+      for (XmlTest t : suite.getTests()) {
+        toYaml(result, "  ", t);
+      }
+    }
+
+    if (suite.getChildSuites().size() > 0) {
+      result.append("suite-files:\n");
+      toYaml(result, "  ", suite.getSuiteFiles());
+    }
+
+    return result;
+  }
+
+  private static void toYaml(StringBuilder result, String sp, XmlTest t) {
+    String sp2 = sp + "  ";
+    result.append(sp).append("- name: ").append(t.getName()).append("\n");
+
+    maybeAdd(result, sp2, "junit", t.isJUnit(), XmlSuite.DEFAULT_JUNIT);
+    maybeAdd(result, sp2, "verbose", t.getVerbose(), XmlSuite.DEFAULT_VERBOSE);
+    maybeAdd(result, sp2, "timeOut", t.getTimeOut(), null);
+    maybeAdd(result, sp2, "parallel", t.getParallel(), XmlSuite.DEFAULT_PARALLEL);
+    maybeAdd(result, sp2, "skipFailedInvocationCounts", t.skipFailedInvocationCounts(),
+        XmlSuite.DEFAULT_SKIP_FAILED_INVOCATION_COUNTS);
+
+    maybeAdd(result, "preserveOrder", sp2, t.getPreserveOrder(), XmlSuite.DEFAULT_PRESERVE_ORDER);
+
+    toYaml(result, "parameters", sp2, t.getTestParameters());
+
+    if (t.getIncludedGroups().size() > 0) {
+      result.append(sp2).append("includedGroups: [ ")
+          .append(Utils.join(t.getIncludedGroups(), ","))
+          .append(" ]\n");
+    }
+
+    if (t.getExcludedGroups().size() > 0) {
+      result.append(sp2).append("excludedGroups: [ ")
+          .append(Utils.join(t.getExcludedGroups(), ","))
+          .append(" ]\n");
+    }
+
+    Map<String, List<String>> mg = t.getMetaGroups();
+    if (mg.size() > 0) {
+      result.append(sp2).append("metaGroups: { ");
+      boolean first = true;
+      for (Map.Entry<String, List<String>> entry : mg.entrySet()) {
+        if (! first) result.append(", ");
+        result.append(entry.getKey()).append(": [ ")
+        .append(Utils.join(entry.getValue(), ",")).append(" ] ");
+        first = false;
+      }
+      result.append(" }\n");
+    }
+
+    if (t.getXmlPackages().size() > 0) {
+      result.append(sp2).append("xmlPackages:\n");
+      for (XmlPackage xp : t.getXmlPackages())  {
+        toYaml(result, sp2 + "  - ", xp);
+      }
+    }
+
+    if (t.getXmlClasses().size() > 0) {
+      result.append(sp2).append("classes:\n");
+      for (XmlClass xc : t.getXmlClasses())  {
+        toYaml(result, sp2 + "  ", xc);
+      }
+    }
+
+
+    result.append("\n");
+  }
+
+  private static void toYaml(StringBuilder result, String sp2, XmlClass xc) {
+    List<XmlInclude> im = xc.getIncludedMethods();
+    List<String> em = xc.getExcludedMethods();
+    String name = im.size() > 0 || em.size() > 0 ? "name: " : "";
+
+    result.append(sp2).append("- " + name).append(xc.getName()).append("\n");
+    if (im.size() > 0) {
+      result.append(sp2 + "  includedMethods:\n");
+      for (XmlInclude xi : im) {
+        toYaml(result, sp2 + "    ", xi);
+      }
+    }
+
+    if (em.size() > 0) {
+      result.append(sp2 + "  excludedMethods:\n");
+      toYaml(result, sp2 + "    ", em);
+    }
+  }
+
+  private static void toYaml(StringBuilder result, String sp2, XmlInclude xi) {
+    result.append(sp2 + "- " + xi.getName()).append("\n");
+  }
+
+  private static void toYaml(StringBuilder result, String sp, List<String> strings) {
+    for (String l : strings) {
+      result.append(sp).append("- ").append(l).append("\n");
+    }
+  }
+
+  private static final String SP = "  ";
+
+  private static void toYaml(StringBuilder sb, List<XmlPackage> packages) {
+    if (packages.size() > 0) {
+      sb.append("packages:\n");
+      for (XmlPackage p : packages) {
+        toYaml(sb, "  ", p);
+      }
+    }
+    for (XmlPackage p : packages) {
+      toYaml(sb, "  ", p);
+    }
+  }
+
+  private static void toYaml(StringBuilder sb, String sp, XmlPackage p) {
+    sb.append(sp).append("name: ").append(p.getName()).append("\n");
+
+    generateIncludeExclude(sb, sp, "includes", p.getInclude());
+    generateIncludeExclude(sb, sp, "excludes", p.getExclude());
+  }
+
+  private static void generateIncludeExclude(StringBuilder sb, String sp,
+      String key, List<String> includes) {
+    if (includes.size() > 0) {
+      sb.append(sp).append("  ").append(key).append("\n");
+      for (String inc : includes) {
+        sb.append(sp).append("    ").append(inc);
+      }
+    }
+  }
+
+  private static void mapToYaml(Map<String, String> map, StringBuilder out) {
+    if (map.size() > 0) {
+      out.append("{ ");
+      boolean first = true;
+      for (Map.Entry<String, String> e : map.entrySet()) {
+        if (! first) out.append(", ");
+        first = false;
+        out.append(e.getKey() + ": " + e.getValue());
+      }
+      out.append(" }\n");
+    }
+  }
+
+  private static void toYaml(StringBuilder sb, String key, String sp,
+      Map<String, String> parameters) {
+    if (parameters.size() > 0) {
+      sb.append(sp).append(key).append(": ");
+      mapToYaml(parameters, sb);
+    }
+  }
+
+  private static class TestNGConstructor extends Constructor {
+    public TestNGConstructor(Class<? extends Object> theRoot) {
+      super(theRoot);
+      yamlClassConstructors.put(NodeId.scalar, new ConstructParallelMode());
+    }
+
+    private class ConstructParallelMode extends ConstructScalar {
+      public Object construct(Node node) {
+        if (node.getType().equals(XmlSuite.ParallelMode.class)) {
+          String parallel = (String) constructScalar((ScalarNode) node);
+          return XmlSuite.ParallelMode.getValidParallel(parallel);
+        }
+        return super.construct(node);
+      }
+    }
+  }
+}
diff --git a/src/main/java/org/testng/internal/YamlParser.java b/src/main/java/org/testng/internal/YamlParser.java
new file mode 100644
index 0000000..23f9ef2
--- /dev/null
+++ b/src/main/java/org/testng/internal/YamlParser.java
@@ -0,0 +1,27 @@
+package org.testng.internal;
+
+import org.testng.TestNGException;
+import org.testng.xml.ISuiteParser;
+import org.testng.xml.XmlSuite;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+public class YamlParser implements ISuiteParser {
+
+  @Override
+  public XmlSuite parse(String filePath, InputStream is, boolean loadClasses)
+      throws TestNGException {
+    try {
+      return Yaml.parse(filePath, is);
+    } catch (FileNotFoundException e) {
+      throw new TestNGException(e);
+    }
+  }
+
+  @Override
+  public boolean accept(String fileName) {
+    return fileName.endsWith(".yaml");
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/AfterSuiteAnnotation.java b/src/main/java/org/testng/internal/annotations/AfterSuiteAnnotation.java
new file mode 100755
index 0000000..6e0be6f
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/AfterSuiteAnnotation.java
@@ -0,0 +1,5 @@
+package org.testng.internal.annotations;
+
+public class AfterSuiteAnnotation extends BaseBeforeAfter {
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/AnnotationHelper.java b/src/main/java/org/testng/internal/annotations/AnnotationHelper.java
new file mode 100755
index 0000000..f08490b
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/AnnotationHelper.java
@@ -0,0 +1,321 @@
+package org.testng.internal.annotations;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Map;
+
+import org.testng.ITestNGMethod;
+import org.testng.annotations.IAnnotation;
+import org.testng.annotations.IConfigurationAnnotation;
+import org.testng.annotations.IDataProviderAnnotation;
+import org.testng.annotations.IExpectedExceptionsAnnotation;
+import org.testng.annotations.IFactoryAnnotation;
+import org.testng.annotations.IParametersAnnotation;
+import org.testng.annotations.ITestAnnotation;
+import org.testng.collections.Maps;
+import org.testng.internal.TestNGMethod;
+import org.testng.internal.Utils;
+import org.testng.xml.XmlTest;
+
+/**
+ * Helper methods to find @Test and @Configuration tags.  They minimize
+ * the amount of casting we need to do.
+ *
+ * Created on Dec 20, 2005
+ * @author cbeust
+ */
+public class AnnotationHelper {
+
+  public static ITestAnnotation findTest(IAnnotationFinder finder, Class<?> cls) {
+    return finder.findAnnotation(cls, ITestAnnotation.class);
+  }
+
+  public static ITestAnnotation findTest(IAnnotationFinder finder, Method m) {
+    return finder.findAnnotation(m, ITestAnnotation.class);
+  }
+
+  public static ITestAnnotation findTest(IAnnotationFinder finder, ITestNGMethod m) {
+    return finder.findAnnotation(m, ITestAnnotation.class);
+  }
+
+  public static IFactoryAnnotation findFactory(IAnnotationFinder finder, Method m) {
+    return finder.findAnnotation(m, IFactoryAnnotation.class);
+  }
+
+  public static IFactoryAnnotation findFactory(IAnnotationFinder finder, Constructor c) {
+    return finder.findAnnotation(c, IFactoryAnnotation.class);
+  }
+
+  public static ITestAnnotation findTest(IAnnotationFinder finder, Constructor ctor) {
+    return finder.findAnnotation(ctor, ITestAnnotation.class);
+  }
+
+  public static IConfigurationAnnotation findConfiguration(IAnnotationFinder finder, Constructor ctor) {
+    IConfigurationAnnotation result = finder.findAnnotation(ctor, IConfigurationAnnotation.class);
+    if (result == null) {
+      IConfigurationAnnotation bs = (IConfigurationAnnotation) finder.findAnnotation(ctor, IBeforeSuite.class);
+      IConfigurationAnnotation as = (IConfigurationAnnotation) finder.findAnnotation(ctor, IAfterSuite.class);
+      IConfigurationAnnotation bt = (IConfigurationAnnotation) finder.findAnnotation(ctor, IBeforeTest.class);
+      IConfigurationAnnotation at = (IConfigurationAnnotation) finder.findAnnotation(ctor, IAfterTest.class);
+      IConfigurationAnnotation bg = (IConfigurationAnnotation) finder.findAnnotation(ctor, IBeforeGroups.class);
+      IConfigurationAnnotation ag = (IConfigurationAnnotation) finder.findAnnotation(ctor, IAfterGroups.class);
+      IConfigurationAnnotation bc = (IConfigurationAnnotation) finder.findAnnotation(ctor, IBeforeClass.class);
+      IConfigurationAnnotation ac = (IConfigurationAnnotation) finder.findAnnotation(ctor, IAfterClass.class);
+      IConfigurationAnnotation bm = (IConfigurationAnnotation) finder.findAnnotation(ctor, IBeforeMethod.class);
+      IConfigurationAnnotation am = (IConfigurationAnnotation) finder.findAnnotation(ctor, IAfterMethod.class);
+
+      if (bs != null || as != null || bt != null || at != null || bg != null || ag != null
+          || bc != null || ac != null || bm != null || am != null)
+      {
+        result = createConfiguration(bs, as, bt, at, bg, ag, bc, ac, bm, am);
+      }
+    }
+
+    return result;
+  }
+
+  public static IConfigurationAnnotation findConfiguration(IAnnotationFinder finder, Method m) {
+    IConfigurationAnnotation result = finder.findAnnotation(m, IConfigurationAnnotation.class);
+    if (result == null) {
+      IConfigurationAnnotation bs = (IConfigurationAnnotation) finder.findAnnotation(m, IBeforeSuite.class);
+      IConfigurationAnnotation as = (IConfigurationAnnotation) finder.findAnnotation(m, IAfterSuite.class);
+      IConfigurationAnnotation bt = (IConfigurationAnnotation) finder.findAnnotation(m, IBeforeTest.class);
+      IConfigurationAnnotation at = (IConfigurationAnnotation) finder.findAnnotation(m, IAfterTest.class);
+      IConfigurationAnnotation bg = (IConfigurationAnnotation) finder.findAnnotation(m, IBeforeGroups.class);
+      IConfigurationAnnotation ag = (IConfigurationAnnotation) finder.findAnnotation(m, IAfterGroups.class);
+      IConfigurationAnnotation bc = (IConfigurationAnnotation) finder.findAnnotation(m, IBeforeClass.class);
+      IConfigurationAnnotation ac = (IConfigurationAnnotation) finder.findAnnotation(m, IAfterClass.class);
+      IConfigurationAnnotation bm = (IConfigurationAnnotation) finder.findAnnotation(m, IBeforeMethod.class);
+      IConfigurationAnnotation am = (IConfigurationAnnotation) finder.findAnnotation(m, IAfterMethod.class);
+
+      if (bs != null || as != null || bt != null || at != null || bg != null || ag != null
+          || bc != null || ac != null || bm != null || am != null)
+      {
+        result = createConfiguration(bs, as, bt, at, bg, ag, bc, ac, bm, am);
+      }
+    }
+
+    return result;
+  }
+
+  private static IConfigurationAnnotation createConfiguration(IConfigurationAnnotation bs, IConfigurationAnnotation as,
+      IConfigurationAnnotation bt, IConfigurationAnnotation at, IConfigurationAnnotation bg, IConfigurationAnnotation ag,
+      IConfigurationAnnotation bc, IConfigurationAnnotation ac, IConfigurationAnnotation bm, IConfigurationAnnotation am)
+  {
+    ConfigurationAnnotation result = new ConfigurationAnnotation();
+
+    if (bs != null) {
+      result.setBeforeSuite(true);
+      finishInitialize(result, bs);
+    }
+    if (as != null) {
+      result.setAfterSuite(true);
+      finishInitialize(result, as);
+    }
+    if (bt != null) {
+      result.setBeforeTest(true);
+      finishInitialize(result, bt);
+    }
+    if (at != null) {
+      result.setAfterTest(true);
+      finishInitialize(result, at);
+    }
+    if (bg != null) {
+      result.setBeforeGroups(bg.getBeforeGroups());
+      finishInitialize(result, bg);
+    }
+    if (ag != null) {
+      result.setAfterGroups(ag.getAfterGroups());
+      finishInitialize(result, ag);
+    }
+    if (bc != null) {
+      result.setBeforeTestClass(true);
+      finishInitialize(result, bc);
+    }
+    if (ac != null) {
+      result.setAfterTestClass(true);
+      finishInitialize(result, ac);
+    }
+    if (bm != null) {
+      result.setBeforeTestMethod(true);
+      finishInitialize(result, bm);
+    }
+    if (am != null) {
+      result.setAfterTestMethod(true);
+      finishInitialize(result, am);
+    }
+
+    return result;
+  }
+
+  @SuppressWarnings({"deprecation"})
+  private static void finishInitialize(ConfigurationAnnotation result, IConfigurationAnnotation bs) {
+    result.setFakeConfiguration(true);
+    result.setAlwaysRun(bs.getAlwaysRun());
+    result.setDependsOnGroups(bs.getDependsOnGroups());
+    result.setDependsOnMethods(bs.getDependsOnMethods());
+    result.setDescription(bs.getDescription());
+    result.setEnabled(bs.getEnabled());
+    result.setGroups(bs.getGroups());
+    result.setInheritGroups(bs.getInheritGroups());
+    result.setParameters(bs.getParameters());
+    result.setTimeOut(bs.getTimeOut());
+  }
+
+  private static final Class[] ALL_ANNOTATIONS = new Class[] {
+    ITestAnnotation.class, IConfigurationAnnotation.class,
+    IBeforeClass.class, IAfterClass.class,
+    IBeforeMethod.class, IAfterMethod.class,
+    IDataProviderAnnotation.class, IExpectedExceptionsAnnotation.class,
+    IFactoryAnnotation.class, IParametersAnnotation.class,
+    IBeforeSuite.class, IAfterSuite.class,
+    IBeforeTest.class, IAfterTest.class,
+    IBeforeGroups.class, IAfterGroups.class
+  };
+
+  public static final Class[] CONFIGURATION_CLASSES = new Class[] {
+    IConfigurationAnnotation.class,
+    IBeforeSuite.class, IAfterSuite.class,
+    IBeforeTest.class, IAfterTest.class,
+    IBeforeGroups.class, IAfterGroups.class,
+    IBeforeClass.class, IAfterClass.class,
+    IBeforeMethod.class, IAfterMethod.class
+  };
+
+  public static Class[] getAllAnnotations() {
+    return ALL_ANNOTATIONS;
+  }
+
+  /**
+   * Delegation method for creating the list of <CODE>ITestMethod</CODE>s to be
+   * analysed.
+   */
+  public static ITestNGMethod[] findMethodsWithAnnotation(Class<?> rootClass,
+      Class<? extends IAnnotation> annotationClass, IAnnotationFinder annotationFinder,
+      XmlTest xmlTest)
+  {
+    // Keep a map of the methods we saw so that we ignore a method in a superclass if it's
+    // already been seen in a child class
+    Map<String, ITestNGMethod> vResult = Maps.newHashMap();
+
+    try {
+      vResult = Maps.newHashMap();
+//    Class[] classes = rootClass.getTestClasses();
+      Class<?> cls = rootClass;
+
+      //
+      // If the annotation is on the class or superclass, it applies to all public methods
+      // except methods marked with @Configuration
+      //
+
+      //
+      // Otherwise walk through all the methods and keep those
+      // that have the annotation
+      //
+//    for (Class<?> cls : classes) {
+        while (null != cls) {
+          boolean hasClassAnnotation = isAnnotationPresent(annotationFinder, cls, annotationClass);
+          Method[] methods = cls.getDeclaredMethods();
+          for (Method m : methods) {
+            boolean hasMethodAnnotation = isAnnotationPresent(annotationFinder, m, annotationClass);
+            boolean hasTestNGAnnotation =
+              isAnnotationPresent(annotationFinder, m, IFactoryAnnotation.class) ||
+              isAnnotationPresent(annotationFinder, m, ITestAnnotation.class) ||
+              isAnnotationPresent(annotationFinder, m, CONFIGURATION_CLASSES);
+            boolean isPublic = Modifier.isPublic(m.getModifiers());
+            boolean isSynthetic = m.isSynthetic();            
+            if ((isPublic && hasClassAnnotation && !isSynthetic && (! hasTestNGAnnotation)) || hasMethodAnnotation) {
+
+              // Small hack to allow users to specify @Configuration classes even though
+              // a class-level @Test annotation is present.  In this case, don't count
+              // that method as a @Test
+              if (isAnnotationPresent(annotationFinder, m, IConfigurationAnnotation.class) &&
+                  isAnnotationPresent(annotationFinder, cls, ITestAnnotation.class))
+              {
+                Utils.log("", 3, "Method " + m + " has a configuration annotation"
+                    + " and a class-level @Test. This method will only be kept as a"
+                    + " configuration method.");
+
+                continue;
+              }
+
+              // Skip the method if it has a return type
+              if (m.getReturnType() != void.class && ! xmlTest.getAllowReturnValues()) {
+                Utils.log("", 2, "Method " + m + " has a @Test annotation"
+                    + " but also a return value:"
+                    + " ignoring it. Use <suite allow-return-values=\"true\"> to fix this");
+                continue;
+              }
+
+              String key = createMethodKey(m);
+              if (null == vResult.get(key)) {
+                ITestNGMethod tm = new TestNGMethod(/* m.getDeclaringClass(), */ m,
+                    annotationFinder, xmlTest, null); /* @@@ */
+                vResult.put(key,tm);
+              }
+            }
+          } // for
+          // Now explore the superclass
+          cls = cls.getSuperclass();
+        } // while
+
+    }
+    catch (SecurityException e) {
+      e.printStackTrace();
+    }
+    ITestNGMethod[] result = vResult.values().toArray(new ITestNGMethod[vResult.size()]);
+
+  //    for (Method m : result) {
+  //      ppp("   METHOD FOUND: " + m);
+  //    }
+
+      return result;
+    }
+
+  public static Annotation findAnnotationSuperClasses(Class<?> annotationClass, Class c) {
+    while (c != null) {
+      Annotation result = c.getAnnotation(annotationClass);
+      if (result != null) return result;
+      else c = c.getSuperclass();
+    }
+    return null;
+  }
+
+  private static boolean isAnnotationPresent(IAnnotationFinder annotationFinder,
+      Method m, Class[] annotationClasses)
+  {
+    for (Class a : annotationClasses) {
+      if (annotationFinder.findAnnotation(m, a) != null) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  private static boolean isAnnotationPresent(IAnnotationFinder annotationFinder, Method m,
+      Class<? extends IAnnotation> annotationClass) {
+    return annotationFinder.findAnnotation(m, annotationClass) != null;
+  }
+
+  private static boolean isAnnotationPresent(IAnnotationFinder annotationFinder, Class<?> cls,
+      Class<? extends IAnnotation> annotationClass) {
+    return annotationFinder.findAnnotation(cls, annotationClass) != null;
+  }
+
+  /**
+   * @return A hashcode representing the name of this method and its parameters,
+   * but without its class
+   */
+  private static String createMethodKey(Method m) {
+    StringBuffer result = new StringBuffer(m.getName());
+    for (Class paramClass : m.getParameterTypes()) {
+      result.append(' ').append(paramClass.toString());
+    }
+
+    return result.toString();
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/BaseAnnotation.java b/src/main/java/org/testng/internal/annotations/BaseAnnotation.java
new file mode 100755
index 0000000..4f5bf49
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/BaseAnnotation.java
@@ -0,0 +1,30 @@
+package org.testng.internal.annotations;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+public class BaseAnnotation {
+  private Class m_testClass;
+  private Method m_method;
+  private Constructor m_constructor;
+
+  public Constructor getConstructor() {
+    return m_constructor;
+  }
+  public void setConstructor(Constructor constructor) {
+    m_constructor = constructor;
+  }
+  public Method getMethod() {
+    return m_method;
+  }
+  public void setMethod(Method method) {
+    m_method = method;
+  }
+  public Class getTestClass() {
+    return m_testClass;
+  }
+  public void setTestClass(Class testClass) {
+    m_testClass = testClass;
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/BaseBeforeAfter.java b/src/main/java/org/testng/internal/annotations/BaseBeforeAfter.java
new file mode 100755
index 0000000..d61b49e
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/BaseBeforeAfter.java
@@ -0,0 +1,74 @@
+package org.testng.internal.annotations;
+
+public class BaseBeforeAfter
+  extends TestOrConfiguration
+  implements IBaseBeforeAfter
+{
+  private String[] m_parameters = {};
+  private boolean m_alwaysRun = false;
+  private boolean m_inheritGroups = true;
+  private String[] m_beforeGroups = {};
+  private String[] m_afterGroups = {};
+  private String m_description;
+
+  /**
+   * @return the description
+   */
+  @Override
+  public String getDescription() {
+    return m_description;
+  }
+
+  /**
+   * @param description the description to set
+   */
+  @Override
+  public void setDescription(String description) {
+    m_description = description;
+  }
+
+  public void setAlwaysRun(boolean alwaysRun) {
+    m_alwaysRun = alwaysRun;
+  }
+
+  public void setInheritGroups(boolean inheritGroups) {
+    m_inheritGroups = inheritGroups;
+  }
+
+  @Override
+  public void setParameters(String[] parameters) {
+    m_parameters = parameters;
+  }
+
+  @Override
+  public String[] getParameters() {
+    return m_parameters;
+  }
+
+  @Override
+  public boolean getAlwaysRun() {
+    return m_alwaysRun;
+  }
+
+  @Override
+  public boolean getInheritGroups() {
+    return m_inheritGroups;
+  }
+
+  public String[] getAfterGroups() {
+    return m_afterGroups;
+  }
+
+  public void setAfterGroups(String[] afterGroups) {
+    m_afterGroups = afterGroups;
+  }
+
+  public String[] getBeforeGroups() {
+    return m_beforeGroups;
+  }
+
+  public void setBeforeGroups(String[] beforeGroups) {
+    m_beforeGroups = beforeGroups;
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/BeforeSuiteAnnotation.java b/src/main/java/org/testng/internal/annotations/BeforeSuiteAnnotation.java
new file mode 100755
index 0000000..5854d8d
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/BeforeSuiteAnnotation.java
@@ -0,0 +1,5 @@
+package org.testng.internal.annotations;
+
+public class BeforeSuiteAnnotation extends BaseBeforeAfter {
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/ConfigurationAnnotation.java b/src/main/java/org/testng/internal/annotations/ConfigurationAnnotation.java
new file mode 100755
index 0000000..b5f2bf6
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/ConfigurationAnnotation.java
@@ -0,0 +1,181 @@
+package org.testng.internal.annotations;
+
+import org.testng.annotations.IConfigurationAnnotation;
+
+
+/**
+ * An implementation of IConfiguration
+ *
+ * Created on Dec 16, 2005
+ * @author cbeust
+ */
+public class ConfigurationAnnotation extends TestOrConfiguration implements IConfigurationAnnotation,
+    IBeforeSuite, IAfterSuite,
+    IBeforeTest, IAfterTest,
+    IBeforeGroups, IAfterGroups,
+    IBeforeClass, IAfterClass,
+    IBeforeMethod, IAfterMethod {
+  private boolean m_beforeTestClass = false;
+  private boolean m_afterTestClass = false;
+  private boolean m_beforeTestMethod = false;
+  private boolean m_afterTestMethod = false;
+  private boolean m_beforeTest = false;
+  private boolean m_afterTest = false;
+  private boolean m_beforeSuite = false;
+  private boolean m_afterSuite = false;
+  private String[] m_parameters = {};
+  private boolean m_alwaysRun = false;
+  private boolean m_inheritGroups = true;
+  private String[] m_beforeGroups = {};
+  private String[] m_afterGroups = {};
+  private boolean m_isFakeConfiguration;
+  private boolean m_firstTimeOnly = false;
+  private boolean m_lastTimeOnly = false;
+
+  public ConfigurationAnnotation() {
+
+  }
+
+  public void setAfterSuite(boolean afterSuite) {
+    m_afterSuite = afterSuite;
+  }
+
+  public void setAfterTest(boolean afterTest) {
+    m_afterTest = afterTest;
+  }
+
+  public void setAfterTestClass(boolean afterTestClass) {
+    m_afterTestClass = afterTestClass;
+  }
+
+  public void setAfterTestMethod(boolean afterTestMethod) {
+    m_afterTestMethod = afterTestMethod;
+  }
+
+  public void setAlwaysRun(boolean alwaysRun) {
+    m_alwaysRun = alwaysRun;
+  }
+
+  public void setBeforeSuite(boolean beforeSuite) {
+    m_beforeSuite = beforeSuite;
+  }
+
+  public void setBeforeTest(boolean beforeTest) {
+    m_beforeTest = beforeTest;
+  }
+
+  public void setBeforeTestClass(boolean beforeTestClass) {
+    m_beforeTestClass = beforeTestClass;
+  }
+
+  public void setBeforeTestMethod(boolean beforeTestMethod) {
+    m_beforeTestMethod = beforeTestMethod;
+  }
+
+  public void setInheritGroups(boolean inheritGroups) {
+    m_inheritGroups = inheritGroups;
+  }
+
+  @Override
+  public void setParameters(String[] parameters) {
+    m_parameters = parameters;
+  }
+
+  @Override
+  public boolean getBeforeTestClass() {
+    return m_beforeTestClass;
+  }
+
+  @Override
+  public boolean getAfterTestClass() {
+    return m_afterTestClass;
+  }
+
+  @Override
+  public boolean getBeforeTestMethod() {
+    return m_beforeTestMethod;
+  }
+
+  @Override
+  public boolean getAfterTestMethod() {
+    return m_afterTestMethod;
+  }
+
+  @Override
+  public boolean getBeforeSuite() {
+    return m_beforeSuite;
+  }
+
+  @Override
+  public boolean getAfterSuite() {
+    return m_afterSuite;
+  }
+
+  @Override
+  public boolean getBeforeTest() {
+    return m_beforeTest;
+  }
+
+  @Override
+  public boolean getAfterTest() {
+    return m_afterTest;
+  }
+
+  @Override
+  public String[] getParameters() {
+    return m_parameters;
+  }
+
+  @Override
+  public boolean getAlwaysRun() {
+    return m_alwaysRun;
+  }
+
+  @Override
+  public boolean getInheritGroups() {
+    return m_inheritGroups;
+  }
+
+  @Override
+  public String[] getAfterGroups() {
+    return m_afterGroups;
+  }
+
+  public void setAfterGroups(String[] afterGroups) {
+    m_afterGroups = afterGroups;
+  }
+
+  @Override
+  public String[] getBeforeGroups() {
+    return m_beforeGroups;
+  }
+
+  public void setBeforeGroups(String[] beforeGroups) {
+    m_beforeGroups = beforeGroups;
+  }
+
+  public void setFakeConfiguration(boolean b) {
+    m_isFakeConfiguration = b;
+  }
+
+  @Override
+  public boolean isFakeConfiguration() {
+    return m_isFakeConfiguration;
+  }
+
+  public void setFirstTimeOnly(boolean f) {
+    m_firstTimeOnly = f;
+  }
+
+  public boolean isFirstTimeOnly() {
+    return m_firstTimeOnly;
+  }
+
+  public void setLastTimeOnly(boolean f) {
+    m_lastTimeOnly = f;
+  }
+
+  public boolean isLastTimeOnly() {
+    return m_lastTimeOnly;
+  }
+}
diff --git a/src/main/java/org/testng/internal/annotations/Converter.java b/src/main/java/org/testng/internal/annotations/Converter.java
new file mode 100755
index 0000000..206bd6b
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/Converter.java
@@ -0,0 +1,89 @@
+package org.testng.internal.annotations;
+
+import org.testng.collections.Lists;
+import org.testng.internal.ClassHelper;
+import org.testng.internal.Utils;
+
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * Convert a string values into primitive types.
+ *
+ * Created on Dec 20, 2005
+ * @author cbeust
+ */
+public class Converter {
+
+  public static boolean  getBoolean(String tagValue, boolean def) {
+    boolean result = def;
+    if (tagValue != null) {
+      result = Boolean.valueOf(tagValue);
+    }
+    return result;
+  }
+
+  public static int getInt(String tagValue, int def) {
+    int result = def;
+    if (tagValue != null) {
+      result = Integer.parseInt(tagValue);
+    }
+    return result;
+  }
+
+  public static String getString(String tagValue, String def) {
+    String result = def;
+    if (tagValue != null) {
+      result = tagValue;
+    }
+    return result;
+  }
+
+  public static long getLong(String tagValue, long def) {
+    long result = def;
+    if (tagValue != null) {
+      result = Long.parseLong(tagValue);
+    }
+    return result;
+  }
+
+  public static String[] getStringArray(String tagValue, String[] def) {
+    String[] result = def;
+    if (tagValue != null) {
+      result = Utils.stringToArray(tagValue);
+    }
+
+    return result;
+  }
+
+  public static Class[] getClassArray(String tagValue, Class[] def) {
+    Class[] result = def;
+    List vResult = Lists.newArrayList();
+    if (tagValue != null) {
+      StringTokenizer st = new StringTokenizer(tagValue, " ,");
+      while (st.hasMoreElements()) {
+        String className = (String) st.nextElement();
+        try {
+          Class cls = Class.forName(className);
+          vResult.add(cls);
+        }
+        catch (ClassNotFoundException e) {
+          e.printStackTrace();
+        }
+      }
+      result = (Class[]) vResult.toArray(new Class[vResult.size()]);
+    }
+
+    return result;
+  }
+
+  public static Class getClass(String namedParameter) {
+    Class result = null;
+    if (namedParameter != null) {
+      result = ClassHelper.forName(namedParameter);
+    }
+
+    return result;
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/DataProviderAnnotation.java b/src/main/java/org/testng/internal/annotations/DataProviderAnnotation.java
new file mode 100755
index 0000000..529f0a1
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/DataProviderAnnotation.java
@@ -0,0 +1,37 @@
+package org.testng.internal.annotations;
+
+import org.testng.annotations.IDataProviderAnnotation;
+
+/**
+ * An implementation of IDataProvider.
+ *
+ * Created on Dec 20, 2005
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public class DataProviderAnnotation
+  extends BaseAnnotation
+  implements IDataProviderAnnotation
+{
+  private String m_name;
+  private boolean m_parallel;
+
+  @Override
+  public boolean isParallel() {
+    return m_parallel;
+  }
+
+  @Override
+  public void setParallel(boolean parallel) {
+    m_parallel = parallel;
+  }
+
+  @Override
+  public String getName() {
+    return m_name;
+  }
+
+  @Override
+  public void setName(String name) {
+    m_name = name;
+  }
+}
diff --git a/src/main/java/org/testng/internal/annotations/DefaultAnnotationTransformer.java b/src/main/java/org/testng/internal/annotations/DefaultAnnotationTransformer.java
new file mode 100755
index 0000000..42d0591
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/DefaultAnnotationTransformer.java
@@ -0,0 +1,19 @@
+package org.testng.internal.annotations;
+
+import org.testng.IAnnotationTransformer;
+import org.testng.annotations.ITestAnnotation;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+public class DefaultAnnotationTransformer
+  implements IAnnotationTransformer
+{
+
+  @Override
+  public void transform(ITestAnnotation annotation, Class testClass,
+      Constructor testConstructor, Method testMethod)
+  {
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/ExpectedExceptionsAnnotation.java b/src/main/java/org/testng/internal/annotations/ExpectedExceptionsAnnotation.java
new file mode 100755
index 0000000..b95dbe9
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/ExpectedExceptionsAnnotation.java
@@ -0,0 +1,25 @@
+package org.testng.internal.annotations;
+
+import org.testng.annotations.IExpectedExceptionsAnnotation;
+
+/**
+ * An implementation of IExpectedExceptions
+ *
+ * Created on Dec 20, 2005
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public class ExpectedExceptionsAnnotation
+  extends BaseAnnotation
+  implements IExpectedExceptionsAnnotation
+{
+  private Class[] m_value = {};
+
+  @Override
+  public Class[] getValue() {
+    return m_value;
+  }
+
+  public void setValue(Class[] value) {
+    m_value = value;
+  }
+}
diff --git a/src/main/java/org/testng/internal/annotations/FactoryAnnotation.java b/src/main/java/org/testng/internal/annotations/FactoryAnnotation.java
new file mode 100755
index 0000000..f82467f
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/FactoryAnnotation.java
@@ -0,0 +1,58 @@
+package org.testng.internal.annotations;
+
+import org.testng.annotations.IFactoryAnnotation;
+
+/**
+ * An implementation of IFactory
+ *
+ * Created on Dec 20, 2005
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public class FactoryAnnotation
+  extends BaseAnnotation
+  implements IFactoryAnnotation
+{
+  private String[] m_parameters = {};
+  private String m_dataProvider = null;
+  private Class<?> m_dataProviderClass;
+  private boolean m_enabled = true;
+
+  @Override
+  public String getDataProvider() {
+    return m_dataProvider;
+  }
+
+  @Override
+  public void setDataProvider(String dataProvider) {
+    m_dataProvider = dataProvider;
+  }
+
+  @Override
+  public String[] getParameters() {
+    return m_parameters;
+  }
+
+  public void setParameters(String[] parameters) {
+    m_parameters = parameters;
+  }
+
+  public void setDataProviderClass(Class<?> dataProviderClass) {
+    m_dataProviderClass = dataProviderClass;
+  }
+
+  @Override
+  public Class<?> getDataProviderClass() {
+    return m_dataProviderClass;
+  }
+
+  @Override
+  public boolean getEnabled() {
+    return m_enabled;
+  }
+
+  @Override
+  public void setEnabled(boolean enabled) {
+    m_enabled = enabled;
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/IAfterClass.java b/src/main/java/org/testng/internal/annotations/IAfterClass.java
new file mode 100755
index 0000000..b9f3b17
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/IAfterClass.java
@@ -0,0 +1,5 @@
+package org.testng.internal.annotations;
+
+public interface IAfterClass extends IBaseBeforeAfter {
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/IAfterGroups.java b/src/main/java/org/testng/internal/annotations/IAfterGroups.java
new file mode 100755
index 0000000..ca21c4c
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/IAfterGroups.java
@@ -0,0 +1,5 @@
+package org.testng.internal.annotations;
+
+public interface IAfterGroups extends IBaseBeforeAfter {
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/IAfterMethod.java b/src/main/java/org/testng/internal/annotations/IAfterMethod.java
new file mode 100755
index 0000000..eb4b122
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/IAfterMethod.java
@@ -0,0 +1,5 @@
+package org.testng.internal.annotations;
+
+public interface IAfterMethod extends IBaseBeforeAfter {
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/IAfterSuite.java b/src/main/java/org/testng/internal/annotations/IAfterSuite.java
new file mode 100755
index 0000000..de53c7b
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/IAfterSuite.java
@@ -0,0 +1,5 @@
+package org.testng.internal.annotations;
+
+public interface IAfterSuite extends IBaseBeforeAfter {
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/IAfterTest.java b/src/main/java/org/testng/internal/annotations/IAfterTest.java
new file mode 100755
index 0000000..2d7f01b
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/IAfterTest.java
@@ -0,0 +1,5 @@
+package org.testng.internal.annotations;
+
+public interface IAfterTest extends IBaseBeforeAfter {
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/IAfterTests.java b/src/main/java/org/testng/internal/annotations/IAfterTests.java
new file mode 100755
index 0000000..39906ba
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/IAfterTests.java
@@ -0,0 +1,5 @@
+package org.testng.internal.annotations;
+
+public interface IAfterTests extends IBaseBeforeAfter {
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/IAnnotationFinder.java b/src/main/java/org/testng/internal/annotations/IAnnotationFinder.java
new file mode 100755
index 0000000..c3e932f
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/IAnnotationFinder.java
@@ -0,0 +1,61 @@
+package org.testng.internal.annotations;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+import org.testng.ITestNGMethod;
+import org.testng.annotations.IAnnotation;
+
+
+/**
+ * This interface defines how annotations are found on classes, methods
+ * and constructors.  It will be implemented by both JDK 1.4 and JDK 5
+ * annotation finders.
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public interface IAnnotationFinder {
+
+  /**
+   * @param cls
+   * @param annotationClass
+   * @return The annotation on the class or null if none found.
+   */
+  public <A extends IAnnotation> A findAnnotation(Class<?> cls, Class<A> annotationClass);
+
+  /**
+   * @param m
+   * @param annotationClass
+   * @return The annotation on the method.
+   * If not found, return the annotation on the declaring class.
+   * If not found, return null.
+   */
+  public <A extends IAnnotation> A findAnnotation(Method m, Class<A> annotationClass);
+  <A extends IAnnotation> A findAnnotation(ITestNGMethod m, Class<A> annotationClass);
+
+  /**
+   * @param cons
+   * @param annotationClass
+   * @return The annotation on the method.
+   * If not found, return the annotation on the declaring class.
+   * If not found, return null.
+   */
+  public <A extends IAnnotation> A findAnnotation(Constructor<?> cons, Class<A> annotationClass);
+
+  /**
+   * @return true if the ith parameter of the given method has the annotation @TestInstance.
+   */
+  public boolean hasTestInstance(Method method, int i);
+
+  /**
+   * @return the @Optional values of this method's parameters (<code>null</code>
+   * if the parameter isn't optional)
+   */
+  public String[] findOptionalValues(Method method);
+
+  /**
+   * @return the @Optional values of this method's parameters (<code>null</code>
+   * if the parameter isn't optional)
+   */
+  public String[] findOptionalValues(Constructor ctor);
+}
diff --git a/src/main/java/org/testng/internal/annotations/IAnnotationTransformer.java b/src/main/java/org/testng/internal/annotations/IAnnotationTransformer.java
new file mode 100755
index 0000000..141a2dc
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/IAnnotationTransformer.java
@@ -0,0 +1,8 @@
+package org.testng.internal.annotations;
+
+/**
+ * For backward compatibility.
+ */
+public interface IAnnotationTransformer extends org.testng.IAnnotationTransformer {
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/IBaseBeforeAfter.java b/src/main/java/org/testng/internal/annotations/IBaseBeforeAfter.java
new file mode 100755
index 0000000..6155db6
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/IBaseBeforeAfter.java
@@ -0,0 +1,69 @@
+package org.testng.internal.annotations;
+
+import org.testng.annotations.ITestOrConfiguration;
+
+/**
+ * Base interface for IBeforeSuite, IAfterSuite, etc...
+ *
+ * @author cbeust
+ * @since Jun 22, 2006
+ */
+public interface IBaseBeforeAfter extends ITestOrConfiguration {
+  /**
+   * Whether methods on this class/method are enabled.
+   */
+  public boolean getEnabled();
+
+  /**
+   * The list of groups this class/method belongs to.
+   */
+  public String[] getGroups();
+
+  /**
+   * The list of groups this method depends on.  Every method
+   * member of one of these groups is guaranteed to have been
+   * invoked before this method.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   */
+  public String[] getDependsOnGroups();
+
+  /**
+   * The list of methods this method depends on.  There is no guarantee
+   * on the order on which the methods depended upon will be run, but you
+   * are guaranteed that all these methods will be run before the test method
+   * that contains this annotation is run.  Furthermore, if any of these
+   * methods was not a SUCCESS, this test method will not be
+   * run and will be flagged as a SKIP.
+   *
+   *  If some of these methods have been overloaded, all the overloaded
+   *  versions will be run.
+   */
+  public String[] getDependsOnMethods();
+
+  /**
+   *  For before methods (beforeSuite, beforeTest, beforeTestClass and
+   *  beforeTestMethod, but not beforeGroups):
+   *  If set to true, this configuration method will be run
+   *  regardless of what groups it belongs to.
+   *  <br>
+   * For after methods (afterSuite, afterClass, ...):
+   *  If set to true, this configuration method will be run
+   *  even if one or more methods invoked previously failed or
+   *  was skipped.
+   */
+  public boolean getAlwaysRun();
+
+  /**
+   * If true, this &#64;Configuration method will belong to groups specified in the
+   * &#64;Test annotation on the class (if any).
+   */
+  public boolean getInheritGroups();
+
+  /**
+   * The description for this method.  The string used will appear in the
+   * HTML report and also on standard output if verbose >= 2.
+   */
+  public String getDescription();
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/IBeforeClass.java b/src/main/java/org/testng/internal/annotations/IBeforeClass.java
new file mode 100755
index 0000000..c4e477f
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/IBeforeClass.java
@@ -0,0 +1,5 @@
+package org.testng.internal.annotations;
+
+public interface IBeforeClass extends IBaseBeforeAfter {
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/IBeforeGroups.java b/src/main/java/org/testng/internal/annotations/IBeforeGroups.java
new file mode 100755
index 0000000..0080d61
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/IBeforeGroups.java
@@ -0,0 +1,5 @@
+package org.testng.internal.annotations;
+
+public interface IBeforeGroups extends IBaseBeforeAfter {
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/IBeforeMethod.java b/src/main/java/org/testng/internal/annotations/IBeforeMethod.java
new file mode 100755
index 0000000..00b9f9f
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/IBeforeMethod.java
@@ -0,0 +1,4 @@
+package org.testng.internal.annotations;
+
+public interface IBeforeMethod extends IBaseBeforeAfter {
+}
diff --git a/src/main/java/org/testng/internal/annotations/IBeforeSuite.java b/src/main/java/org/testng/internal/annotations/IBeforeSuite.java
new file mode 100755
index 0000000..b5d2fa9
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/IBeforeSuite.java
@@ -0,0 +1,5 @@
+package org.testng.internal.annotations;
+
+public interface IBeforeSuite extends IBaseBeforeAfter {
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/IBeforeTest.java b/src/main/java/org/testng/internal/annotations/IBeforeTest.java
new file mode 100755
index 0000000..de6417a
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/IBeforeTest.java
@@ -0,0 +1,5 @@
+package org.testng.internal.annotations;
+
+public interface IBeforeTest extends IBaseBeforeAfter {
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/IDataProvidable.java b/src/main/java/org/testng/internal/annotations/IDataProvidable.java
new file mode 100644
index 0000000..3570e3f
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/IDataProvidable.java
@@ -0,0 +1,14 @@
+package org.testng.internal.annotations;
+
+/**
+ * A trait shared by all the annotations that have dataProvider/dataProviderClass attributes.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public interface IDataProvidable {
+  public String getDataProvider();
+  public void setDataProvider(String v);
+
+  public Class<?> getDataProviderClass();
+  public void setDataProviderClass(Class<?> v);
+}
diff --git a/src/main/java/org/testng/internal/annotations/IListeners.java b/src/main/java/org/testng/internal/annotations/IListeners.java
new file mode 100644
index 0000000..e53772d
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/IListeners.java
@@ -0,0 +1,7 @@
+package org.testng.internal.annotations;
+
+import org.testng.annotations.IListenersAnnotation;
+
+public interface IListeners extends IListenersAnnotation {
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/ITest.java b/src/main/java/org/testng/internal/annotations/ITest.java
new file mode 100755
index 0000000..06f5c75
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/ITest.java
@@ -0,0 +1,7 @@
+package org.testng.internal.annotations;
+
+import org.testng.annotations.ITestAnnotation;
+
+public interface ITest extends ITestAnnotation {
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/JDK15AnnotationFinder.java b/src/main/java/org/testng/internal/annotations/JDK15AnnotationFinder.java
new file mode 100755
index 0000000..d1b9383
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/JDK15AnnotationFinder.java
@@ -0,0 +1,261 @@
+package org.testng.internal.annotations;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.testng.IAnnotationTransformer;
+import org.testng.IAnnotationTransformer2;
+import org.testng.IAnnotationTransformer3;
+import org.testng.ITestNGMethod;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterGroups;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeGroups;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Configuration;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.ExpectedExceptions;
+import org.testng.annotations.Factory;
+import org.testng.annotations.IAnnotation;
+import org.testng.annotations.IConfigurationAnnotation;
+import org.testng.annotations.IDataProviderAnnotation;
+import org.testng.annotations.IExpectedExceptionsAnnotation;
+import org.testng.annotations.IFactoryAnnotation;
+import org.testng.annotations.IListenersAnnotation;
+import org.testng.annotations.IObjectFactoryAnnotation;
+import org.testng.annotations.IParametersAnnotation;
+import org.testng.annotations.ITestAnnotation;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.ObjectFactory;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+import org.testng.annotations.TestInstance;
+import org.testng.internal.collections.Pair;
+
+/**
+ * This class implements IAnnotationFinder with JDK5 annotations
+ *
+ * Created on Dec 20, 2005
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public class JDK15AnnotationFinder implements IAnnotationFinder {
+  private JDK15TagFactory m_tagFactory = new JDK15TagFactory();
+  private Map<Class<? extends IAnnotation>, Class<? extends Annotation>> m_annotationMap =
+      new ConcurrentHashMap<>();
+  private Map<Pair<Annotation, ?>, IAnnotation> m_annotations = new ConcurrentHashMap<>();
+
+  private IAnnotationTransformer m_transformer = null;
+
+  @SuppressWarnings({"deprecation"})
+  public JDK15AnnotationFinder(IAnnotationTransformer transformer) {
+    m_transformer = transformer;
+    m_annotationMap.put(IListenersAnnotation.class, Listeners.class);
+    m_annotationMap.put(IConfigurationAnnotation.class, Configuration.class);
+    m_annotationMap.put(IDataProviderAnnotation.class, DataProvider.class);
+    m_annotationMap.put(IExpectedExceptionsAnnotation.class, ExpectedExceptions.class);
+    m_annotationMap.put(IFactoryAnnotation.class, Factory.class);
+    m_annotationMap.put(IObjectFactoryAnnotation.class, ObjectFactory.class);
+    m_annotationMap.put(IParametersAnnotation.class, Parameters.class);
+    m_annotationMap.put(ITestAnnotation.class, Test.class);
+    // internal
+    m_annotationMap.put(IBeforeSuite.class, BeforeSuite.class);
+    m_annotationMap.put(IAfterSuite.class, AfterSuite.class);
+    m_annotationMap.put(IBeforeTest.class, BeforeTest.class);
+    m_annotationMap.put(IAfterTest.class, AfterTest.class);
+    m_annotationMap.put(IBeforeClass.class, BeforeClass.class);
+    m_annotationMap.put(IAfterClass.class, AfterClass.class);
+    m_annotationMap.put(IBeforeGroups.class, BeforeGroups.class);
+    m_annotationMap.put(IAfterGroups.class, AfterGroups.class);
+    m_annotationMap.put(IBeforeMethod.class, BeforeMethod.class);
+    m_annotationMap.put(IAfterMethod.class, AfterMethod.class);
+  }
+
+  private <A extends Annotation> A findAnnotationInSuperClasses(Class<?> cls, Class<A> a) {
+    // Hack for @Listeners: we don't look in superclasses for this annotation
+    // because inheritance of this annotation causes aggregation instead of
+    // overriding
+    if (a.equals(org.testng.annotations.Listeners.class)) {
+      return cls.getAnnotation(a);
+    }
+    else {
+      while (cls != null) {
+        A result = cls.getAnnotation(a);
+        if (result != null) {
+          return result;
+        } else {
+          cls = cls.getSuperclass();
+        }
+      }
+    }
+
+    return null;
+  }
+
+  @Override
+  public <A extends IAnnotation> A findAnnotation(Method m, Class<A> annotationClass) {
+    final Class<? extends Annotation> a = m_annotationMap.get(annotationClass);
+    if (a == null) {
+      throw new IllegalArgumentException("Java @Annotation class for '"
+          + annotationClass + "' not found.");
+    }
+    Annotation annotation = m.getAnnotation(a);
+    return findAnnotation(m.getDeclaringClass(), annotation, annotationClass, null, null, m,
+        new Pair<>(annotation, m));
+  }
+
+  @Override
+  public <A extends IAnnotation> A findAnnotation(ITestNGMethod tm, Class<A> annotationClass) {
+    final Class<? extends Annotation> a = m_annotationMap.get(annotationClass);
+    if (a == null) {
+      throw new IllegalArgumentException("Java @Annotation class for '"
+            + annotationClass + "' not found.");
+    }
+    Method m = tm.getMethod();
+    Class<?> testClass;
+    if (tm.getInstance() == null) {
+      testClass = m.getDeclaringClass();
+    } else {
+      testClass = tm.getInstance().getClass();
+    }
+    Annotation annotation = m.getAnnotation(a);
+    if (annotation == null) {
+      annotation = testClass.getAnnotation(a);
+    }
+    return findAnnotation(testClass, annotation, annotationClass, null, null, m,
+        new Pair<>(annotation, m));
+  }
+
+  private void transform(IAnnotation a, Class<?> testClass,
+      Constructor<?> testConstructor, Method testMethod)  {
+    //
+    // Transform @Test
+    //
+    if (a instanceof ITestAnnotation) {
+      m_transformer.transform((ITestAnnotation) a, testClass, testConstructor, testMethod);
+    }
+
+    else if (m_transformer instanceof IAnnotationTransformer2) {
+      IAnnotationTransformer2 transformer2 = (IAnnotationTransformer2) m_transformer;
+
+      //
+      // Transform a configuration annotation
+      //
+      if (a instanceof IConfigurationAnnotation) {
+        IConfigurationAnnotation configuration = (IConfigurationAnnotation) a;
+        transformer2.transform(configuration,testClass, testConstructor, testMethod);
+      }
+
+      //
+      // Transform @DataProvider
+      //
+      else if (a instanceof IDataProviderAnnotation) {
+        transformer2.transform((IDataProviderAnnotation) a, testMethod);
+      }
+
+      //
+      // Transform @Factory
+      //
+      else if (a instanceof IFactoryAnnotation) {
+        transformer2.transform((IFactoryAnnotation) a, testMethod);
+      }
+
+      else if (m_transformer instanceof IAnnotationTransformer3) {
+        IAnnotationTransformer3 transformer = (IAnnotationTransformer3) m_transformer;
+
+        //
+        // Transform @Listeners
+        //
+        if (a instanceof IListenersAnnotation) {
+          transformer.transform((IListenersAnnotation)a, testClass);
+        }
+      } // End IAnnotationTransformer3
+    } // End IAnnotationTransformer2
+  }
+
+  @Override
+  public <A extends IAnnotation> A findAnnotation(Class<?> cls, Class<A> annotationClass) {
+    final Class<? extends Annotation> a = m_annotationMap.get(annotationClass);
+    if (a == null) {
+      throw new IllegalArgumentException("Java @Annotation class for '"
+          + annotationClass + "' not found.");
+    }
+    Annotation annotation = findAnnotationInSuperClasses(cls, a);
+    return findAnnotation(cls, annotation, annotationClass, cls, null, null,
+        new Pair<>(annotation, annotationClass));
+  }
+
+  @Override
+  public <A extends IAnnotation> A findAnnotation(Constructor<?> cons, Class<A> annotationClass) {
+    final Class<? extends Annotation> a = m_annotationMap.get(annotationClass);
+    if (a == null) {
+      throw new IllegalArgumentException("Java @Annotation class for '"
+          + annotationClass + "' not found.");
+    }
+    Annotation annotation = cons.getAnnotation(a);
+    return findAnnotation(cons.getDeclaringClass(), annotation, annotationClass, null, cons, null,
+        new Pair<>(annotation, cons));
+  }
+
+  private <A extends IAnnotation> A findAnnotation(Class cls, Annotation a,
+      Class<A> annotationClass, Class<?> testClass,
+      Constructor<?> testConstructor, Method testMethod, Pair<Annotation, ?> p) {
+    if (a == null) {
+      return null;
+    }
+
+    IAnnotation result = m_annotations.get(p);
+    if (result == null) {
+      result = m_tagFactory.createTag(cls, a, annotationClass, m_transformer);
+      m_annotations.put(p, result);
+      transform(result, testClass, testConstructor, testMethod);
+    }
+    //noinspection unchecked
+    return (A) result;
+  }
+
+  @Override
+  public boolean hasTestInstance(Method method, int i) {
+    final Annotation[][] annotations = method.getParameterAnnotations();
+    if (annotations.length > 0 && annotations[i].length > 0) {
+      final Annotation[] pa = annotations[i];
+      for (Annotation a : pa) {
+        if (a instanceof TestInstance) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public String[] findOptionalValues(Method method) {
+    return optionalValues(method.getParameterAnnotations());
+  }
+
+  @Override
+  public String[] findOptionalValues(Constructor method) {
+    return optionalValues(method.getParameterAnnotations());
+  }
+
+  private String[] optionalValues(Annotation[][] annotations) {
+    String[] result = new String[annotations.length];
+    for (int i = 0; i < annotations.length; i++) {
+      for (Annotation a : annotations[i]) {
+        if (a instanceof Optional) {
+          result[i] = ((Optional)a).value();
+          break;
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/src/main/java/org/testng/internal/annotations/JDK15TagFactory.java b/src/main/java/org/testng/internal/annotations/JDK15TagFactory.java
new file mode 100755
index 0000000..63d413c
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/JDK15TagFactory.java
@@ -0,0 +1,533 @@
+package org.testng.internal.annotations;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.testng.IAnnotationTransformer;
+import org.testng.TestNGException;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterGroups;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeGroups;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Configuration;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.ExpectedExceptions;
+import org.testng.annotations.Factory;
+import org.testng.annotations.IAnnotation;
+import org.testng.annotations.IConfigurationAnnotation;
+import org.testng.annotations.IDataProviderAnnotation;
+import org.testng.annotations.IExpectedExceptionsAnnotation;
+import org.testng.annotations.IFactoryAnnotation;
+import org.testng.annotations.IListenersAnnotation;
+import org.testng.annotations.IObjectFactoryAnnotation;
+import org.testng.annotations.IParametersAnnotation;
+import org.testng.annotations.ITestAnnotation;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+import org.testng.internal.Utils;
+
+/**
+ * This class creates implementations of IAnnotations based on the JDK5
+ * annotation that was found on the Java element.
+ *
+ * Created on Dec 20, 2005
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public class JDK15TagFactory {
+
+  public <A extends IAnnotation> A createTag(Class<?> cls, Annotation a,
+                                             Class<A> annotationClass, IAnnotationTransformer transformer) {
+    IAnnotation result = null;
+
+    if (a != null) {
+      if (annotationClass == IConfigurationAnnotation.class) {
+        result = createConfigurationTag(cls, a);
+      }
+      else if (annotationClass == IDataProviderAnnotation.class) {
+        result = createDataProviderTag(a);
+      }
+      else if (annotationClass == IExpectedExceptionsAnnotation.class) {
+        result = createExpectedExceptionsTag(a);
+      }
+      else if (annotationClass == IFactoryAnnotation.class) {
+        result = createFactoryTag(cls, a);
+      }
+      else if (annotationClass == IParametersAnnotation.class) {
+        result = createParametersTag(a);
+      }
+      else if (annotationClass == IObjectFactoryAnnotation.class) {
+        result = createObjectFactoryTag(a);
+      }
+      else if (annotationClass == ITestAnnotation.class) {
+        result = createTestTag(cls, a, transformer);
+      }
+      else if (annotationClass == IListenersAnnotation.class) {
+        result = createListenersTag(cls, a, transformer);
+      }
+      else if (annotationClass == IBeforeSuite.class || annotationClass == IAfterSuite.class ||
+          annotationClass == IBeforeTest.class || annotationClass == IAfterTest.class ||
+          annotationClass == IBeforeGroups.class || annotationClass == IAfterGroups.class ||
+          annotationClass == IBeforeClass.class || annotationClass == IAfterClass.class ||
+          annotationClass == IBeforeMethod.class || annotationClass == IAfterMethod.class)
+      {
+        result = maybeCreateNewConfigurationTag(cls, a, annotationClass);
+      }
+
+      else {
+        throw new TestNGException("Unknown annotation requested:" + annotationClass);
+      }
+    }
+
+    //noinspection unchecked
+    return (A) result;
+  }
+
+  private IAnnotation maybeCreateNewConfigurationTag(Class<?> cls, Annotation a,
+      Class<?> annotationClass)
+  {
+    IAnnotation result = null;
+
+    if (annotationClass == IBeforeSuite.class) {
+      BeforeSuite bs = (BeforeSuite) a;
+      result = createConfigurationTag(cls, a,
+          true, false,
+          false, false,
+          new String[0], new String[0],
+          false, false,
+          false, false,
+          bs.alwaysRun(),
+          bs.dependsOnGroups(), bs.dependsOnMethods(),
+          bs.description(), bs.enabled(), bs.groups(),
+          bs.inheritGroups(), null,
+          false, false,
+          bs.timeOut());
+    }
+    else if (annotationClass == IAfterSuite.class) {
+      AfterSuite bs = (AfterSuite) a;
+      result = createConfigurationTag(cls, a,
+          false, true,
+          false, false,
+          new String[0], new String[0],
+          false, false,
+          false, false,
+          bs.alwaysRun(),
+          bs.dependsOnGroups(), bs.dependsOnMethods(),
+          bs.description(), bs.enabled(), bs.groups(),
+          bs.inheritGroups(), null,
+          false, false,
+          bs.timeOut());
+    }
+    else if (annotationClass == IBeforeTest.class) {
+      BeforeTest bs = (BeforeTest) a;
+      result = createConfigurationTag(cls, a,
+          false, false,
+          true, false,
+          new String[0], new String[0],
+          false, false,
+          false, false,
+          bs.alwaysRun(),
+          bs.dependsOnGroups(), bs.dependsOnMethods(),
+          bs.description(), bs.enabled(), bs.groups(),
+          bs.inheritGroups(), null,
+          false, false,
+          bs.timeOut());
+    }
+    else if (annotationClass == IAfterTest.class) {
+      AfterTest bs = (AfterTest) a;
+      result = createConfigurationTag(cls, a,
+          false, false,
+          false, true,
+          new String[0], new String[0],
+          false, false,
+          false, false,
+          bs.alwaysRun(),
+          bs.dependsOnGroups(), bs.dependsOnMethods(),
+          bs.description(), bs.enabled(), bs.groups(),
+          bs.inheritGroups(), null,
+          false, false,
+          bs.timeOut());
+    }
+    else if (annotationClass == IBeforeGroups.class) {
+      BeforeGroups bs = (BeforeGroups) a;
+      final String[] groups= bs.value().length > 0 ? bs.value() : bs.groups();
+      result = createConfigurationTag(cls, a,
+          false, false,
+          false, false,
+          groups, new String[0],
+          false, false,
+          false, false,
+          bs.alwaysRun(),
+          bs.dependsOnGroups(), bs.dependsOnMethods(),
+          bs.description(), bs.enabled(), bs.groups(),
+          bs.inheritGroups(), null,
+          false, false,
+          bs.timeOut());
+    }
+    else if (annotationClass == IAfterGroups.class) {
+      AfterGroups bs = (AfterGroups) a;
+      final String[] groups= bs.value().length > 0 ? bs.value() : bs.groups();
+      result = createConfigurationTag(cls, a,
+          false, false,
+          false, false,
+          new String[0], groups,
+          false, false,
+          false, false,
+          bs.alwaysRun(),
+          bs.dependsOnGroups(), bs.dependsOnMethods(),
+          bs.description(), bs.enabled(), bs.groups(),
+          bs.inheritGroups(), null,
+          false, false,
+          bs.timeOut());
+    }
+    else if (annotationClass == IBeforeClass.class) {
+      BeforeClass bs = (BeforeClass) a;
+      result = createConfigurationTag(cls, a,
+          false, false,
+          false, false,
+          new String[0], new String[0],
+          true, false,
+          false, false,
+          bs.alwaysRun(),
+          bs.dependsOnGroups(), bs.dependsOnMethods(),
+          bs.description(), bs.enabled(), bs.groups(),
+          bs.inheritGroups(), null,
+          false, false,
+          bs.timeOut());
+    }
+    else if (annotationClass == IAfterClass.class) {
+      AfterClass bs = (AfterClass) a;
+      result = createConfigurationTag(cls, a,
+          false, false,
+          false, false,
+          new String[0], new String[0],
+          false, true,
+          false, false,
+          bs.alwaysRun(),
+          bs.dependsOnGroups(), bs.dependsOnMethods(),
+          bs.description(), bs.enabled(), bs.groups(),
+          bs.inheritGroups(), null,
+          false, false,
+          bs.timeOut());
+    }
+    else if (annotationClass == IBeforeMethod.class) {
+      BeforeMethod bs = (BeforeMethod) a;
+      result = createConfigurationTag(cls, a,
+          false, false,
+          false, false,
+          new String[0], new String[0],
+          false, false,
+          true, false,
+          bs.alwaysRun(),
+          bs.dependsOnGroups(), bs.dependsOnMethods(),
+          bs.description(), bs.enabled(), bs.groups(),
+          bs.inheritGroups(), null,
+          bs.firstTimeOnly(), false,
+          bs.timeOut());
+    }
+    else if (annotationClass == IAfterMethod.class) {
+      AfterMethod bs = (AfterMethod) a;
+      result = createConfigurationTag(cls, a,
+          false, false,
+          false, false,
+          new String[0], new String[0],
+          false, false,
+          false, true,
+          bs.alwaysRun(),
+          bs.dependsOnGroups(), bs.dependsOnMethods(),
+          bs.description(), bs.enabled(), bs.groups(),
+          bs.inheritGroups(), null,
+          false, bs.lastTimeOnly(),
+          bs.timeOut());
+    }
+
+    return result;
+  }
+
+  @SuppressWarnings({"deprecation"})
+  private ConfigurationAnnotation createConfigurationTag(Class<?> cls, Annotation a) {
+    ConfigurationAnnotation result = new ConfigurationAnnotation();
+    Configuration c = (Configuration) a;
+    result.setBeforeTestClass(c.beforeTestClass());
+    result.setAfterTestClass(c.afterTestClass());
+    result.setBeforeTestMethod(c.beforeTestMethod());
+    result.setAfterTestMethod(c.afterTestMethod());
+    result.setBeforeTest(c.beforeTest());
+    result.setAfterTest(c.afterTest());
+    result.setBeforeSuite(c.beforeSuite());
+    result.setAfterSuite(c.afterSuite());
+    result.setBeforeGroups(c.beforeGroups());
+    result.setAfterGroups(c.afterGroups());
+    result.setParameters(c.parameters());
+    result.setEnabled(c.enabled());
+
+    result.setGroups(join(c.groups(), findInheritedStringArray(cls, Test.class, "groups")));
+    result.setDependsOnGroups(c.dependsOnGroups());
+    result.setDependsOnMethods(c.dependsOnMethods());
+    result.setAlwaysRun(c.alwaysRun());
+    result.setInheritGroups(c.inheritGroups());
+    result.setDescription(c.description());
+
+    return result;
+  }
+
+  private IAnnotation createConfigurationTag(Class<?> cls, Annotation a,
+      boolean beforeSuite, boolean afterSuite,
+      boolean beforeTest, boolean afterTest,
+      String[] beforeGroups, String[] afterGroups,
+      boolean beforeClass, boolean afterClass,
+      boolean beforeMethod, boolean afterMethod,
+      boolean alwaysRun,
+      String[] dependsOnGroups, String[] dependsOnMethods,
+      String description, boolean enabled, String[] groups,
+      boolean inheritGroups, String[] parameters,
+      boolean firstTimeOnly, boolean lastTimeOnly,
+      long timeOut)
+  {
+    ConfigurationAnnotation result = new ConfigurationAnnotation();
+    result.setFakeConfiguration(true);
+    result.setBeforeSuite(beforeSuite);
+    result.setAfterSuite(afterSuite);
+    result.setBeforeTest(beforeTest);
+    result.setAfterTest(afterTest);
+    result.setBeforeTestClass(beforeClass);
+    result.setAfterTestClass(afterClass);
+    result.setBeforeGroups(beforeGroups);
+    result.setAfterGroups(afterGroups);
+    result.setBeforeTestMethod(beforeMethod);
+    result.setAfterTestMethod(afterMethod);
+
+    result.setAlwaysRun(alwaysRun);
+    result.setDependsOnGroups(dependsOnGroups);
+    result.setDependsOnMethods(dependsOnMethods);
+    result.setDescription(description);
+    result.setEnabled(enabled);
+    result.setGroups(groups);
+    result.setInheritGroups(inheritGroups);
+    result.setParameters(parameters);
+    result.setFirstTimeOnly(firstTimeOnly);
+    result.setLastTimeOnly(lastTimeOnly);
+    result.setTimeOut(timeOut);
+
+    return result;
+  }
+
+  private IAnnotation createDataProviderTag(Annotation a) {
+    DataProviderAnnotation result = new DataProviderAnnotation();
+    DataProvider c = (DataProvider) a;
+    result.setName(c.name());
+    result.setParallel(c.parallel());
+
+    return result;
+  }
+
+  @SuppressWarnings({"deprecation"})
+  private IAnnotation createExpectedExceptionsTag(Annotation a) {
+    ExpectedExceptionsAnnotation result = new ExpectedExceptionsAnnotation ();
+    ExpectedExceptions c = (ExpectedExceptions ) a;
+    result.setValue(c.value());
+
+    return result;
+  }
+
+  @SuppressWarnings({"deprecation"})
+  private IAnnotation createFactoryTag(Class<?> cls, Annotation a) {
+    FactoryAnnotation result = new FactoryAnnotation();
+    Factory c = (Factory) a;
+    result.setParameters(c.parameters());
+    result.setDataProvider(c.dataProvider());
+    result.setDataProviderClass(
+        findInherited(c.dataProviderClass(), cls, Factory.class, "dataProviderClass",
+            DEFAULT_CLASS));
+    result.setEnabled(c.enabled());
+
+    return result;
+  }
+
+  private IAnnotation createObjectFactoryTag(Annotation a) {
+    return new ObjectFactoryAnnotation();
+  }
+
+  private IAnnotation createParametersTag(Annotation a) {
+    ParametersAnnotation result = new ParametersAnnotation();
+    Parameters c = (Parameters) a;
+    result.setValue(c.value());
+
+    return result;
+  }
+
+  @SuppressWarnings({"deprecation"})
+  private IAnnotation createListenersTag(Class<?> cls, Annotation a,
+      IAnnotationTransformer transformer)
+  {
+    ListenersAnnotation result = new ListenersAnnotation();
+    Listeners l = (Listeners) a;
+    result.setValue(l.value());
+
+    return result;
+  }
+
+  @SuppressWarnings({"deprecation"})
+  private IAnnotation createTestTag(Class<?> cls, Annotation a,
+      IAnnotationTransformer transformer)
+  {
+    TestAnnotation result = new TestAnnotation();
+    Test test = (Test) a;
+
+    result.setEnabled(test.enabled());
+    result.setGroups(join(test.groups(), findInheritedStringArray(cls, Test.class, "groups")));
+    result.setParameters(test.parameters());
+    result.setDependsOnGroups(join(test.dependsOnGroups(),
+        findInheritedStringArray(cls, Test.class, "dependsOnGroups")));
+    result.setDependsOnMethods(join(test.dependsOnMethods(),
+        findInheritedStringArray(cls, Test.class, "dependsOnMethods")));
+    result.setTimeOut(test.timeOut());
+    result.setInvocationTimeOut(test.invocationTimeOut());
+    result.setInvocationCount(test.invocationCount());
+    result.setThreadPoolSize(test.threadPoolSize());
+    result.setSuccessPercentage(test.successPercentage());
+    result.setDataProvider(test.dataProvider());
+//    result.setDataProviderClass(test.dataProviderClass() != Object.class ?
+//        test.dataProviderClass() : null);
+    result.setDataProviderClass(
+        findInherited(test.dataProviderClass(), cls, Test.class, "dataProviderClass",
+            DEFAULT_CLASS));
+    result.setAlwaysRun(test.alwaysRun());
+    result.setDescription(
+        findInherited(test.description(), cls, Test.class, "description", DEFAULT_STRING));
+    result.setExpectedExceptions(test.expectedExceptions());
+    result.setExpectedExceptionsMessageRegExp(test.expectedExceptionsMessageRegExp());
+    result.setSuiteName(test.suiteName());
+    result.setTestName(test.testName());
+    result.setSequential(test.sequential());
+    result.setSingleThreaded(test.singleThreaded());
+    result.setRetryAnalyzer(test.retryAnalyzer());
+    result.setSkipFailedInvocations(test.skipFailedInvocations());
+    result.setIgnoreMissingDependencies(test.ignoreMissingDependencies());
+    result.setPriority(test.priority());
+
+    return result;
+  }
+
+  private String[] join(String[] strings, String[] strings2) {
+    List<String> result = Lists.newArrayList(strings);
+    Set<String> seen = new HashSet<>(Lists.newArrayList(strings));
+    for (String s : strings2) {
+      if (! seen.contains(s)) {
+        result.add(s);
+      }
+    }
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  /**
+   * This interface is used to calculate the default value for various
+   * annotation return types. This is used when looking for an annotation in a
+   * hierarchy. We can't use null as a default since annotation don't allow
+   * nulls, so each type has a different way of defining its own default.
+   */
+  static interface Default<T> {
+    boolean isDefault(T t);
+  }
+
+  private static final Default<Class<?>> DEFAULT_CLASS = new Default<Class<?>>() {
+    @Override
+    public boolean isDefault(Class<?> c) {
+      return c == Object.class;
+    }
+  };
+
+  private static final Default<String> DEFAULT_STRING = new Default<String>() {
+    @Override
+    public boolean isDefault(String s) {
+      return Utils.isStringEmpty(s);
+    }
+  };
+
+  /**
+   * Find the value of an annotation, starting with the annotation found on the
+   * method, then the class and then parent classes until we either find a
+   * non-default value or we reach the top of the hierarchy (Object).
+   */
+  private <T> T findInherited(T methodValue, Class<?> cls,
+      Class<? extends Annotation> annotationClass, String methodName,
+      Default<T> def) {
+
+    // Look on the method first and return right away if the annotation is there
+    if (!def.isDefault(methodValue)) {
+      return methodValue;
+    }
+
+    // Not found, look on the class and then up the hierarchy
+    while (cls != null && cls != Object.class) {
+      Annotation annotation = cls.getAnnotation(annotationClass);
+      if (annotation != null) {
+        T result = (T) invokeMethod(annotation, methodName);
+        if (!def.isDefault(result)) {
+          return result;
+        }
+      }
+      cls = cls.getSuperclass();
+    }
+
+    return null;
+  }
+
+  /**
+   * Find the value of a String[] annotation. The difference with the
+   * findInherited method above is that TestNG aggregates String[] values across
+   * hierarchies. For example, of the method annotation has { "a", "b" } and the
+   * class has { "c" }, the returned value will be { "a", "b", "c" }.
+   */
+  private String[] findInheritedStringArray(Class<?> cls,
+      Class<? extends Annotation> annotationClass, String methodName)
+  {
+    if (null == cls) {
+      return new String[0];
+    }
+
+    List<String> result = Lists.newArrayList();
+
+    while (cls != null && cls != Object.class) {
+      Annotation annotation = cls.getAnnotation(annotationClass);
+      if (annotation != null) {
+        String[] g = (String[]) invokeMethod(annotation, methodName);
+        for (String s : g) {
+          result.add(s);
+        }
+      }
+      cls = cls.getSuperclass();
+    }
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  private Object invokeMethod(Annotation test, String methodName) {
+    Object result = null;
+    try {
+      // Note:  we should cache methods already looked up
+      Method m = test.getClass().getMethod(methodName, new Class[0]);
+      result = m.invoke(test, new Object[0]);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+    return result;
+  }
+
+  private void ppp(String string) {
+    System.out.println("[JDK15TagFactory] " + string);
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/ListenersAnnotation.java b/src/main/java/org/testng/internal/annotations/ListenersAnnotation.java
new file mode 100644
index 0000000..128f11d
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/ListenersAnnotation.java
@@ -0,0 +1,20 @@
+package org.testng.internal.annotations;
+
+import org.testng.ITestNGListener;
+import org.testng.annotations.IAnnotation;
+
+public class ListenersAnnotation implements IListeners, IAnnotation {
+
+  private Class<? extends ITestNGListener>[] m_value;
+
+  @Override
+  public Class<? extends ITestNGListener>[] getValue() {
+    return m_value;
+  }
+
+  @Override
+  public void setValue(Class<? extends ITestNGListener>[] value) {
+    m_value = value;
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/ObjectFactoryAnnotation.java b/src/main/java/org/testng/internal/annotations/ObjectFactoryAnnotation.java
new file mode 100755
index 0000000..ae394d6
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/ObjectFactoryAnnotation.java
@@ -0,0 +1,10 @@
+package org.testng.internal.annotations;
+
+import org.testng.annotations.IObjectFactoryAnnotation;
+
+/**
+ * The internal representation of @ObjectFactory
+ * @author Hani
+ */
+public class ObjectFactoryAnnotation extends BaseAnnotation implements IObjectFactoryAnnotation
+{}
diff --git a/src/main/java/org/testng/internal/annotations/ParametersAnnotation.java b/src/main/java/org/testng/internal/annotations/ParametersAnnotation.java
new file mode 100755
index 0000000..14fb799
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/ParametersAnnotation.java
@@ -0,0 +1,27 @@
+package org.testng.internal.annotations;
+
+import org.testng.annotations.IParametersAnnotation;
+
+
+/**
+ * An implementation of IParameters
+ *
+ * Created on Dec 20, 2005
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public class ParametersAnnotation
+  extends BaseAnnotation
+  implements IParametersAnnotation
+{
+  private String[] m_value  = {};
+
+  @Override
+  public String[] getValue() {
+    return m_value;
+  }
+
+  public void setValue(String[] value) {
+    m_value = value;
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/annotations/TestAnnotation.java b/src/main/java/org/testng/internal/annotations/TestAnnotation.java
new file mode 100755
index 0000000..19612a9
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/TestAnnotation.java
@@ -0,0 +1,207 @@
+package org.testng.internal.annotations;

+

+import org.testng.IRetryAnalyzer;

+import org.testng.annotations.ITestAnnotation;

+

+

+/**

+ * An implementation of ITest

+ *

+ * Created on Dec 20, 2005

+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>

+ */

+public class TestAnnotation extends TestOrConfiguration implements ITestAnnotation {

+  private long m_invocationTimeOut = 0;

+  private int m_invocationCount = 1;

+  private int m_threadPoolSize = 0;

+  private int m_successPercentage = 100;

+  private String m_dataProvider = "";

+  private boolean m_alwaysRun = false;

+  private Class<?>[] m_expectedExceptions = {};

+  private String m_expectedExceptionsMessageRegExp = ".*";

+  private String m_suiteName = "";

+  private String m_testName = "";

+  private boolean m_singleThreaded = false;

+  private boolean m_sequential = false;

+  private Class<?> m_dataProviderClass = null;

+  private IRetryAnalyzer m_retryAnalyzer = null;

+  private boolean m_skipFailedInvocations = false;

+  private boolean m_ignoreMissingDependencies = false;

+

+  /**

+   * @return the expectedExceptions

+   */

+  @Override

+  public Class<?>[] getExpectedExceptions() {

+    return m_expectedExceptions;

+  }

+

+  /**

+   * @param expectedExceptions the expectedExceptions to set

+   */

+  @Override

+  public void setExpectedExceptions(Class<?>[] expectedExceptions) {

+    m_expectedExceptions = expectedExceptions;

+  }

+

+  @Override

+  public String getExpectedExceptionsMessageRegExp() {

+    return m_expectedExceptionsMessageRegExp;

+  }

+

+  @Override

+  public void setExpectedExceptionsMessageRegExp(

+      String expectedExceptionsMessageRegExp) {

+    m_expectedExceptionsMessageRegExp = expectedExceptionsMessageRegExp;

+  }

+

+  @Override

+  public void setAlwaysRun(boolean alwaysRun) {

+    m_alwaysRun = alwaysRun;

+  }

+

+  @Override

+  public void setDataProvider(String dataProvider) {

+    m_dataProvider = dataProvider;

+  }

+

+  @Override

+  public Class<?> getDataProviderClass() {

+    return m_dataProviderClass;

+  }

+

+  @Override

+  public void setDataProviderClass(Class<?> dataProviderClass) {

+    m_dataProviderClass = dataProviderClass;

+  }

+

+  @Override

+  public void setInvocationCount(int invocationCount) {

+    m_invocationCount = invocationCount;

+  }

+

+  @Override

+  public void setSuccessPercentage(int successPercentage) {

+    m_successPercentage = successPercentage;

+  }

+

+  @Override

+  public int getInvocationCount() {

+    return m_invocationCount;

+  }

+

+  @Override

+  public long invocationTimeOut() {

+   return m_invocationTimeOut;

+  }

+

+  @Override

+  public void setInvocationTimeOut(long timeOut) {

+    m_invocationTimeOut = timeOut;

+  }

+

+

+  @Override

+  public int getSuccessPercentage() {

+    return m_successPercentage;

+  }

+

+  @Override

+  public String getDataProvider() {

+    return m_dataProvider;

+  }

+

+  @Override

+  public boolean getAlwaysRun() {

+    return m_alwaysRun;

+  }

+

+  @Override

+  public int getThreadPoolSize() {

+    return m_threadPoolSize;

+  }

+

+  @Override

+  public void setThreadPoolSize(int threadPoolSize) {

+    m_threadPoolSize = threadPoolSize;

+  }

+

+  @Override

+  public String getSuiteName() {

+    return m_suiteName;

+  }

+

+  @Override

+  public void setSuiteName(String xmlSuite) {

+    m_suiteName = xmlSuite;

+  }

+

+  @Override

+  public String getTestName() {

+    return m_testName;

+  }

+

+  @Override

+  public void setTestName(String xmlTest) {

+    m_testName = xmlTest;

+  }

+

+  @Override

+  public boolean getSingleThreaded() {

+    return m_singleThreaded;

+  }

+

+  @Override

+  public void setSingleThreaded(boolean singleThreaded) {

+    m_singleThreaded = singleThreaded;

+  }

+

+  @Override

+  public boolean getSequential() {

+    return m_sequential;

+  }

+

+  @Override

+  public void setSequential(boolean sequential) {

+    m_sequential = sequential;

+  }

+

+  @Override

+  public IRetryAnalyzer getRetryAnalyzer() {

+    return m_retryAnalyzer;

+  }

+

+  @Override

+  public void setRetryAnalyzer(Class<?> c) {

+    m_retryAnalyzer = null;

+

+    if (c != null && IRetryAnalyzer.class.isAssignableFrom(c)) {

+      try {

+        m_retryAnalyzer = (IRetryAnalyzer) c.newInstance();

+      }

+      catch (InstantiationException | IllegalAccessException e) {

+        // The class will never be called.

+      }

+    }

+  }

+

+  @Override

+  public void setSkipFailedInvocations(boolean skip) {

+    m_skipFailedInvocations = skip;

+  }

+

+  @Override

+  public boolean skipFailedInvocations() {

+    return m_skipFailedInvocations;

+  }

+

+  @Override

+  public void setIgnoreMissingDependencies(boolean ignore) {

+    m_ignoreMissingDependencies = ignore;

+  }

+

+  @Override

+  public boolean ignoreMissingDependencies() {

+    return m_ignoreMissingDependencies;

+  }

+}

diff --git a/src/main/java/org/testng/internal/annotations/TestOrConfiguration.java b/src/main/java/org/testng/internal/annotations/TestOrConfiguration.java
new file mode 100755
index 0000000..88fc5db
--- /dev/null
+++ b/src/main/java/org/testng/internal/annotations/TestOrConfiguration.java
@@ -0,0 +1,94 @@
+package org.testng.internal.annotations;
+
+import org.testng.annotations.ITestOrConfiguration;
+
+public class TestOrConfiguration
+  extends BaseAnnotation
+  implements ITestOrConfiguration
+{
+  private String[] m_parameters = {};
+  private String[] m_groups = {};
+  private boolean m_enabled = true;
+  private String[] m_dependsOnGroups = {};
+  private String[] m_dependsOnMethods = {};
+  private String m_description = "";
+  private int m_priority;
+  private long m_timeOut = 0;
+
+  @Override
+  public String[] getGroups() {
+    return m_groups;
+  }
+
+  @Override
+  public boolean getEnabled() {
+    return m_enabled;
+  }
+
+  @Override
+  public void setDependsOnGroups(String[] dependsOnGroups) {
+    m_dependsOnGroups = dependsOnGroups;
+  }
+
+  @Override
+  public void setDependsOnMethods(String[] dependsOnMethods) {
+    m_dependsOnMethods = dependsOnMethods;
+  }
+
+  @Override
+  public void setGroups(String[] groups) {
+    m_groups = groups;
+  }
+
+  @Override
+  public String getDescription() {
+    return m_description;
+  }
+
+  @Override
+  public void setEnabled(boolean enabled) {
+    m_enabled = enabled;
+  }
+
+  @Override
+  public String[] getDependsOnGroups() {
+    return m_dependsOnGroups;
+  }
+
+  @Override
+  public String[] getDependsOnMethods() {
+    return m_dependsOnMethods;
+  }
+
+  @Override
+  public String[] getParameters() {
+    return m_parameters;
+  }
+
+  public void setParameters(String[] parameters) {
+    m_parameters = parameters;
+  }
+
+  @Override
+  public void setDescription(String description) {
+    m_description = description;
+  }
+
+  public int getPriority() {
+    return m_priority;
+  }
+
+  public void setPriority(int priority) {
+    m_priority = priority;
+  }
+
+  @Override
+  public void setTimeOut(long timeOut) {
+    m_timeOut = timeOut;
+  }
+
+  @Override
+  public long getTimeOut() {
+    return m_timeOut;
+  }
+}
diff --git a/src/main/java/org/testng/internal/collections/Pair.java b/src/main/java/org/testng/internal/collections/Pair.java
new file mode 100644
index 0000000..815536a
--- /dev/null
+++ b/src/main/java/org/testng/internal/collections/Pair.java
@@ -0,0 +1,81 @@
+package org.testng.internal.collections;
+
+import org.testng.collections.Objects;
+
+
+
+
+
+public class Pair<A, B> {
+  private final A first;
+  private final B second;
+
+  public Pair(A first, B second) {
+    this.first = first;
+    this.second = second;
+  }
+
+  public A first() {
+    return first;
+  }
+
+  public B second() {
+    return second;
+  }
+
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((first == null) ? 0 : first.hashCode());
+    result = prime * result + ((second == null) ? 0 : second.hashCode());
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null) {
+      return false;
+    }
+    if (getClass() != obj.getClass()) {
+      return false;
+    }
+    final Pair other = (Pair) obj;
+    if (first == null) {
+      if (other.first != null) {
+        return false;
+      }
+    }
+    else if (!first.equals(other.first)) {
+      return false;
+    }
+    if (second == null) {
+      if (other.second != null) {
+        return false;
+      }
+    }
+    else if (!second.equals(other.second)) {
+      return false;
+    }
+    return true;
+  }
+
+  public static <A, B> Pair<A, B> create(A first, B second) {
+    return of(first, second);
+  }
+
+  public static <A, B> Pair<A, B> of(A a, B b) {
+    return new Pair<>(a, b);
+  }
+
+  @Override
+  public String toString() {
+    return Objects.toStringHelper(getClass())
+        .add("first", first())
+        .add("second", second())
+        .toString();
+  }
+}
\ No newline at end of file
diff --git a/src/main/java/org/testng/internal/invokers/InvokedMethodListenerInvoker.java b/src/main/java/org/testng/internal/invokers/InvokedMethodListenerInvoker.java
new file mode 100644
index 0000000..b562de0
--- /dev/null
+++ b/src/main/java/org/testng/internal/invokers/InvokedMethodListenerInvoker.java
@@ -0,0 +1,132 @@
+package org.testng.internal.invokers;
+
+import org.testng.IInvokedMethod;
+import org.testng.IInvokedMethodListener;
+import org.testng.IInvokedMethodListener2;
+import org.testng.ITestContext;
+import org.testng.ITestResult;
+import org.testng.collections.Maps;
+
+import java.util.Map;
+
+import static org.testng.internal.invokers.InvokedMethodListenerMethod.AFTER_INVOCATION;
+import static org.testng.internal.invokers.InvokedMethodListenerMethod.BEFORE_INVOCATION;
+import static org.testng.internal.invokers.InvokedMethodListenerSubtype.EXTENDED_LISTENER;
+import static org.testng.internal.invokers.InvokedMethodListenerSubtype.SIMPLE_LISTENER;
+
+/**
+ * Hides complexity of calling methods of {@link IInvokedMethodListener} and
+ * {@link IInvokedMethodListener2}.
+ *
+ * @author Ansgar Konermann
+ */
+public class InvokedMethodListenerInvoker {
+
+  private InvokedMethodListenerMethod m_listenerMethod;
+  private ITestContext m_testContext;
+  private ITestResult m_testResult;
+
+  /**
+   * Creates a new invoker instance which can be used to call the specified {@code listenerMethod}
+   * on any number of {@link IInvokedMethodListener}s.
+   *
+   * @param listenerMethod method which should be called
+   * @param testResult test result which should be passed to the listener method upon invocation
+   * @param testContext test context which should be passed to the listener method upon invocation.
+   *        This parameter is only used when calling methods on an {@link IInvokedMethodListener2}.
+   */
+  public InvokedMethodListenerInvoker(InvokedMethodListenerMethod listenerMethod,
+                                      ITestResult testResult, ITestContext testContext) {
+    m_listenerMethod = listenerMethod;
+    m_testContext = testContext;
+    m_testResult = testResult;
+  }
+
+  /**
+   * Invoke the given {@code listenerInstance}, calling the method specified in the constructor of
+   * this {@link InvokedMethodListenerInvoker}.
+   *
+   * @param listenerInstance the listener instance which should be invoked.
+   * @param invokedMethod the {@link IInvokedMethod} instance which should be passed to the
+   *        {@link IInvokedMethodListener#beforeInvocation(IInvokedMethod, ITestResult)},
+   *        {@link IInvokedMethodListener#afterInvocation(IInvokedMethod, ITestResult)},
+   *        {@link IInvokedMethodListener2#beforeInvocation(IInvokedMethod, ITestResult, ITestContext)}
+   *        or {@link IInvokedMethodListener2#afterInvocation(IInvokedMethod, ITestResult, ITestContext)}
+   *        method.
+   */
+
+  @SuppressWarnings("unchecked")
+  public void invokeListener(IInvokedMethodListener listenerInstance,
+                             IInvokedMethod invokedMethod) {
+    final InvocationStrategy strategy = obtainStrategyFor(listenerInstance, m_listenerMethod);
+    strategy.callMethod(listenerInstance, invokedMethod, m_testResult, m_testContext);
+  }
+
+  private InvocationStrategy obtainStrategyFor(IInvokedMethodListener listenerInstance,
+      InvokedMethodListenerMethod listenerMethod) {
+    InvokedMethodListenerSubtype invokedMethodListenerSubtype = InvokedMethodListenerSubtype
+        .fromListener(listenerInstance);
+    Map<InvokedMethodListenerMethod, InvocationStrategy> strategiesForListenerType = strategies
+        .get(invokedMethodListenerSubtype);
+    InvocationStrategy invocationStrategy = strategiesForListenerType.get(listenerMethod);
+    return invocationStrategy;
+  }
+
+  private static interface InvocationStrategy<LISTENER_TYPE extends IInvokedMethodListener> {
+    void callMethod(LISTENER_TYPE listener, IInvokedMethod invokedMethod, ITestResult testResult,
+        ITestContext testContext);
+  }
+
+  private static class InvokeBeforeInvocationWithoutContextStrategy implements
+      InvocationStrategy<IInvokedMethodListener> {
+    public void callMethod(IInvokedMethodListener listener, IInvokedMethod invokedMethod,
+        ITestResult testResult, ITestContext testContext) {
+      listener.beforeInvocation(invokedMethod, testResult);
+    }
+  }
+
+  private static class InvokeBeforeInvocationWithContextStrategy implements
+      InvocationStrategy<IInvokedMethodListener2> {
+    public void callMethod(IInvokedMethodListener2 listener, IInvokedMethod invokedMethod,
+        ITestResult testResult, ITestContext testContext) {
+      listener.beforeInvocation(invokedMethod, testResult, testContext);
+    }
+  }
+
+  private static class InvokeAfterInvocationWithoutContextStrategy implements
+      InvocationStrategy<IInvokedMethodListener> {
+    public void callMethod(IInvokedMethodListener listener, IInvokedMethod invokedMethod,
+        ITestResult testResult, ITestContext testContext) {
+      listener.afterInvocation(invokedMethod, testResult);
+    }
+  }
+
+  private static class InvokeAfterInvocationWithContextStrategy implements
+      InvocationStrategy<IInvokedMethodListener2> {
+    public void callMethod(IInvokedMethodListener2 listener, IInvokedMethod invokedMethod,
+        ITestResult testResult, ITestContext testContext) {
+      listener.afterInvocation(invokedMethod, testResult, testContext);
+    }
+  }
+
+  private static final Map<InvokedMethodListenerSubtype, Map<InvokedMethodListenerMethod,
+      InvocationStrategy>> strategies = Maps.newHashMap();
+  private static final Map<InvokedMethodListenerMethod, InvocationStrategy>
+      INVOKE_WITH_CONTEXT_STRATEGIES = Maps.newHashMap();
+  private static final Map<InvokedMethodListenerMethod, InvocationStrategy>
+      INVOKE_WITHOUT_CONTEXT_STRATEGIES = Maps.newHashMap();
+
+  static {
+    INVOKE_WITH_CONTEXT_STRATEGIES.put(BEFORE_INVOCATION,
+        new InvokeBeforeInvocationWithContextStrategy());
+    INVOKE_WITH_CONTEXT_STRATEGIES.put(AFTER_INVOCATION,
+        new InvokeAfterInvocationWithContextStrategy());
+    INVOKE_WITHOUT_CONTEXT_STRATEGIES.put(BEFORE_INVOCATION,
+        new InvokeBeforeInvocationWithoutContextStrategy());
+    INVOKE_WITHOUT_CONTEXT_STRATEGIES.put(AFTER_INVOCATION,
+        new InvokeAfterInvocationWithoutContextStrategy());
+
+    strategies.put(EXTENDED_LISTENER, INVOKE_WITH_CONTEXT_STRATEGIES);
+    strategies.put(SIMPLE_LISTENER, INVOKE_WITHOUT_CONTEXT_STRATEGIES);
+  }
+}
diff --git a/src/main/java/org/testng/internal/invokers/InvokedMethodListenerMethod.java b/src/main/java/org/testng/internal/invokers/InvokedMethodListenerMethod.java
new file mode 100644
index 0000000..7a73bcb
--- /dev/null
+++ b/src/main/java/org/testng/internal/invokers/InvokedMethodListenerMethod.java
@@ -0,0 +1,15 @@
+package org.testng.internal.invokers;
+
+import org.testng.IInvokedMethodListener;
+import org.testng.IInvokedMethodListener2;
+
+/**
+ * Indicates which of the methods of a {@link IInvokedMethodListener} or
+ * {@link IInvokedMethodListener2} should be called.
+ *
+ * @author Ansgar Konermann
+ */
+public enum InvokedMethodListenerMethod {
+  BEFORE_INVOCATION,
+  AFTER_INVOCATION
+}
diff --git a/src/main/java/org/testng/internal/invokers/InvokedMethodListenerSubtype.java b/src/main/java/org/testng/internal/invokers/InvokedMethodListenerSubtype.java
new file mode 100644
index 0000000..e939fae
--- /dev/null
+++ b/src/main/java/org/testng/internal/invokers/InvokedMethodListenerSubtype.java
@@ -0,0 +1,40 @@
+package org.testng.internal.invokers;
+
+import org.testng.IInvokedMethodListener;
+import org.testng.IInvokedMethodListener2;
+import org.testng.TestNGException;
+
+/**
+ * Indicates whether a {@link InvokedMethodListenerMethod} is to be called on a simple or an
+ * extended invoked method listener. All {@link IInvokedMethodListener}s are considered
+ * {@link #SIMPLE_LISTENER}, instances of {@link IInvokedMethodListener2} are all considered
+ * {@link #EXTENDED_LISTENER}.
+ *
+ * @author Ansgar Konermann
+ */
+enum InvokedMethodListenerSubtype {
+
+  EXTENDED_LISTENER(IInvokedMethodListener2.class),
+  SIMPLE_LISTENER(IInvokedMethodListener.class);
+
+  private Class<? extends IInvokedMethodListener> m_matchingInterface;
+
+  private InvokedMethodListenerSubtype(Class<? extends IInvokedMethodListener> listenerClass) {
+    m_matchingInterface = listenerClass;
+  }
+
+  private boolean isInstance(IInvokedMethodListener listenerInstance) {
+    return m_matchingInterface.isInstance(listenerInstance);
+  }
+
+  public static InvokedMethodListenerSubtype fromListener(IInvokedMethodListener listenerInstance) {
+    if (EXTENDED_LISTENER.isInstance(listenerInstance)) {
+      return EXTENDED_LISTENER;
+    }
+    else if (SIMPLE_LISTENER.isInstance(listenerInstance)) {
+      return SIMPLE_LISTENER;
+    }
+    throw new TestNGException("Illegal " + IInvokedMethodListener.class.getSimpleName()
+        + " instance: " + listenerInstance.getClass().getName() + ".");
+  }
+}
diff --git a/src/main/java/org/testng/internal/junit/ArrayAsserts.java b/src/main/java/org/testng/internal/junit/ArrayAsserts.java
new file mode 100644
index 0000000..2a01269
--- /dev/null
+++ b/src/main/java/org/testng/internal/junit/ArrayAsserts.java
@@ -0,0 +1,273 @@
+package org.testng.internal.junit;
+
+public class ArrayAsserts {
+  /**
+   * Asserts that two object arrays are equal. If they are not, an
+   * {@link AssertionError} is thrown with the given message. If
+   * <code>expecteds</code> and <code>actuals</code> are <code>null</code>,
+   * they are considered equal.
+   * 
+   * @param message
+   *            the identifying message for the {@link AssertionError} (<code>null</code>
+   *            okay)
+   * @param expecteds
+   *            Object array or array of arrays (multi-dimensional array) with
+   *            expected values.
+   * @param actuals
+   *            Object array or array of arrays (multi-dimensional array) with
+   *            actual values
+   */
+  public static void assertArrayEquals(String message, Object[] expecteds,
+          Object[] actuals) throws ArrayComparisonFailure {
+      internalArrayEquals(message, expecteds, actuals);
+  }
+
+  /**
+   * Asserts that two object arrays are equal. If they are not, an
+   * {@link AssertionError} is thrown. If <code>expected</code> and
+   * <code>actual</code> are <code>null</code>, they are considered
+   * equal.
+   * 
+   * @param expecteds
+   *            Object array or array of arrays (multi-dimensional array) with
+   *            expected values
+   * @param actuals
+   *            Object array or array of arrays (multi-dimensional array) with
+   *            actual values
+   */
+  public static void assertArrayEquals(Object[] expecteds, Object[] actuals) {
+      assertArrayEquals(null, expecteds, actuals);
+  }
+
+  /**
+   * Asserts that two byte arrays are equal. If they are not, an
+   * {@link AssertionError} is thrown with the given message.
+   * 
+   * @param message
+   *            the identifying message for the {@link AssertionError} (<code>null</code>
+   *            okay)
+   * @param expecteds
+   *            byte array with expected values.
+   * @param actuals
+   *            byte array with actual values
+   */
+  public static void assertArrayEquals(String message, byte[] expecteds,
+          byte[] actuals) throws ArrayComparisonFailure {
+      internalArrayEquals(message, expecteds, actuals);
+  }
+
+  /**
+   * Asserts that two byte arrays are equal. If they are not, an
+   * {@link AssertionError} is thrown.
+   * 
+   * @param expecteds
+   *            byte array with expected values.
+   * @param actuals
+   *            byte array with actual values
+   */
+  public static void assertArrayEquals(byte[] expecteds, byte[] actuals) {
+      assertArrayEquals(null, expecteds, actuals);
+  }
+
+  /**
+   * Asserts that two char arrays are equal. If they are not, an
+   * {@link AssertionError} is thrown with the given message.
+   * 
+   * @param message
+   *            the identifying message for the {@link AssertionError} (<code>null</code>
+   *            okay)
+   * @param expecteds
+   *            char array with expected values.
+   * @param actuals
+   *            char array with actual values
+   */
+  public static void assertArrayEquals(String message, char[] expecteds,
+          char[] actuals) throws ArrayComparisonFailure {
+      internalArrayEquals(message, expecteds, actuals);
+  }
+
+  /**
+   * Asserts that two char arrays are equal. If they are not, an
+   * {@link AssertionError} is thrown.
+   * 
+   * @param expecteds
+   *            char array with expected values.
+   * @param actuals
+   *            char array with actual values
+   */
+  public static void assertArrayEquals(char[] expecteds, char[] actuals) {
+      assertArrayEquals(null, expecteds, actuals);
+  }
+
+  /**
+   * Asserts that two short arrays are equal. If they are not, an
+   * {@link AssertionError} is thrown with the given message.
+   * 
+   * @param message
+   *            the identifying message for the {@link AssertionError} (<code>null</code>
+   *            okay)
+   * @param expecteds
+   *            short array with expected values.
+   * @param actuals
+   *            short array with actual values
+   */
+  public static void assertArrayEquals(String message, short[] expecteds,
+          short[] actuals) throws ArrayComparisonFailure {
+      internalArrayEquals(message, expecteds, actuals);
+  }
+
+  /**
+   * Asserts that two short arrays are equal. If they are not, an
+   * {@link AssertionError} is thrown.
+   * 
+   * @param expecteds
+   *            short array with expected values.
+   * @param actuals
+   *            short array with actual values
+   */
+  public static void assertArrayEquals(short[] expecteds, short[] actuals) {
+      assertArrayEquals(null, expecteds, actuals);
+  }
+
+  /**
+   * Asserts that two int arrays are equal. If they are not, an
+   * {@link AssertionError} is thrown with the given message.
+   * 
+   * @param message
+   *            the identifying message for the {@link AssertionError} (<code>null</code>
+   *            okay)
+   * @param expecteds
+   *            int array with expected values.
+   * @param actuals
+   *            int array with actual values
+   */
+  public static void assertArrayEquals(String message, int[] expecteds,
+          int[] actuals) throws ArrayComparisonFailure {
+      internalArrayEquals(message, expecteds, actuals);
+  }
+
+  /**
+   * Asserts that two int arrays are equal. If they are not, an
+   * {@link AssertionError} is thrown.
+   * 
+   * @param expecteds
+   *            int array with expected values.
+   * @param actuals
+   *            int array with actual values
+   */
+  public static void assertArrayEquals(int[] expecteds, int[] actuals) {
+      assertArrayEquals(null, expecteds, actuals);
+  }
+
+  /**
+   * Asserts that two long arrays are equal. If they are not, an
+   * {@link AssertionError} is thrown with the given message.
+   * 
+   * @param message
+   *            the identifying message for the {@link AssertionError} (<code>null</code>
+   *            okay)
+   * @param expecteds
+   *            long array with expected values.
+   * @param actuals
+   *            long array with actual values
+   */
+  public static void assertArrayEquals(String message, long[] expecteds,
+          long[] actuals) throws ArrayComparisonFailure {
+      internalArrayEquals(message, expecteds, actuals);
+  }
+
+  /**
+   * Asserts that two long arrays are equal. If they are not, an
+   * {@link AssertionError} is thrown.
+   * 
+   * @param expecteds
+   *            long array with expected values.
+   * @param actuals
+   *            long array with actual values
+   */
+  public static void assertArrayEquals(long[] expecteds, long[] actuals) {
+      assertArrayEquals(null, expecteds, actuals);
+  }
+  
+  /**
+   * Asserts that two double arrays are equal. If they are not, an
+   * {@link AssertionError} is thrown with the given message.
+   * 
+   * @param message
+   *            the identifying message for the {@link AssertionError} (<code>null</code>
+   *            okay)
+   * @param expecteds
+   *            double array with expected values.
+   * @param actuals
+   *            double array with actual values
+   */
+  public static void assertArrayEquals(String message, double[] expecteds,
+          double[] actuals, double delta) throws ArrayComparisonFailure {
+      new InexactComparisonCriteria(delta).arrayEquals(message, expecteds, actuals);
+  }
+
+  /**
+   * Asserts that two double arrays are equal. If they are not, an
+   * {@link AssertionError} is thrown.
+   * 
+   * @param expecteds
+   *            double array with expected values.
+   * @param actuals
+   *            double array with actual values
+   */
+  public static void assertArrayEquals(double[] expecteds, double[] actuals, double delta) {
+      assertArrayEquals(null, expecteds, actuals, delta);
+  }
+
+  /**
+   * Asserts that two float arrays are equal. If they are not, an
+   * {@link AssertionError} is thrown with the given message.
+   * 
+   * @param message
+   *            the identifying message for the {@link AssertionError} (<code>null</code>
+   *            okay)
+   * @param expecteds
+   *            float array with expected values.
+   * @param actuals
+   *            float array with actual values
+   */
+  public static void assertArrayEquals(String message, float[] expecteds,
+          float[] actuals, float delta) throws ArrayComparisonFailure {
+      new InexactComparisonCriteria(delta).arrayEquals(message, expecteds, actuals);
+  }
+
+  /**
+   * Asserts that two float arrays are equal. If they are not, an
+   * {@link AssertionError} is thrown.
+   * 
+   * @param expecteds
+   *            float array with expected values.
+   * @param actuals
+   *            float array with actual values
+   */
+  public static void assertArrayEquals(float[] expecteds, float[] actuals, float delta) {
+      assertArrayEquals(null, expecteds, actuals, delta);
+  }
+
+  /**
+   * Asserts that two object arrays are equal. If they are not, an
+   * {@link AssertionError} is thrown with the given message. If
+   * <code>expecteds</code> and <code>actuals</code> are <code>null</code>,
+   * they are considered equal.
+   * 
+   * @param message
+   *            the identifying message for the {@link AssertionError} (<code>null</code>
+   *            okay)
+   * @param expecteds
+   *            Object array or array of arrays (multi-dimensional array) with
+   *            expected values.
+   * @param actuals
+   *            Object array or array of arrays (multi-dimensional array) with
+   *            actual values
+   */
+  private static void internalArrayEquals(String message, Object expecteds,
+          Object actuals) throws ArrayComparisonFailure {
+      new ExactComparisonCriteria().arrayEquals(message, expecteds, actuals);
+  }   
+
+}
diff --git a/src/main/java/org/testng/internal/junit/ArrayComparisonFailure.java b/src/main/java/org/testng/internal/junit/ArrayComparisonFailure.java
new file mode 100644
index 0000000..294c076
--- /dev/null
+++ b/src/main/java/org/testng/internal/junit/ArrayComparisonFailure.java
@@ -0,0 +1,55 @@
+package org.testng.internal.junit;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Thrown when two array elements differ
+ */
+public class ArrayComparisonFailure extends AssertionError {
+
+    private static final long serialVersionUID= 1L;
+    
+    private List<Integer> fIndices= new ArrayList<>();
+    private final String fMessage;
+    private final AssertionError fCause;
+
+    /**
+     * Construct a new <code>ArrayComparisonFailure</code> with an error text and the array's
+     * dimension that was not equal
+     * @param cause the exception that caused the array's content to fail the assertion test 
+     * @param index the array position of the objects that are not equal.
+     */
+    public ArrayComparisonFailure(String message, AssertionError cause, int index) {
+        fMessage= message;
+        fCause= cause;
+        addDimension(index);
+    }
+
+    public void addDimension(int index) {
+        fIndices.add(0, index);
+    }
+
+    @Override
+    public String getMessage() {
+        StringBuilder builder= new StringBuilder();
+        if (fMessage != null)
+            builder.append(fMessage);
+        builder.append("arrays first differed at element ");
+        for (int each : fIndices) {
+            builder.append("[");
+            builder.append(each);
+            builder.append("]");
+        }
+        builder.append("; ");
+        builder.append(fCause.getMessage());
+        return builder.toString();
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    @Override public String toString() {
+        return getMessage();
+    }
+}
diff --git a/src/main/java/org/testng/internal/junit/ComparisonCriteria.java b/src/main/java/org/testng/internal/junit/ComparisonCriteria.java
new file mode 100644
index 0000000..fda079e
--- /dev/null
+++ b/src/main/java/org/testng/internal/junit/ComparisonCriteria.java
@@ -0,0 +1,76 @@
+package org.testng.internal.junit;
+
+import org.testng.AssertJUnit;
+
+import java.lang.reflect.Array;
+
+/**
+ * Defines criteria for finding two items "equal enough". Concrete subclasses
+ * may demand exact equality, or, for example, equality within a given delta.
+ */
+public abstract class ComparisonCriteria {
+    /**
+     * Asserts that two arrays are equal, according to the criteria defined by
+     * the concrete subclass. If they are not, an {@link AssertionError} is
+     * thrown with the given message. If <code>expecteds</code> and
+     * <code>actuals</code> are <code>null</code>, they are considered equal.
+     * 
+     * @param message
+     *            the identifying message for the {@link AssertionError} (
+     *            <code>null</code> okay)
+     * @param expecteds
+     *            Object array or array of arrays (multi-dimensional array) with
+     *            expected values.
+     * @param actuals
+     *            Object array or array of arrays (multi-dimensional array) with
+     *            actual values
+     */
+    public void arrayEquals(String message, Object expecteds, Object actuals)
+            throws ArrayComparisonFailure {
+        if (expecteds == actuals)
+            return;
+        String header= message == null ? "" : message + ": ";
+
+        int expectedsLength= assertArraysAreSameLength(expecteds,
+                actuals, header);
+
+        for (int i= 0; i < expectedsLength; i++) {
+            Object expected= Array.get(expecteds, i);
+            Object actual= Array.get(actuals, i);
+
+            if (isArray(expected) && isArray(actual)) {
+                try {
+                    arrayEquals(message, expected, actual);
+                } catch (ArrayComparisonFailure e) {
+                    e.addDimension(i);
+                    throw e;
+                }
+            } else
+                try {
+                    assertElementsEqual(expected, actual);
+                } catch (AssertionError e) {
+                    throw new ArrayComparisonFailure(header, e, i);
+                }
+        }
+    }
+
+    private boolean isArray(Object expected) {
+        return expected != null && expected.getClass().isArray();
+    }
+
+    private int assertArraysAreSameLength(Object expecteds,
+            Object actuals, String header) {
+        if (expecteds == null)
+            AssertJUnit.fail(header + "expected array was null");
+        if (actuals == null)
+            AssertJUnit.fail(header + "actual array was null");
+        int actualsLength= Array.getLength(actuals);
+        int expectedsLength= Array.getLength(expecteds);
+        if (actualsLength != expectedsLength)
+            AssertJUnit.fail(header + "array lengths differed, expected.length="
+                    + expectedsLength + " actual.length=" + actualsLength);
+        return expectedsLength;
+    }
+
+    protected abstract void assertElementsEqual(Object expected, Object actual);
+}
diff --git a/src/main/java/org/testng/internal/junit/ExactComparisonCriteria.java b/src/main/java/org/testng/internal/junit/ExactComparisonCriteria.java
new file mode 100644
index 0000000..51a5ca6
--- /dev/null
+++ b/src/main/java/org/testng/internal/junit/ExactComparisonCriteria.java
@@ -0,0 +1,10 @@
+package org.testng.internal.junit;
+
+import org.testng.AssertJUnit;
+
+public class ExactComparisonCriteria extends ComparisonCriteria {
+    @Override
+    protected void assertElementsEqual(Object expected, Object actual) {
+        AssertJUnit.assertEquals(expected, actual);
+    }
+}
diff --git a/src/main/java/org/testng/internal/junit/InexactComparisonCriteria.java b/src/main/java/org/testng/internal/junit/InexactComparisonCriteria.java
new file mode 100644
index 0000000..cd18d46
--- /dev/null
+++ b/src/main/java/org/testng/internal/junit/InexactComparisonCriteria.java
@@ -0,0 +1,19 @@
+package org.testng.internal.junit;
+
+import org.testng.AssertJUnit;
+
+public class InexactComparisonCriteria extends ComparisonCriteria {
+    public double fDelta;
+
+    public InexactComparisonCriteria(double delta) {
+        fDelta= delta;
+    }
+
+    @Override
+    protected void assertElementsEqual(Object expected, Object actual) {
+        if (expected instanceof Double)
+            AssertJUnit.assertEquals((Double)expected, (Double)actual, fDelta);
+        else
+            AssertJUnit.assertEquals((Float)expected, (Float)actual, fDelta);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/testng/internal/remote/SlavePool.java b/src/main/java/org/testng/internal/remote/SlavePool.java
new file mode 100755
index 0000000..03b859f
--- /dev/null
+++ b/src/main/java/org/testng/internal/remote/SlavePool.java
@@ -0,0 +1,64 @@
+package org.testng.internal.remote;
+
+import org.testng.collections.Maps;
+import org.testng.remote.ConnectionInfo;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.util.Map;
+
+
+/**
+ * This class maintains a pool of slaves (represented by sockets).
+ *
+ * @author cbeust
+ */
+public class SlavePool {
+  private static SocketLinkedBlockingQueue m_hosts = new SocketLinkedBlockingQueue();
+  private static Map<Socket, ConnectionInfo> m_connectionInfos = Maps.newHashMap();
+
+  public void addSlaves(Socket[] slaves) throws IOException {
+    for (Socket s : slaves) {
+      addSlave(s);
+    }
+  }
+
+  public void addSlave(Socket s) {
+	  if( s==null) {
+      return;
+    }
+	  ConnectionInfo ci = new ConnectionInfo();
+	  ci.setSocket(s);
+	  addSlave(s, ci);
+  }
+
+  private void addSlave(Socket s, ConnectionInfo ci) {
+    m_hosts.add(s);
+    m_connectionInfos.put(s, ci);
+  }
+
+  public ConnectionInfo getSlave() {
+    ConnectionInfo result = null;
+    Socket host = null;
+
+    try {
+      host = m_hosts.take();
+      result = m_connectionInfos.get(host);
+    }
+    catch (InterruptedException handled) {
+      handled.printStackTrace();
+      Thread.currentThread().interrupt();
+    }
+
+    return result;
+  }
+
+  public void returnSlave(ConnectionInfo slave) throws IOException {
+    m_hosts.add(slave.getSocket());
+//    ConnectionInfo ci = m_connectionInfos.remove(slave.socket);
+//    ci.oos.close();
+//    ci.ois.close();
+//    addSlave(slave.socket);
+  }
+
+}
diff --git a/src/main/java/org/testng/internal/remote/SocketLinkedBlockingQueue.java b/src/main/java/org/testng/internal/remote/SocketLinkedBlockingQueue.java
new file mode 100755
index 0000000..bffaccb
--- /dev/null
+++ b/src/main/java/org/testng/internal/remote/SocketLinkedBlockingQueue.java
@@ -0,0 +1,21 @@
+package org.testng.internal.remote;

+

+import java.net.Socket;

+

+/**

+ * <code>SocketLinkedBlockingQueue</code> is a wrapper on LinkedBlockingQueue so

+ * we may factor out code common to JDK14 and JDK5+ using different implementation

+ * of LinkedBlockingQueue

+ *

+ * @author cquezel

+ * @since 5.2

+ */

+public class SocketLinkedBlockingQueue extends java.util.concurrent.LinkedBlockingQueue<Socket>

+{

+

+  /**

+   *

+   */

+  private static final long serialVersionUID = 4548450495806527985L;

+  // wrapper

+}

diff --git a/src/main/java/org/testng/internal/thread/ExecutorAdapter.java b/src/main/java/org/testng/internal/thread/ExecutorAdapter.java
new file mode 100755
index 0000000..b94a266
--- /dev/null
+++ b/src/main/java/org/testng/internal/thread/ExecutorAdapter.java
@@ -0,0 +1,62 @@
+package org.testng.internal.thread;
+
+
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An implementation for <code>IExecutor</code> based on <code>ThreadPoolExecutor</code>
+ *
+ * @author <a href="mailto:the_mindstorm@evolva.ro>Alexandru Popescu</a>
+ */
+public class ExecutorAdapter extends ThreadPoolExecutor implements IExecutor {
+  private IThreadFactory m_threadFactory;
+
+  public ExecutorAdapter(int threadCount, IThreadFactory tf) {
+      super(threadCount,
+            threadCount,
+            0L,
+            TimeUnit.MILLISECONDS,
+            new LinkedBlockingQueue<Runnable>(),
+            (ThreadFactory) tf.getThreadFactory());
+      m_threadFactory = tf;
+   }
+
+   @Override
+  public IFutureResult submitRunnable(final Runnable runnable) {
+      return new FutureResultAdapter(super.submit(runnable));
+   }
+
+   @Override
+  public void stopNow() {
+      super.shutdownNow();
+   }
+
+   @Override
+  public boolean awaitTermination(long timeout) {
+     boolean result= false;
+     try {
+      result= super.awaitTermination(timeout, TimeUnit.MILLISECONDS);
+     }
+     catch(InterruptedException handled) {
+       System.out.println("[WARN] ThreadPoolExecutor has been interrupted while awaiting termination");
+       Thread.currentThread().interrupt();
+     }
+
+     return result;
+   }
+
+  @Override
+  public StackTraceElement[][] getStackTraces() {
+    List<Thread> threads = m_threadFactory.getThreads();
+    int threadCount = threads.size();
+    StackTraceElement[][] result = new StackTraceElement[threadCount][];
+    for (int i = 0; i < result.length; i++) {
+      result[i] = threads.get(i).getStackTrace();
+    }
+    return result;
+  }
+}
\ No newline at end of file
diff --git a/src/main/java/org/testng/internal/thread/FutureResultAdapter.java b/src/main/java/org/testng/internal/thread/FutureResultAdapter.java
new file mode 100755
index 0000000..a13ae72
--- /dev/null
+++ b/src/main/java/org/testng/internal/thread/FutureResultAdapter.java
@@ -0,0 +1,28 @@
+package org.testng.internal.thread;
+
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+/**
+ * A very reduced interface of <code>Future</code>.
+ *
+ * @author <a href="mailto:the_mindstorm@evolva.ro>the_mindstorm</a>
+ */
+public class FutureResultAdapter implements IFutureResult {
+   Future<?> m_future;
+
+   public FutureResultAdapter(Future<?> future) {
+      m_future = future;
+   }
+
+   @Override
+  public Object get() throws InterruptedException, ThreadExecutionException {
+      try {
+         return m_future.get();
+      }
+      catch(ExecutionException ee) {
+         throw new ThreadExecutionException(ee.getCause()); // NOTE there is no need to keep the EE
+      }
+   }
+}
\ No newline at end of file
diff --git a/src/main/java/org/testng/internal/thread/IExecutor.java b/src/main/java/org/testng/internal/thread/IExecutor.java
new file mode 100755
index 0000000..3e3e32d
--- /dev/null
+++ b/src/main/java/org/testng/internal/thread/IExecutor.java
@@ -0,0 +1,18 @@
+package org.testng.internal.thread;
+
+/**
+ * Reduced interface to mimic an ExecutorService.
+ *
+ * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
+ */
+public interface IExecutor {
+   IFutureResult submitRunnable(Runnable runnable);
+
+   void shutdown();
+
+   boolean awaitTermination(long timeout);
+
+   void stopNow();
+
+   StackTraceElement[][] getStackTraces();
+}
diff --git a/src/main/java/org/testng/internal/thread/IFutureResult.java b/src/main/java/org/testng/internal/thread/IFutureResult.java
new file mode 100755
index 0000000..6a95e38
--- /dev/null
+++ b/src/main/java/org/testng/internal/thread/IFutureResult.java
@@ -0,0 +1,10 @@
+package org.testng.internal.thread;
+
+/**
+ * Reduced interface to mimic Future.
+ *
+ * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
+ */
+public interface IFutureResult {
+   Object get() throws InterruptedException, ThreadExecutionException;
+}
diff --git a/src/main/java/org/testng/internal/thread/IThreadFactory.java b/src/main/java/org/testng/internal/thread/IThreadFactory.java
new file mode 100755
index 0000000..7009097
--- /dev/null
+++ b/src/main/java/org/testng/internal/thread/IThreadFactory.java
@@ -0,0 +1,17 @@
+package org.testng.internal.thread;
+
+import java.util.List;
+
+/**
+ * Reduced interface to mimic ThreadFactory.
+ *
+ * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
+ * @version $Revision$
+ */
+public interface IThreadFactory {
+   Thread newThread(Runnable run);
+
+   Object getThreadFactory();
+
+   List<Thread> getThreads();
+}
diff --git a/src/main/java/org/testng/internal/thread/TestNGThread.java b/src/main/java/org/testng/internal/thread/TestNGThread.java
new file mode 100755
index 0000000..205b412
--- /dev/null
+++ b/src/main/java/org/testng/internal/thread/TestNGThread.java
@@ -0,0 +1,16 @@
+package org.testng.internal.thread;
+
+/**
+ * Custom named thread.
+ *
+ * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
+ */
+public class TestNGThread extends Thread {
+   public TestNGThread(String methodName) {
+      super("TestNGInvoker-" + methodName + "()");
+   }
+
+   public TestNGThread(Runnable target, String methodName) {
+      super(target, "TestNGInvoker-" + methodName + "()");
+   }
+}
\ No newline at end of file
diff --git a/src/main/java/org/testng/internal/thread/ThreadExecutionException.java b/src/main/java/org/testng/internal/thread/ThreadExecutionException.java
new file mode 100755
index 0000000..f1ff189
--- /dev/null
+++ b/src/main/java/org/testng/internal/thread/ThreadExecutionException.java
@@ -0,0 +1,14 @@
+package org.testng.internal.thread;
+
+/**
+ * Wrapper exception for ExecutionExceptions.
+ *
+ * @author <a href="mailto:the_mindstorm@evolva.ro>the_mindstorm</a>
+ */
+public class ThreadExecutionException extends Exception {
+	static final long serialVersionUID = -7766644143333236263L;
+
+   public ThreadExecutionException(Throwable t) {
+		super(t);
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/testng/internal/thread/ThreadTimeoutException.java b/src/main/java/org/testng/internal/thread/ThreadTimeoutException.java
new file mode 100755
index 0000000..437100b
--- /dev/null
+++ b/src/main/java/org/testng/internal/thread/ThreadTimeoutException.java
@@ -0,0 +1,22 @@
+package org.testng.internal.thread;
+
+/**
+ * Exception used to signal a thread timeout.
+ *
+ * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
+ */
+public class ThreadTimeoutException extends Exception {
+  static final long serialVersionUID = 7009400729783393548L;
+
+   public ThreadTimeoutException(String msg) {
+      super(msg);
+   }
+
+   public ThreadTimeoutException(Throwable cause) {
+      super(cause);
+   }
+
+   public ThreadTimeoutException(String msg, Throwable cause) {
+      super(msg, cause);
+   }
+}
\ No newline at end of file
diff --git a/src/main/java/org/testng/internal/thread/ThreadUtil.java b/src/main/java/org/testng/internal/thread/ThreadUtil.java
new file mode 100644
index 0000000..e74cfeb
--- /dev/null
+++ b/src/main/java/org/testng/internal/thread/ThreadUtil.java
@@ -0,0 +1,129 @@
+package org.testng.internal.thread;

+

+import org.testng.collections.Lists;

+import org.testng.internal.Utils;

+

+import java.util.List;

+import java.util.concurrent.Callable;

+import java.util.concurrent.CountDownLatch;

+import java.util.concurrent.ExecutorService;

+import java.util.concurrent.LinkedBlockingQueue;

+import java.util.concurrent.ThreadFactory;

+import java.util.concurrent.ThreadPoolExecutor;

+import java.util.concurrent.TimeUnit;

+

+/**

+ * A helper class to interface TestNG concurrency usage.

+ *

+ * @author <a href="mailto:the_mindstorm@evolva.ro>Alex Popescu</a>

+ */

+public class ThreadUtil {

+  private static final String THREAD_NAME = "TestNG";

+

+  /**

+   * @return true if the current thread was created by TestNG.

+   */

+  public static boolean isTestNGThread() {

+    return Thread.currentThread().getName().contains(THREAD_NAME);

+  }

+

+  /**

+   * Parallel execution of the <code>tasks</code>. The startup is synchronized so this method

+   * emulates a load test.

+   * @param tasks the list of tasks to be run

+   * @param threadPoolSize the size of the parallel threads to be used to execute the tasks

+   * @param timeout a maximum timeout to wait for tasks finalization

+   * @param triggerAtOnce <tt>true</tt> if the parallel execution of tasks should be trigger at once

+   */

+  public static final void execute(List<? extends Runnable> tasks, int threadPoolSize,

+      long timeout, boolean triggerAtOnce) {

+    final CountDownLatch startGate= new CountDownLatch(1);

+    final CountDownLatch endGate= new CountDownLatch(tasks.size());

+

+    Utils.log("ThreadUtil", 2, "Starting executor timeOut:" + timeout + "ms"

+        + " workers:" + tasks.size() + " threadPoolSize:" + threadPoolSize);

+    ExecutorService pooledExecutor = // Executors.newFixedThreadPool(threadPoolSize);

+        new ThreadPoolExecutor(threadPoolSize, threadPoolSize,

+        timeout, TimeUnit.MILLISECONDS,

+        new LinkedBlockingQueue<Runnable>(),

+        new ThreadFactory() {

+          @Override

+          public Thread newThread(Runnable r) {

+            Thread result = new Thread(r);

+            result.setName(THREAD_NAME);

+            return result;

+          }

+        });

+

+    List<Callable<Object>> callables = Lists.newArrayList();

+    for (final Runnable task : tasks) {

+      callables.add(new Callable<Object>() {

+

+        @Override

+        public Object call() throws Exception {

+          task.run();

+          return null;

+        }

+

+      });

+    }

+    try {

+      if (timeout != 0) {

+        pooledExecutor.invokeAll(callables, timeout, TimeUnit.MILLISECONDS);

+      } else {

+        pooledExecutor.invokeAll(callables);

+      }

+    } catch (InterruptedException handled) {

+      handled.printStackTrace();

+      Thread.currentThread().interrupt();

+    } finally {

+      pooledExecutor.shutdown();

+    }

+  }

+

+  /**

+   * Returns a readable name of the current executing thread.

+   */

+  public static final String currentThreadInfo() {

+    Thread thread= Thread.currentThread();

+    return String.valueOf(thread.getName() + "@" + thread.hashCode());

+  }

+

+  public static final IExecutor createExecutor(int threadCount, String threadFactoryName) {

+    return new ExecutorAdapter(threadCount, createFactory(threadFactoryName));

+  }

+

+  private static final IThreadFactory createFactory(String name) {

+    return new ThreadFactoryImpl(name);

+  }

+

+  private static void log(int level, String msg) {

+    Utils.log("ThreadUtil:" + ThreadUtil.currentThreadInfo(), level, msg);

+  }

+

+  public static class ThreadFactoryImpl implements IThreadFactory, ThreadFactory {

+    private String m_methodName;

+    private List<Thread> m_threads = Lists.newArrayList();

+

+    public ThreadFactoryImpl(String name) {

+      m_methodName= name;

+    }

+

+    @Override

+    public Thread newThread(Runnable run) {

+      Thread result = new TestNGThread(run, m_methodName);

+      m_threads.add(result);

+      return result;

+    }

+

+    @Override

+    public Object getThreadFactory() {

+      return this;

+    }

+

+    @Override

+    public List<Thread> getThreads() {

+      return m_threads;

+    }

+  }

+}

diff --git a/src/main/java/org/testng/internal/thread/graph/GraphThreadPoolExecutor.java b/src/main/java/org/testng/internal/thread/graph/GraphThreadPoolExecutor.java
new file mode 100644
index 0000000..5912588
--- /dev/null
+++ b/src/main/java/org/testng/internal/thread/graph/GraphThreadPoolExecutor.java
@@ -0,0 +1,154 @@
+package org.testng.internal.thread.graph;
+
+import org.testng.TestNGException;
+import org.testng.collections.Lists;
+import org.testng.internal.DynamicGraph;
+import org.testng.internal.DynamicGraph.Status;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An Executor that launches tasks per batches. It takes a {@code DynamicGraph}
+ * of tasks to be run and a {@code IThreadWorkerFactory} to initialize/create
+ * {@code Runnable} wrappers around those tasks
+ */
+public class GraphThreadPoolExecutor<T> extends ThreadPoolExecutor {
+  private static final boolean DEBUG = false;
+  /** Set to true if you want to generate GraphViz graphs */
+  private static final boolean DOT_FILES = false;
+
+  private DynamicGraph<T> m_graph;
+  private List<Runnable> m_activeRunnables = Lists.newArrayList();
+  private IThreadWorkerFactory<T> m_factory;
+  private List<String> m_dotFiles = Lists.newArrayList();
+  private int m_threadCount;
+
+  public GraphThreadPoolExecutor(DynamicGraph<T> graph, IThreadWorkerFactory<T> factory, int corePoolSize,
+      int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
+    super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue
+        /* , new TestNGThreadPoolFactory() */);
+    ppp("Initializing executor with " + corePoolSize + " threads and following graph " + graph);
+    m_threadCount = maximumPoolSize;
+    m_graph = graph;
+    m_factory = factory;
+
+    if (m_graph.getFreeNodes().isEmpty()) {
+      throw new TestNGException("The graph of methods contains a cycle:" + graph.getEdges());
+    }
+  }
+
+  public void run() {
+    synchronized(m_graph) {
+      if (DOT_FILES) {
+        m_dotFiles.add(m_graph.toDot());
+      }
+      List<T> freeNodes = m_graph.getFreeNodes();
+      runNodes(freeNodes);
+    }
+  }
+
+  /**
+   * Create one worker per node and execute them.
+   */
+  private void runNodes(List<T> freeNodes) {
+    List<IWorker<T>> runnables = m_factory.createWorkers(freeNodes);
+    for (IWorker<T> r : runnables) {
+      m_activeRunnables.add(r);
+      ppp("Added to active runnable");
+      setStatus(r, Status.RUNNING);
+      ppp("Executing: " + r);
+      try {
+        execute(r);
+//        if (m_threadCount > 1) execute(r);
+//        else r.run();
+      }
+      catch(Exception ex) {
+        ex.printStackTrace();
+      }
+    }
+  }
+
+  private void setStatus(IWorker<T> worker, Status status) {
+    ppp("Set status:" + worker + " status:" + status);
+    if (status == Status.FINISHED) {
+      m_activeRunnables.remove(worker);
+    }
+    synchronized(m_graph) {
+      for (T m : worker.getTasks()) {
+        m_graph.setStatus(m, status);
+      }
+    }
+  }
+
+  @Override
+  public void afterExecute(Runnable r, Throwable t) {
+    ppp("Finished runnable:" + r);
+    setStatus((IWorker<T>) r, Status.FINISHED);
+    synchronized(m_graph) {
+      ppp("Node count:" + m_graph.getNodeCount() + " and "
+          + m_graph.getNodeCountWithStatus(Status.FINISHED) + " finished");
+      if (m_graph.getNodeCount() == m_graph.getNodeCountWithStatus(Status.FINISHED)) {
+        ppp("Shutting down executor " + this);
+        if (DOT_FILES) {
+          generateFiles(m_dotFiles);
+        }
+        shutdown();
+      } else {
+        if (DOT_FILES) {
+          m_dotFiles.add(m_graph.toDot());
+        }
+        List<T> freeNodes = m_graph.getFreeNodes();
+        runNodes(freeNodes);
+      }
+    }
+//    if (m_activeRunnables.isEmpty() && m_index < m_runnables.getSize()) {
+//      runNodes(m_index++);
+//    }
+  }
+
+  private void generateFiles(List<String> files) {
+    try {
+      File dir = File.createTempFile("TestNG-", "");
+      dir.delete();
+      dir.mkdir();
+      for (int i = 0; i < files.size(); i++) {
+        File f = new File(dir, "" + (i < 10 ? "0" : "") + i + ".dot");
+        BufferedWriter bw = new BufferedWriter(new FileWriter(f));
+        bw.append(files.get(i));
+        bw.close();
+      }
+      if (DOT_FILES) {
+        System.out.println("Created graph files in " + dir);
+      }
+    } catch(IOException ex) {
+      ex.printStackTrace();
+    }
+  }
+
+  private void ppp(String string) {
+    if (DEBUG) {
+      System.out.println("============ [GraphThreadPoolExecutor] " + Thread.currentThread().getId() + " "
+          + string);
+    }
+  }
+
+}
+
+class TestNGThreadPoolFactory implements ThreadFactory {
+  private int m_count = 0;
+
+  @Override
+  public Thread newThread(Runnable r) {
+    Thread result = new Thread(r);
+    result.setName("TestNG-" + m_count++);
+    return result;
+  }
+}
diff --git a/src/main/java/org/testng/internal/thread/graph/IThreadWorkerFactory.java b/src/main/java/org/testng/internal/thread/graph/IThreadWorkerFactory.java
new file mode 100644
index 0000000..04b5a13
--- /dev/null
+++ b/src/main/java/org/testng/internal/thread/graph/IThreadWorkerFactory.java
@@ -0,0 +1,23 @@
+package org.testng.internal.thread.graph;
+
+import java.util.List;
+import java.util.Set;
+
+
+/**
+ * A factory that creates workers used by {@code GraphThreadPoolExecutor}
+ * @author nullin
+ *
+ * @param <T>
+ */
+public interface IThreadWorkerFactory<T> {
+
+  /**
+   * Creates {@code IWorker} for specified set of tasks. It is not necessary that
+   * number of workers returned be same as number of tasks entered.
+   *
+   * @param freeNodes tasks that need to be executed
+   * @return list of workers
+   */
+  List<IWorker<T>> createWorkers(List<T> freeNodes);
+}
diff --git a/src/main/java/org/testng/internal/thread/graph/IWorker.java b/src/main/java/org/testng/internal/thread/graph/IWorker.java
new file mode 100644
index 0000000..b30dbf5
--- /dev/null
+++ b/src/main/java/org/testng/internal/thread/graph/IWorker.java
@@ -0,0 +1,25 @@
+package org.testng.internal.thread.graph;

+

+import java.util.List;

+

+/**

+ * A runnable object that is used by {@code GraphThreadPoolExecutor} to execute

+ * tasks

+ */

+public interface IWorker<T> extends Runnable, Comparable<IWorker<T>> {

+

+  /**

+   * @return list of tasks this worker is working on.

+   */

+  List<T> getTasks();

+

+  /**

+   * @return the maximum time allowed for the worker to complete the task.

+   */

+  long getTimeOut();

+

+  /**

+   * @return the priority of this task.

+   */

+  int getPriority();

+}

diff --git a/src/main/java/org/testng/internal/thread/graph/SuiteWorkerFactory.java b/src/main/java/org/testng/internal/thread/graph/SuiteWorkerFactory.java
new file mode 100644
index 0000000..e7be20d
--- /dev/null
+++ b/src/main/java/org/testng/internal/thread/graph/SuiteWorkerFactory.java
@@ -0,0 +1,46 @@
+package org.testng.internal.thread.graph;
+
+import org.testng.ISuite;
+import org.testng.SuiteRunnerWorker;
+import org.testng.collections.Lists;
+import org.testng.internal.SuiteRunnerMap;
+
+import java.util.List;
+
+/**
+ * An {@code IThreadWorkerFactory} for {@code SuiteRunner}s
+ *
+ * @author nullin
+ *
+ */
+public class SuiteWorkerFactory implements IThreadWorkerFactory<ISuite>
+{
+  private Integer m_verbose;
+  private String m_defaultSuiteName;
+  private SuiteRunnerMap m_suiteRunnerMap;
+
+  public SuiteWorkerFactory(SuiteRunnerMap suiteRunnerMap,
+      Integer verbose, String defaultSuiteName) {
+    m_suiteRunnerMap = suiteRunnerMap;
+    m_verbose = verbose;
+    m_defaultSuiteName = defaultSuiteName;
+  }
+
+  /**
+   * For each suite, creates a {@code SuiteRunnerWorker}
+   * @param suites set of suite runners
+   * @return list of suite runner workers
+   */
+  @Override
+  public List<IWorker<ISuite>> createWorkers(List<ISuite> suites)
+  {
+    List<IWorker<ISuite>> suiteWorkers = Lists.newArrayList();
+    for (ISuite suiteRunner : suites) {
+      SuiteRunnerWorker worker = new SuiteRunnerWorker(suiteRunner, m_suiteRunnerMap,
+        m_verbose, m_defaultSuiteName);
+      suiteWorkers.add(worker);
+    }
+    return suiteWorkers;
+  }
+
+}
diff --git a/src/main/java/org/testng/junit/IJUnitTestRunner.java b/src/main/java/org/testng/junit/IJUnitTestRunner.java
new file mode 100755
index 0000000..e238c78
--- /dev/null
+++ b/src/main/java/org/testng/junit/IJUnitTestRunner.java
@@ -0,0 +1,26 @@
+package org.testng.junit;

+

+import java.util.Collection;

+import java.util.List;

+

+import org.testng.IInvokedMethodListener;

+import org.testng.ITestNGMethod;

+import org.testng.internal.ITestResultNotifier;

+

+

+/**

+ * An abstraction interface over JUnit test runners.

+ *

+ * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>

+ */

+public interface IJUnitTestRunner {

+

+  void setInvokedMethodListeners(Collection<IInvokedMethodListener> listener);

+

+  void setTestResultNotifier(ITestResultNotifier notifier);

+

+  void run(Class junitTestClass, String... methods);

+

+  List<ITestNGMethod> getTestMethods();

+

+}

diff --git a/src/main/java/org/testng/junit/JUnit3TestClass.java b/src/main/java/org/testng/junit/JUnit3TestClass.java
new file mode 100644
index 0000000..22f0b0b
--- /dev/null
+++ b/src/main/java/org/testng/junit/JUnit3TestClass.java
@@ -0,0 +1,14 @@
+package org.testng.junit;
+
+import junit.framework.Test;
+
+/**
+ *
+ * @author lukas
+ */
+public class JUnit3TestClass extends JUnitTestClass {
+
+    public JUnit3TestClass(Test test) {
+        super(test.getClass());
+    }
+}
diff --git a/src/main/java/org/testng/junit/JUnit3TestMethod.java b/src/main/java/org/testng/junit/JUnit3TestMethod.java
new file mode 100644
index 0000000..5cf3351
--- /dev/null
+++ b/src/main/java/org/testng/junit/JUnit3TestMethod.java
@@ -0,0 +1,29 @@
+package org.testng.junit;
+
+import java.lang.reflect.Method;
+import junit.framework.Test;
+import org.testng.internal.Utils;
+
+/**
+ *
+ * @author lukas
+ */
+public class JUnit3TestMethod extends JUnitTestMethod {
+
+    public JUnit3TestMethod(JUnitTestClass owner, Test test) {
+        super(owner, getMethod(test), test);
+    }
+
+    private static Method getMethod(Test t) {
+        String name = null;
+        try {
+            Method nameMethod = t.getClass().getMethod("getName");
+            name = (String) nameMethod.invoke(t);
+            return t.getClass().getMethod(name);
+        } catch (Throwable th) {
+            Utils.log("JUnit3TestMethod", 2,
+                    "Method '" + name + "' not found in class '" + t + "': " + th.getMessage());
+            return null;
+        }
+    }
+}
diff --git a/src/main/java/org/testng/junit/JUnit3TestRecognizer.java b/src/main/java/org/testng/junit/JUnit3TestRecognizer.java
new file mode 100644
index 0000000..86c4df8
--- /dev/null
+++ b/src/main/java/org/testng/junit/JUnit3TestRecognizer.java
@@ -0,0 +1,41 @@
+package org.testng.junit;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import junit.framework.Test;
+
+/**
+ *
+ * @author lukas
+ */
+public class JUnit3TestRecognizer implements JUnitTestRecognizer {
+
+    public JUnit3TestRecognizer() {
+    }
+
+    public boolean isTest(Class c) {
+        //class implementing junit.framework.Test with at least one test* method
+        if (Test.class.isAssignableFrom(c)) {
+            boolean haveTest = false;
+            for (Method m : c.getMethods()) {
+                if (m.getName().startsWith("test")) {
+                    haveTest = true;
+                    break;
+                }
+            }
+            if (haveTest) {
+                return true;
+            }
+        }
+        try {
+            //or a class with public static Test suite() method
+            Method m = c.getDeclaredMethod("suite");
+            if (Modifier.isPublic(m.getModifiers()) && Modifier.isStatic(m.getModifiers())) {
+                return m.getReturnType().isAssignableFrom(Test.class);
+            }
+        } catch (Throwable t) {
+            return false;
+        }
+        return false;
+    }
+}
diff --git a/src/main/java/org/testng/junit/JUnit4TestClass.java b/src/main/java/org/testng/junit/JUnit4TestClass.java
new file mode 100644
index 0000000..7751831
--- /dev/null
+++ b/src/main/java/org/testng/junit/JUnit4TestClass.java
@@ -0,0 +1,14 @@
+package org.testng.junit;
+
+import org.junit.runner.Description;
+
+/**
+ *
+ * @author lukas
+ */
+public class JUnit4TestClass extends JUnitTestClass {
+
+    public JUnit4TestClass(Description test) {
+        super(test.getTestClass());
+    }
+}
diff --git a/src/main/java/org/testng/junit/JUnit4TestMethod.java b/src/main/java/org/testng/junit/JUnit4TestMethod.java
new file mode 100644
index 0000000..04a0e79
--- /dev/null
+++ b/src/main/java/org/testng/junit/JUnit4TestMethod.java
@@ -0,0 +1,38 @@
+package org.testng.junit;
+
+import java.lang.reflect.Method;
+import org.junit.runner.Description;
+import org.testng.internal.Utils;
+
+/**
+ *
+ * @author lukas
+ */
+public class JUnit4TestMethod extends JUnitTestMethod {
+
+    public JUnit4TestMethod(JUnitTestClass owner, Description desc) {
+        super(owner, desc.getMethodName(), getMethod(desc), desc);
+    }
+
+    @Override
+    public Object[] getInstances() {
+        return new Object[0];
+    }
+
+    private static Method getMethod(Description desc) {
+        Class<?> c = desc.getTestClass();
+        String method = desc.getMethodName();
+        // remove [index] from method name in case of parameterized test
+        int idx = method.indexOf('[');
+        if (idx != -1) {
+            method = method.substring(0, idx);
+        }
+        try {
+            return c.getMethod(method);
+        } catch (Throwable t) {
+            Utils.log("JUnit4TestMethod", 2,
+                    "Method '" + method + "' not found in class '" + c.getName() + "': " + t.getMessage());
+            return null;
+        }
+    }
+}
diff --git a/src/main/java/org/testng/junit/JUnit4TestRecognizer.java b/src/main/java/org/testng/junit/JUnit4TestRecognizer.java
new file mode 100644
index 0000000..8758812
--- /dev/null
+++ b/src/main/java/org/testng/junit/JUnit4TestRecognizer.java
@@ -0,0 +1,33 @@
+package org.testng.junit;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import org.junit.runner.RunWith;
+
+/**
+ *
+ * @author lukas
+ */
+public final class JUnit4TestRecognizer implements JUnitTestRecognizer {
+
+    public JUnit4TestRecognizer() {
+    }
+
+    public boolean isTest(Class c) {
+        for (Annotation an: c.getAnnotations()) {
+            if (RunWith.class.isAssignableFrom(an.annotationType())) {
+                return true;
+            }
+        }
+        boolean haveTest = false;
+        for (Method m : c.getMethods()) {
+            for (Annotation a : m.getDeclaredAnnotations()) {
+                if (org.junit.Test.class.isAssignableFrom(a.annotationType())) {
+                    haveTest = true;
+                    break;
+                }
+            }
+        }
+        return haveTest;
+    }
+}
diff --git a/src/main/java/org/testng/junit/JUnit4TestRunner.java b/src/main/java/org/testng/junit/JUnit4TestRunner.java
new file mode 100644
index 0000000..dd841ad
--- /dev/null
+++ b/src/main/java/org/testng/junit/JUnit4TestRunner.java
@@ -0,0 +1,222 @@
+package org.testng.junit;
+
+import java.util.*;
+import java.util.regex.Pattern;
+import org.junit.runner.Description;
+import org.junit.runner.JUnitCore;
+import org.junit.runner.Request;
+import org.junit.runner.Result;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+import org.testng.*;
+import org.testng.collections.Lists;
+import org.testng.internal.ITestResultNotifier;
+import org.testng.internal.InvokedMethod;
+import org.testng.internal.TestResult;
+
+/**
+ * A JUnit TestRunner that records/triggers all information/events necessary to
+ * TestNG.
+ *
+ * @author Lukas Jungmann
+ */
+public class JUnit4TestRunner implements IJUnitTestRunner {
+
+    private ITestResultNotifier m_parentRunner;
+    private List<ITestNGMethod> m_methods = Lists.newArrayList();
+    private List<ITestListener> m_listeners = Lists.newArrayList();
+    private Collection<IInvokedMethodListener> m_invokeListeners = Lists.newArrayList();
+
+    public JUnit4TestRunner() {
+    }
+
+    public JUnit4TestRunner(ITestResultNotifier tr) {
+        m_parentRunner = tr;
+        m_listeners = m_parentRunner.getTestListeners();
+    }
+
+    /**
+     * Needed from TestRunner in order to figure out what JUnit test methods
+     * were run.
+     *
+     * @return the list of all JUnit test methods run
+     */
+    @Override
+    public List<ITestNGMethod> getTestMethods() {
+        return m_methods;
+    }
+
+    @Override
+    public void setTestResultNotifier(ITestResultNotifier notifier) {
+        m_parentRunner = notifier;
+        m_listeners = m_parentRunner.getTestListeners();
+    }
+
+    public void setInvokedMethodListeners(Collection<IInvokedMethodListener> listeners) {
+        m_invokeListeners = listeners;
+    }
+
+    /**
+     * A
+     * <code>start</code> implementation that ignores the
+     * <code>TestResult</code>
+     *
+     * @param testClass the JUnit test class
+     */
+    @Override
+    public void run(Class testClass, String... methods) {
+        start(testClass, methods);
+    }
+
+    /**
+     * Starts a test run. Analyzes the command line arguments and runs the given
+     * test suite.
+     */
+    public Result start(final Class testCase, final String... methods) {
+        try {
+            JUnitCore core = new JUnitCore();
+            core.addListener(new RL());
+            Request r = Request.aClass(testCase);
+            return core.run(r.filterWith(new Filter() {
+
+                @Override
+                public boolean shouldRun(Description description) {
+                    if (description == null) {
+                        return false;
+                    }
+                    if (methods.length == 0) {
+                        //run everything
+                        return true;
+                    }
+                    for (String m: methods) {
+                        Pattern p = Pattern.compile(m);
+                        if (p.matcher(description.getMethodName()).matches()) {
+                            return true;
+                        }
+                    }
+                    return false;
+                }
+
+                @Override
+                public String describe() {
+                    return "TestNG method filter";
+                }
+            }));
+        } catch (Throwable t) {
+            throw new TestNGException("Failure in JUnit mode for class " + testCase.getName(), t);
+        }
+    }
+
+    private class RL extends RunListener {
+
+        private Map<Description, ITestResult> runs = new WeakHashMap<>();
+        private List<Description> notified = new LinkedList<>();
+
+        @Override
+        public void testAssumptionFailure(Failure failure) {
+            notified.add(failure.getDescription());
+            ITestResult tr = runs.get(failure.getDescription());
+            tr.setStatus(TestResult.SKIP);
+            tr.setEndMillis(Calendar.getInstance().getTimeInMillis());
+            tr.setThrowable(failure.getException());
+            m_parentRunner.addSkippedTest(tr.getMethod(), tr);
+            for (ITestListener l : m_listeners) {
+                l.onTestSkipped(tr);
+            }
+        }
+
+        @Override
+        public void testFailure(Failure failure) throws Exception {
+            if (isAssumptionFailed(failure)) {
+                this.testAssumptionFailure(failure);
+                return;
+            }
+            notified.add(failure.getDescription());
+            ITestResult tr = runs.get(failure.getDescription());
+            tr.setStatus(TestResult.FAILURE);
+            tr.setEndMillis(Calendar.getInstance().getTimeInMillis());
+            tr.setThrowable(failure.getException());
+            m_parentRunner.addFailedTest(tr.getMethod(), tr);
+            for (ITestListener l : m_listeners) {
+                l.onTestFailure(tr);
+            }
+        }
+
+        @Override
+        public void testFinished(Description description) throws Exception {
+            ITestResult tr = runs.get(description);
+            if (!notified.contains(description)) {
+                tr.setStatus(TestResult.SUCCESS);
+                tr.setEndMillis(Calendar.getInstance().getTimeInMillis());
+                m_parentRunner.addPassedTest(tr.getMethod(), tr);
+                for (ITestListener l : m_listeners) {
+                    l.onTestSuccess(tr);
+                }
+            }
+            m_methods.add(tr.getMethod());
+        }
+
+        @Override
+        public void testIgnored(Description description) throws Exception {
+            ITestResult tr = createTestResult(description);
+            tr.setStatus(TestResult.SKIP);
+            tr.setEndMillis(tr.getStartMillis());
+            m_parentRunner.addSkippedTest(tr.getMethod(), tr);
+            m_methods.add(tr.getMethod());
+            for (ITestListener l : m_listeners) {
+                l.onTestSkipped(tr);
+            }
+        }
+
+        @Override
+        public void testRunFinished(Result result) throws Exception {
+        }
+
+        @Override
+        public void testRunStarted(Description description) throws Exception {
+        }
+
+        @Override
+        public void testStarted(Description description) throws Exception {
+            ITestResult tr = createTestResult(description);
+            runs.put(description, tr);
+            for (ITestListener l : m_listeners) {
+                l.onTestStart(tr);
+            }
+        }
+
+        private ITestResult createTestResult(Description test) {
+            JUnit4TestClass tc = new JUnit4TestClass(test);
+            JUnitTestMethod tm = new JUnit4TestMethod(tc, test);
+
+            TestResult tr = new TestResult(tc,
+                    test,
+                    tm,
+                    null,
+                    Calendar.getInstance().getTimeInMillis(),
+                    0,
+                    null);
+
+            InvokedMethod im = new InvokedMethod(tr.getTestClass(), tr.getMethod(), new Object[0], tr.getStartMillis(), tr);
+            m_parentRunner.addInvokedMethod(im);
+            for (IInvokedMethodListener l: m_invokeListeners) {
+                l.beforeInvocation(im, tr);
+            }
+            return tr;
+        }
+    }
+
+    private static boolean isAssumptionFailed(Failure failure) {
+        if (failure == null) {
+            return false;
+        }
+        //noinspection ThrowableResultOfMethodCallIgnored
+        final Throwable exception = failure.getException();
+        //noinspection SimplifiableIfStatement
+        if (exception == null) {
+            return false;
+        }
+        return "org.junit.internal.AssumptionViolatedException".equals(exception.getClass().getCanonicalName());
+    }
+}
diff --git a/src/main/java/org/testng/junit/JUnitMethodFinder.java b/src/main/java/org/testng/junit/JUnitMethodFinder.java
new file mode 100755
index 0000000..cdc6ae7
--- /dev/null
+++ b/src/main/java/org/testng/junit/JUnitMethodFinder.java
@@ -0,0 +1,199 @@
+package org.testng.junit;
+
+import org.testng.ITestMethodFinder;
+import org.testng.ITestNGMethod;
+import org.testng.collections.Lists;
+import org.testng.internal.TestNGMethod;
+import org.testng.internal.annotations.IAnnotationFinder;
+import org.testng.xml.XmlTest;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+
+/**
+ * This class locates all test and configuration methods according to JUnit.
+ * It is used to change the strategy used by TestRunner to locate its test
+ * methods.
+ *
+ * @author Cedric Beust, May 3, 2004
+ *
+ */
+public class JUnitMethodFinder implements ITestMethodFinder {
+  private String m_testName = null;
+  private IAnnotationFinder m_annotationFinder = null;
+
+  public JUnitMethodFinder(String testName, IAnnotationFinder finder) {
+    m_testName = testName;
+    m_annotationFinder = finder;
+  }
+
+  private Constructor findConstructor(Class cls, Class[] parameters) {
+    Constructor result = null;
+
+    try {
+      result = cls.getConstructor(parameters);
+    }
+    catch (SecurityException | NoSuchMethodException ex) {
+      // ignore
+    }
+
+    return result;
+  }
+
+  @Override
+  public ITestNGMethod[] getTestMethods(Class cls, XmlTest xmlTest) {
+    ITestNGMethod[] result =
+      privateFindTestMethods(new INameFilter() {
+        @Override
+        public boolean accept(Method method) {
+          return method.getName().startsWith("test") &&
+            method.getParameterTypes().length == 0;
+        }
+    }, cls);
+
+//    ppp("=====");
+//    ppp("FIND TEST METHOD RETURNING ");
+//    for (ITestMethod m : result) {
+//      ppp("  " + m);
+//    }
+//    ppp("=====");
+    return result;
+  }
+
+  private ITestNGMethod[] privateFindTestMethods(INameFilter filter, Class cls) {
+    List<ITestNGMethod> vResult = Lists.newArrayList();
+
+    // We do not want to walk up the class hierarchy and accept the
+    // same method twice (e.g. setUp) which would lead to double-invocation.
+    // All relevant JUnit methods are parameter-less so we store accepted
+    // method names in a Set to filter out duplicates.
+    Set<String> acceptedMethodNames = new HashSet<>();
+
+    //
+    // Collect all methods that start with test
+    //
+    Class current = cls;
+    while(!(current == Object.class)) {
+      Method[] allMethods = current.getDeclaredMethods();
+      for(Method allMethod : allMethods) {
+        ITestNGMethod m = new TestNGMethod(/* allMethods[i].getDeclaringClass(), */ allMethod,
+            m_annotationFinder, null,
+            null); /* @@@ */
+        Method method = m.getMethod();
+        String methodName = method.getName();
+        if(filter.accept(method) && !acceptedMethodNames.contains(methodName)) {
+          //          if (m.getName().startsWith("test")) {
+          //            ppp("Found JUnit test method: " + tm);
+          vResult.add(m);
+          acceptedMethodNames.add(methodName);
+        }
+      }
+      current = current.getSuperclass();
+    }
+
+    return vResult.toArray(new ITestNGMethod[vResult.size()]);
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[JUnitMethodFinder] " + s);
+  }
+
+  private Object instantiate(Class cls) {
+    Object result = null;
+
+    Constructor ctor = findConstructor(cls, new Class[] { String.class });
+    try {
+      if (null != ctor) {
+        result = ctor.newInstance(new Object[] { m_testName });
+      }
+      else {
+        ctor = cls.getConstructor(new Class[0]);
+        result = ctor.newInstance(new Object[0]);
+      }
+    }
+    catch (IllegalArgumentException | NoSuchMethodException | InvocationTargetException | IllegalAccessException | SecurityException ex) {
+      ex.printStackTrace();
+    } catch (InstantiationException ex) {
+      System.err.println("Couldn't find a constructor with a String parameter on your JUnit test class.");
+      ex.printStackTrace();
+    }
+
+    return result;
+  }
+
+
+  @Override
+  public ITestNGMethod[] getBeforeTestMethods(Class cls) {
+    ITestNGMethod[] result = privateFindTestMethods(new INameFilter() {
+        @Override
+        public boolean accept(Method method) {
+          return "setUp".equals(method.getName());
+        }
+      }, cls);
+
+    return result;
+  }
+
+  @Override
+  public ITestNGMethod[] getAfterTestMethods(Class cls) {
+    ITestNGMethod[] result =  privateFindTestMethods(new INameFilter() {
+        @Override
+        public boolean accept(Method method) {
+          return "tearDown".equals(method.getName());
+        }
+      }, cls);
+
+    return result;
+  }
+
+  @Override
+  public ITestNGMethod[] getAfterClassMethods(Class cls) {
+    return new ITestNGMethod[0];
+  }
+
+  @Override
+  public ITestNGMethod[] getBeforeClassMethods(Class cls) {
+    return new ITestNGMethod[0];
+  }
+
+  @Override
+  public ITestNGMethod[] getBeforeSuiteMethods(Class cls) {
+    return new ITestNGMethod[0];
+  }
+
+  @Override
+  public ITestNGMethod[] getAfterSuiteMethods(Class cls) {
+    return new ITestNGMethod[0];
+  }
+
+  @Override
+  public ITestNGMethod[] getBeforeTestConfigurationMethods(Class testClass) {
+    return new ITestNGMethod[0];
+  }
+
+  @Override
+  public ITestNGMethod[] getAfterTestConfigurationMethods(Class testClass) {
+    return new ITestNGMethod[0];
+  }
+
+  @Override
+  public ITestNGMethod[] getBeforeGroupsConfigurationMethods(Class testClass) {
+    return new ITestNGMethod[0];
+  }
+
+  @Override
+  public ITestNGMethod[] getAfterGroupsConfigurationMethods(Class testClass) {
+    return new ITestNGMethod[0];
+  }
+}
+
+/////////////
+
+interface INameFilter {
+  public boolean accept(Method method);
+}
diff --git a/src/main/java/org/testng/junit/JUnitTestClass.java b/src/main/java/org/testng/junit/JUnitTestClass.java
new file mode 100644
index 0000000..25b4d13
--- /dev/null
+++ b/src/main/java/org/testng/junit/JUnitTestClass.java
@@ -0,0 +1,191 @@
+package org.testng.junit;
+
+import java.util.List;
+import org.testng.ITestClass;
+import org.testng.ITestNGMethod;
+import org.testng.collections.Lists;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlTest;
+
+/**
+ *
+ * @author lukas
+ */
+//NO JUnit specific code here to avoid runtime errors
+public abstract class JUnitTestClass implements ITestClass {
+
+    private static final long serialVersionUID = 405598615794850925L;
+    private List<ITestNGMethod> m_testMethods = Lists.newArrayList();
+    private List<ITestNGMethod> m_beforeClass = Lists.newArrayList();
+    private List<ITestNGMethod> m_afterClass = Lists.newArrayList();
+    private List<ITestNGMethod> m_beforeTest = Lists.newArrayList();
+    private List<ITestNGMethod> m_afterTest = Lists.newArrayList();
+    private Class m_realClass;
+    private Object[] m_instances;
+    private long[] m_instanceHashes;
+
+    public JUnitTestClass(Class test) {
+        m_realClass = test;
+        m_instances = new Object[]{test};
+        m_instanceHashes = new long[]{test.hashCode()};
+    }
+
+    List<ITestNGMethod> getTestMethodList() {
+      return m_testMethods;
+    }
+    
+    /**
+     * @see org.testng.IClass#addInstance(java.lang.Object)
+     */
+    @Override
+    public void addInstance(Object instance) {
+        throw new IllegalStateException("addInstance is not supported for JUnit");
+    }
+
+    /**
+     * @see org.testng.IClass#getName()
+     */
+    @Override
+    public String getName() {
+        return m_realClass.getName();
+    }
+
+    /**
+     * @see org.testng.IClass#getRealClass()
+     */
+    @Override
+    public Class getRealClass() {
+        return m_realClass;
+    }
+
+    @Override
+    public String getTestName() {
+        return null;
+    }
+
+    @Override
+    public XmlTest getXmlTest() {
+        return null;
+    }
+
+    @Override
+    public XmlClass getXmlClass() {
+        return null;
+    }
+
+    /**
+     * @see org.testng.ITestClass#getInstanceCount()
+     */
+    @Override
+    public int getInstanceCount() {
+        return 1;
+    }
+
+    /**
+     * @see org.testng.ITestClass#getInstanceHashCodes()
+     */
+    @Override
+    public long[] getInstanceHashCodes() {
+        return m_instanceHashes;
+    }
+
+    /**
+     * @see org.testng.ITestClass#getInstances(boolean)
+     */
+    @Override
+    public Object[] getInstances(boolean reuse) {
+        return m_instances;
+    }
+
+    /**
+     * @see org.testng.ITestClass#getTestMethods()
+     */
+    @Override
+    public ITestNGMethod[] getTestMethods() {
+        return m_testMethods.toArray(new ITestNGMethod[m_testMethods.size()]);
+    }
+
+    /**
+     * @see org.testng.ITestClass#getBeforeTestMethods()
+     */
+    @Override
+    public ITestNGMethod[] getBeforeTestMethods() {
+        return m_beforeTest.toArray(new ITestNGMethod[m_beforeTest.size()]);
+    }
+
+    /**
+     * @see org.testng.ITestClass#getAfterTestMethods()
+     */
+    @Override
+    public ITestNGMethod[] getAfterTestMethods() {
+        return m_afterTest.toArray(new ITestNGMethod[m_afterTest.size()]);
+    }
+
+    /**
+     * @see org.testng.ITestClass#getBeforeClassMethods()
+     */
+    @Override
+    public ITestNGMethod[] getBeforeClassMethods() {
+        return m_beforeClass.toArray(new ITestNGMethod[m_beforeClass.size()]);
+    }
+
+    /**
+     * @see org.testng.ITestClass#getAfterClassMethods()
+     */
+    @Override
+    public ITestNGMethod[] getAfterClassMethods() {
+        return m_afterClass.toArray(new ITestNGMethod[m_afterClass.size()]);
+    }
+
+    //features not supported by JUnit
+    private static final ITestNGMethod[] EMPTY_METHODARRAY = new ITestNGMethod[0];
+
+    /**
+     * @see org.testng.ITestClass#getBeforeSuiteMethods()
+     */
+    @Override
+    public ITestNGMethod[] getBeforeSuiteMethods() {
+        return EMPTY_METHODARRAY;
+    }
+
+    /**
+     * @see org.testng.ITestClass#getAfterSuiteMethods()
+     */
+    @Override
+    public ITestNGMethod[] getAfterSuiteMethods() {
+        return EMPTY_METHODARRAY;
+    }
+
+    /**
+     * @see org.testng.ITestClass#getBeforeGroupsMethods()
+     */
+    @Override
+    public ITestNGMethod[] getBeforeGroupsMethods() {
+        return EMPTY_METHODARRAY;
+    }
+
+    /**
+     * @see org.testng.ITestClass#getAfterGroupsMethods()
+     */
+    @Override
+    public ITestNGMethod[] getAfterGroupsMethods() {
+        return EMPTY_METHODARRAY;
+    }
+
+    //already deprecated stuff, not usable in junit
+    /**
+     * @see org.testng.ITestClass#getBeforeTestConfigurationMethods()
+     */
+    @Override
+    public ITestNGMethod[] getBeforeTestConfigurationMethods() {
+        return EMPTY_METHODARRAY;
+    }
+
+    /**
+     * @see org.testng.ITestClass#getAfterTestConfigurationMethods()
+     */
+    @Override
+    public ITestNGMethod[] getAfterTestConfigurationMethods() {
+        return EMPTY_METHODARRAY;
+    }
+}
diff --git a/src/main/java/org/testng/junit/JUnitTestFinder.java b/src/main/java/org/testng/junit/JUnitTestFinder.java
new file mode 100644
index 0000000..13e55b5
--- /dev/null
+++ b/src/main/java/org/testng/junit/JUnitTestFinder.java
@@ -0,0 +1,56 @@
+package org.testng.junit;
+
+import java.lang.reflect.Modifier;
+import org.testng.internal.Utils;
+
+/**
+ *
+ * @author ljungman
+ */
+public final class JUnitTestFinder {
+
+    private static final String JUNIT3_TEST = "junit.framework.Test";
+    private static final String JUNIT3_FINDER = "org.testng.junit.JUnit3TestRecognizer";
+    private static final String JUNIT4_TEST = "org.junit.Test";
+    private static final String JUNIT4_FINDER = "org.testng.junit.JUnit4TestRecognizer";
+    private static final JUnitTestRecognizer junit3;
+    private static final JUnitTestRecognizer junit4;
+
+    static {
+        junit3 = getJUnitTestRecognizer(JUNIT3_TEST, JUNIT3_FINDER);
+        junit4 = getJUnitTestRecognizer(JUNIT4_TEST, JUNIT4_FINDER);
+        if (junit3 == null) {
+            Utils.log("JUnitTestFinder", 2, "JUnit3 was not found on the classpath");
+
+        }
+        if (junit4 == null) {
+            Utils.log("JUnitTestFinder", 2, "JUnit4 was not found on the classpath");
+        }
+    }
+
+    public static boolean isJUnitTest(Class c) {
+        if (!haveJUnit()) {
+            return false;
+        }
+        //only public classes are interesting, so filter out the rest
+        if (!Modifier.isPublic(c.getModifiers()) || c.isInterface() || c.isAnnotation() || c.isEnum()) {
+            return false;
+        }
+        return (junit3 != null && junit3.isTest(c)) || (junit4 != null && junit4.isTest(c));
+    }
+
+    private static boolean haveJUnit() {
+        return junit3 != null || junit4 != null;
+    }
+
+    private static JUnitTestRecognizer getJUnitTestRecognizer(String test, String name) {
+        try {
+            Class.forName(test);
+            Class<JUnitTestRecognizer> c = (Class<JUnitTestRecognizer>) Class.forName(name);
+            return c.newInstance();
+        } catch (Throwable t) {
+            //ignore
+            return null;
+        }
+    }
+}
diff --git a/src/main/java/org/testng/junit/JUnitTestMethod.java b/src/main/java/org/testng/junit/JUnitTestMethod.java
new file mode 100644
index 0000000..91ee459
--- /dev/null
+++ b/src/main/java/org/testng/junit/JUnitTestMethod.java
@@ -0,0 +1,34 @@
+package org.testng.junit;
+
+import java.lang.reflect.Method;
+import org.testng.ITestNGMethod;
+import org.testng.internal.BaseTestMethod;
+
+/**
+ *
+ * @author lukas
+ */
+//NO JUnit specific code here to avoid runtime errors
+public abstract class JUnitTestMethod extends BaseTestMethod {
+
+    protected JUnitTestMethod(JUnitTestClass owner, Method method, Object instance) {
+        this(owner, method.getName(), method, instance);
+    }
+
+    protected JUnitTestMethod(JUnitTestClass owner, String methodName, Method method, Object instance) {
+        super(methodName, method, null, instance);
+        setTestClass(owner);
+        owner.getTestMethodList().add(this);
+    }
+
+    @Override
+    public boolean isTest() {
+        return true;
+    }
+
+    @Override
+    public ITestNGMethod clone() {
+        throw new IllegalStateException("clone is not supported for JUnit");
+    }
+
+}
diff --git a/src/main/java/org/testng/junit/JUnitTestRecognizer.java b/src/main/java/org/testng/junit/JUnitTestRecognizer.java
new file mode 100644
index 0000000..ddb358c
--- /dev/null
+++ b/src/main/java/org/testng/junit/JUnitTestRecognizer.java
@@ -0,0 +1,10 @@
+package org.testng.junit;
+
+/**
+ *
+ * @author lukas
+ */
+interface JUnitTestRecognizer {
+
+    boolean isTest(Class c);
+}
diff --git a/src/main/java/org/testng/junit/JUnitTestRunner.java b/src/main/java/org/testng/junit/JUnitTestRunner.java
new file mode 100755
index 0000000..f92d510
--- /dev/null
+++ b/src/main/java/org/testng/junit/JUnitTestRunner.java
@@ -0,0 +1,309 @@
+package org.testng.junit;

+

+

+import java.lang.reflect.Constructor;

+import org.testng.ITestListener;

+import org.testng.ITestNGMethod;

+import org.testng.ITestResult;

+import org.testng.TestNGException;

+import org.testng.collections.Lists;

+import org.testng.internal.ITestResultNotifier;

+import org.testng.internal.InvokedMethod;

+

+import java.lang.reflect.InvocationTargetException;

+import java.lang.reflect.Method;

+import java.lang.reflect.Modifier;

+import java.util.Calendar;

+import java.util.Collection;

+import java.util.List;

+import java.util.Map;

+import java.util.WeakHashMap;

+

+import junit.framework.AssertionFailedError;

+import junit.framework.Test;

+import junit.framework.TestListener;

+import junit.framework.TestResult;

+import junit.framework.TestSuite;

+import org.testng.*;

+

+/**

+ * A JUnit TestRunner that records/triggers all information/events necessary to TestNG.

+ *

+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>

+ */

+public class JUnitTestRunner implements TestListener, IJUnitTestRunner {

+  public static final String SUITE_METHODNAME = "suite";

+

+  private ITestResultNotifier m_parentRunner;

+

+  private Map<Test, TestRunInfo> m_tests= new WeakHashMap<>();

+  private List<ITestNGMethod> m_methods= Lists.newArrayList();

+  private Collection<IInvokedMethodListener> m_invokedMethodListeners = Lists.newArrayList();

+

+  public JUnitTestRunner() {

+  }

+

+  public JUnitTestRunner(ITestResultNotifier tr) {

+    m_parentRunner= tr;

+  }

+

+  /**

+   * Needed from TestRunner in order to figure out what JUnit test methods were run.

+   *

+   * @return the list of all JUnit test methods run

+   */

+  @Override

+  public List<ITestNGMethod> getTestMethods() {

+    return m_methods;

+  }

+

+  @Override

+  public void setTestResultNotifier(ITestResultNotifier notifier) {

+    m_parentRunner= notifier;

+  }

+

+  /**

+   * @see junit.framework.TestListener#startTest(junit.framework.Test)

+   */

+  @Override

+  public void startTest(Test test) {

+    m_tests.put(test, new TestRunInfo(Calendar.getInstance().getTimeInMillis()));

+  }

+

+

+  /**

+   * @see junit.framework.TestListener#addError(junit.framework.Test, java.lang.Throwable)

+   */

+  @Override

+  public void addError(Test test, Throwable t) {

+    recordFailure(test, t);

+  }

+

+  /**

+   * @see junit.framework.TestListener#addFailure(junit.framework.Test, junit.framework.AssertionFailedError)

+   */

+  @Override

+  public void addFailure(Test test, AssertionFailedError t) {

+    recordFailure(test, t);

+  }

+

+  private void recordFailure(Test test, Throwable t) {

+    TestRunInfo tri= m_tests.get(test);

+    if(null != tri) {

+      tri.setThrowable(t);

+    }

+  }

+

+  /**

+   * @see junit.framework.TestListener#endTest(junit.framework.Test)

+   */

+  @Override

+  public void endTest(Test test) {

+    TestRunInfo tri= m_tests.get(test);

+    if(null == tri) {

+      return; // HINT: this should never happen. How do I protect myself?

+    }

+

+    org.testng.internal.TestResult tr= recordResults(test, tri);

+

+    runTestListeners(tr, m_parentRunner.getTestListeners());

+  }

+

+    public void setInvokedMethodListeners(Collection<IInvokedMethodListener> listeners) {

+        m_invokedMethodListeners = listeners;

+    }

+

+

+  private org.testng.internal.TestResult recordResults(Test test, TestRunInfo tri)  {

+    JUnitTestClass tc= new JUnit3TestClass(test);

+    JUnitTestMethod tm= new JUnit3TestMethod(tc, test);

+

+    org.testng.internal.TestResult tr= new org.testng.internal.TestResult(tc,

+                                                                          test,

+                                                                          tm,

+                                                                          tri.m_failure,

+                                                                          tri.m_start,

+                                                                          Calendar.getInstance().getTimeInMillis(),

+                                                                          null);

+

+    if(tri.isFailure()) {

+      tr.setStatus(ITestResult.FAILURE);

+      m_parentRunner.addFailedTest(tm, tr);

+    }

+    else {

+      m_parentRunner.addPassedTest(tm, tr);

+    }

+

+    InvokedMethod im = new InvokedMethod(test, tm, new Object[0], tri.m_start, tr);

+    m_parentRunner.addInvokedMethod(im);

+    m_methods.add(tm);

+    for (IInvokedMethodListener l: m_invokedMethodListeners) {

+        l.beforeInvocation(im, tr);

+    }

+

+    return tr;

+  }

+

+  private static void runTestListeners(ITestResult tr, List<ITestListener> listeners) {

+    for (ITestListener itl : listeners) {

+      switch(tr.getStatus()) {

+        case ITestResult.SKIP: {

+          itl.onTestSkipped(tr);

+          break;

+        }

+        case ITestResult.SUCCESS_PERCENTAGE_FAILURE: {

+          itl.onTestFailedButWithinSuccessPercentage(tr);

+          break;

+        }

+        case ITestResult.FAILURE: {

+          itl.onTestFailure(tr);

+          break;

+        }

+        case ITestResult.SUCCESS: {

+          itl.onTestSuccess(tr);

+          break;

+        }

+

+        case ITestResult.STARTED: {

+          itl.onTestStart(tr);

+          break;

+        }

+

+        default: {

+          assert false : "UNKNOWN STATUS:" + tr;

+        }

+      }

+    }

+  }

+

+  /**

+   * Returns the Test corresponding to the given suite. This is

+   * a template method, subclasses override runFailed(), clearStatus().

+   */

+  protected Test getTest(Class testClass, String... methods) {

+    if (methods.length > 0) {

+      TestSuite ts = new TestSuite();

+      try {

+        Constructor c = testClass.getConstructor(String.class);

+        for (String m: methods) {

+          try {

+            ts.addTest((Test) c.newInstance(m));

+          } catch (InstantiationException ex) {

+            runFailed(testClass, "abstract class " + ex);

+          } catch (IllegalAccessException ex) {

+            runFailed(testClass, "constructor is not public " + ex);

+          } catch (IllegalArgumentException ex) {

+            runFailed(testClass, "actual and formal parameters differ " + ex);

+          } catch (InvocationTargetException ex) {

+            runFailed(testClass, "exception while instatiating test for method '" + m + "' " + ex);

+          }

+        }

+      } catch (NoSuchMethodException ex) {

+        runFailed(testClass, "no constructor accepting String argument found " + ex);

+      } catch (SecurityException ex) {

+        runFailed(testClass, "security exception " + ex);

+      }

+      return ts;

+    }

+    Method suiteMethod = null;

+    try {

+      suiteMethod = testClass.getMethod(SUITE_METHODNAME, new Class[0]);

+    }

+    catch (Exception e) {

+

+      // try to extract a test suite automatically

+      return new TestSuite(testClass);

+    }

+    if (!Modifier.isStatic(suiteMethod.getModifiers())) {

+      runFailed(testClass, "suite() method must be static");

+

+      return null;

+    }

+    Test test = null;

+    try {

+      test = (Test) suiteMethod.invoke(null, (Object[]) new Class[0]); // static method

+      if (test == null) {

+        return test;

+      }

+    }

+    catch (InvocationTargetException e) {

+      runFailed(testClass, "failed to invoke method suite():" + e.getTargetException().toString());

+

+      return null;

+    }

+    catch (IllegalAccessException e) {

+      runFailed(testClass, "failed to invoke method suite():" + e.toString());

+

+      return null;

+    }

+

+    return test;

+  }

+

+  /**

+   * A <code>start</code> implementation that ignores the <code>TestResult</code>

+   * @param testClass the JUnit test class

+   */

+  @Override

+  public void run(Class testClass, String... methods) {

+    start(testClass, methods);

+  }

+

+  /**

+   * Starts a test run. Analyzes the command line arguments and runs the given

+   * test suite.

+   */

+  public TestResult start(Class testCase, String... methods) {

+    try {

+      Test suite = getTest(testCase, methods);

+

+      if(null != suite) {

+        return doRun(suite);

+      }

+      else {

+        runFailed(testCase, "could not create/run JUnit test suite");

+      }

+    }

+    catch (Exception e) {

+      runFailed(testCase, "could not create/run JUnit test suite: " + e.getMessage());

+    }

+

+    return null;

+  }

+

+  protected void runFailed(Class clazz, String message) {

+    throw new TestNGException("Failure in JUnit mode for class " + clazz.getName() + ": " + message);

+  }

+

+  /**

+   * Creates the TestResult to be used for the test run.

+   */

+  protected TestResult createTestResult() {

+    return new TestResult();

+  }

+

+  protected TestResult doRun(Test suite) {

+    TestResult result = createTestResult();

+    result.addListener(this);

+    suite.run(result);

+

+    return result;

+  }

+

+  private static class TestRunInfo {

+    private final long m_start;

+    private Throwable m_failure;

+

+    public TestRunInfo(long start) {

+      m_start= start;

+    }

+

+    public boolean isFailure() {

+      return null != m_failure;

+    }

+

+    public void setThrowable(Throwable t) {

+      m_failure= t;

+    }

+  }

+}

diff --git a/src/main/java/org/testng/log/TextFormatter.java b/src/main/java/org/testng/log/TextFormatter.java
new file mode 100755
index 0000000..76078f7
--- /dev/null
+++ b/src/main/java/org/testng/log/TextFormatter.java
@@ -0,0 +1,25 @@
+package org.testng.log;
+
+import java.util.logging.LogRecord;
+import java.util.logging.SimpleFormatter;
+
+
+/**
+ * This class implements a simple TextFormatter because the brainded
+ * default formatter of java.util.logging outputs everything on two
+ * lines and it's ugly as butt.
+ *
+ * @author Cedric Beust, May 2, 2004
+ *
+ */
+public class TextFormatter extends SimpleFormatter {
+  @Override
+  public synchronized String format(LogRecord record) {
+    StringBuffer result = new StringBuffer();
+
+    result.append(record.getMessage()).append("\n");
+
+    return result.toString();
+  }
+
+}
diff --git a/src/main/java/org/testng/log4testng/Logger.java b/src/main/java/org/testng/log4testng/Logger.java
new file mode 100755
index 0000000..834051c
--- /dev/null
+++ b/src/main/java/org/testng/log4testng/Logger.java
@@ -0,0 +1,723 @@
+package org.testng.log4testng;

+

+

+import java.io.ByteArrayOutputStream;

+import java.io.IOException;

+import java.io.InputStream;

+import java.io.PrintStream;

+import java.util.Iterator;

+import java.util.Map;

+import java.util.Map.Entry;

+import java.util.Properties;

+

+import org.testng.Assert;

+import org.testng.collections.Maps;

+

+/**

+ * TestNG support logging via a custom logging framework similar to

+ * <a href="http://logging.apache.org/log4j"> Log4j</a>. To control logging, add a

+ * resource named "log4testng.properties" to your classpath. The logging levels are

+ * TRACE, DEBUG, INFO, WARN, ERROR and FATAL.

+ * The Logging framework has the following characteristics:

+ *

+ * <ul>

+ * <li>All logging is done using System.out (for levels &lt; ERROR) or System.err. There

+ * is no way to specify Appenders.</li>

+ * <li>There is no way to control logging programmatically.</li>

+ * <li>The log4testng.properties resource is searched in the classpath on the first

+ * call to the logging API. If it is not present, logging defaults to the WARN

+ * level.</li>

+ * </ul>

+ *

+ * The property file contains lines in the following format:

+ *

+ * <pre><code>

+ * # log4testng will log its own behavior (generally used for debugging this package only).

+ * log4testng.debug=true

+ *

+ * # Specifies the root Loggers logging level. Will log DEBUG level and above

+ * log4testng.rootLogger=DEBUG

+ *

+ * # The org.testng.reporters.EmailableReporter Logger will log TRACE level and above

+ * log4testng.logger.org.testng.reporters.EmailableReporter=TRACE

+ *

+ * # All Logger in packages below org.testng will log WARN level and above

+ * log4testng.logger.org.testng=WARN

+ * </code></pre>

+ *

+ * In your source files you will typically instantiate and use loggers this ways:

+ * <pre><code>

+ * import org.testng.log4testng.Logger;

+ *

+ * class ThisClass {

+ *     private static final Logger LOGGER = Logger.getLogger(ThisClass.class);

+ *

+ *     ...

+ *     LOGGER.debug("entering myMethod()");

+ *     ...

+ *     LOGGER.warn("unknown file: " + filename);

+ *     ...

+ *     LOGGER.error("Unexpected error", exception);

+ * </code></pre>

+ */

+public class Logger {

+

+  // Attribute an hierarchical integer value to all levels.

+  private static int i= 0;

+  private static final int TRACE= i++;

+  private static final int DEBUG= i++;

+  private static final int INFO= i++;

+  private static final int WARN= i++;

+  private static final int ERROR= i++;

+  private static final int FATAL= i++;

+  private static final int LEVEL_COUNT= i;

+

+  /** Standard prefix of all property names in log4testng.properties. */

+  private static final String PREFIX= "log4testng.";

+

+  /** Standard prefix of all logger names in log4testng.properties. */

+  private static final String LOGGER_PREFIX= PREFIX + "logger.";

+

+  /** Root logger name in log4testng.properties. */

+  private static final String ROOT_LOGGER= PREFIX + "rootLogger";

+

+  /** Debug property name in log4testng.properties. */

+  private static final String DEBUG_PROPERTY= PREFIX + "debug";

+

+  /** The standard error stream (this is allways System.err except for unit tests) */

+  private static PrintStream err= System.err;

+

+  /** The standard output stream (this is allways System.out except for unit tests) */

+  private static PrintStream out= System.out;

+

+  /** An ordered list of level names. */

+  private static final String[] levelNames= new String[LEVEL_COUNT];

+

+  static {

+    levelNames[TRACE]= "TRACE";

+    levelNames[DEBUG]= "DEBUG";

+    levelNames[INFO] = "INFO";

+    levelNames[WARN] = "WARN";

+    levelNames[ERROR] = "ERROR";

+    levelNames[FATAL] = "FATAL";

+  }

+

+  /** A map from level name to level integer index (TRACE->0, DEBUG->1 ...) */

+  private static final Map<String, Integer> levelMap= Maps.newHashMap();

+

+  static {

+    for(i= 0; i < LEVEL_COUNT; ++i) {

+      levelMap.put(levelNames[i], i);

+    }

+  }

+

+  /** true if the Logging system has been initialized. */

+  private static boolean initialized;

+

+  /** Map from Logger names to level index (as specified in log4testng.properties) */

+  private static final Map<String, Integer> loggerLevels = Maps.newHashMap();

+

+  /** Map of all known loggers. */

+  private static final Map<Class, Logger> loggers = Maps.newHashMap();

+

+  /** The logging level of the root logger (defaults to warn). */

+  private static int rootLoggerLevel= WARN;

+

+  /** Should log4testng log what it is doing (defaults to false). */

+  private static boolean debug= false;

+

+  /** The logger's level */

+  private final int level;

+

+  /** The logger's name. */

+  private final Class klass;

+  private final String m_className;

+

+  /**

+   * Retrieve a logger named according to the value of the pClass.getName()

+   * parameter. If the named logger already exists, then the existing instance

+   * will be returned. Otherwise, a new instance is created. By default, loggers

+   * do not have a set level but inherit it from their nearest ancestor with

+   * a set level.

+   *

+   * @param pClass The class' logger to retrieve.

+   * @return a logger named according to the value of the pClass.getName().

+   */

+  public static synchronized Logger getLogger(Class pClass) {

+    initialize();

+    Logger logger= loggers.get(pClass);

+    if(logger != null) {

+      return logger;

+    }

+    int level= getLevel(pClass);

+    logger= new Logger(pClass, level);

+    loggers.put(pClass, logger);

+

+    return logger;

+  }

+

+  /**

+   * Check whether this logger is enabled for the TRACE Level.

+   * @return true if this logger is enabled for level TRACE, false otherwise.

+   */

+  public boolean isTraceEnabled() {

+    return isLevelEnabled(TRACE);

+  }

+

+  /**

+   * Log a message object with the TRACE level. This method first checks if this

+   * logger is TRACE enabled. If this logger is TRACE enabled, then it converts

+   * the message object (passed as parameter) to a string by invoking toString().

+   * WARNING Note that passing a Throwable to this method will print the name of

+   * the Throwable but no stack trace. To print a stack trace use the

+   * trace(Object, Throwable) form instead.

+   * @param message the message object to log.

+   */

+  public void trace(Object message) {

+    log(TRACE, message, null);

+  }

+

+  /**

+   * Log a message object with the TRACE level including the stack trace of the

+   * Throwable t passed as parameter.

+   * See Logger.trace(Object) form for more detailed information.

+   * @param message the message object to log.

+   * @param t the exception to log, including its stack trace.

+   */

+  public void trace(Object message, Throwable t) {

+    log(TRACE, message, t);

+  }

+

+  /**

+   * Check whether this logger is enabled for the DEBUG Level.

+   * @return true if this logger is enabled for level DEBUG, false otherwise.

+   */

+  public boolean isDebugEnabled() {

+    return isLevelEnabled(DEBUG);

+  }

+

+  /**

+   * Log a message object with the DEBUG level.

+   * See Logger.trace(Object) form for more detailed information.

+   * @param message the message object to log.

+   */

+  public void debug(Object message) {

+    log(DEBUG, message, null);

+  }

+

+  /**

+   * Log a message object with the DEBUG level including the stack trace of the

+   * Throwable t passed as parameter.

+   * See Logger.trace(Object, Throwable) form for more detailed information.

+   * @param message the message object to log.

+   * @param t the exception to log, including its stack trace.

+   */

+  public void debug(Object message, Throwable t) {

+    log(DEBUG, message, t);

+  }

+

+  /**

+   * Check whether this logger is enabled for the INFO Level.

+   * @return true if this logger is enabled for level INFO, false otherwise.

+   */

+  public boolean isInfoEnabled() {

+    return isLevelEnabled(INFO);

+  }

+

+  /**

+   * Log a message object with the INFO level.

+   * See Logger.trace(Object) form for more detailed information.

+   * @param message the message object to log.

+   */

+  public void info(Object message) {

+    log(INFO, message, null);

+  }

+

+  /**

+   * Log a message object with the WARN level including the stack trace of the

+   * Throwable t passed as parameter.

+   * See Logger.trace(Object, Throwable) form for more detailed information.

+   * @param message the message object to log.

+   * @param t the exception to log, including its stack trace.

+   */

+  public void info(Object message, Throwable t) {

+    log(INFO, message, t);

+  }

+

+  /**

+   * Log a message object with the WARN level.

+   * See Logger.trace(Object) form for more detailed information.

+   * @param message the message object to log.

+   */

+  public void warn(Object message) {

+    log(WARN, message, null);

+  }

+

+  /**

+   * Log a message object with the ERROR level including the stack trace of the

+   * Throwable t passed as parameter.

+   * See Logger.trace(Object, Throwable) form for more detailed information.

+   * @param message the message object to log.

+   * @param t the exception to log, including its stack trace.

+   */

+  public void warn(Object message, Throwable t) {

+    log(WARN, message, t);

+  }

+

+  /**

+   * Log a message object with the ERROR level.

+   * See Logger.trace(Object) form for more detailed information.

+   * @param message the message object to log.

+   */

+  public void error(Object message) {

+    log(ERROR, message, null);

+  }

+

+  /**

+   * Log a message object with the DEBUG level including the stack trace of the

+   * Throwable t passed as parameter.

+   * See Logger.trace(Object, Throwable) form for more detailed information.

+   * @param message the message object to log.

+   * @param t the exception to log, including its stack trace.

+   */

+  public void error(Object message, Throwable t) {

+    log(ERROR, message, t);

+  }

+

+  /**

+   * Log a message object with the FATAL level.

+   * See Logger.trace(Object) form for more detailed information.

+   * @param message the message object to log.

+   */

+  public void fatal(Object message) {

+    log(FATAL, message, null);

+  }

+

+  /**

+   * Log a message object with the FATAL level including the stack trace of the

+   * Throwable t passed as parameter.

+   * See Logger.trace(Object, Throwable) form for more detailed information.

+   * @param message the message object to log.

+   * @param t the exception to log, including its stack trace.

+   */

+  public void fatal(Object message, Throwable t) {

+    log(FATAL, message, t);

+  }

+

+  private Logger(Class pClass, int pLevel) {

+    level= pLevel;

+    klass= pClass;

+    m_className= pClass.getName().substring(pClass.getName().lastIndexOf('.') + 1);

+  }

+

+  private static synchronized void initialize() {

+    if(initialized) {

+      return;

+    }

+

+    // We flag as initialized right away because if anything goes wrong

+    // We still consider it initialized. TODO Is this OK?

+    initialized= true;

+

+    InputStream is= Thread.currentThread().getContextClassLoader().getResourceAsStream("log4testng.properties");

+    if(is == null) {

+      return;

+    }

+    Properties properties= new Properties();

+    try {

+      properties.load(is);

+    }

+    catch(IOException e) {

+      throw new RuntimeException(e);

+    }

+

+    checkProperties(properties);

+  }

+

+  private static void checkProperties(Properties pProperties) {

+    {

+      // See if we want to debug log4testng

+      String debugStr= pProperties.getProperty(DEBUG_PROPERTY);

+      if(debugStr != null) {

+        if(debugStr.equalsIgnoreCase("true")) {

+          debug= true;

+        }

+        else if(debugStr.equalsIgnoreCase("false")) {

+          debug= false;

+        }

+        else {

+          throw new IllegalArgumentException("Unknown " + DEBUG_PROPERTY

+                                             + " value " + debugStr);

+        }

+      }

+      loglog4testng("log4testng.debug set to " + debug);

+    }

+

+    {

+      // Set the value of the root logger (if any).

+      String rootLevelStr= pProperties.getProperty(ROOT_LOGGER);

+      if(rootLevelStr != null) {

+        Integer ilevel= levelMap.get(rootLevelStr.toUpperCase());

+        if(ilevel == null) {

+          throw new IllegalArgumentException("Unknown level for log4testng.rootLogger "

+                                             + rootLevelStr + " in log4testng.properties");

+        }

+        rootLoggerLevel= ilevel;

+        loglog4testng("Root level logger set to " + rootLevelStr + " level.");

+      }

+    }

+

+    Iterator it= pProperties.entrySet().iterator();

+    while(it.hasNext()) {

+      Map.Entry entry= (Entry) it.next();

+      String logger= (String) entry.getKey();

+      String level= (String) entry.getValue();

+

+      if(!logger.startsWith(PREFIX)) {

+        throw new IllegalArgumentException("Illegal property value: " + logger);

+      }

+      if(logger.equals(DEBUG_PROPERTY)) {

+        // Already handled

+      }

+      else if(logger.equals(ROOT_LOGGER)) {

+        // Already handled

+      }

+      else {

+        if(!logger.startsWith(LOGGER_PREFIX)) {

+          throw new IllegalArgumentException("Illegal property value: " + logger);

+        }

+

+        Integer ilevel= levelMap.get(level.toUpperCase());

+        if(ilevel == null) {

+          throw new IllegalArgumentException("Unknown level " + level + " for logger " + logger

+                                             + " in log4testng.properties");

+        }

+

+        loggerLevels.put(logger.substring(LOGGER_PREFIX.length()), ilevel);

+        loglog4testng("logger " + logger + " set to " + ilevel + " level.");

+      }

+    }

+  }

+

+  /**

+   * Returns the level associated to the current class. The level is obtain by searching

+   * for a logger in the "testng-logging.properties" resource. For example, if class is

+   * "org.testng.TestNG" the the following loggers are searched in this order:

+   * <ol>

+   * <li>"org.testng.TestNG"</li>

+   * <li>"org.testng"</li>

+   * <li>"org"</li>

+   * <li>The root level</li>

+   * </ol>

+   *

+   * @param pClass the class name used for logger name.

+   * @return the level associated to the current class.

+   */

+  private static int getLevel(Class pClass) {

+    String name= pClass.getName();

+    loglog4testng("Getting level for logger " + name);

+    while(true) {

+      Integer level= loggerLevels.get(name);

+      if(level != null) {

+        loglog4testng("Found level " + level + " for logger " + name);

+

+        return level;

+      }

+      int dot= name.lastIndexOf('.');

+      if(dot == -1) {

+        loglog4testng("Found level " + rootLoggerLevel + " for root logger");

+

+        // Logger name not found. Defaults to root logger level.

+        return rootLoggerLevel;

+      }

+      name= name.substring(0, dot);

+    }

+  }

+

+  private boolean isLevelEnabled(int pLevel) {

+    return level <= pLevel;

+  }

+

+  private void log(int pLevel, Object pMessage, Throwable pT) {

+    if(isLevelEnabled(pLevel)) {

+      PrintStream ps= (pLevel >= ERROR) ? err : out;

+      if(null != pT) {

+        synchronized(ps) {

+          ps.println("[" + m_className + "] [" + levelNames[pLevel] + "] " + pMessage);

+          pT.printStackTrace(ps);

+        }

+      }

+      else {

+        ps.println("[" + m_className + "] [" + levelNames[pLevel] + "] " + pMessage);

+      }

+    }

+  }

+

+  /**

+   * Logs the message to System.out of debug is on.

+   * @param pmessage the message to log to the console

+   */

+  private static void loglog4testng(String pmessage) {

+    if(debug) {

+      out.println("[log4testng] [debug] " + pmessage);

+    }

+  }

+

+  /**

+   * This method is for debugging purpose only.

+   *

+   * @param pProperties a properties bundle initialised as log4testng

+   * property file would be.

+   * @param pOut the standard output stream to be used for logging.

+   * @param pErr the standard error stream to be used for logging.

+   */

+  private static synchronized void testInitialize(Properties pProperties,

+                                                  PrintStream pOut,

+                                                  PrintStream pErr) {

+    initialized= true;

+    loggers.clear();

+    rootLoggerLevel= WARN;

+    debug= false;

+    out= pOut;

+    err= pErr;

+    checkProperties(pProperties);

+  }

+

+  /**

+   * Makes sure the default debug value is false.

+   */

+  private static void testDebugDefault() {

+    Properties props= new Properties();

+    ByteArrayOutputStream out1= new ByteArrayOutputStream();

+    ByteArrayOutputStream err1= new ByteArrayOutputStream();

+    PrintStream out2= new PrintStream(out1);

+    PrintStream err2= new PrintStream(err1);

+    props.put("log4testng.rootLogger", "WARN");

+    testInitialize(props, out2, err2);

+    Assert.assertEquals(out1.toString(), "");

+    Assert.assertEquals(err1.toString(), "");

+  }

+

+  /**

+   * Makes sure the debug value can be turned on and actualls logs something.

+   */

+  private static void testDebugOn() {

+    Properties props= new Properties();

+    ByteArrayOutputStream out1= new ByteArrayOutputStream();

+    ByteArrayOutputStream err1= new ByteArrayOutputStream();

+    PrintStream out2= new PrintStream(out1);

+    PrintStream err2= new PrintStream(err1);

+    props.put("log4testng.debug", "true");

+    props.put("log4testng.rootLogger", "WARN");

+    testInitialize(props, out2, err2);

+    Assert.assertTrue(out1.toString().startsWith("[log4testng][debug]"));

+    Assert.assertEquals(err1.toString(), "");

+  }

+

+  /**

+   * Makes sure the debug value can be turned off and logs nothing.

+   */

+  private static void testDebugOff() {

+    Properties props= new Properties();

+    ByteArrayOutputStream out1= new ByteArrayOutputStream();

+    ByteArrayOutputStream err1= new ByteArrayOutputStream();

+    PrintStream out2= new PrintStream(out1);

+    PrintStream err2= new PrintStream(err1);

+    props.put("log4testng.debug", "false");

+    props.put("log4testng.rootLogger", "WARN");

+    testInitialize(props, out2, err2);

+    Assert.assertEquals(out1.toString(), "");

+    Assert.assertEquals(err1.toString(), "");

+  }

+

+  /**

+   * Makes sure an illegal debug value throws an exception.

+   */

+  private static void testDebugError() {

+    Properties props= new Properties();

+    ByteArrayOutputStream out1= new ByteArrayOutputStream();

+    ByteArrayOutputStream err1= new ByteArrayOutputStream();

+    PrintStream out2= new PrintStream(out1);

+    PrintStream err2= new PrintStream(err1);

+    props.put("log4testng.debug", "unknown");

+    props.put("log4testng.rootLogger", "WARN");

+    try {

+      testInitialize(props, out2, err2);

+      throw new RuntimeException("failure");

+    }

+    catch(IllegalArgumentException pEx) {

+

+      // Normal case

+      Assert.assertEquals(out1.toString(), "");

+      Assert.assertEquals(err1.toString(), "");

+    }

+  }

+

+  /**

+   * Tests that the root logger's default level is WARN and that loggers do not

+   * log bellow this level and do log in the correct stream for levels equal to

+   * and above WARN.

+   */

+  private static void testRootLoggerDefault() {

+    Properties props= new Properties();

+    ByteArrayOutputStream out1= new ByteArrayOutputStream();

+    ByteArrayOutputStream err1= new ByteArrayOutputStream();

+    PrintStream out2= new PrintStream(out1);

+    PrintStream err2= new PrintStream(err1);

+    testInitialize(props, out2, err2);

+

+    Logger strLogger= Logger.getLogger(String.class);

+    strLogger.trace("trace should not appear");

+    Assert.assertEquals(out1.toString(), "");

+    Assert.assertEquals(err1.toString(), "");

+    strLogger.debug("debug should not appear");

+    Assert.assertEquals(out1.toString(), "");

+    Assert.assertEquals(err1.toString(), "");

+    strLogger.info("info should not appear");

+    Assert.assertEquals(out1.toString(), "");

+    Assert.assertEquals(err1.toString(), "");

+    strLogger.warn("warn should appear");

+    int outlength= out1.toString().length();

+    Assert.assertTrue(out1.toString().startsWith("[java.lang.String] [WARN] warn should appear"));

+    Assert.assertEquals(err1.toString(), "");

+    strLogger.error("error should appear");

+    Assert.assertEquals(out1.toString().length(), outlength);

+    Assert.assertTrue(err1.toString().startsWith("[java.lang.String] [ERROR] error should appear"));

+    strLogger.fatal("fatal should appear");

+    Assert.assertEquals(out1.toString().length(), outlength);

+    Assert.assertTrue(err1.toString().contains("[java.lang.String] [FATAL] fatal should appear"));

+  }

+

+  /**

+   * Test setting the root logger level

+   */

+  private static void testRootLoggerSet() {

+    Properties props= new Properties();

+    ByteArrayOutputStream out1= new ByteArrayOutputStream();

+    ByteArrayOutputStream err1= new ByteArrayOutputStream();

+    PrintStream out2= new PrintStream(out1);

+    PrintStream err2= new PrintStream(err1);

+    props.put("log4testng.rootLogger", "DEBUG");

+    testInitialize(props, out2, err2);

+

+    Logger strLogger= Logger.getLogger(String.class);

+    strLogger.trace("trace should appear");

+    Assert.assertEquals(out1.toString(), "");

+    Assert.assertEquals(err1.toString(), "");

+    strLogger.debug("debug should appear");

+    Assert.assertTrue(out1.toString().startsWith("[java.lang.String] [DEBUG] debug should appear"));

+    Assert.assertEquals(err1.toString(), "");

+  }

+

+  /**

+   * Test setting the root logger to an illegal level value throws an exception.

+   */

+  private static void testRootLoggerSetError() {

+    Properties props= new Properties();

+    ByteArrayOutputStream out1= new ByteArrayOutputStream();

+    ByteArrayOutputStream err1= new ByteArrayOutputStream();

+    PrintStream out2= new PrintStream(out1);

+    PrintStream err2= new PrintStream(err1);

+    props.put("log4testng.rootLogger", "unknown");

+    try {

+      testInitialize(props, out2, err2);

+      throw new RuntimeException("failure");

+    }

+    catch(IllegalArgumentException pEx) {

+

+      // Normal case

+      Assert.assertEquals(out1.toString(), "");

+      Assert.assertEquals(err1.toString(), "");

+    }

+  }

+

+  /**

+   * Test setting a user logger level

+   */

+  private static void testUserLoggerSet() {

+    Properties props= new Properties();

+    ByteArrayOutputStream out1= new ByteArrayOutputStream();

+    ByteArrayOutputStream err1= new ByteArrayOutputStream();

+    PrintStream out2= new PrintStream(out1);

+    PrintStream err2= new PrintStream(err1);

+    props.put("log4testng.logger.java.lang.String", "DEBUG");

+    testInitialize(props, out2, err2);

+

+    Logger strLogger= Logger.getLogger(String.class);

+    strLogger.trace("trace should not appear");

+    Assert.assertEquals(out1.toString(), "");

+    Assert.assertEquals(err1.toString(), "");

+    strLogger.debug("debug should appear");

+    int outLength= out1.toString().length();

+    Assert.assertTrue(out1.toString().startsWith("[java.lang.String] [DEBUG] debug should appear"));

+    Assert.assertEquals(err1.toString(), "");

+

+    Logger classLogger= Logger.getLogger(Class.class);

+    classLogger.debug("debug should not appear");

+    Assert.assertEquals(out1.toString().length(), outLength);

+    Assert.assertEquals(err1.toString(), "");

+    classLogger.warn("warn should appear");

+    Assert.assertTrue(out1.toString().contains("[java.lang.Class] [WARN] warn should appear"));

+    Assert.assertEquals(err1.toString(), "");

+  }

+

+  /**

+   * Test setting a user logger to an illegal level value throws an exception

+   */

+  private static void testUserLoggerSetError() {

+    Properties props= new Properties();

+    ByteArrayOutputStream out1= new ByteArrayOutputStream();

+    ByteArrayOutputStream err1= new ByteArrayOutputStream();

+    PrintStream out2= new PrintStream(out1);

+    PrintStream err2= new PrintStream(err1);

+    props.put("log4testng.logger.java.lang.String", "unknown");

+    try {

+      testInitialize(props, out2, err2);

+      throw new RuntimeException("failure");

+    }

+    catch(IllegalArgumentException pEx) {

+

+      // Normal case

+      Assert.assertEquals(out1.toString(), "");

+      Assert.assertEquals(err1.toString(), "");

+    }

+  }

+

+  /**

+   * Tests setting a partial logger name (a hierarchy scope)

+   */

+  private static void testUserLoggerSetHierarchy() {

+    Properties props= new Properties();

+    ByteArrayOutputStream out1= new ByteArrayOutputStream();

+    ByteArrayOutputStream err1= new ByteArrayOutputStream();

+    PrintStream out2= new PrintStream(out1);

+    PrintStream err2= new PrintStream(err1);

+    props.put("log4testng.logger.java.lang", "DEBUG");

+    testInitialize(props, out2, err2);

+

+    Logger strLogger= Logger.getLogger(String.class);

+    strLogger.trace("trace should not appear");

+    Assert.assertEquals(out1.toString(), "");

+    Assert.assertEquals(err1.toString(), "");

+    strLogger.debug("debug should appear");

+    Assert.assertTrue(out1.toString().startsWith("[java.lang.String] [DEBUG] debug should appear"));

+    Assert.assertEquals(err1.toString(), "");

+  }

+

+  /**

+   * Run all tests. (very crusty ...)

+   * @param pArgs not used

+   */

+  public static void main(String[] pArgs) {

+    testDebugDefault();

+    testDebugOn();

+    testDebugOff();

+    testDebugError();

+    testRootLoggerDefault();

+    testRootLoggerSet();

+    testRootLoggerSetError();

+    testUserLoggerSet();

+    testUserLoggerSetError();

+    testUserLoggerSetHierarchy();

+  }

+}

diff --git a/src/main/java/org/testng/mustache/BaseChunk.java b/src/main/java/org/testng/mustache/BaseChunk.java
new file mode 100644
index 0000000..bb96e69
--- /dev/null
+++ b/src/main/java/org/testng/mustache/BaseChunk.java
@@ -0,0 +1,18 @@
+package org.testng.mustache;
+
+abstract public class BaseChunk {
+
+  protected Model m_model;
+
+  public BaseChunk(Model model) {
+    m_model = model;
+  }
+
+  protected void p(String string) {
+    if (false) {
+      System.out.println(string);
+    }
+  }
+
+  abstract String compose();
+}
diff --git a/src/main/java/org/testng/mustache/Model.java b/src/main/java/org/testng/mustache/Model.java
new file mode 100644
index 0000000..1607020
--- /dev/null
+++ b/src/main/java/org/testng/mustache/Model.java
@@ -0,0 +1,83 @@
+package org.testng.mustache;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+import java.util.Stack;
+
+public class Model {
+  private Map<String, Object> m_model;
+  private Stack<SubModel> m_subModels = new Stack<>();
+  private static class SubModel {
+    String variable;
+    Object subModel;
+  }
+
+  public Model(Map<String, Object> model) {
+    m_model = model;
+  }
+
+  public void push(String variable, Object subModel) {
+    SubModel sl = new SubModel();
+    sl.variable = variable;
+    sl.subModel = subModel;
+    m_subModels.push(sl);
+  }
+
+  public Value resolveValue(String variable) {
+    if (! m_subModels.isEmpty()) {
+      for (SubModel object : m_subModels) {
+        Value value = resolveOnClass(object.subModel, variable);
+        if (value != null) {
+          return value;
+        }
+      }
+    }
+
+    return new Value(m_model.get(variable));
+  }
+
+  private Value resolveOnClass(Object object, String variable) {
+//    if (object instanceof Iterable) {
+//      Iterable it = (Iterable) object;
+//      List<Object> result = Lists.newArrayList();
+//      for (Object o : it) {
+//        List<Object> values = resolveOnClass(o, variable);
+//        result.addAll(values);
+//      }
+//      return result;
+//    } else {
+      Class<? extends Object> cls = object.getClass();
+      try {
+        Field f = cls.getField(variable);
+        return new Value(f.get(object));
+      } catch (IllegalAccessException | NoSuchFieldException | SecurityException e) {
+//        e.printStackTrace();
+      }
+//    }
+
+    return null;
+  }
+
+  public Object getTopSubModel() {
+    return m_subModels.peek().variable;
+  }
+
+  public void popSubModel() {
+    m_subModels.pop();
+  }
+
+  public String resolveValueToString(String variable) {
+    StringBuilder result = new StringBuilder();
+    Value value = resolveValue(variable);
+    if (value.get() != null) {
+      return value.get().toString();
+    } else {
+      return "";
+    }
+  }
+
+  @Override
+  public String toString() {
+    return "[Model " + m_model + " subModel:" + m_subModels + "]";
+  }
+}
diff --git a/src/main/java/org/testng/mustache/Mustache.java b/src/main/java/org/testng/mustache/Mustache.java
new file mode 100644
index 0000000..40777e4
--- /dev/null
+++ b/src/main/java/org/testng/mustache/Mustache.java
@@ -0,0 +1,112 @@
+package org.testng.mustache;
+
+import org.testng.collections.Lists;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+public class Mustache {
+
+  public String run(String template, Map<String, Object> m) throws IOException {
+    return run(template, new Model(m));
+  }
+
+  String run(String template, Model model) throws IOException {
+    int lineNumber = 0;
+
+    List<BaseChunk> chunks = Lists.newArrayList();
+    int ti = 0;
+    while (ti < template.length()) {
+      int start;
+      int end;
+      if (template.charAt(ti) == '\n') lineNumber++;
+
+      if (template.charAt(ti) == '{' && ti + 1 < template.length()
+          && template.charAt(ti + 1) == '{') {
+        int index = ti + 2;
+        start = index;
+        boolean foundEnd = false;
+        while (index < template.length() && ! foundEnd) {
+          index++;
+          foundEnd = template.charAt(index) == '}' && index + 1 < template.length()
+              && template.charAt(index + 1) == '}';
+        }
+
+        if (foundEnd) {
+          end = index;
+          String variable = template.substring(start, end);
+          p("Found variable:" + variable);
+          if (variable.startsWith("#")) {
+            // Complex variable {{#foo}}.
+            String conditionalVariable = variable.substring(1);
+            Value value = model.resolveValue(conditionalVariable);
+            int endIndex = findClosingIndex(template, ti, conditionalVariable);
+            Object v = value.get();
+            if (v == null) {
+              // Null condition, do nothing
+            } else if (v instanceof Iterable) {
+              // Iterable, call a sub Mustache to process this chunk in a loop
+              // after pushing a new submodel
+              Iterable it = (Iterable) v;
+              String subTemplate = template.substring(ti + variable.length() + 4, endIndex);
+              for (Object o : it) {
+                model.push(conditionalVariable, o);
+                String r = new Mustache().run(subTemplate, model);
+                model.popSubModel();
+                chunks.add(new StringChunk(model, r));
+              }
+            } else {
+              // Scope, call a sub Mustache to process this chunk
+              // after pushing a new submodel
+              String subTemplate = template.substring(ti + variable.length() + 4, endIndex);
+              model.push(conditionalVariable, v);
+              String r = new Mustache().run(subTemplate, model);
+              model.popSubModel();
+              chunks.add(new StringChunk(model, r));
+            }
+            ti = endIndex + variable.length() + 4;
+          } else {
+            // Regular variable {{foo}}
+            chunks.add(new VariableChunk(model, variable));
+            ti += variable.length() + 4;
+          }
+        } else {
+          throw new RuntimeException("Unclosed variable at line " + lineNumber);
+        }
+      } else {
+        chunks.add(new StringChunk(model, "" + template.charAt(ti)));
+        ti++;
+      }
+    } // while
+
+    p("******************** Final composition, chunks:");
+    StringBuilder result = new StringBuilder();
+    p("*** Template:" + template);
+    for (BaseChunk bc : chunks) {
+      p("***  " + bc);
+    }
+
+    for (BaseChunk bc : chunks) {
+      String c = bc.compose();
+      result.append(c);
+    }
+    p("*** Final result:" + result);
+    return result.toString();
+  }
+
+  private int findClosingIndex(String template, int ti,
+      String conditionalVariable) {
+    int result = template.lastIndexOf("{{/" + conditionalVariable);
+    return result;
+  }
+
+  private void p(String string) {
+    if (false) {
+      System.out.println(string);
+    }
+  }
+
+  public static void main(String[] args) throws IOException {
+  }
+}
diff --git a/src/main/java/org/testng/mustache/StringChunk.java b/src/main/java/org/testng/mustache/StringChunk.java
new file mode 100644
index 0000000..9ba28b7
--- /dev/null
+++ b/src/main/java/org/testng/mustache/StringChunk.java
@@ -0,0 +1,21 @@
+package org.testng.mustache;
+
+public class StringChunk extends BaseChunk {
+
+  private String m_string;
+
+  public StringChunk(Model model, String string) {
+    super(model);
+    m_string = string;
+  }
+
+  @Override
+  public String compose() {
+    return m_string;
+  }
+
+  @Override
+  public String toString() {
+    return "[StringChunk " + m_string + "]";
+  }
+}
diff --git a/src/main/java/org/testng/mustache/Value.java b/src/main/java/org/testng/mustache/Value.java
new file mode 100644
index 0000000..3afe804
--- /dev/null
+++ b/src/main/java/org/testng/mustache/Value.java
@@ -0,0 +1,14 @@
+package org.testng.mustache;
+
+public class Value {
+  private Object m_object;
+
+  public Value(Object object) {
+    m_object = object;
+  }
+
+  public Object get() {
+    return m_object;
+  }
+
+}
diff --git a/src/main/java/org/testng/mustache/VariableChunk.java b/src/main/java/org/testng/mustache/VariableChunk.java
new file mode 100644
index 0000000..16e9468
--- /dev/null
+++ b/src/main/java/org/testng/mustache/VariableChunk.java
@@ -0,0 +1,23 @@
+package org.testng.mustache;
+
+public class VariableChunk extends BaseChunk {
+
+  private String m_variable;
+
+  public VariableChunk(Model model, String variable) {
+    super(model);
+    m_variable = variable;
+  }
+
+  @Override
+  public String compose() {
+    String result = m_model.resolveValueToString(m_variable);
+    p("VariableChunk returning: " + result);
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return "[VariableChunk " + m_variable + " model:" + m_model + "]";
+  }
+}
diff --git a/src/main/java/org/testng/remote/ConnectionInfo.java b/src/main/java/org/testng/remote/ConnectionInfo.java
new file mode 100755
index 0000000..882ff3d
--- /dev/null
+++ b/src/main/java/org/testng/remote/ConnectionInfo.java
@@ -0,0 +1,35 @@
+package org.testng.remote;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.Socket;
+
+public class ConnectionInfo {
+  private Socket m_socket;
+  private ObjectInputStream m_ois;
+  private ObjectOutputStream m_oos;
+
+  public ObjectInputStream getOis() throws IOException {
+    if (m_ois == null) {
+      m_ois = new ObjectInputStream(m_socket.getInputStream());
+    }
+    return m_ois;
+  }
+
+  public ObjectOutputStream getOos() throws IOException {
+    if (m_oos == null) {
+      m_oos = new ObjectOutputStream(m_socket.getOutputStream());
+    }
+    return m_oos;
+  }
+
+  public void setSocket(Socket s) {
+    m_socket = s;
+  }
+
+  public Socket getSocket() {
+    return m_socket;
+  }
+
+}
diff --git a/src/main/java/org/testng/remote/RemoteArgs.java b/src/main/java/org/testng/remote/RemoteArgs.java
new file mode 100644
index 0000000..af89db1
--- /dev/null
+++ b/src/main/java/org/testng/remote/RemoteArgs.java
@@ -0,0 +1,17 @@
+package org.testng.remote;
+
+import com.beust.jcommander.Parameter;
+
+public class RemoteArgs {
+  public static final String PORT = "-serport";
+  @Parameter(names = PORT, description = "The port for the serialization protocol")
+  public Integer serPort;
+
+  public static final String DONT_EXIT= "-dontexit";
+  @Parameter(names = DONT_EXIT, description = "Do not exit the JVM once done")
+  public boolean dontExit = false;
+
+  public static final String ACK = "-ack";
+  @Parameter(names = ACK, description = "Use ACK's")
+  public boolean ack = false;
+}
diff --git a/src/main/java/org/testng/remote/RemoteSuiteWorker.java b/src/main/java/org/testng/remote/RemoteSuiteWorker.java
new file mode 100755
index 0000000..2beca4e
--- /dev/null
+++ b/src/main/java/org/testng/remote/RemoteSuiteWorker.java
@@ -0,0 +1,34 @@
+package org.testng.remote;
+
+import org.testng.SuiteRunner;
+import org.testng.internal.remote.SlavePool;
+import org.testng.remote.adapter.RemoteResultListener;
+import org.testng.xml.XmlSuite;
+
+/**
+ * A worker that will be put into an Executor and that sends a suite
+ * This class
+ *
+ * @author cbeust
+ */
+public class RemoteSuiteWorker extends RemoteWorker implements Runnable {
+  private XmlSuite m_suite;
+
+  public RemoteSuiteWorker(XmlSuite suite, SlavePool slavePool, RemoteResultListener listener) {
+    super(listener, slavePool);
+    m_suite = suite;
+  }
+
+  @Override
+  public void run() {
+    try {
+      SuiteRunner result = sendSuite(getSlavePool().getSlave(), m_suite);
+      m_listener.onResult(result);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+
+  }
+}
+
diff --git a/src/main/java/org/testng/remote/RemoteTestNG.java b/src/main/java/org/testng/remote/RemoteTestNG.java
new file mode 100644
index 0000000..1e1ebea
--- /dev/null
+++ b/src/main/java/org/testng/remote/RemoteTestNG.java
@@ -0,0 +1,278 @@
+package org.testng.remote;

+

+import static org.testng.internal.Utils.defaultIfStringEmpty;

+

+import com.beust.jcommander.JCommander;

+import com.beust.jcommander.ParameterException;

+

+import org.testng.CommandLineArgs;

+import org.testng.IClassListener;

+import org.testng.IInvokedMethodListener;

+import org.testng.ISuite;

+import org.testng.ISuiteListener;

+import org.testng.ITestRunnerFactory;

+import org.testng.TestNG;

+import org.testng.TestNGException;

+import org.testng.TestRunner;

+import org.testng.collections.Lists;

+import org.testng.remote.strprotocol.GenericMessage;

+import org.testng.remote.strprotocol.IMessageSender;

+import org.testng.remote.strprotocol.MessageHelper;

+import org.testng.remote.strprotocol.MessageHub;

+import org.testng.remote.strprotocol.RemoteTestListener;

+import org.testng.remote.strprotocol.SerializedMessageSender;

+import org.testng.remote.strprotocol.StringMessageSender;

+import org.testng.remote.strprotocol.SuiteMessage;

+import org.testng.reporters.JUnitXMLReporter;

+import org.testng.reporters.TestHTMLReporter;

+import org.testng.xml.XmlSuite;

+import org.testng.xml.XmlTest;

+

+import java.util.Arrays;

+import java.util.Collection;

+import java.util.List;

+

+/**

+ * Extension of TestNG registering a remote TestListener.

+ *

+ * @author Cedric Beust <cedric@beust.com>

+ */

+public class RemoteTestNG extends TestNG {

+  private static final String LOCALHOST = "localhost";

+

+  // The following constants are referenced by the Eclipse plug-in, make sure you

+  // modify the plug-in as well if you change any of them.

+  public static final String DEBUG_PORT = "12345";

+  public static final String DEBUG_SUITE_FILE = "testng-customsuite.xml";

+  public static final String DEBUG_SUITE_DIRECTORY = System.getProperty("java.io.tmpdir");

+  public static final String PROPERTY_DEBUG = "testng.eclipse.debug";

+  public static final String PROPERTY_VERBOSE = "testng.eclipse.verbose";

+  // End of Eclipse constants.

+

+  private ITestRunnerFactory m_customTestRunnerFactory;

+  private String m_host;

+

+  /** Port used for the string protocol */

+  private Integer m_port = null;

+

+  /** Port used for the serialized protocol */

+  private static Integer m_serPort = null;

+

+  private static boolean m_debug;

+

+  private static boolean m_dontExit;

+

+  private static boolean m_ack;

+

+  public void setHost(String host) {

+    m_host = defaultIfStringEmpty(host, LOCALHOST);

+  }

+

+  private void calculateAllSuites(List<XmlSuite> suites, List<XmlSuite> outSuites) {

+    for (XmlSuite s : suites) {

+      outSuites.add(s);

+//      calculateAllSuites(s.getChildSuites(), outSuites);

+    }

+  }

+

+  @Override

+  public void run() {

+    IMessageSender sender = m_serPort != null

+        ? new SerializedMessageSender(m_host, m_serPort, m_ack)

+        : new StringMessageSender(m_host, m_port);

+    final MessageHub msh = new MessageHub(sender);

+    msh.setDebug(isDebug());

+    try {

+      msh.connect();

+      // We couldn't do this until now in debug mode since the .xml file didn't exist yet.

+      // Now that we have connected with the Eclipse client, we know that it created the .xml

+      // file so we can proceed with the initialization

+      initializeSuitesAndJarFile();

+

+      List<XmlSuite> suites = Lists.newArrayList();

+      calculateAllSuites(m_suites, suites);

+//      System.out.println("Suites: " + m_suites.get(0).getChildSuites().size()

+//          + " and:" + suites.get(0).getChildSuites().size());

+      if(suites.size() > 0) {

+

+        int testCount= 0;

+

+        for (XmlSuite suite : suites) {

+          testCount += suite.getTests().size();

+        }

+

+        GenericMessage gm= new GenericMessage(MessageHelper.GENERIC_SUITE_COUNT);

+        gm.setSuiteCount(suites.size());

+        gm.setTestCount(testCount);

+        msh.sendMessage(gm);

+

+        addListener(new RemoteSuiteListener(msh));

+        setTestRunnerFactory(new DelegatingTestRunnerFactory(buildTestRunnerFactory(), msh));

+

+//        System.out.println("RemoteTestNG starting");

+        super.run();

+      }

+      else {

+        System.err.println("No test suite found. Nothing to run");

+      }

+    }

+    catch(Throwable cause) {

+      cause.printStackTrace(System.err);

+    }

+    finally {

+//      System.out.println("RemoteTestNG finishing: " + (getEnd() - getStart()) + " ms");

+      msh.shutDown();

+      if (! m_debug && ! m_dontExit) {

+        System.exit(0);

+      }

+    }

+  }

+

+  /**

+   * Override by the plugin if you need to configure differently the <code>TestRunner</code>

+   * (usually this is needed if different listeners/reporters are needed).

+   * <b>Note</b>: you don't need to worry about the wiring listener, because it is added

+   * automatically.

+   */

+  protected ITestRunnerFactory buildTestRunnerFactory() {

+    if(null == m_customTestRunnerFactory) {

+      m_customTestRunnerFactory= new ITestRunnerFactory() {

+          @Override

+          public TestRunner newTestRunner(ISuite suite, XmlTest xmlTest,

+              Collection<IInvokedMethodListener> listeners, List<IClassListener> classListeners) {

+            TestRunner runner =

+              new TestRunner(getConfiguration(), suite, xmlTest,

+                  false /*skipFailedInvocationCounts */,

+                  listeners, classListeners);

+            if (m_useDefaultListeners) {

+              runner.addListener(new TestHTMLReporter());

+              runner.addListener(new JUnitXMLReporter());

+            }

+

+            return runner;

+          }

+        };

+    }

+

+    return m_customTestRunnerFactory;

+  }

+

+  public static void main(String[] args) throws ParameterException {

+    CommandLineArgs cla = new CommandLineArgs();

+    RemoteArgs ra = new RemoteArgs();

+    new JCommander(Arrays.asList(cla, ra), args);

+    m_dontExit = ra.dontExit;

+    if (cla.port != null && ra.serPort != null) {

+      throw new TestNGException("Can only specify one of " + CommandLineArgs.PORT

+          + " and " + RemoteArgs.PORT);

+    }

+    m_debug = cla.debug;

+    m_ack = ra.ack;

+    if (m_debug) {

+//      while (true) {

+        initAndRun(args, cla, ra);

+//      }

+    }

+    else {

+      initAndRun(args, cla, ra);

+    }

+  }

+

+  private static void initAndRun(String[] args, CommandLineArgs cla, RemoteArgs ra) {

+    RemoteTestNG remoteTestNg = new RemoteTestNG();

+    if (m_debug) {

+      // In debug mode, override the port and the XML file to a fixed location

+      cla.port = Integer.parseInt(DEBUG_PORT);

+      ra.serPort = cla.port;

+      cla.suiteFiles = Arrays.asList(new String[] {

+          DEBUG_SUITE_DIRECTORY + DEBUG_SUITE_FILE

+      });

+    }

+    remoteTestNg.configure(cla);

+    remoteTestNg.setHost(cla.host);

+    m_serPort = ra.serPort;

+    remoteTestNg.m_port = cla.port;

+    if (isVerbose()) {

+      StringBuilder sb = new StringBuilder("Invoked with ");

+      for (String s : args) {

+        sb.append(s).append(" ");

+      }

+      p(sb.toString());

+//      remoteTestNg.setVerbose(1);

+//    } else {

+//      remoteTestNg.setVerbose(0);

+    }

+    validateCommandLineParameters(cla);

+    remoteTestNg.run();

+//    if (m_debug) {

+//      // Run in a loop if in debug mode so it is possible to run several launches

+//      // without having to relauch RemoteTestNG.

+//      while (true) {

+//        remoteTestNg.run();

+//        remoteTestNg.configure(cla);

+//      }

+//    } else {

+//      remoteTestNg.run();

+//    }

+  }

+

+  private static void p(String s) {

+    if (isVerbose()) {

+      System.out.println("[RemoteTestNG] " + s);

+    }

+  }

+

+  public static boolean isVerbose() {

+    boolean result = System.getProperty(PROPERTY_VERBOSE) != null || isDebug();

+    return result;

+  }

+

+  public static boolean isDebug() {

+    return m_debug || System.getProperty(PROPERTY_DEBUG) != null;

+  }

+

+  private String getHost() {

+    return m_host;

+  }

+

+  private int getPort() {

+    return m_port;

+  }

+

+  /** A ISuiteListener wiring the results using the internal string-based protocol. */

+  private static class RemoteSuiteListener implements ISuiteListener {

+    private final MessageHub m_messageSender;

+

+    RemoteSuiteListener(MessageHub smsh) {

+      m_messageSender= smsh;

+    }

+

+    @Override

+    public void onFinish(ISuite suite) {

+      m_messageSender.sendMessage(new SuiteMessage(suite, false /*start*/));

+    }

+

+    @Override

+    public void onStart(ISuite suite) {

+      m_messageSender.sendMessage(new SuiteMessage(suite, true /*start*/));

+    }

+  }

+

+  private static class DelegatingTestRunnerFactory implements ITestRunnerFactory {

+    private final ITestRunnerFactory m_delegateFactory;

+    private final MessageHub m_messageSender;

+

+    DelegatingTestRunnerFactory(ITestRunnerFactory trf, MessageHub smsh) {

+      m_delegateFactory= trf;

+      m_messageSender= smsh;

+    }

+

+    @Override

+    public TestRunner newTestRunner(ISuite suite, XmlTest test,

+        Collection<IInvokedMethodListener> listeners, List<IClassListener> classListeners) {

+      TestRunner tr = m_delegateFactory.newTestRunner(suite, test, listeners, classListeners);

+      tr.addListener(new RemoteTestListener(suite, test, m_messageSender));

+      return tr;

+    }

+  }

+}

diff --git a/src/main/java/org/testng/remote/RemoteWorker.java b/src/main/java/org/testng/remote/RemoteWorker.java
new file mode 100755
index 0000000..10abde0
--- /dev/null
+++ b/src/main/java/org/testng/remote/RemoteWorker.java
@@ -0,0 +1,42 @@
+package org.testng.remote;
+
+import org.testng.SuiteRunner;
+import org.testng.internal.Utils;
+import org.testng.internal.remote.SlavePool;
+import org.testng.remote.adapter.RemoteResultListener;
+import org.testng.xml.XmlSuite;
+
+import java.io.IOException;
+
+public class RemoteWorker {
+  protected RemoteResultListener m_listener;
+  private SlavePool m_slavePool;
+
+  public RemoteWorker(RemoteResultListener listener, SlavePool slavePool) {
+    m_listener = listener;
+    m_slavePool = slavePool;
+  }
+
+  protected SlavePool getSlavePool() {
+    return m_slavePool;
+  }
+
+  protected SuiteRunner sendSuite(ConnectionInfo ci, XmlSuite suite)
+    throws IOException, ClassNotFoundException
+  {
+    log("Sending " + suite.getName() + " to "
+        + ci.getSocket().getInetAddress().getCanonicalHostName() + ":"
+        + ci.getSocket().getRemoteSocketAddress());
+    ci.getOos().writeObject(suite);
+    ci.getOos().flush();
+    SuiteRunner result = (SuiteRunner) ci.getOis().readObject();
+    log("Received results for " + result.getName());
+    return result;
+  }
+
+  private void log(String string) {
+    Utils.log("", 2, string);
+  }
+
+
+}
diff --git a/src/main/java/org/testng/remote/SuiteDispatcher.java b/src/main/java/org/testng/remote/SuiteDispatcher.java
new file mode 100644
index 0000000..2064792
--- /dev/null
+++ b/src/main/java/org/testng/remote/SuiteDispatcher.java
@@ -0,0 +1,168 @@
+package org.testng.remote;

+

+import java.util.Collection;

+import java.util.List;

+import java.util.Properties;

+

+import org.testng.ISuite;

+import org.testng.ISuiteResult;

+import org.testng.ITestListener;

+import org.testng.ITestResult;

+import org.testng.SuiteRunner;

+import org.testng.TestNGException;

+import org.testng.collections.Lists;

+import org.testng.internal.IConfiguration;

+import org.testng.internal.Invoker;

+import org.testng.internal.PropertiesFile;

+import org.testng.remote.adapter.DefaultMastertAdapter;

+import org.testng.remote.adapter.IMasterAdapter;

+import org.testng.remote.adapter.RemoteResultListener;

+import org.testng.xml.XmlSuite;

+import org.testng.xml.XmlTest;

+

+/**

+ * Dispatches test suits according to the strategy defined.

+ *

+ *

+ * @author	Guy Korland

+ * @since 	April 20, 2007

+ */

+public class SuiteDispatcher

+{

+	/**

+	 * Properties allowed in remote.properties

+	 */

+	public static final String MASTER_STRATEGY = "testng.master.strategy";

+	public static final String VERBOSE = "testng.verbose";

+	public static final String MASTER_ADPATER = "testng.master.adpter";

+

+	/**

+	 * Values allowed for STRATEGY

+	 */

+	public static final String STRATEGY_TEST = "test";

+	public static final String STRATEGY_SUITE = "suite";

+

+	final private int m_verbose;

+	final private boolean m_isStrategyTest;

+

+	final private IMasterAdapter m_masterAdpter;

+

+

+	/**

+	 * Creates a new suite dispatcher.

+	 */

+	public SuiteDispatcher( String propertiesFile) throws TestNGException

+	{

+		try

+		{

+			PropertiesFile file = new PropertiesFile( propertiesFile);

+			Properties properties = file.getProperties();

+

+			m_verbose = Integer.parseInt( properties.getProperty(VERBOSE, "1"));

+

+			String strategy = properties.getProperty(MASTER_STRATEGY, STRATEGY_SUITE);

+			m_isStrategyTest = STRATEGY_TEST.equalsIgnoreCase(strategy);

+

+			String adapter = properties.getProperty(MASTER_ADPATER);

+			if( adapter == null)

+			{

+				m_masterAdpter = new DefaultMastertAdapter();

+			}

+			else

+			{

+				Class clazz = Class.forName(adapter);

+				m_masterAdpter = (IMasterAdapter)clazz.newInstance();

+			}

+			m_masterAdpter.init(properties);

+		}

+		catch( Exception e)

+		{

+			throw new TestNGException( "Fail to initialize master mode", e);

+		}

+	}

+

+	/**

+	 * Dispatch test suites

+	 * @return suites result

+	 */

+	public List<ISuite> dispatch(IConfiguration configuration,

+	    List<XmlSuite> suites, String outputDir, List<ITestListener> testListeners){

+		List<ISuite> result = Lists.newArrayList();

+		try

+		{

+			//

+			// Dispatch the suites/tests

+			//

+

+			for (XmlSuite suite : suites) {

+				suite.setVerbose(m_verbose);

+				SuiteRunner suiteRunner = new SuiteRunner(configuration, suite, outputDir);

+				RemoteResultListener listener = new RemoteResultListener( suiteRunner);

+				if (m_isStrategyTest) {

+					for (XmlTest test : suite.getTests()) {

+						XmlSuite tmpSuite = new XmlSuite();

+						tmpSuite.setXmlPackages(suite.getXmlPackages());

+						tmpSuite.setJUnit(suite.isJUnit());

+            tmpSuite.setSkipFailedInvocationCounts(suite.skipFailedInvocationCounts());

+						tmpSuite.setName("Temporary suite for " + test.getName());

+						tmpSuite.setParallel(suite.getParallel());

+						tmpSuite.setParentModule(suite.getParentModule());

+						tmpSuite.setGuiceStage(suite.getGuiceStage());

+						tmpSuite.setParameters(suite.getParameters());

+						tmpSuite.setThreadCount(suite.getThreadCount());

+            tmpSuite.setDataProviderThreadCount(suite.getDataProviderThreadCount());

+						tmpSuite.setVerbose(suite.getVerbose());

+						tmpSuite.setObjectFactory(suite.getObjectFactory());

+						XmlTest tmpTest = new XmlTest(tmpSuite);

+						tmpTest.setBeanShellExpression(test.getExpression());

+						tmpTest.setXmlClasses(test.getXmlClasses());

+						tmpTest.setExcludedGroups(test.getExcludedGroups());

+						tmpTest.setIncludedGroups(test.getIncludedGroups());

+						tmpTest.setJUnit(test.isJUnit());

+						tmpTest.setMethodSelectors(test.getMethodSelectors());

+						tmpTest.setName(test.getName());

+						tmpTest.setParallel(test.getParallel());

+						tmpTest.setParameters(test.getLocalParameters());

+						tmpTest.setVerbose(test.getVerbose());

+						tmpTest.setXmlClasses(test.getXmlClasses());

+						tmpTest.setXmlPackages(test.getXmlPackages());

+

+						m_masterAdpter.runSuitesRemotely(tmpSuite, listener);

+					}

+				}

+				else

+				{

+					m_masterAdpter.runSuitesRemotely(suite, listener);

+				}

+				result.add(suiteRunner);

+			}

+

+			m_masterAdpter.awaitTermination(100000);

+

+			//

+			// Run test listeners

+			//

+			for (ISuite suite : result) {

+				for (ISuiteResult suiteResult : suite.getResults().values()) {

+					Collection<ITestResult> allTests[] = new Collection[] {

+							suiteResult.getTestContext().getPassedTests().getAllResults(),

+							suiteResult.getTestContext().getFailedTests().getAllResults(),

+							suiteResult.getTestContext().getSkippedTests().getAllResults(),

+							suiteResult.getTestContext().getFailedButWithinSuccessPercentageTests().getAllResults(),

+					};

+					for (Collection<ITestResult> all : allTests) {

+						for (ITestResult tr : all) {

+							Invoker.runTestListeners(tr, testListeners);

+						}

+					}

+				}

+			}

+		}

+		catch( Exception ex)

+		{

+			//TODO add to logs

+			ex.printStackTrace();

+		}

+		return result;

+	}

+}

diff --git a/src/main/java/org/testng/remote/SuiteSlave.java b/src/main/java/org/testng/remote/SuiteSlave.java
new file mode 100644
index 0000000..be3d15f
--- /dev/null
+++ b/src/main/java/org/testng/remote/SuiteSlave.java
@@ -0,0 +1,101 @@
+package org.testng.remote;

+

+import java.util.List;

+import java.util.Properties;

+

+import org.testng.ISuite;

+import org.testng.TestNG;

+import org.testng.TestNGException;

+import org.testng.collections.Lists;

+import org.testng.internal.PropertiesFile;

+import org.testng.internal.Utils;

+import org.testng.remote.adapter.DefaultWorkerAdapter;

+import org.testng.remote.adapter.IWorkerAdapter;

+import org.testng.xml.XmlSuite;

+

+/**

+ * Run test suits sent by the dispatcher.

+ *

+ *

+ * @author	Guy Korland

+ * @since 	April 20, 2007

+ */

+public class SuiteSlave

+{

+

+	/**

+	 * Properties allowed in remote.properties

+	 */

+	public static final String VERBOSE = "testng.verbose";

+	public static final String SLAVE_ADPATER = "testng.slave.adpter";

+

+

+	final private int m_verbose;

+	final private IWorkerAdapter m_slaveAdpter;

+	final private TestNG m_testng;

+

+	/**

+	 * Creates a new suite dispatcher.

+	 */

+	public SuiteSlave( String propertiesFile, TestNG testng) throws TestNGException

+	{

+		try

+		{

+			m_testng = testng;

+

+			PropertiesFile file = new PropertiesFile( propertiesFile);

+			Properties properties = file.getProperties();

+

+			m_verbose = Integer.parseInt( properties.getProperty(VERBOSE, "1"));

+

+			String adapter = properties.getProperty(SLAVE_ADPATER);

+			if( adapter == null)

+			{

+				m_slaveAdpter = new DefaultWorkerAdapter();

+			}

+			else

+			{

+				Class clazz = Class.forName(adapter);

+				m_slaveAdpter = (IWorkerAdapter)clazz.newInstance();

+			}

+			m_slaveAdpter.init(properties);

+		}

+		catch( Exception e)

+		{

+			throw new TestNGException( "Fail to initialize slave mode", e);

+		}

+	}

+

+	/**

+	 * Invoked in client mode.  In this case, wait for a connection

+	 * on the given port, run the XmlSuite we received and return the SuiteRunner

+	 * created to run it.

+	 */

+	public void waitForSuites() {

+		try {

+			while (true) {

+				//TODO set timeout

+				XmlSuite s = m_slaveAdpter.getSuite(Long.MAX_VALUE);

+				if( s== null) {

+          continue;

+        }

+				log("Processing " + s.getName());

+				List<XmlSuite> suites = Lists.newArrayList();

+				suites.add(s);

+				m_testng.setXmlSuites(suites);

+				List<ISuite> suiteRunners = m_testng.runSuitesLocally();

+				ISuite sr = suiteRunners.get(0);

+				log("Done processing " + s.getName());

+				m_slaveAdpter.returnResult(sr);

+			}

+		}

+		catch(Exception ex) {

+			ex.printStackTrace(System.out);

+		}

+	}

+

+	private static void log(String string) {

+		Utils.log("", 2, string);

+	}

+

+}

diff --git a/src/main/java/org/testng/remote/adapter/DefaultMastertAdapter.java b/src/main/java/org/testng/remote/adapter/DefaultMastertAdapter.java
new file mode 100755
index 0000000..70fcef7
--- /dev/null
+++ b/src/main/java/org/testng/remote/adapter/DefaultMastertAdapter.java
@@ -0,0 +1,84 @@
+package org.testng.remote.adapter;

+

+import java.io.IOException;

+import java.net.Socket;

+import java.net.UnknownHostException;

+import java.util.List;

+import java.util.Properties;

+

+import org.testng.collections.Lists;

+import org.testng.internal.Utils;

+import org.testng.internal.remote.SlavePool;

+import org.testng.internal.thread.ThreadUtil;

+import org.testng.remote.RemoteSuiteWorker;

+import org.testng.xml.XmlSuite;

+

+/**

+ * Default Master adapter, provides an adapter based on hosts file.

+ *

+ *

+ * @author	Guy Korland

+ * @since 	April 20, 2007

+ */

+public class DefaultMastertAdapter

+implements IMasterAdapter

+{

+	public static final String HOSTS 	= "testng.hosts";

+

+	private String[] m_hosts;

+

+	final private SlavePool m_slavePool = new SlavePool();

+	final private List<Runnable> m_workers = Lists.newArrayList();

+

+	/*

+	 * @see org.testng.remote.adapter.IMasterAdapter#init(java.util.Properties)

+	 */

+	@Override

+  public void init(Properties properties)

+	{

+		String hostLine = properties.getProperty(HOSTS);

+		m_hosts =  hostLine.split(" ");

+

+		//

+		// Create one socket per host found

+		//

+		Socket[] sockets = new Socket[m_hosts.length];

+		for (int i = 0; i < m_hosts.length; i++) {

+			String host = m_hosts[i];

+			String[] s = host.split(":");

+			try {

+				sockets[i] = new Socket(s[0], Integer.parseInt(s[1]));

+			}

+			catch (NumberFormatException | UnknownHostException e) {

+				e.printStackTrace(System.out);

+			} catch (IOException e) {

+				Utils.error("Couldn't connect to " + host + ": " + e.getMessage());

+			}

+		}

+

+		//

+		// Add these hosts to the pool

+		//

+		try {

+			m_slavePool.addSlaves(sockets);

+		}

+		catch (IOException e1) {

+			e1.printStackTrace(System.out);

+		}

+	}

+

+	/*

+	 * @see org.testng.remote.adapter.IMasterAdapter#runSuitesRemotely(java.util.List, org.testng.internal.annotations.IAnnotationFinder, org.testng.internal.annotations.IAnnotationFinder)

+	 */

+	@Override

+  public void runSuitesRemotely( XmlSuite suite, RemoteResultListener listener) throws IOException

+	{

+		m_workers.add(new RemoteSuiteWorker(suite, m_slavePool, listener));

+	}

+

+	@Override

+  public void awaitTermination(long timeout) throws InterruptedException

+	{

+		ThreadUtil.execute(m_workers, 1, 10 * 1000L, false);

+	}

+}

diff --git a/src/main/java/org/testng/remote/adapter/DefaultWorkerAdapter.java b/src/main/java/org/testng/remote/adapter/DefaultWorkerAdapter.java
new file mode 100644
index 0000000..4a65694
--- /dev/null
+++ b/src/main/java/org/testng/remote/adapter/DefaultWorkerAdapter.java
@@ -0,0 +1,88 @@
+package org.testng.remote.adapter;

+

+import java.io.IOException;

+import java.net.ServerSocket;

+import java.net.Socket;

+import java.util.Properties;

+

+import org.testng.ISuite;

+import org.testng.internal.Utils;

+import org.testng.remote.ConnectionInfo;

+import org.testng.xml.XmlSuite;

+

+

+/**

+ * Default Slave adapter, provides an adapter based on static port.

+ *

+ *

+ * @author	Guy Korland

+ * @since 	April 20, 2007

+ */

+public class DefaultWorkerAdapter implements IWorkerAdapter

+{

+	public static final String SLAVE_PORT = "slave.port";

+

+	private ConnectionInfo m_connectionInfo;

+	private int m_clientPort;

+

+	@Override

+  public void init( Properties prop) throws Exception

+	{

+		m_clientPort = Integer.parseInt( prop.getProperty(SLAVE_PORT, "0"));

+		m_connectionInfo = resetSocket( m_clientPort, null);

+	}

+

+	/*

+	 * @see org.testng.remote.adapter.IWorkerApadter#getSuite(long)

+	 */

+	@Override

+  public XmlSuite getSuite(long timeout) throws InterruptedException, IOException

+	{

+      try {

+        return (XmlSuite) m_connectionInfo.getOis().readObject();

+      }

+      catch (ClassNotFoundException e) {

+        e.printStackTrace(System.out);

+        throw new RuntimeException( e);

+      }

+      catch(IOException ex) {

+        log("Connection closed " + ex.getMessage());

+        m_connectionInfo = resetSocket(m_clientPort, m_connectionInfo);

+        throw ex;

+      }

+	}

+

+	/*

+	 * @see org.testng.remote.adapter.IWorkerApadter#returnResult(org.testng.ISuite)

+	 */

+	@Override

+  public void returnResult(ISuite result) throws IOException

+	{

+		try

+		{

+			m_connectionInfo.getOos().writeObject(result);

+		}

+		catch(IOException ex) {

+			log("Connection closed " + ex.getMessage());

+			m_connectionInfo = resetSocket(m_clientPort, m_connectionInfo);

+			throw ex;

+		}

+	}

+

+	private static ConnectionInfo resetSocket(int clientPort, ConnectionInfo oldCi)

+	throws IOException

+	{

+		ConnectionInfo result = new ConnectionInfo();

+		ServerSocket serverSocket = new ServerSocket(clientPort);

+		serverSocket.setReuseAddress(true);

+		log("Waiting for connections on port " + clientPort);

+		Socket socket = serverSocket.accept();

+		result.setSocket(socket);

+

+		return result;

+	}

+

+	private static void log(String string) {

+		Utils.log("", 2, string);

+	}

+}

diff --git a/src/main/java/org/testng/remote/adapter/IMasterAdapter.java b/src/main/java/org/testng/remote/adapter/IMasterAdapter.java
new file mode 100644
index 0000000..ebddb37
--- /dev/null
+++ b/src/main/java/org/testng/remote/adapter/IMasterAdapter.java
@@ -0,0 +1,40 @@
+package org.testng.remote.adapter;

+

+import java.io.IOException;

+import java.util.Properties;

+

+import org.testng.xml.XmlSuite;

+

+/**

+ * This interface should be implemented by the Master-Slave transport adapter.

+ * This interface is used by the Master to push suites and get results.

+ *

+ * @author Guy Korland

+ * @since April 9, 2007

+ * @see IWorkerAdapter

+ */

+public interface IMasterAdapter

+{

+	/**

+	 * Initializes the Master adapter.

+	 * @param prop holds the properties loaded from the remote.properties file.

+	 * @throws Exception adapter might throw any exception on initialization, which will abort this adapter.

+	 */

+	void init( Properties prop) throws Exception;

+

+	/**

+	 * Run a suite remotely.

+	 * @param suite the suite to send.

+	 * @param listener the corresponded listener, should be called when result is ready.

+	 * @throws IOException might be thrown on IO error.

+	 */

+	void runSuitesRemotely( XmlSuite suite, RemoteResultListener listener) throws IOException;

+

+	/**

+	 * A blocking wait for the remote results to return.

+	 *

+	 * @param timeout the maximum time to wait for all the suites to return a result.

+	 * @throws InterruptedException

+	 */

+	public void awaitTermination(long timeout) throws InterruptedException;

+}
\ No newline at end of file
diff --git a/src/main/java/org/testng/remote/adapter/IWorkerAdapter.java b/src/main/java/org/testng/remote/adapter/IWorkerAdapter.java
new file mode 100644
index 0000000..d1d966b
--- /dev/null
+++ b/src/main/java/org/testng/remote/adapter/IWorkerAdapter.java
@@ -0,0 +1,41 @@
+package org.testng.remote.adapter;

+

+import java.io.IOException;

+import java.util.Properties;

+

+import org.testng.ISuite;

+import org.testng.xml.XmlSuite;

+

+/**

+ * This interface should be implemented by the Master-Slave transport adapter.

+ * This interface is used by the Slave to pull suites and return results.

+ *

+ * @author	Guy Korland

+ * @since April 9, 2007

+ * @see IMasterAdapter

+ */

+public interface IWorkerAdapter

+{

+	/**

+	 * Initializes the worker adapter.

+	 * @param properties holds the properties loaded from the remote.properties file.

+	 * @throws Exception adapter might throw any exception on initialization, which will abort this adapter.

+	 */

+	void init( Properties properties) throws Exception;

+

+	/**

+	 * A blocking call to get the next Suite to test.

+	 * @param timeout the maximum time to wait for the next suite.

+	 * @return the next suite avaliable or <code>null</code> if the timeout has reached.

+	 * @throws IOException might be thrown on IO error.

+	 * @throws InterruptedException if interrupted while waiting.

+	 */

+	XmlSuite getSuite( long timeout) throws InterruptedException, IOException;

+

+	/**

+	 * Return a suite result.

+	 * @param result the result to return

+	 * @throws IOException might be thrown on IO error.

+	 */

+	void returnResult( ISuite result) throws IOException;

+}

diff --git a/src/main/java/org/testng/remote/adapter/RemoteResultListener.java b/src/main/java/org/testng/remote/adapter/RemoteResultListener.java
new file mode 100644
index 0000000..7895423
--- /dev/null
+++ b/src/main/java/org/testng/remote/adapter/RemoteResultListener.java
@@ -0,0 +1,64 @@
+/*

+ * @(#)ResultListener.java   Apr 9, 2007

+ *

+ * Copyright 2007 GigaSpaces Technologies Inc.

+ */

+

+package org.testng.remote.adapter;

+

+import java.util.Map;

+

+import org.testng.ISuite;

+import org.testng.ISuiteResult;

+import org.testng.ITestContext;

+import org.testng.SuiteRunner;

+import org.testng.reporters.TestHTMLReporter;

+

+/**

+ * This listener is called by the {@link IWorkerAdapter} implementation when a remote test result is ready.

+ *

+ * @author Guy Korland

+ * @since April 9, 2007

+ * @see IWorkerAdapter

+ */

+public class RemoteResultListener

+{

+	/**

+	 * Holds the corresponded {@link SuiteRunner} for the processed {@link org.testng.xml.XmlSuite}.

+	 */

+	final private SuiteRunner m_runner;

+

+	/**

+	 * Creates a listener for an {@link org.testng.xml.XmlSuite} result.

+	 * @param runner the corresponded {@link SuiteRunner}

+	 */

+	public RemoteResultListener( SuiteRunner runner)

+	{

+		m_runner = runner;

+	}

+

+	/**

+	 * Should called by the {@link IWorkerAdapter} implementation when a remote suite result is ready.

+	 * @param remoteSuiteRunner remote result.

+	 */

+	public void onResult( ISuite remoteSuiteRunner)

+	{

+		m_runner.setHost(remoteSuiteRunner.getHost());

+		Map<String, ISuiteResult> tmpResults = remoteSuiteRunner.getResults();

+		Map<String, ISuiteResult> suiteResults = m_runner.getResults();

+		for (Map.Entry<String, ISuiteResult> entry : tmpResults.entrySet())

+		{

+			ISuiteResult suiteResult = entry.getValue();

+			suiteResults.put(entry.getKey(), suiteResult);

+			ITestContext tc = suiteResult.getTestContext();

+			TestHTMLReporter.generateLog(tc, remoteSuiteRunner.getHost(),

+			                             m_runner.getOutputDirectory(),

+			                             tc.getFailedConfigurations().getAllResults(),

+			                             tc.getSkippedConfigurations().getAllResults(),

+			                             tc.getPassedTests().getAllResults(),

+			                             tc.getFailedTests().getAllResults(),

+			                             tc.getSkippedTests().getAllResults(),

+			                             tc.getFailedButWithinSuccessPercentageTests().getAllResults());

+		}

+	}

+}

diff --git a/src/main/java/org/testng/remote/strprotocol/AbstractRemoteTestRunnerClient.java b/src/main/java/org/testng/remote/strprotocol/AbstractRemoteTestRunnerClient.java
new file mode 100755
index 0000000..15170fc
--- /dev/null
+++ b/src/main/java/org/testng/remote/strprotocol/AbstractRemoteTestRunnerClient.java
@@ -0,0 +1,242 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *     Julien Ruaux: jruaux@octo.com
+ *     Vincent Massol: vmassol@octo.com
+ *
+ * Adapted by:
+ *     Alexandru Popescu: the_mindstorm@evolva.ro
+ ******************************************************************************/
+package org.testng.remote.strprotocol;
+
+
+import org.testng.TestNGException;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+/**
+ * The client side of the RemoteTestRunner. Handles the
+ * marshaling of the different messages.
+ */
+public abstract class AbstractRemoteTestRunnerClient {
+  /**
+   * An array of listeners that are informed about test events.
+   */
+  protected IRemoteSuiteListener[] m_suiteListeners;
+  protected IRemoteTestListener[] m_testListeners;
+
+  /**
+   * The server socket
+   */
+  private ServerSocket       fServerSocket;
+  private Socket             fSocket;
+  private ServerConnection m_serverConnection;
+//  private PrintWriter        m_outputWriter;
+//  private BufferedReader     m_inputReader;
+
+  /**
+   * Start listening to a test run. Start a server connection that
+   * the RemoteTestRunner can connect to.
+   */
+  public synchronized void startListening(IRemoteSuiteListener[] suiteListeners,
+                                          IRemoteTestListener[] testListeners,
+                                          ServerConnection serverConnection) {
+    m_suiteListeners= suiteListeners;
+    m_testListeners= testListeners;
+    m_serverConnection = serverConnection;
+
+    serverConnection.start();
+  }
+
+  public IRemoteSuiteListener[] getSuiteListeners() {
+    return m_suiteListeners;
+  }
+
+  public IRemoteTestListener[] getTestListeners() {
+    return m_testListeners;
+  }
+
+  private synchronized void shutdown() {
+//    if(m_outputWriter != null) {
+//      m_outputWriter.close();
+//      m_outputWriter = null;
+//    }
+//    try {
+//      if(m_inputReader != null) {
+//        m_inputReader.close();
+//        m_inputReader = null;
+//      }
+//    }
+//    catch(IOException e) {
+//      e.printStackTrace();
+//    }
+    try {
+      if(fSocket != null) {
+        fSocket.close();
+        fSocket = null;
+      }
+    }
+    catch(IOException e) {
+      e.printStackTrace();
+    }
+    try {
+      if(fServerSocket != null) {
+        fServerSocket.close();
+        fServerSocket = null;
+      }
+    }
+    catch(IOException e) {
+      e.printStackTrace();
+    }
+  }
+
+  public boolean isRunning() {
+    return m_serverConnection.getMessageSender() != null;
+  }
+
+  /**
+   * Requests to stop the remote test run.
+   */
+  public synchronized void stopTest() {
+    if(isRunning()) {
+      m_serverConnection.getMessageSender().sendStop();
+      shutdown();
+    }
+  }
+
+//  private String readMessage(BufferedReader in) throws IOException {
+//    return in.readLine();
+//  }
+//
+//  private void receiveMessage(String message) {
+//    int messageType = MessageHelper.getMessageType(message);
+//
+//    try {
+//      if(messageType < MessageHelper.SUITE) {
+//        // Generic message
+//        GenericMessage gm = MessageHelper.unmarshallGenericMessage(message);
+//        notifyStart(gm);
+//      }
+//      else if(messageType < MessageHelper.TEST) {
+//        // Suite message
+//        SuiteMessage sm = MessageHelper.createSuiteMessage(message);
+//        notifySuiteEvents(sm);
+//      }
+//      else if(messageType < MessageHelper.TEST_RESULT) {
+//        // Test message
+//        TestMessage tm = MessageHelper.createTestMessage(message);
+//        notifyTestEvents(tm);
+//      }
+//      else {
+//        // TestResult message
+//        TestResultMessage trm = MessageHelper.unmarshallTestResultMessage(message);
+//        notifyResultEvents(trm);
+//      }
+//    }
+//    finally {
+//      if(isRunning() && (null != m_outputWriter)) {
+//        m_outputWriter.println(MessageHelper.ACK_MSG);
+//        m_outputWriter.flush();
+//      }
+//    }
+//  }
+
+  protected abstract void notifyStart(final GenericMessage genericMessage);
+
+  protected abstract void notifySuiteEvents(final SuiteMessage suiteMessage);
+
+  protected abstract void notifyTestEvents(final TestMessage testMessage);
+
+  protected abstract void notifyResultEvents(final TestResultMessage testResultMessage);
+
+
+  /**
+   * Reads the message stream from the RemoteTestRunner
+   */
+  public abstract class ServerConnection extends Thread {
+    private MessageHub m_messageHub;
+
+    public ServerConnection(IMessageSender messageMarshaller) {
+      super("TestNG - ServerConnection"); //$NON-NLS-1$
+      m_messageHub = new MessageHub(messageMarshaller);
+    }
+
+    IMessageSender getMessageSender() {
+      return m_messageHub != null ? m_messageHub.getMessageSender() : null;
+    }
+
+    @Override
+    public void run() {
+      try {
+        IMessage message = m_messageHub.receiveMessage();
+        while (message != null) {
+          if (message instanceof GenericMessage) {
+            notifyStart((GenericMessage) message);
+          }
+          else if (message instanceof SuiteMessage) {
+            notifySuiteEvents((SuiteMessage) message);
+          }
+          else if (message instanceof TestMessage) {
+            notifyTestEvents((TestMessage) message);
+          }
+          else if (message instanceof TestResultMessage) {
+            notifyResultEvents((TestResultMessage) message);
+          }
+          else {
+            throw new TestNGException("Unknown message type:" + message);
+          }
+//          if (isRunning()) {
+//            m_messageMarshaller.sendAck();
+//          }
+          message = m_messageHub.receiveMessage();
+        }
+      }
+      finally {
+        m_messageHub.shutDown();
+        m_messageHub = null;
+      }
+//      try {
+//        fServerSocket = new ServerSocket(fServerPort);
+//        fSocket = fServerSocket.accept();
+//        try {
+//          m_inputReader = new BufferedReader(new InputStreamReader(fSocket.getInputStream(),
+//                                                                     "UTF-8")); //$NON-NLS-1$
+//        }
+//        catch(UnsupportedEncodingException e) {
+//          m_inputReader = new BufferedReader(new InputStreamReader(fSocket.getInputStream()));
+//        }
+//        try {
+//          m_outputWriter = new PrintWriter(new OutputStreamWriter(fSocket.getOutputStream(), "UTF-8"),
+//                                    true);
+//        }
+//        catch(UnsupportedEncodingException e1) {
+//          m_outputWriter = new PrintWriter(new OutputStreamWriter(fSocket.getOutputStream()), true);
+//        }
+//        String message;
+//        while((m_inputReader != null) && ((message = readMessage(m_inputReader)) != null)) {
+//          receiveMessage(message);
+//        }
+//      }
+//      catch(SocketException e) {
+//        handleThrowable(e);
+//      }
+//      catch(IOException e) {
+//        handleThrowable(e);
+//      }
+//      finally {
+//        shutdown();
+//      }
+    }
+
+    protected abstract void handleThrowable(Throwable cause);
+  }
+
+}
diff --git a/src/main/java/org/testng/remote/strprotocol/BaseMessageSender.java b/src/main/java/org/testng/remote/strprotocol/BaseMessageSender.java
new file mode 100644
index 0000000..2a86bff
--- /dev/null
+++ b/src/main/java/org/testng/remote/strprotocol/BaseMessageSender.java
@@ -0,0 +1,279 @@
+package org.testng.remote.strprotocol;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.ConnectException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+
+import org.testng.TestNGException;
+
+import static org.testng.remote.RemoteTestNG.isVerbose;
+
+abstract public class BaseMessageSender implements IMessageSender {
+  private boolean m_debug = false;
+  protected Socket m_clientSocket;
+  private String m_host;
+  private int m_port;
+  protected final Object m_ackLock = new Object();
+
+  private boolean m_requestStopReceiver;
+  /** Outgoing message stream. */
+  protected OutputStream m_outStream;
+  /** Used to send ACK and STOP */
+  private PrintWriter m_outWriter;
+
+  /** Incoming message stream. */
+  protected volatile InputStream m_inStream;
+  /** Used to receive ACK and STOP */
+  protected volatile BufferedReader m_inReader;
+
+  private ReaderThread m_readerThread;
+  private boolean m_ack;
+//  protected InputStream m_receiverInputStream;
+
+  public BaseMessageSender(String host, int port, boolean ack) {
+    m_host = host;
+    m_port = port;
+    m_ack = ack;
+  }
+
+  /**
+   * Starts the connection.
+   *
+   * @throws TestNGException if an exception occurred while establishing the connection
+   */
+  @Override
+  public void connect() throws IOException {
+    p("Waiting for Eclipse client on " + m_host + ":" + m_port);
+    while (true) {
+      try {
+        m_clientSocket = new Socket(m_host, m_port);
+        p("Received a connection from Eclipse on " + m_host + ":" + m_port);
+
+        // Output streams
+        m_outStream = m_clientSocket.getOutputStream();
+        m_outWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(m_outStream)));
+
+        // Input streams
+        m_inStream = m_clientSocket.getInputStream();
+        try {
+          m_inReader = new BufferedReader(new InputStreamReader(m_inStream,
+              "UTF-8")); //$NON-NLS-1$
+        }
+        catch(UnsupportedEncodingException ueex) {
+          // Should never happen
+          m_inReader = new BufferedReader(new InputStreamReader(m_inStream));
+        }
+
+        p("Connection established, starting reader thread");
+        m_readerThread = new ReaderThread();
+        m_readerThread.start();
+        return;
+      }
+      catch(ConnectException ex) {
+        // ignore and retry
+        try {
+          Thread.sleep(4000);
+        }
+        catch(InterruptedException handled) {
+          Thread.currentThread().interrupt();
+        }
+      }
+    }
+  }
+
+  private void sendAdminMessage(String message) {
+    m_outWriter.println(message);
+    m_outWriter.flush();
+  }
+
+  private int m_serial = 0;
+
+  @Override
+  public void sendAck() {
+    p("Sending ACK " + m_serial);
+    // Note: adding the serial at the end of this message causes a lock up if interacting
+    // with TestNG 5.14 and older (reported by JetBrains). The following git commit:
+    // 5730bdfb33ec7a8bf4104852cd4a5f2875ba8267
+    // changed equals() to startsWith().
+    // It's ok to add this serial back for debugging, but don't commit it until JetBrains
+    // confirms they no longer need backward compatibility with 5.14.
+    sendAdminMessage(MessageHelper.ACK_MSG); // + m_serial++);
+  }
+
+  @Override
+  public void sendStop() {
+    sendAdminMessage(MessageHelper.STOP_MSG);
+  }
+
+  @Override
+  public void initReceiver() throws SocketTimeoutException {
+    if (m_inStream != null) {
+      p("Receiver already initialized");
+    }
+    ServerSocket serverSocket = null;
+    try {
+      p("initReceiver on port " + m_port);
+      serverSocket = new ServerSocket(m_port);
+      serverSocket.setSoTimeout(5000);
+
+      Socket socket = null;
+      while (!m_requestStopReceiver) {
+        try {
+          if (m_debug) {
+            p("polling the client connection");
+          }
+          socket = serverSocket.accept();
+          // break the loop once the first client connected
+          break;
+        }
+        catch (IOException ioe) {
+          try {
+            Thread.sleep(100L);
+          }
+          catch (InterruptedException ie) {
+            // Do nothing.
+          }
+        }
+      }
+      if (socket != null) {
+        m_inStream = socket.getInputStream();
+        m_inReader = new BufferedReader(new InputStreamReader(m_inStream));
+        m_outStream = socket.getOutputStream();
+        m_outWriter = new PrintWriter(new OutputStreamWriter(m_outStream));
+      }
+    }
+    catch(SocketTimeoutException ste) {
+      throw ste;
+    }
+    catch (IOException ioe) {
+      closeQuietly(serverSocket);
+    }
+  }
+
+  public void stopReceiver() {
+    m_requestStopReceiver = true;
+  }
+
+  @Override
+  public void shutDown() {
+    closeQuietly(m_outStream);
+    m_outStream = null;
+
+    if (null != m_readerThread) {
+      m_readerThread.interrupt();
+    }
+
+    closeQuietly(m_inReader);
+    m_inReader = null;
+
+    closeQuietly(m_clientSocket);
+    m_clientSocket = null;
+  }
+
+  private void closeQuietly(Closeable c) {
+    if (c != null) {
+      try {
+        c.close();
+      } catch (IOException e) {
+        if (m_debug) {
+          e.printStackTrace();
+        }
+      }
+    }
+  }
+
+  private String m_latestAck;
+
+  protected void waitForAck() {
+    if (m_ack) {
+      try {
+        p("Message sent, waiting for ACK...");
+        synchronized(m_ackLock) {
+          m_ackLock.wait();
+        }
+        p("... ACK received:" + m_latestAck);
+      }
+      catch(InterruptedException handled) {
+        Thread.currentThread().interrupt();
+      }
+    }
+  }
+
+  private static void p(String msg) {
+    if (isVerbose()) {
+      System.out.println("[BaseMessageSender] " + msg); //$NON-NLS-1$
+    }
+  }
+
+  /**
+   * Reader thread that processes messages from the client.
+   */
+  private class ReaderThread extends Thread {
+
+    public ReaderThread() {
+      super("ReaderThread"); //$NON-NLS-1$
+    }
+
+    @Override
+    public void run() {
+      try {
+        p("ReaderThread waiting for an admin message");
+        String message = m_inReader.readLine();
+        p("ReaderThread received admin message:" + message);
+        while (message != null) {
+          if (m_debug) {
+            p("Admin message:" + message); //$NON-NLS-1$
+          }
+          boolean acknowledge = message.startsWith(MessageHelper.ACK_MSG);
+          boolean stop = MessageHelper.STOP_MSG.equals(message);
+          if(acknowledge || stop) {
+            if (acknowledge) {
+              p("Received ACK:" + message);
+              m_latestAck = message;
+            }
+            synchronized(m_ackLock) {
+              m_ackLock.notifyAll();
+            }
+            if (stop) {
+              break;
+            }
+          } else {
+            p("Received unknown message: '" + message + "'");
+          }
+          message = m_inReader != null ? m_inReader.readLine() : null;
+        }
+//        while((m_reader != null) && (message = m_reader.readLine()) != null) {
+//          if (m_debug) {
+//            p("Admin message:" + message); //$NON-NLS-1$
+//          }
+//          boolean acknowledge = MessageHelper.ACK_MSG.equals(message);
+//          boolean stop = MessageHelper.STOP_MSG.equals(message);
+//          if(acknowledge || stop) {
+//            synchronized(m_lock) {
+//              m_lock.notifyAll();
+//            }
+//            if (stop) {
+//              break;
+//            }
+//          }
+//        }
+      }
+      catch(IOException ioe) {
+        if (isVerbose()) {
+          ioe.printStackTrace();
+        }
+      }
+    }
+  }
+}
diff --git a/src/main/java/org/testng/remote/strprotocol/GenericMessage.java b/src/main/java/org/testng/remote/strprotocol/GenericMessage.java
new file mode 100755
index 0000000..53a2718
--- /dev/null
+++ b/src/main/java/org/testng/remote/strprotocol/GenericMessage.java
@@ -0,0 +1,56 @@
+package org.testng.remote.strprotocol;
+
+
+
+
+/**
+ * A generic message to be used with remote listeners.
+ * It is described by a {@link #m_messageType} and can contain a <code>Map</code>
+ * or values.
+ *
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ */
+public class GenericMessage implements IStringMessage {
+  private static final long serialVersionUID = 1440074281953763545L;
+//  protected Map m_properties;
+  protected final int m_messageType;
+  private int m_suiteCount;
+
+  private int m_testCount;
+
+  public GenericMessage(final int type) {
+    m_messageType = type;
+  }
+
+  public int getSuiteCount() {
+    return m_suiteCount;
+  }
+
+  public void setSuiteCount(int suiteCount) {
+    m_suiteCount = suiteCount;
+  }
+
+  public int getTestCount() {
+    return m_testCount;
+  }
+
+  public void setTestCount(int testCount) {
+    m_testCount = testCount;
+  }
+
+  @Override
+  public String getMessageAsString() {
+    StringBuffer buf = new StringBuffer();
+
+    buf.append(m_messageType);
+    buf.append(MessageHelper.DELIMITER).append("testCount").append(getTestCount())
+        .append(MessageHelper.DELIMITER).append("suiteCount").append(getSuiteCount());
+
+    return buf.toString();
+  }
+
+  @Override
+  public String toString() {
+    return "[GenericMessage suiteCount:" + m_suiteCount + " testCount:" + m_testCount + "]";
+  }
+}
diff --git a/src/main/java/org/testng/remote/strprotocol/IMessage.java b/src/main/java/org/testng/remote/strprotocol/IMessage.java
new file mode 100755
index 0000000..3aeaa4a
--- /dev/null
+++ b/src/main/java/org/testng/remote/strprotocol/IMessage.java
@@ -0,0 +1,12 @@
+package org.testng.remote.strprotocol;
+
+import java.io.Serializable;
+
+
+/**
+ * Marker interface for messages exchanged between RemoteTestNG and a client.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public interface IMessage extends Serializable {
+}
diff --git a/src/main/java/org/testng/remote/strprotocol/IMessageSender.java b/src/main/java/org/testng/remote/strprotocol/IMessageSender.java
new file mode 100644
index 0000000..8dc6c50
--- /dev/null
+++ b/src/main/java/org/testng/remote/strprotocol/IMessageSender.java
@@ -0,0 +1,42 @@
+package org.testng.remote.strprotocol;
+
+import java.io.IOException;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+
+public interface IMessageSender {
+
+  void connect() throws IOException;
+
+  /**
+   * Initialize the receiver.
+   * the underlying socket server will be polling until a first client connect.
+   *
+   * @throws SocketException This exception will be thrown if a connection
+   * to the remote TestNG instance could not be established after ten
+   * seconds.
+   */
+  void initReceiver() throws SocketTimeoutException;
+
+  /**
+   * Stop the receiver.
+   * it provides a way that allow the API invoker to stop the receiver,
+   * e.g. break from a dead while loop
+   */
+  void stopReceiver();
+
+  void sendMessage(IMessage message) throws Exception;
+
+  /**
+   * Will return null or throw EOFException when the connection has been severed.
+   */
+  IMessage receiveMessage() throws Exception;
+
+  void shutDown();
+
+  // These two methods should probably be in a separate class since they should all be
+  // the same for implementers of this interface.
+  void sendAck();
+
+  void sendStop();
+}
diff --git a/src/main/java/org/testng/remote/strprotocol/IRemoteSuiteListener.java b/src/main/java/org/testng/remote/strprotocol/IRemoteSuiteListener.java
new file mode 100755
index 0000000..8e97308
--- /dev/null
+++ b/src/main/java/org/testng/remote/strprotocol/IRemoteSuiteListener.java
@@ -0,0 +1,33 @@
+package org.testng.remote.strprotocol;
+
+
+
+/**
+ * Interface replicating the <code>ISuiteListener</code> used for remote listeners.
+ *
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ * @see org.testng.ISuiteListener
+ */
+public interface IRemoteSuiteListener {
+  /**
+   * General information about the number of suites to be run.
+   * This is called once before all suites.
+   *
+   * @param genericMessage a message containing the number of suites that will be run
+   */
+  void onInitialization(GenericMessage genericMessage);
+
+  /**
+   * @see org.testng.ISuiteListener#onStart(org.testng.ISuite)
+   *
+   * @param suiteMessage the suite message containing the description of the suite to be run.
+   */
+  void onStart(SuiteMessage suiteMessage);
+
+  /**
+   * @see org.testng.ISuiteListener#onFinish(org.testng.ISuite)
+   *
+   * @param suiteMessage the suite message containing infos about the finished suite.
+   */
+  void onFinish(SuiteMessage suiteMessage);
+}
diff --git a/src/main/java/org/testng/remote/strprotocol/IRemoteTestListener.java b/src/main/java/org/testng/remote/strprotocol/IRemoteTestListener.java
new file mode 100755
index 0000000..6e9324a
--- /dev/null
+++ b/src/main/java/org/testng/remote/strprotocol/IRemoteTestListener.java
@@ -0,0 +1,25 @@
+package org.testng.remote.strprotocol;
+
+
+
+/**
+ * Interface replicating <code>ITestListener</code> for remote listeners.
+ *
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ * @see org.testng.ITestListener
+ */
+public interface IRemoteTestListener {
+  void onStart(TestMessage tm);
+
+  void onFinish(TestMessage tm);
+
+  void onTestStart(TestResultMessage trm);
+
+  void onTestSuccess(TestResultMessage trm);
+
+  void onTestFailure(TestResultMessage trm);
+
+  void onTestSkipped(TestResultMessage trm);
+
+  void onTestFailedButWithinSuccessPercentage(TestResultMessage trm);
+}
diff --git a/src/main/java/org/testng/remote/strprotocol/IStringMessage.java b/src/main/java/org/testng/remote/strprotocol/IStringMessage.java
new file mode 100755
index 0000000..4e507c1
--- /dev/null
+++ b/src/main/java/org/testng/remote/strprotocol/IStringMessage.java
@@ -0,0 +1,11 @@
+package org.testng.remote.strprotocol;
+
+
+/**
+ * String based protocol main interface to be used with remote listeners.
+ *
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ */
+public interface IStringMessage extends IMessage {
+  String getMessageAsString();
+}
diff --git a/src/main/java/org/testng/remote/strprotocol/MessageHelper.java b/src/main/java/org/testng/remote/strprotocol/MessageHelper.java
new file mode 100755
index 0000000..0939601
--- /dev/null
+++ b/src/main/java/org/testng/remote/strprotocol/MessageHelper.java
@@ -0,0 +1,255 @@
+package org.testng.remote.strprotocol;
+
+
+import org.testng.ITestResult;
+import org.testng.collections.Lists;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+
+/**
+ * Marshal/unmarshal tool for <code>IStringMessage</code>s.
+ *
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ */
+public class MessageHelper {
+  public static final char DELIMITER = '\u0001';
+  public static final char PARAM_DELIMITER = '\u0004';
+  private static final char LINE_SEP_DELIMITER_1 = '\u0002';
+  private static final char LINE_SEP_DELIMITER_2 = '\u0003';
+
+  public static final int GENERIC_SUITE_COUNT = 1;
+
+  public static final int SUITE = 10;
+  public static final int SUITE_START = 11;
+  public static final int SUITE_FINISH = 12;
+
+  public static final int TEST = 100;
+  public static final int TEST_START = 101;
+  public static final int TEST_FINISH = 102;
+
+  public static final int TEST_RESULT = 1000;
+  public static final int PASSED_TEST = TEST_RESULT + ITestResult.SUCCESS;
+  public static final int FAILED_TEST = TEST_RESULT + ITestResult.FAILURE;
+  public static final int SKIPPED_TEST = TEST_RESULT + ITestResult.SKIP;
+  public static final int FAILED_ON_PERCENTAGE_TEST = TEST_RESULT + ITestResult.SUCCESS_PERCENTAGE_FAILURE;
+  public static final int TEST_STARTED = TEST_RESULT + ITestResult.STARTED;
+
+  public static final String STOP_MSG = ">STOP";
+  public static final String ACK_MSG = ">ACK";
+
+  public static int getMessageType(final String message) {
+    int idx = message.indexOf(DELIMITER);
+
+    return idx == -1 ? Integer.parseInt(message) : Integer.parseInt(message.substring(0, idx));
+  }
+
+  public static GenericMessage unmarshallGenericMessage(final String message) {
+    String[] messageParts = parseMessage(message);
+    if(messageParts.length == 1) {
+      return new GenericMessage(Integer.parseInt(messageParts[0]));
+    }
+    else {
+      GenericMessage result = new GenericMessage(Integer.parseInt(messageParts[0]));
+
+      for(int i = 1; i < messageParts.length; i+=2) {
+        if ("testCount".equals(messageParts[i])) {
+          result.setTestCount(Integer.parseInt(messageParts[i + 1]));
+        } else if ("suiteCount".equals(messageParts[i])) {
+          result.setSuiteCount(Integer.parseInt(messageParts[i + 1]));
+        }
+      }
+
+      return result;
+    }
+  }
+
+  public static SuiteMessage createSuiteMessage(final String message) {
+    int type = getMessageType(message);
+    String[] messageParts = parseMessage(message);
+
+    SuiteMessage result = new SuiteMessage(messageParts[1],
+                            MessageHelper.SUITE_START == type,
+                            Integer.parseInt(messageParts[2]));
+    // Any excluded methods?
+    if (messageParts.length > 3) {
+      int count = Integer.parseInt(messageParts[3]);
+      if (count > 0) {
+        List<String> methods = Lists.newArrayList();
+        int i = 4;
+        while (count-- > 0) {
+          methods.add(messageParts[i++]);
+        }
+        result.setExcludedMethods(methods);
+      }
+    }
+
+    return result;
+  }
+
+  public static TestMessage createTestMessage(final String message) {
+    int type = getMessageType(message);
+    String[] messageParts = parseMessage(message);
+
+    return new TestMessage(MessageHelper.TEST_START == type,
+                           messageParts[1],
+                           messageParts[2],
+                           Integer.parseInt(messageParts[3]),
+                           Integer.parseInt(messageParts[4]),
+                           Integer.parseInt(messageParts[5]),
+                           Integer.parseInt(messageParts[6]),
+                           Integer.parseInt(messageParts[7]));
+  }
+
+  public static TestResultMessage unmarshallTestResultMessage(final String message) {
+    String[] messageParts = parseMessage(message);
+
+    String parametersFragment= null;
+    String startTimestampFragment= null;
+    String stopTimestampFragment= null;
+    String stackTraceFragment= null;
+    String testDescriptor= null;
+    switch(messageParts.length) {
+      case 10:
+      {
+        parametersFragment= messageParts[5];
+        startTimestampFragment= messageParts[6];
+        stopTimestampFragment= messageParts[7];
+        stackTraceFragment= messageParts[8];
+        testDescriptor= messageParts[9];
+      }
+      break;
+      case 9:
+      {
+        parametersFragment= messageParts[5];
+        startTimestampFragment= messageParts[6];
+        stopTimestampFragment= messageParts[7];
+        stackTraceFragment= messageParts[8];
+      }
+      break;
+      default:
+      {
+        // HINT: old protocol without parameters
+        parametersFragment= null;
+        startTimestampFragment= messageParts[5];
+        stopTimestampFragment= messageParts[6];
+        stackTraceFragment= messageParts[7];
+      }
+    }
+    return new TestResultMessage(Integer.parseInt(messageParts[0]),
+                                 messageParts[1],
+                                 messageParts[2],
+                                 messageParts[3],
+                                 messageParts[4],
+                                 replaceAsciiCharactersWithUnicode(replaceNewLineReplacer(testDescriptor)),
+                                 replaceAsciiCharactersWithUnicode(replaceNewLineReplacer(testDescriptor)),
+                                 parseParameters(parametersFragment),
+                                 Long.parseLong(startTimestampFragment),
+                                 Long.parseLong(stopTimestampFragment),
+                                 replaceAsciiCharactersWithUnicode(replaceNewLineReplacer(stackTraceFragment)),
+                                 0, 0 /* invocation counts not supported by this protocol */
+            );
+  }
+
+  public static String replaceNewLine(String message) {
+    if(null == message) {
+      return message;
+    }
+
+    return message.replace('\n', LINE_SEP_DELIMITER_1).replace('\r', LINE_SEP_DELIMITER_2);
+  }
+
+  public static String replaceUnicodeCharactersWithAscii(String message) {
+    if(null == message) {
+      return message;
+    }
+
+    return replace(
+              replace(
+                  replace(
+                    replace(message, "\u0004", "\\u0004"),
+                  "\u0003", "\\u0003"),
+              "\u0002", "\\u0002"),
+           "\u0001", "\\u0001");
+  }
+
+  public static String replaceAsciiCharactersWithUnicode(String message) {
+    if(null == message) {
+      return message;
+    }
+
+    return replace(
+            replace(
+                replace(
+                    replace(message, "\\u0004", "\u0004"),
+                    "\\u0003", "\u0003"),
+                "\\u0002", "\u0002"),
+            "\\u0001", "\u0001");
+  }
+
+  public static String replaceNewLineReplacer(String message) {
+    if(null == message) {
+      return message;
+    }
+
+    return message.replace(LINE_SEP_DELIMITER_1, '\n').replace(LINE_SEP_DELIMITER_2, '\r');
+  }
+
+  private static String[] parseParameters(final String messagePart) {
+    return tokenize(messagePart, PARAM_DELIMITER);
+  }
+
+  private static String[] parseMessage(final String message) {
+    return tokenize(message, DELIMITER);
+  }
+
+  private static String[] tokenize(final String message, final char separator) {
+    if(null == message) {
+      return new String[0];
+    }
+
+    List<String> tokens = Lists.newArrayList();
+    int start = 0;
+    for(int i = 0; i < message.length(); i++) {
+      if(separator == message.charAt(i)) {
+        tokens.add(message.substring(start, i));
+        start = i + 1;
+      }
+    }
+    if(start < message.length()) {
+      tokens.add(message.substring(start, message.length()));
+    }
+
+    return tokens.toArray(new String[tokens.size()]);
+  }
+
+  /**
+   * Implementation according to JDK5 String.replace(CharSequence,CharSequence)
+   */
+  private static final String replace(String original, CharSequence target, CharSequence replacement) {
+      return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(original)
+          .replaceAll(quoteReplacement(replacement.toString()));
+  }
+
+  /**
+   * Implementation according to JDK5 String.replace(CharSequence,CharSequence)
+   */
+  private static String quoteReplacement(String s) {
+      if ((s.indexOf('\\') == -1) && (s.indexOf('$') == -1)) {
+        return s;
+      }
+      StringBuffer sb = new StringBuffer();
+      for (int i=0; i<s.length(); i++) {
+          char c = s.charAt(i);
+          if (c == '\\') {
+              sb.append('\\'); sb.append('\\');
+          } else if (c == '$') {
+              sb.append('\\'); sb.append('$');
+          } else {
+              sb.append(c);
+          }
+      }
+      return sb.toString();
+  }
+}
diff --git a/src/main/java/org/testng/remote/strprotocol/MessageHub.java b/src/main/java/org/testng/remote/strprotocol/MessageHub.java
new file mode 100755
index 0000000..30f2a50
--- /dev/null
+++ b/src/main/java/org/testng/remote/strprotocol/MessageHub.java
@@ -0,0 +1,79 @@
+package org.testng.remote.strprotocol;
+
+
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+
+import org.testng.TestNGException;
+import org.testng.remote.RemoteTestNG;
+
+/**
+ * Central class to connect to the host and send message.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public class MessageHub {
+
+  private boolean m_debug = false;
+
+  private IMessageSender m_messageSender;
+
+  public MessageHub(IMessageSender messageSender) {
+    m_messageSender = messageSender;
+  }
+
+  /**
+   * Starts the connection.
+   *
+   * @throws TestNGException if an exception occurred while establishing the connection
+   */
+  public void connect() throws IOException {
+    m_messageSender.connect();
+  }
+
+  /**
+   * Shutsdown the connection to the remote test listener.
+   */
+  public void shutDown() {
+    m_messageSender.shutDown();
+  }
+
+  public void sendMessage(IMessage message) {
+    try {
+      m_messageSender.sendMessage(message);
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  public IMessage receiveMessage() {
+    IMessage result = null;
+    try {
+      result = m_messageSender.receiveMessage();
+      m_messageSender.sendAck();
+    } catch (Exception e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }
+    return result;
+  }
+
+  private static void p(String msg) {
+    if (RemoteTestNG.isVerbose()) {
+      System.out.println("[StringMessageSenderHelper] " + msg); //$NON-NLS-1$
+    }
+  }
+
+
+  public void setDebug(boolean debug) {
+    m_debug = debug;
+  }
+
+  public void initReceiver() throws SocketTimeoutException {
+    m_messageSender.initReceiver();
+  }
+
+  public IMessageSender getMessageSender() {
+    return m_messageSender;
+  }
+}
diff --git a/src/main/java/org/testng/remote/strprotocol/RemoteTestListener.java b/src/main/java/org/testng/remote/strprotocol/RemoteTestListener.java
new file mode 100755
index 0000000..0130145
--- /dev/null
+++ b/src/main/java/org/testng/remote/strprotocol/RemoteTestListener.java
@@ -0,0 +1,119 @@
+package org.testng.remote.strprotocol;
+
+import org.testng.ISuite;
+import org.testng.ITestContext;
+import org.testng.ITestResult;
+import org.testng.internal.IResultListener2;
+import org.testng.xml.XmlTest;
+
+/**
+ * A special listener that remote the event with string protocol.
+ *
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ */
+public class RemoteTestListener implements IResultListener2 {
+  private final MessageHub m_sender;
+  private ISuite m_suite;
+  private XmlTest m_xmlTest;
+  private ITestContext m_currentTestContext;
+
+  public RemoteTestListener(ISuite suite, XmlTest test, MessageHub msh) {
+    m_sender = msh;
+    m_suite= suite;
+    m_xmlTest= test;
+  }
+
+  @Override
+  public void onStart(ITestContext testCtx) {
+    m_currentTestContext = testCtx;
+    m_sender.sendMessage(new TestMessage(testCtx, true /*start*/));
+  }
+
+  @Override
+  public void onFinish(ITestContext testCtx) {
+    m_sender.sendMessage(new TestMessage(testCtx, false /*end*/));
+    m_currentTestContext = null;
+  }
+
+  @Override
+  public void onTestStart(ITestResult testResult) {
+    TestResultMessage trm= null;
+
+    if (null == m_currentTestContext) {
+      trm= new TestResultMessage(m_suite.getName(), m_xmlTest.getName(), testResult);
+    }
+    else {
+      trm= new TestResultMessage(m_currentTestContext, testResult);
+    }
+
+    m_sender.sendMessage(trm);
+  }
+
+  @Override
+  public void beforeConfiguration(ITestResult tr) {
+  }
+
+  @Override
+  public void onTestFailedButWithinSuccessPercentage(ITestResult testResult) {
+    if (null == m_currentTestContext) {
+      m_sender.sendMessage(new TestResultMessage(m_suite.getName(), m_xmlTest.getName(), testResult));
+    }
+    else {
+      m_sender.sendMessage(new TestResultMessage(m_currentTestContext, testResult));
+    }
+  }
+
+  @Override
+  public void onTestFailure(ITestResult testResult) {
+    if (null == m_currentTestContext) {
+      m_sender.sendMessage(new TestResultMessage(m_suite.getName(), m_xmlTest.getName(), testResult));
+    }
+    else {
+      m_sender.sendMessage(new TestResultMessage(m_currentTestContext, testResult));
+    }
+  }
+
+  @Override
+  public void onTestSkipped(ITestResult testResult) {
+    if (null == m_currentTestContext) {
+      m_sender.sendMessage(new TestResultMessage(m_suite.getName(), m_xmlTest.getName(), testResult));
+    }
+    else {
+      m_sender.sendMessage(new TestResultMessage(m_currentTestContext, testResult));
+    }
+  }
+
+  @Override
+  public void onTestSuccess(ITestResult testResult) {
+    if (null == m_currentTestContext) {
+      m_sender.sendMessage(new TestResultMessage(m_suite.getName(), m_xmlTest.getName(), testResult));
+    }
+    else {
+      m_sender.sendMessage(new TestResultMessage(m_currentTestContext, testResult));
+    }
+  }
+
+  /**
+   * @see org.testng.IConfigurationListener#onConfigurationFailure(org.testng.ITestResult)
+   */
+  @Override
+  public void onConfigurationFailure(ITestResult itr) {
+    // Show configuration failures in the main view for convenience
+    onTestFailure(itr);
+  }
+
+  /**
+   * @see org.testng.IConfigurationListener#onConfigurationSkip(org.testng.ITestResult)
+   */
+  @Override
+  public void onConfigurationSkip(ITestResult itr) {
+    onTestSkipped(itr);
+  }
+
+  /**
+   * @see org.testng.IConfigurationListener#onConfigurationSuccess(org.testng.ITestResult)
+   */
+  @Override
+  public void onConfigurationSuccess(ITestResult itr) {
+  }
+}
\ No newline at end of file
diff --git a/src/main/java/org/testng/remote/strprotocol/SerializedMessageSender.java b/src/main/java/org/testng/remote/strprotocol/SerializedMessageSender.java
new file mode 100644
index 0000000..cd6dc41
--- /dev/null
+++ b/src/main/java/org/testng/remote/strprotocol/SerializedMessageSender.java
@@ -0,0 +1,57 @@
+package org.testng.remote.strprotocol;
+
+import org.testng.remote.RemoteTestNG;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+
+public class SerializedMessageSender extends BaseMessageSender {
+
+  public SerializedMessageSender(String host, int port) {
+    super(host, port, false /* no ack */);
+  }
+
+  public SerializedMessageSender(String host, int port, boolean ack) {
+    super(host, port, ack);
+  }
+
+  @Override
+  public void sendMessage(IMessage message) throws IOException {
+    synchronized(m_outStream) {
+      p("Sending message " + message);
+      ObjectOutputStream oos = new ObjectOutputStream(m_outStream);
+      oos.writeObject(message);
+      oos.flush();
+
+      waitForAck();
+    }
+  }
+
+  @Override
+  public IMessage receiveMessage() throws IOException, ClassNotFoundException {
+
+    IMessage result = null;
+    try {
+      ObjectInputStream ios = new ObjectInputStream(m_inStream);
+//      synchronized(m_input) {
+        result = (IMessage) ios.readObject();
+        p("Received message " + result);
+//        sendAck();
+//      }
+    }
+    catch(Exception ex) {
+      if (RemoteTestNG.isVerbose()) {
+        ex.printStackTrace();
+      }
+    }
+    return result;
+  }
+
+  static void p(String s) {
+    if (RemoteTestNG.isVerbose()) {
+      System.out.println("[SerializedMessageSender] " + s);
+    }
+  }
+}
diff --git a/src/main/java/org/testng/remote/strprotocol/StringMessageSender.java b/src/main/java/org/testng/remote/strprotocol/StringMessageSender.java
new file mode 100644
index 0000000..c924edd
--- /dev/null
+++ b/src/main/java/org/testng/remote/strprotocol/StringMessageSender.java
@@ -0,0 +1,149 @@
+package org.testng.remote.strprotocol;
+
+import org.testng.remote.RemoteTestNG;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.SocketException;
+
+public class StringMessageSender extends BaseMessageSender {
+
+  private PrintWriter writer;
+
+  public StringMessageSender(String host, int port) {
+    super(host, port, false /* no ack */);
+  }
+
+  public StringMessageSender(String host, int port, boolean ack) {
+    super(host, port, ack);
+  }
+
+  @Override
+  public void sendMessage(IMessage message) {
+    if (m_outStream == null) {
+      throw new IllegalStateException("Trying to send a message on a shutdown sender");
+    }
+    if (writer == null) {
+      try {
+        writer = new PrintWriter(new BufferedWriter(
+            new OutputStreamWriter(m_outStream, "UTF-8")), //$NON-NLS-1$
+            false /* autoflush */);
+      } catch (UnsupportedEncodingException e1) {
+        writer = new PrintWriter(new BufferedWriter(
+            new OutputStreamWriter(m_outStream)),
+            false /* autoflush */);
+      }
+    }
+
+    String msg = ((IStringMessage) message).getMessageAsString();
+    if (RemoteTestNG.isVerbose()) {
+      p("Sending message:" + message);
+      p("  String version:" + msg);
+
+      StringBuffer buf = new StringBuffer();
+      for(int i = 0; i < msg.length(); i++) {
+        if('\u0001' == msg.charAt(i)) {
+          p("  word:[" + buf.toString() + "]");
+          buf.delete(0, buf.length());
+        }
+        else {
+          buf.append(msg.charAt(i));
+        }
+      }
+      p("  word:[" + buf.toString() + "]");
+    }
+
+    synchronized(m_ackLock) {
+      writer.println(msg);
+      writer.flush();
+      waitForAck();
+    }
+  }
+
+  private static void p(String msg) {
+    if (RemoteTestNG.isVerbose()) {
+      System.out.println("[StringMessageSender] " + msg); //$NON-NLS-1$
+    }
+  }
+
+  @Override
+  public IMessage receiveMessage() {
+    IMessage result = null;
+
+    if (m_inReader == null) {
+      try {
+        m_inReader = new BufferedReader(new InputStreamReader(m_inStream, "UTF-8"));
+      } catch (UnsupportedEncodingException e) {
+        m_inReader = new BufferedReader(new InputStreamReader(m_inStream));
+      }
+    }
+    try {
+//      try {
+//        m_outputWriter = new PrintWriter(new OutputStreamWriter(fSocket.getOutputStream(), "UTF-8"),
+//                                  true);
+//      }
+//      catch(UnsupportedEncodingException e1) {
+//        m_outputWriter = new PrintWriter(new OutputStreamWriter(fSocket.getOutputStream()), true);
+//      }
+      result = receiveMessage(m_inReader.readLine());
+    } catch(IOException e) {
+      handleThrowable(e);
+    }
+
+    return result;
+//    finally {
+//      shutDown();
+//      return null;
+//    }
+  }
+
+  protected void handleThrowable(Throwable cause) {
+    if (RemoteTestNG.isVerbose()) {
+      cause.printStackTrace();
+    }
+  }
+
+//  private String readMessage(BufferedReader in) throws IOException {
+//    return in.readLine();
+//  }
+
+  private IMessage receiveMessage(String message) {
+    if (message == null) return null;
+    IMessage result = null;
+
+    int messageType = MessageHelper.getMessageType(message);
+
+//    try {
+      if(messageType < MessageHelper.SUITE) {
+        // Generic message
+        result = MessageHelper.unmarshallGenericMessage(message);
+      }
+      else if(messageType < MessageHelper.TEST) {
+        // Suite message
+        result = MessageHelper.createSuiteMessage(message);
+      }
+      else if(messageType < MessageHelper.TEST_RESULT) {
+        // Test message
+        result = MessageHelper.createTestMessage(message);
+      }
+      else {
+        // TestResult message
+        result = MessageHelper.unmarshallTestResultMessage(message);
+      }
+//    }
+//    finally {
+//      if(isRunning() && (null != m_outputWriter)) {
+//        m_outputWriter.println(MessageHelper.ACK_MSG);
+//        m_outputWriter.flush();
+//      }
+//    }
+
+    p("receiveMessage() received:" + result);
+    return result;
+  }
+}
diff --git a/src/main/java/org/testng/remote/strprotocol/SuiteMessage.java b/src/main/java/org/testng/remote/strprotocol/SuiteMessage.java
new file mode 100755
index 0000000..aae2a84
--- /dev/null
+++ b/src/main/java/org/testng/remote/strprotocol/SuiteMessage.java
@@ -0,0 +1,103 @@
+package org.testng.remote.strprotocol;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.testng.ISuite;
+import org.testng.ITestNGMethod;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+
+
+/**
+ * A <code>IStringMessage</code> implementation for suite running events.
+ *
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ */
+public class SuiteMessage implements IStringMessage {
+  private static final long serialVersionUID = -4298528261942620419L;
+  protected final String m_suiteName;
+  protected final int m_testMethodCount;
+  protected final boolean m_startSuite;
+  private List<String> m_excludedMethods = Lists.newArrayList();
+  private Map<String, String> m_descriptions;
+
+  public SuiteMessage(final String suiteName, final boolean startSuiteRun, final int methodCount) {
+    m_suiteName = suiteName;
+    m_startSuite = startSuiteRun;
+    m_testMethodCount = methodCount;
+  }
+
+  public SuiteMessage(final ISuite suite, final boolean startSuiteRun) {
+    m_suiteName = suite.getName();
+    m_testMethodCount =suite.getInvokedMethods().size();
+    m_startSuite = startSuiteRun;
+    Collection<ITestNGMethod> excludedMethods = suite.getExcludedMethods();
+    if (excludedMethods != null && excludedMethods.size() > 0) {
+      m_excludedMethods = Lists.newArrayList();
+      m_descriptions = Maps.newHashMap();
+      for (ITestNGMethod m : excludedMethods) {
+        String methodName = m.getTestClass().getName() + "." + m.getMethodName();
+        m_excludedMethods.add(methodName);
+        if (m.getDescription() != null) m_descriptions.put(methodName, m.getDescription());
+      }
+    }
+  }
+
+  public void setExcludedMethods(List<String> methods) {
+    m_excludedMethods = Lists.newArrayList();
+    m_excludedMethods.addAll(methods);
+  }
+
+  public List<String> getExcludedMethods() {
+    return m_excludedMethods;
+  }
+
+  public String getDescriptionForMethod(String methodName) {
+    return m_descriptions.get(methodName);
+  }
+
+  public boolean isMessageOnStart() {
+    return m_startSuite;
+  }
+
+  public String getSuiteName() {
+    return m_suiteName;
+  }
+
+  public int getTestMethodCount() {
+    return m_testMethodCount;
+  }
+
+  @Override
+  public String getMessageAsString() {
+    StringBuffer buf = new StringBuffer();
+
+    buf.append(m_startSuite ? MessageHelper.SUITE_START : MessageHelper.SUITE_FINISH)
+        .append(MessageHelper.DELIMITER)
+        .append(m_suiteName)
+        .append(MessageHelper.DELIMITER)
+        .append(m_testMethodCount)
+        ;
+
+    if (m_excludedMethods != null && m_excludedMethods.size() > 0) {
+      buf.append(MessageHelper.DELIMITER);
+      buf.append(m_excludedMethods.size());
+      for (String method : m_excludedMethods) {
+        buf.append(MessageHelper.DELIMITER);
+        buf.append(method);
+      }
+    }
+    return buf.toString();
+  }
+
+  @Override
+  public String toString() {
+    return "[SuiteMessage suite:" + m_suiteName
+        + (m_startSuite ? " starting" : " ending")
+        + " methodCount:" + m_testMethodCount
+        + "]";
+  }
+
+}
diff --git a/src/main/java/org/testng/remote/strprotocol/TestMessage.java b/src/main/java/org/testng/remote/strprotocol/TestMessage.java
new file mode 100755
index 0000000..0a684e0
--- /dev/null
+++ b/src/main/java/org/testng/remote/strprotocol/TestMessage.java
@@ -0,0 +1,113 @@
+package org.testng.remote.strprotocol;
+
+import org.testng.ITestContext;
+
+
+/**
+ * An <code>IStringMessage</code> implementation for test events.
+ *
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ */
+public class TestMessage implements IStringMessage {
+  private static final long serialVersionUID = -5039267143570559640L;
+  protected final boolean m_testStart;
+  protected final String m_suiteName;
+  protected final String m_testName;
+  protected final int m_testMethodCount;
+  protected final int m_passedTestCount;
+  protected final int m_failedTestCount;
+  protected final int m_skippedTestCount;
+  protected final int m_successPercentageFailedTestCount;
+
+  public TestMessage(final boolean isTestStart,
+              final String suiteName,
+              final String testName,
+              final int methodCount,
+              final int passedCount,
+              final int failedCount,
+              final int skippedCount,
+              final int percentageCount) {
+    m_testStart = isTestStart;
+    m_suiteName = suiteName;
+    m_testName = testName;
+    m_testMethodCount = methodCount;
+    m_passedTestCount = passedCount;
+    m_failedTestCount = failedCount;
+    m_skippedTestCount = skippedCount;
+    m_successPercentageFailedTestCount = percentageCount;
+  }
+
+  public TestMessage(final ITestContext testContext, final boolean isTestStart) {
+    this(isTestStart,
+         testContext.getSuite().getName(),
+         testContext.getCurrentXmlTest().getName(),
+         testContext.getAllTestMethods().length,
+         testContext.getPassedTests().size(),
+         testContext.getFailedTests().size(),
+         testContext.getSkippedTests().size(),
+         testContext.getFailedButWithinSuccessPercentageTests().size());
+  }
+
+  public boolean isMessageOnStart() {
+    return m_testStart;
+  }
+
+  @Override
+  public String getMessageAsString() {
+    StringBuffer buf = new StringBuffer();
+
+    buf.append(m_testStart ? MessageHelper.TEST_START : MessageHelper.TEST_FINISH)
+        .append(MessageHelper.DELIMITER)
+        .append(m_suiteName)
+        .append(MessageHelper.DELIMITER)
+        .append(m_testName)
+        .append(MessageHelper.DELIMITER)
+        .append(m_testMethodCount)
+        .append(MessageHelper.DELIMITER)
+        .append(m_passedTestCount)
+        .append(MessageHelper.DELIMITER)
+        .append(m_failedTestCount)
+        .append(MessageHelper.DELIMITER)
+        .append(m_skippedTestCount)
+        .append(MessageHelper.DELIMITER)
+        .append(m_successPercentageFailedTestCount)
+        ;
+
+    return buf.toString();
+  }
+
+  public String getSuiteName() {
+    return m_suiteName;
+  }
+
+  public String getTestName() {
+    return m_testName;
+  }
+
+  public boolean isTestStart() {
+    return m_testStart;
+  }
+  public int getTestMethodCount() {
+    return m_testMethodCount;
+  }
+  public int getSuccessPercentageFailedTestCount() {
+    return m_successPercentageFailedTestCount;
+  }
+  public int getFailedTestCount() {
+    return m_failedTestCount;
+  }
+  public int getPassedTestCount() {
+    return m_passedTestCount;
+  }
+  public int getSkippedTestCount() {
+    return m_skippedTestCount;
+  }
+
+  @Override
+  public String toString() {
+    return "[TestMessage suite:" + m_suiteName + " testName:" + m_testName
+        + " passed:" + m_passedTestCount + " failed:" + m_failedTestCount
+        + "]";
+  }
+
+}
diff --git a/src/main/java/org/testng/remote/strprotocol/TestResultMessage.java b/src/main/java/org/testng/remote/strprotocol/TestResultMessage.java
new file mode 100755
index 0000000..5131fa5
--- /dev/null
+++ b/src/main/java/org/testng/remote/strprotocol/TestResultMessage.java
@@ -0,0 +1,442 @@
+package org.testng.remote.strprotocol;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+
+import org.testng.ITestContext;
+import org.testng.ITestResult;
+import org.testng.SkipException;
+import org.testng.collections.Lists;
+
+import static org.testng.internal.Utils.isStringEmpty;
+
+
+/**
+ * An <code>IStringMessage</code> implementation for test results events.
+ *
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ */
+public class TestResultMessage implements IStringMessage {
+  private static final long serialVersionUID = -4157150777889117479L;
+  protected int    m_messageType;
+  protected String m_suiteName;
+  protected String m_testName;
+  protected String m_testClassName;
+  protected String m_testMethodName;
+  protected String m_stackTrace;
+  protected long m_startMillis;
+  protected long m_endMillis;
+  protected String[] m_parameters= new String[0];
+  protected String[] m_paramTypes= new String[0];
+  private String m_testDescription;
+  private int m_invocationCount;
+  private int m_currentInvocationCount;
+  private String m_instanceName;
+
+  /**
+   * This constructor is used by the Eclipse client to initialize a result message based
+   * on what was received over the network.
+   */
+  public TestResultMessage(final int resultType,
+                    final String suiteName,
+                    final String testName,
+                    final String className,
+                    final String methodName,
+                    final String testDescriptor,
+                    String instanceName,
+                    final String[] params,
+                    final long startMillis,
+                    final long endMillis,
+                    final String stackTrace,
+                    int invocationCount,
+                    int currentInvocationCount)
+  {
+    init(resultType,
+         suiteName,
+         testName,
+         className,
+         methodName,
+         stackTrace,
+         startMillis,
+         endMillis,
+         extractParams(params),
+         extractParamTypes(params),
+         testDescriptor,
+         instanceName,
+         invocationCount,
+         currentInvocationCount
+    );
+  }
+
+  /**
+   * This constructor is used by RemoteTestNG to initialize a result message
+   * from an ITestResult.
+   */
+  public TestResultMessage(final String suiteName,
+                           final String testName,
+                           final ITestResult result)
+  {
+    Throwable throwable = result.getThrowable();
+    String stackTrace = null;
+
+    if((ITestResult.FAILURE == result.getStatus())
+      || (ITestResult.SUCCESS_PERCENTAGE_FAILURE == result.getStatus())) {
+      StringWriter sw = new StringWriter();
+      PrintWriter  pw = new PrintWriter(sw);
+      Throwable cause= throwable;
+      if (null != cause) {
+        cause.printStackTrace(pw);
+        stackTrace = sw.getBuffer().toString();
+      }
+      else {
+        stackTrace= "unknown stack trace";
+      }
+    }
+    else if(ITestResult.SKIP == result.getStatus()
+        && (throwable != null && SkipException.class.isAssignableFrom(throwable.getClass()))) {
+      stackTrace= throwable.getMessage();
+    } else if (throwable != null) {
+      StringWriter sw = new StringWriter();
+      PrintWriter pw = new PrintWriter(sw);
+      throwable.printStackTrace(pw);
+      stackTrace = sw.toString();
+    }
+
+    init(MessageHelper.TEST_RESULT + result.getStatus(),
+         suiteName,
+         testName,
+         result.getTestClass().getName(),
+         result.getMethod().getMethod().getName(),
+         MessageHelper.replaceUnicodeCharactersWithAscii(stackTrace),
+         result.getStartMillis(),
+         result.getEndMillis(),
+         toString(result.getParameters(), result.getMethod().getMethod().getParameterTypes()),
+         toString(result.getMethod().getMethod().getParameterTypes()),
+         MessageHelper.replaceUnicodeCharactersWithAscii(result.getName()),
+         MessageHelper.replaceUnicodeCharactersWithAscii(result.getInstanceName()),
+         result.getMethod().getInvocationCount(),
+         result.getMethod().getCurrentInvocationCount()
+    );
+  }
+
+  public TestResultMessage(final ITestContext testCtx, final ITestResult result) {
+    this(testCtx.getSuite().getName(), testCtx.getCurrentXmlTest().getName(), result);
+//    this(testCtx.getSuite().getName(),
+//        result.getTestName() != null ? result.getTestName() : result.getName(), result);
+  }
+
+  private void init(final int resultType,
+                    final String suiteName,
+                    final String testName,
+                    final String className,
+                    final String methodName,
+                    final String stackTrace,
+                    final long startMillis,
+                    final long endMillis,
+                    final String[] parameters,
+                    final String[] types,
+                    final String testDescription,
+                    String instanceName,
+                    int invocationCount,
+                    int currentInvocationCount) {
+    m_messageType = resultType;
+    m_suiteName = suiteName;
+    m_testName = testName;
+    m_testClassName = className;
+    m_testMethodName = methodName;
+    m_stackTrace = stackTrace;
+    m_startMillis= startMillis;
+    m_endMillis= endMillis;
+    m_parameters= parameters;
+    m_paramTypes= types;
+    m_testDescription= testDescription;
+    m_invocationCount = invocationCount;
+    m_currentInvocationCount = currentInvocationCount;
+    m_instanceName = instanceName;
+  }
+
+  public int getResult() {
+    return m_messageType;
+  }
+
+  @Override
+  public String getMessageAsString() {
+    StringBuffer buf = new StringBuffer();
+    StringBuffer parambuf = new StringBuffer();
+
+    if(null != m_parameters && m_parameters.length > 0) {
+      for (int j = 0; j < m_parameters.length; j++) {
+        if (j > 0) {
+          parambuf.append(MessageHelper.PARAM_DELIMITER);
+        }
+        parambuf.append(m_paramTypes[j] + ":" + m_parameters[j]);
+      }
+    }
+
+    buf.append(m_messageType)
+       .append(MessageHelper.DELIMITER)
+       .append(m_suiteName)
+       .append(MessageHelper.DELIMITER)
+       .append(m_testName)
+       .append(MessageHelper.DELIMITER)
+       .append(m_testClassName)
+       .append(MessageHelper.DELIMITER)
+       .append(m_testMethodName)
+       .append(MessageHelper.DELIMITER)
+       .append(parambuf)
+       .append(MessageHelper.DELIMITER)
+       .append(m_startMillis)
+       .append(MessageHelper.DELIMITER)
+       .append(m_endMillis)
+       .append(MessageHelper.DELIMITER)
+       .append(MessageHelper.replaceNewLine(m_stackTrace))
+       .append(MessageHelper.DELIMITER)
+       .append(MessageHelper.replaceNewLine(m_testDescription))
+       ;
+
+    return buf.toString();
+  }
+
+  public String getSuiteName() {
+    return m_suiteName;
+  }
+
+  public String getTestClass() {
+    return m_testClassName;
+  }
+
+  public String getMethod() {
+    return m_testMethodName;
+  }
+
+  public String getName() {
+    return m_testName;
+  }
+
+  public String getStackTrace() {
+    return m_stackTrace;
+  }
+
+  public long getEndMillis() {
+    return m_endMillis;
+  }
+
+  public long getStartMillis() {
+    return m_startMillis;
+  }
+
+  public String[] getParameters() {
+    return m_parameters;
+  }
+
+  public String[] getParameterTypes() {
+    return m_paramTypes;
+  }
+
+  public String getTestDescription() {
+    return m_testDescription;
+  }
+
+  public String toDisplayString() {
+    StringBuffer buf= new StringBuffer(m_testName != null ? m_testName : m_testMethodName);
+
+    if(null != m_parameters && m_parameters.length > 0) {
+      buf.append("(");
+      for(int i= 0; i < m_parameters.length; i++) {
+        if(i > 0) {
+          buf.append(", ");
+        }
+        if("java.lang.String".equals(m_paramTypes[i]) && !("null".equals(m_parameters[i]) || "\"\"".equals(m_parameters[i]))) {
+          buf.append("\"").append(m_parameters[i]).append("\"");
+        }
+        else {
+          buf.append(m_parameters[i]);
+        }
+
+      }
+      buf.append(")");
+    }
+
+    return buf.toString();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if(this == o) {
+      return true;
+    }
+    if(o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    final TestResultMessage that = (TestResultMessage) o;
+
+    if(m_suiteName != null ? !m_suiteName.equals(that.m_suiteName) : that.m_suiteName != null) {
+      return false;
+    }
+    if(m_testName != null ? !m_testName.equals(that.m_testName) : that.m_testName != null) {
+      return false;
+    }
+    if(m_testClassName != null ? !m_testClassName.equals(that.m_testClassName) : that.m_testClassName != null) {
+      return false;
+    }
+    String toDisplayString= toDisplayString();
+    if(toDisplayString != null ? !toDisplayString.equals(that.toDisplayString()) : that.toDisplayString() != null) {
+      return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = (m_suiteName != null ? m_suiteName.hashCode() : 0);
+    result = 29 * result + (m_testName != null ? m_testName.hashCode() : 0);
+    result = 29 * result + m_testClassName.hashCode();
+    result = 29 * result + toDisplayString().hashCode();
+    return result;
+  }
+
+  String[] toString(Object[] objects, Class<?>[] objectClasses) {
+    if(null == objects) {
+      return new String[0];
+    }
+    List<String> result= Lists.newArrayList(objects.length);
+    for(Object o: objects) {
+      if(null == o) {
+        result.add("null");
+      }
+      else if (o.getClass().isArray()) {
+        String[] strArray;
+        if (o.getClass().getComponentType().isPrimitive()){
+          strArray = primitiveArrayToString(o);
+        } else {
+          strArray = toString((Object[]) o, null);
+        }
+        StringBuilder sb = new StringBuilder("[");
+        for (int i = 0; i < strArray.length; i++)
+        {
+          sb.append(strArray[i]);
+          if (i + 1 < strArray.length)
+          {
+            sb.append(",");
+          }
+        }
+        sb.append("]");
+        result.add(sb.toString());
+      }
+      else {
+        String tostring= o.toString();
+        if(isStringEmpty(tostring)) {
+          result.add("\"\"");
+        }
+        else {
+          result.add(MessageHelper.replaceNewLine(tostring));
+        }
+      }
+    }
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  private String[] primitiveArrayToString(Object o) {
+    List<String> results = Lists.newArrayList();
+    if (o instanceof byte[]) {
+      byte[] array = (byte[]) o;
+        for (byte anArray : array) {
+            results.add(String.valueOf(anArray));
+        }
+    } else if (o instanceof boolean[]) {
+      boolean[] array = (boolean[]) o;
+        for (boolean anArray : array) {
+            results.add(String.valueOf(anArray));
+        }
+    } else if (o instanceof char[]) {
+      char[] array = (char[]) o;
+        for (char anArray : array) {
+            results.add(String.valueOf(anArray));
+        }
+    } else if (o instanceof double[]) {
+      double[] array = (double[]) o;
+        for (double anArray : array) {
+            results.add(String.valueOf(anArray));
+        }
+    } else if (o instanceof float[]) {
+      float[] array = (float[]) o;
+        for (float anArray : array) {
+            results.add(String.valueOf(anArray));
+        }
+    } else if (o instanceof short[]) {
+      short[] array = (short[]) o;
+        for (short anArray : array) {
+            results.add(String.valueOf(anArray));
+        }
+    } else if (o instanceof int[]) {
+      int[] array = (int[]) o;
+        for (int anArray : array) {
+            results.add(String.valueOf(anArray));
+        }
+    } else if (o instanceof long[]) {
+      long[] array = (long[]) o;
+        for (long anArray : array) {
+            results.add(String.valueOf(anArray));
+        }
+    }
+    return results.toArray(new String[results.size()]);
+  }
+
+  private String[] toString(Class<?>[] classes) {
+    if(null == classes) {
+      return new String[0];
+    }
+    List<String> result= Lists.newArrayList(classes.length);
+    for(Class<?> cls: classes) {
+      result.add(cls.getName());
+    }
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  private String[] extractParamTypes(String[] params) {
+    List<String> result= Lists.newArrayList(params.length);
+    for(String s: params) {
+      result.add(s.substring(0, s.indexOf(':')));
+    }
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  private String[] extractParams(String[] params) {
+    List<String> result= Lists.newArrayList(params.length);
+    for(String s: params) {
+      result.add(MessageHelper.replaceNewLineReplacer(s.substring(s.indexOf(':') + 1)));
+    }
+
+    return result.toArray(new String[result.size()]);
+  }
+
+  public int getInvocationCount() {
+    return m_invocationCount;
+  }
+
+  public int getCurrentInvocationCount() {
+    return m_currentInvocationCount;
+  }
+
+  @Override
+  public String toString() {
+    return "[TestResultMessage suite:" + m_suiteName + " test:" + m_testName
+        + " method:" + m_testMethodName
+        + "]";
+  }
+
+  public void setParameters(String[] params) {
+    m_parameters = extractParams(params);
+    m_paramTypes = extractParamTypes(params);
+  }
+
+  public String getInstanceName() {
+    return m_instanceName;
+  }
+}
diff --git a/src/main/java/org/testng/reporters/Buffer.java b/src/main/java/org/testng/reporters/Buffer.java
new file mode 100644
index 0000000..2e473e8
--- /dev/null
+++ b/src/main/java/org/testng/reporters/Buffer.java
@@ -0,0 +1,7 @@
+package org.testng.reporters;
+
+public class Buffer {
+  public static IBuffer create() {
+    return new FileStringBuffer();
+  }
+}
diff --git a/src/main/java/org/testng/reporters/DotTestListener.java b/src/main/java/org/testng/reporters/DotTestListener.java
new file mode 100755
index 0000000..a05aba9
--- /dev/null
+++ b/src/main/java/org/testng/reporters/DotTestListener.java
@@ -0,0 +1,31 @@
+package org.testng.reporters;
+
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+
+public class DotTestListener extends TestListenerAdapter {
+  private int m_count = 0;
+
+  @Override
+  public void onTestFailure(ITestResult tr) {
+    log("F");
+  }
+
+  @Override
+  public void onTestSkipped(ITestResult tr) {
+    log("S");
+  }
+
+  @Override
+  public void onTestSuccess(ITestResult tr) {
+    log(".");
+  }
+
+  private void log(String string) {
+    System.out.print(string);
+    if (m_count++ % 40 == 0) {
+      System.out.println("");
+    }
+  }
+
+}
diff --git a/src/main/java/org/testng/reporters/EmailableReporter.java b/src/main/java/org/testng/reporters/EmailableReporter.java
new file mode 100755
index 0000000..890bf8c
--- /dev/null
+++ b/src/main/java/org/testng/reporters/EmailableReporter.java
@@ -0,0 +1,507 @@
+package org.testng.reporters;
+
+import org.testng.IInvokedMethod;
+import org.testng.IReporter;
+import org.testng.IResultMap;
+import org.testng.ISuite;
+import org.testng.ISuiteResult;
+import org.testng.ITestClass;
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+import org.testng.Reporter;
+import org.testng.collections.Lists;
+import org.testng.internal.Utils;
+import org.testng.log4testng.Logger;
+import org.testng.xml.XmlSuite;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Reported designed to render self-contained HTML top down view of a testing
+ * suite.
+ *
+ * @author Paul Mendelson
+ * @since 5.2
+ * @version $Revision: 719 $
+ */
+public class EmailableReporter implements IReporter {
+  private static final Logger L = Logger.getLogger(EmailableReporter.class);
+
+  // ~ Instance fields ------------------------------------------------------
+
+  private PrintWriter m_out;
+
+  private int m_row;
+
+  private Integer m_testIndex;
+
+  private int m_methodIndex;
+
+  // ~ Methods --------------------------------------------------------------
+
+  /** Creates summary of the run */
+  @Override
+  public void generateReport(List<XmlSuite> xml, List<ISuite> suites, String outdir) {
+    try {
+      m_out = createWriter(outdir);
+    }
+    catch (IOException e) {
+      L.error("output file", e);
+      return;
+    }
+    startHtml(m_out);
+    generateSuiteSummaryReport(suites);
+    generateMethodSummaryReport(suites);
+    generateMethodDetailReport(suites);
+    endHtml(m_out);
+    m_out.flush();
+    m_out.close();
+  }
+
+  protected PrintWriter createWriter(String outdir) throws IOException {
+    new File(outdir).mkdirs();
+    return new PrintWriter(new BufferedWriter(new FileWriter(new File(outdir,
+        "emailable-report.html"))));
+  }
+
+  /** Creates a table showing the highlights of each test method with links to the method details */
+  protected void generateMethodSummaryReport(List<ISuite> suites) {
+    m_methodIndex = 0;
+    startResultSummaryTable("methodOverview");
+    int testIndex = 1;
+    for (ISuite suite : suites) {
+      if(suites.size()>1) {
+        titleRow(suite.getName(), 5);
+      }
+      Map<String, ISuiteResult> r = suite.getResults();
+      for (ISuiteResult r2 : r.values()) {
+        ITestContext testContext = r2.getTestContext();
+        String testName = testContext.getName();
+        m_testIndex = testIndex;
+        resultSummary(suite, testContext.getFailedConfigurations(), testName,
+            "failed", " (configuration methods)");
+        resultSummary(suite, testContext.getFailedTests(), testName, "failed",
+            "");
+        resultSummary(suite, testContext.getSkippedConfigurations(), testName,
+            "skipped", " (configuration methods)");
+        resultSummary(suite, testContext.getSkippedTests(), testName,
+            "skipped", "");
+        resultSummary(suite, testContext.getPassedTests(), testName, "passed",
+            "");
+        testIndex++;
+      }
+    }
+    m_out.println("</table>");
+  }
+
+  /** Creates a section showing known results for each method */
+  protected void generateMethodDetailReport(List<ISuite> suites) {
+    m_methodIndex = 0;
+    for (ISuite suite : suites) {
+      Map<String, ISuiteResult> r = suite.getResults();
+      for (ISuiteResult r2 : r.values()) {
+        ITestContext testContext = r2.getTestContext();
+        if (r.values().size() > 0) {
+          m_out.println("<h1>" + testContext.getName() + "</h1>");
+        }
+        resultDetail(testContext.getFailedConfigurations());
+        resultDetail(testContext.getFailedTests());
+        resultDetail(testContext.getSkippedConfigurations());
+        resultDetail(testContext.getSkippedTests());
+        resultDetail(testContext.getPassedTests());
+      }
+    }
+  }
+
+  /**
+   * @param tests
+   */
+  private void resultSummary(ISuite suite, IResultMap tests, String testname, String style,
+      String details) {
+    if (tests.getAllResults().size() > 0) {
+      StringBuffer buff = new StringBuffer();
+      String lastClassName = "";
+      int mq = 0;
+      int cq = 0;
+      for (ITestNGMethod method : getMethodSet(tests, suite)) {
+        m_row += 1;
+        m_methodIndex += 1;
+        ITestClass testClass = method.getTestClass();
+        String className = testClass.getName();
+        if (mq == 0) {
+          String id = (m_testIndex == null ? null : "t" + Integer.toString(m_testIndex));
+          titleRow(testname + " &#8212; " + style + details, 5, id);
+          m_testIndex = null;
+        }
+        if (!className.equalsIgnoreCase(lastClassName)) {
+          if (mq > 0) {
+            cq += 1;
+            m_out.print("<tr class=\"" + style
+                + (cq % 2 == 0 ? "even" : "odd") + "\">" + "<td");
+            if (mq > 1) {
+              m_out.print(" rowspan=\"" + mq + "\"");
+            }
+            m_out.println(">" + lastClassName + "</td>" + buff);
+          }
+          mq = 0;
+          buff.setLength(0);
+          lastClassName = className;
+        }
+        Set<ITestResult> resultSet = tests.getResults(method);
+        long end = Long.MIN_VALUE;
+        long start = Long.MAX_VALUE;
+        for (ITestResult testResult : tests.getResults(method)) {
+          if (testResult.getEndMillis() > end) {
+            end = testResult.getEndMillis();
+          }
+          if (testResult.getStartMillis() < start) {
+            start = testResult.getStartMillis();
+          }
+        }
+        mq += 1;
+        if (mq > 1) {
+          buff.append("<tr class=\"" + style + (cq % 2 == 0 ? "odd" : "even")
+              + "\">");
+        }
+        String description = method.getDescription();
+        String testInstanceName = resultSet.toArray(new ITestResult[]{})[0].getTestName();
+        buff.append("<td><a href=\"#m" + m_methodIndex + "\">"
+            + qualifiedName(method)
+            + " " + (description != null && description.length() > 0
+                ? "(\"" + description + "\")"
+                : "")
+            + "</a>" + (null == testInstanceName ? "" : "<br>(" + testInstanceName + ")")
+            + "</td>"
+            + "<td class=\"numi\">" + resultSet.size() + "</td>"
+            + "<td>" + start + "</td>"
+            + "<td class=\"numi\">" + (end - start) + "</td>"
+            + "</tr>");
+      }
+      if (mq > 0) {
+        cq += 1;
+        m_out.print("<tr class=\"" + style + (cq % 2 == 0 ? "even" : "odd")
+            + "\">" + "<td");
+        if (mq > 1) {
+          m_out.print(" rowspan=\"" + mq + "\"");
+        }
+        m_out.println(">" + lastClassName + "</td>" + buff);
+      }
+    }
+  }
+
+  /** Starts and defines columns result summary table */
+  private void startResultSummaryTable(String style) {
+    tableStart(style, "summary");
+    m_out.println("<tr><th>Class</th>"
+            + "<th>Method</th><th># of<br/>Scenarios</th><th>Start</th><th>Time<br/>(ms)</th></tr>");
+    m_row = 0;
+  }
+
+  private String qualifiedName(ITestNGMethod method) {
+    StringBuilder addon = new StringBuilder();
+    String[] groups = method.getGroups();
+    int length = groups.length;
+    if (length > 0 && !"basic".equalsIgnoreCase(groups[0])) {
+      addon.append("(");
+      for (int i = 0; i < length; i++) {
+        if (i > 0) {
+          addon.append(", ");
+        }
+          addon.append(groups[i]);
+        }
+      addon.append(")");
+    }
+
+    return "<b>" + method.getMethodName() + "</b> " + addon;
+  }
+
+  private void resultDetail(IResultMap tests) {
+    for (ITestResult result : tests.getAllResults()) {
+      ITestNGMethod method = result.getMethod();
+        m_methodIndex++;
+        String cname = method.getTestClass().getName();
+        m_out.println("<h2 id=\"m" + m_methodIndex + "\">" + cname + ":"
+            + method.getMethodName() + "</h2>");
+        Set<ITestResult> resultSet = tests.getResults(method);
+        generateForResult(result, method, resultSet.size());
+        m_out.println("<p class=\"totop\"><a href=\"#summary\">back to summary</a></p>");
+
+    }
+  }
+
+  private void generateForResult(ITestResult ans, ITestNGMethod method, int resultSetSize) {
+    Object[] parameters = ans.getParameters();
+    boolean hasParameters = parameters != null && parameters.length > 0;
+    if (hasParameters) {
+      tableStart("result", null);
+      m_out.print("<tr class=\"param\">");
+      for (int x = 1; x <= parameters.length; x++) {
+        m_out.print("<th>Parameter #" + x + "</th>");
+      }
+      m_out.println("</tr>");
+      m_out.print("<tr class=\"param stripe\">");
+      for (Object p : parameters) {
+        m_out.println("<td>" + Utils.escapeHtml(Utils.toString(p)) + "</td>");
+      }
+      m_out.println("</tr>");
+    }
+    List<String> msgs = Reporter.getOutput(ans);
+    boolean hasReporterOutput = msgs.size() > 0;
+    Throwable exception=ans.getThrowable();
+    boolean hasThrowable = exception!=null;
+    if (hasReporterOutput||hasThrowable) {
+      if (hasParameters) {
+        m_out.print("<tr><td");
+        if (parameters.length > 1) {
+          m_out.print(" colspan=\"" + parameters.length + "\"");
+        }
+        m_out.println(">");
+      }
+      else {
+        m_out.println("<div>");
+      }
+      if (hasReporterOutput) {
+        if(hasThrowable) {
+          m_out.println("<h3>Test Messages</h3>");
+        }
+        for (String line : msgs) {
+          m_out.println(line + "<br/>");
+        }
+      }
+      if(hasThrowable) {
+        boolean wantsMinimalOutput = ans.getStatus()==ITestResult.SUCCESS;
+        if(hasReporterOutput) {
+          m_out.println("<h3>"
+              +(wantsMinimalOutput?"Expected Exception":"Failure")
+              +"</h3>");
+        }
+        generateExceptionReport(exception,method);
+      }
+      if (hasParameters) {
+        m_out.println("</td></tr>");
+      }
+      else {
+        m_out.println("</div>");
+      }
+    }
+    if (hasParameters) {
+      m_out.println("</table>");
+    }
+  }
+
+  protected void generateExceptionReport(Throwable exception,ITestNGMethod method) {
+    m_out.print("<div class=\"stacktrace\">");
+    m_out.print(Utils.stackTrace(exception, true)[0]);
+    m_out.println("</div>");
+  }
+
+  /**
+   * Since the methods will be sorted chronologically, we want to return
+   * the ITestNGMethod from the invoked methods.
+   */
+  private Collection<ITestNGMethod> getMethodSet(IResultMap tests, ISuite suite) {
+    List<IInvokedMethod> r = Lists.newArrayList();
+    List<IInvokedMethod> invokedMethods = suite.getAllInvokedMethods();
+    for (IInvokedMethod im : invokedMethods) {
+      if (tests.getAllMethods().contains(im.getTestMethod())) {
+        r.add(im);
+      }
+    }
+    Arrays.sort(r.toArray(new IInvokedMethod[r.size()]), new TestSorter());
+    List<ITestNGMethod> result = Lists.newArrayList();
+
+    // Add all the invoked methods
+    for (IInvokedMethod m : r) {
+      result.add(m.getTestMethod());
+    }
+
+    // Add all the methods that weren't invoked (e.g. skipped) that we
+    // haven't added yet
+    for (ITestNGMethod m : tests.getAllMethods()) {
+      if (!result.contains(m)) {
+        result.add(m);
+      }
+    }
+    return result;
+  }
+
+  public void generateSuiteSummaryReport(List<ISuite> suites) {
+    tableStart("testOverview", null);
+    m_out.print("<tr>");
+    tableColumnStart("Test");
+    tableColumnStart("Methods<br/>Passed");
+    tableColumnStart("Scenarios<br/>Passed");
+    tableColumnStart("# skipped");
+    tableColumnStart("# failed");
+    tableColumnStart("Total<br/>Time");
+    tableColumnStart("Included<br/>Groups");
+    tableColumnStart("Excluded<br/>Groups");
+    m_out.println("</tr>");
+    NumberFormat formatter = new DecimalFormat("#,##0.0");
+    int qty_tests = 0;
+    int qty_pass_m = 0;
+    int qty_pass_s = 0;
+    int qty_skip = 0;
+    int qty_fail = 0;
+    long time_start = Long.MAX_VALUE;
+    long time_end = Long.MIN_VALUE;
+    m_testIndex = 1;
+    for (ISuite suite : suites) {
+      if (suites.size() > 1) {
+        titleRow(suite.getName(), 8);
+      }
+      Map<String, ISuiteResult> tests = suite.getResults();
+      for (ISuiteResult r : tests.values()) {
+        qty_tests += 1;
+        ITestContext overview = r.getTestContext();
+        startSummaryRow(overview.getName());
+        int q = getMethodSet(overview.getPassedTests(), suite).size();
+        qty_pass_m += q;
+        summaryCell(q,Integer.MAX_VALUE);
+        q = overview.getPassedTests().size();
+        qty_pass_s += q;
+        summaryCell(q,Integer.MAX_VALUE);
+        q = getMethodSet(overview.getSkippedTests(), suite).size();
+        qty_skip += q;
+        summaryCell(q,0);
+        q = getMethodSet(overview.getFailedTests(), suite).size();
+        qty_fail += q;
+        summaryCell(q,0);
+        time_start = Math.min(overview.getStartDate().getTime(), time_start);
+        time_end = Math.max(overview.getEndDate().getTime(), time_end);
+        summaryCell(formatter.format(
+            (overview.getEndDate().getTime() - overview.getStartDate().getTime()) / 1000.)
+            + " seconds", true);
+        summaryCell(overview.getIncludedGroups());
+        summaryCell(overview.getExcludedGroups());
+        m_out.println("</tr>");
+        m_testIndex++;
+      }
+    }
+    if (qty_tests > 1) {
+      m_out.println("<tr class=\"total\"><td>Total</td>");
+      summaryCell(qty_pass_m,Integer.MAX_VALUE);
+      summaryCell(qty_pass_s,Integer.MAX_VALUE);
+      summaryCell(qty_skip,0);
+      summaryCell(qty_fail,0);
+      summaryCell(formatter.format((time_end - time_start) / 1000.) + " seconds", true);
+      m_out.println("<td colspan=\"2\">&nbsp;</td></tr>");
+    }
+    m_out.println("</table>");
+  }
+
+  private void summaryCell(String[] val) {
+    StringBuffer b = new StringBuffer();
+    for (String v : val) {
+      b.append(v + " ");
+    }
+    summaryCell(b.toString(),true);
+  }
+
+  private void summaryCell(String v,boolean isgood) {
+    m_out.print("<td class=\"numi"+(isgood?"":"_attn")+"\">" + v + "</td>");
+  }
+
+  private void startSummaryRow(String label) {
+    m_row += 1;
+    m_out.print("<tr" + (m_row % 2 == 0 ? " class=\"stripe\"" : "")
+            + "><td style=\"text-align:left;padding-right:2em\"><a href=\"#t"
+            + m_testIndex + "\">" + label + "</a>"
+            + "</td>");
+  }
+
+  private void summaryCell(int v,int maxexpected) {
+    summaryCell(String.valueOf(v),v<=maxexpected);
+  }
+
+  private void tableStart(String cssclass, String id) {
+    m_out.println("<table cellspacing=\"0\" cellpadding=\"0\""
+        + (cssclass != null ? " class=\"" + cssclass + "\""
+            : " style=\"padding-bottom:2em\"")
+        + (id != null ? " id=\"" + id + "\"" : "")
+        + ">");
+    m_row = 0;
+  }
+
+  private void tableColumnStart(String label) {
+    m_out.print("<th>" + label + "</th>");
+  }
+
+  private void titleRow(String label, int cq) {
+    titleRow(label, cq, null);
+  }
+
+  private void titleRow(String label, int cq, String id) {
+    m_out.print("<tr");
+    if (id != null) {
+      m_out.print(" id=\"" + id + "\"");
+    }
+    m_out.println( "><th colspan=\"" + cq + "\">" + label + "</th></tr>");
+    m_row = 0;
+  }
+
+  /** Starts HTML stream */
+  protected void startHtml(PrintWriter out) {
+    out.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">");
+    out.println("<html xmlns=\"http://www.w3.org/1999/xhtml\">");
+    out.println("<head>");
+    out.println("<title>TestNG Report</title>");
+    out.println("<style type=\"text/css\">");
+    out.println("table {margin-bottom:10px;border-collapse:collapse;empty-cells:show}");
+    out.println("td,th {border:1px solid #009;padding:.25em .5em}");
+    out.println(".result th {vertical-align:bottom}");
+    out.println(".param th {padding-left:1em;padding-right:1em}");
+    out.println(".param td {padding-left:.5em;padding-right:2em}");
+    out.println(".stripe td,.stripe th {background-color: #E6EBF9}");
+    out.println(".numi,.numi_attn {text-align:right}");
+    out.println(".total td {font-weight:bold}");
+    out.println(".passedodd td {background-color: #0A0}");
+    out.println(".passedeven td {background-color: #3F3}");
+    out.println(".skippedodd td {background-color: #CCC}");
+    out.println(".skippedodd td {background-color: #DDD}");
+    out.println(".failedodd td,.numi_attn {background-color: #F33}");
+    out.println(".failedeven td,.stripe .numi_attn {background-color: #D00}");
+    out.println(".stacktrace {white-space:pre;font-family:monospace}");
+    out.println(".totop {font-size:85%;text-align:center;border-bottom:2px solid #000}");
+    out.println("</style>");
+    out.println("</head>");
+    out.println("<body>");
+  }
+
+  /** Finishes HTML stream */
+  protected void endHtml(PrintWriter out) {
+    out.println("</body></html>");
+  }
+
+  // ~ Inner Classes --------------------------------------------------------
+  /** Arranges methods by classname and method name */
+  private static final class TestSorter implements Comparator<IInvokedMethod> {
+    // ~ Methods -------------------------------------------------------------
+
+    /** Arranges methods by classname and method name */
+    @Override
+    public int compare(IInvokedMethod o1, IInvokedMethod o2) {
+//      System.out.println("Comparing " + o1.getMethodName() + " " + o1.getDate()
+//          + " and " + o2.getMethodName() + " " + o2.getDate());
+      return (int) (o1.getDate() - o2.getDate());
+//      int r = ((T) o1).getTestClass().getName().compareTo(((T) o2).getTestClass().getName());
+//      if (r == 0) {
+//        r = ((T) o1).getMethodName().compareTo(((T) o2).getMethodName());
+//      }
+//      return r;
+    }
+  }
+}
diff --git a/src/main/java/org/testng/reporters/EmailableReporter2.java b/src/main/java/org/testng/reporters/EmailableReporter2.java
new file mode 100644
index 0000000..2367633
--- /dev/null
+++ b/src/main/java/org/testng/reporters/EmailableReporter2.java
@@ -0,0 +1,834 @@
+package org.testng.reporters;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.NumberFormat;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.testng.IReporter;
+import org.testng.ISuite;
+import org.testng.ISuiteResult;
+import org.testng.ITestContext;
+import org.testng.ITestResult;
+import org.testng.Reporter;
+import org.testng.collections.Lists;
+import org.testng.internal.Utils;
+import org.testng.log4testng.Logger;
+import org.testng.xml.XmlSuite;
+
+/**
+ * Reporter that generates a single-page HTML report of the test results.
+ * <p>
+ * Based on an earlier implementation by Paul Mendelson.
+ * </p>
+ * 
+ * @author Abraham Lin
+ */
+public class EmailableReporter2 implements IReporter {
+    private static final Logger LOG = Logger.getLogger(EmailableReporter.class);
+
+    protected PrintWriter writer;
+
+    protected List<SuiteResult> suiteResults = Lists.newArrayList();
+
+    // Reusable buffer
+    private StringBuilder buffer = new StringBuilder();
+
+    @Override
+    public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites,
+            String outputDirectory) {
+        try {
+            writer = createWriter(outputDirectory);
+        } catch (IOException e) {
+            LOG.error("Unable to create output file", e);
+            return;
+        }
+        for (ISuite suite : suites) {
+            suiteResults.add(new SuiteResult(suite));
+        }
+
+        writeDocumentStart();
+        writeHead();
+        writeBody();
+        writeDocumentEnd();
+
+        writer.close();
+    }
+
+    protected PrintWriter createWriter(String outdir) throws IOException {
+        new File(outdir).mkdirs();
+        return new PrintWriter(new BufferedWriter(new FileWriter(new File(
+                outdir, "emailable-report.html"))));
+    }
+
+    protected void writeDocumentStart() {
+        writer.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">");
+        writer.print("<html xmlns=\"http://www.w3.org/1999/xhtml\">");
+    }
+
+    protected void writeHead() {
+        writer.print("<head>");
+        writer.print("<title>TestNG Report</title>");
+        writeStylesheet();
+        writer.print("</head>");
+    }
+
+    protected void writeStylesheet() {
+        writer.print("<style type=\"text/css\">");
+        writer.print("table {margin-bottom:10px;border-collapse:collapse;empty-cells:show}");
+        writer.print("th,td {border:1px solid #009;padding:.25em .5em}");
+        writer.print("th {vertical-align:bottom}");
+        writer.print("td {vertical-align:top}");
+        writer.print("table a {font-weight:bold}");
+        writer.print(".stripe td {background-color: #E6EBF9}");
+        writer.print(".num {text-align:right}");
+        writer.print(".passedodd td {background-color: #3F3}");
+        writer.print(".passedeven td {background-color: #0A0}");
+        writer.print(".skippedodd td {background-color: #DDD}");
+        writer.print(".skippedeven td {background-color: #CCC}");
+        writer.print(".failedodd td,.attn {background-color: #F33}");
+        writer.print(".failedeven td,.stripe .attn {background-color: #D00}");
+        writer.print(".stacktrace {white-space:pre;font-family:monospace}");
+        writer.print(".totop {font-size:85%;text-align:center;border-bottom:2px solid #000}");
+        writer.print("</style>");
+    }
+
+    protected void writeBody() {
+        writer.print("<body>");
+        writeSuiteSummary();
+        writeScenarioSummary();
+        writeScenarioDetails();
+        writer.print("</body>");
+    }
+
+    protected void writeDocumentEnd() {
+        writer.print("</html>");
+    }
+
+    protected void writeSuiteSummary() {
+        NumberFormat integerFormat = NumberFormat.getIntegerInstance();
+        NumberFormat decimalFormat = NumberFormat.getNumberInstance();
+
+        int totalPassedTests = 0;
+        int totalSkippedTests = 0;
+        int totalFailedTests = 0;
+        long totalDuration = 0;
+
+        writer.print("<table>");
+        writer.print("<tr>");
+        writer.print("<th>Test</th>");
+        writer.print("<th># Passed</th>");
+        writer.print("<th># Skipped</th>");
+        writer.print("<th># Failed</th>");
+        writer.print("<th>Time (ms)</th>");
+        writer.print("<th>Included Groups</th>");
+        writer.print("<th>Excluded Groups</th>");
+        writer.print("</tr>");
+
+        int testIndex = 0;
+        for (SuiteResult suiteResult : suiteResults) {
+            writer.print("<tr><th colspan=\"7\">");
+            writer.print(Utils.escapeHtml(suiteResult.getSuiteName()));
+            writer.print("</th></tr>");
+
+            for (TestResult testResult : suiteResult.getTestResults()) {
+                int passedTests = testResult.getPassedTestCount();
+                int skippedTests = testResult.getSkippedTestCount();
+                int failedTests = testResult.getFailedTestCount();
+                long duration = testResult.getDuration();
+
+                writer.print("<tr");
+                if ((testIndex % 2) == 1) {
+                    writer.print(" class=\"stripe\"");
+                }
+                writer.print(">");
+
+                buffer.setLength(0);
+                writeTableData(buffer.append("<a href=\"#t").append(testIndex)
+                        .append("\">")
+                        .append(Utils.escapeHtml(testResult.getTestName()))
+                        .append("</a>").toString());
+                writeTableData(integerFormat.format(passedTests), "num");
+                writeTableData(integerFormat.format(skippedTests),
+                        (skippedTests > 0 ? "num attn" : "num"));
+                writeTableData(integerFormat.format(failedTests),
+                        (failedTests > 0 ? "num attn" : "num"));
+                writeTableData(decimalFormat.format(duration), "num");
+                writeTableData(testResult.getIncludedGroups());
+                writeTableData(testResult.getExcludedGroups());
+
+                writer.print("</tr>");
+
+                totalPassedTests += passedTests;
+                totalSkippedTests += skippedTests;
+                totalFailedTests += failedTests;
+                totalDuration += duration;
+
+                testIndex++;
+            }
+        }
+
+        // Print totals if there was more than one test
+        if (testIndex > 1) {
+            writer.print("<tr>");
+            writer.print("<th>Total</th>");
+            writeTableHeader(integerFormat.format(totalPassedTests), "num");
+            writeTableHeader(integerFormat.format(totalSkippedTests),
+                    (totalSkippedTests > 0 ? "num attn" : "num"));
+            writeTableHeader(integerFormat.format(totalFailedTests),
+                    (totalFailedTests > 0 ? "num attn" : "num"));
+            writeTableHeader(decimalFormat.format(totalDuration), "num");
+            writer.print("<th colspan=\"2\"></th>");
+            writer.print("</tr>");
+        }
+
+        writer.print("</table>");
+    }
+
+    /**
+     * Writes a summary of all the test scenarios.
+     */
+    protected void writeScenarioSummary() {
+        writer.print("<table>");
+        writer.print("<thead>");
+        writer.print("<tr>");
+        writer.print("<th>Class</th>");
+        writer.print("<th>Method</th>");
+        writer.print("<th>Start</th>");
+        writer.print("<th>Time (ms)</th>");
+        writer.print("</tr>");
+        writer.print("</thead>");
+
+        int testIndex = 0;
+        int scenarioIndex = 0;
+        for (SuiteResult suiteResult : suiteResults) {
+            writer.print("<tbody><tr><th colspan=\"4\">");
+            writer.print(Utils.escapeHtml(suiteResult.getSuiteName()));
+            writer.print("</th></tr></tbody>");
+
+            for (TestResult testResult : suiteResult.getTestResults()) {
+                writer.print("<tbody id=\"t");
+                writer.print(testIndex);
+                writer.print("\">");
+
+                String testName = Utils.escapeHtml(testResult.getTestName());
+
+                scenarioIndex += writeScenarioSummary(testName
+                        + " &#8212; failed (configuration methods)",
+                        testResult.getFailedConfigurationResults(), "failed",
+                        scenarioIndex);
+                scenarioIndex += writeScenarioSummary(testName
+                        + " &#8212; failed", testResult.getFailedTestResults(),
+                        "failed", scenarioIndex);
+                scenarioIndex += writeScenarioSummary(testName
+                        + " &#8212; skipped (configuration methods)",
+                        testResult.getSkippedConfigurationResults(), "skipped",
+                        scenarioIndex);
+                scenarioIndex += writeScenarioSummary(testName
+                        + " &#8212; skipped",
+                        testResult.getSkippedTestResults(), "skipped",
+                        scenarioIndex);
+                scenarioIndex += writeScenarioSummary(testName
+                        + " &#8212; passed", testResult.getPassedTestResults(),
+                        "passed", scenarioIndex);
+
+                writer.print("</tbody>");
+
+                testIndex++;
+            }
+        }
+
+        writer.print("</table>");
+    }
+
+    /**
+     * Writes the scenario summary for the results of a given state for a single
+     * test.
+     */
+    private int writeScenarioSummary(String description,
+            List<ClassResult> classResults, String cssClassPrefix,
+            int startingScenarioIndex) {
+        int scenarioCount = 0;
+        if (!classResults.isEmpty()) {
+            writer.print("<tr><th colspan=\"4\">");
+            writer.print(description);
+            writer.print("</th></tr>");
+
+            int scenarioIndex = startingScenarioIndex;
+            int classIndex = 0;
+            for (ClassResult classResult : classResults) {
+                String cssClass = cssClassPrefix
+                        + ((classIndex % 2) == 0 ? "even" : "odd");
+
+                buffer.setLength(0);
+
+                int scenariosPerClass = 0;
+                int methodIndex = 0;
+                for (MethodResult methodResult : classResult.getMethodResults()) {
+                    List<ITestResult> results = methodResult.getResults();
+                    int resultsCount = results.size();
+                    assert resultsCount > 0;
+
+                    ITestResult firstResult = results.iterator().next();
+                    String methodName = Utils.escapeHtml(firstResult
+                            .getMethod().getMethodName());
+                    long start = firstResult.getStartMillis();
+                    long duration = firstResult.getEndMillis() - start;
+
+                    // The first method per class shares a row with the class
+                    // header
+                    if (methodIndex > 0) {
+                        buffer.append("<tr class=\"").append(cssClass)
+                                .append("\">");
+
+                    }
+
+                    // Write the timing information with the first scenario per
+                    // method
+                    buffer.append("<td><a href=\"#m").append(scenarioIndex)
+                            .append("\">").append(methodName)
+                            .append("</a></td>").append("<td rowspan=\"")
+                            .append(resultsCount).append("\">").append(start)
+                            .append("</td>").append("<td rowspan=\"")
+                            .append(resultsCount).append("\">")
+                            .append(duration).append("</td></tr>");
+                    scenarioIndex++;
+
+                    // Write the remaining scenarios for the method
+                    for (int i = 1; i < resultsCount; i++) {
+                        buffer.append("<tr class=\"").append(cssClass)
+                                .append("\">").append("<td><a href=\"#m")
+                                .append(scenarioIndex).append("\">")
+                                .append(methodName).append("</a></td></tr>");
+                        scenarioIndex++;
+                    }
+
+                    scenariosPerClass += resultsCount;
+                    methodIndex++;
+                }
+
+                // Write the test results for the class
+                writer.print("<tr class=\"");
+                writer.print(cssClass);
+                writer.print("\">");
+                writer.print("<td rowspan=\"");
+                writer.print(scenariosPerClass);
+                writer.print("\">");
+                writer.print(Utils.escapeHtml(classResult.getClassName()));
+                writer.print("</td>");
+                writer.print(buffer);
+
+                classIndex++;
+            }
+            scenarioCount = scenarioIndex - startingScenarioIndex;
+        }
+        return scenarioCount;
+    }
+
+    /**
+     * Writes the details for all test scenarios.
+     */
+    protected void writeScenarioDetails() {
+        int scenarioIndex = 0;
+        for (SuiteResult suiteResult : suiteResults) {
+            for (TestResult testResult : suiteResult.getTestResults()) {
+                writer.print("<h2>");
+                writer.print(Utils.escapeHtml(testResult.getTestName()));
+                writer.print("</h2>");
+
+                scenarioIndex += writeScenarioDetails(
+                        testResult.getFailedConfigurationResults(),
+                        scenarioIndex);
+                scenarioIndex += writeScenarioDetails(
+                        testResult.getFailedTestResults(), scenarioIndex);
+                scenarioIndex += writeScenarioDetails(
+                        testResult.getSkippedConfigurationResults(),
+                        scenarioIndex);
+                scenarioIndex += writeScenarioDetails(
+                        testResult.getSkippedTestResults(), scenarioIndex);
+                scenarioIndex += writeScenarioDetails(
+                        testResult.getPassedTestResults(), scenarioIndex);
+            }
+        }
+    }
+
+    /**
+     * Writes the scenario details for the results of a given state for a single
+     * test.
+     */
+    private int writeScenarioDetails(List<ClassResult> classResults,
+            int startingScenarioIndex) {
+        int scenarioIndex = startingScenarioIndex;
+        for (ClassResult classResult : classResults) {
+            String className = classResult.getClassName();
+            for (MethodResult methodResult : classResult.getMethodResults()) {
+                List<ITestResult> results = methodResult.getResults();
+                assert !results.isEmpty();
+
+                String label = Utils
+                        .escapeHtml(className
+                                + "#"
+                                + results.iterator().next().getMethod()
+                                        .getMethodName());
+                for (ITestResult result : results) {
+                    writeScenario(scenarioIndex, label, result);
+                    scenarioIndex++;
+                }
+            }
+        }
+
+        return scenarioIndex - startingScenarioIndex;
+    }
+
+    /**
+     * Writes the details for an individual test scenario.
+     */
+    private void writeScenario(int scenarioIndex, String label,
+            ITestResult result) {
+        writer.print("<h3 id=\"m");
+        writer.print(scenarioIndex);
+        writer.print("\">");
+        writer.print(label);
+        writer.print("</h3>");
+
+        writer.print("<table class=\"result\">");
+
+        // Write test parameters (if any)
+        Object[] parameters = result.getParameters();
+        int parameterCount = (parameters == null ? 0 : parameters.length);
+        if (parameterCount > 0) {
+            writer.print("<tr class=\"param\">");
+            for (int i = 1; i <= parameterCount; i++) {
+                writer.print("<th>Parameter #");
+                writer.print(i);
+                writer.print("</th>");
+            }
+            writer.print("</tr><tr class=\"param stripe\">");
+            for (Object parameter : parameters) {
+                writer.print("<td>");
+                writer.print(Utils.escapeHtml(Utils.toString(parameter)));
+                writer.print("</td>");
+            }
+            writer.print("</tr>");
+        }
+
+        // Write reporter messages (if any)
+        List<String> reporterMessages = Reporter.getOutput(result);
+        if (!reporterMessages.isEmpty()) {
+            writer.print("<tr><th");
+            if (parameterCount > 1) {
+                writer.print(" colspan=\"");
+                writer.print(parameterCount);
+                writer.print("\"");
+            }
+            writer.print(">Messages</th></tr>");
+
+            writer.print("<tr><td");
+            if (parameterCount > 1) {
+                writer.print(" colspan=\"");
+                writer.print(parameterCount);
+                writer.print("\"");
+            }
+            writer.print(">");
+            writeReporterMessages(reporterMessages);
+            writer.print("</td></tr>");
+        }
+
+        // Write exception (if any)
+        Throwable throwable = result.getThrowable();
+        if (throwable != null) {
+            writer.print("<tr><th");
+            if (parameterCount > 1) {
+                writer.print(" colspan=\"");
+                writer.print(parameterCount);
+                writer.print("\"");
+            }
+            writer.print(">");
+            writer.print((result.getStatus() == ITestResult.SUCCESS ? "Expected Exception"
+                    : "Exception"));
+            writer.print("</th></tr>");
+
+            writer.print("<tr><td");
+            if (parameterCount > 1) {
+                writer.print(" colspan=\"");
+                writer.print(parameterCount);
+                writer.print("\"");
+            }
+            writer.print(">");
+            writeStackTrace(throwable);
+            writer.print("</td></tr>");
+        }
+
+        writer.print("</table>");
+        writer.print("<p class=\"totop\"><a href=\"#summary\">back to summary</a></p>");
+    }
+
+    protected void writeReporterMessages(List<String> reporterMessages) {
+        writer.print("<div class=\"messages\">");
+        Iterator<String> iterator = reporterMessages.iterator();
+        assert iterator.hasNext();
+        writer.print(Utils.escapeHtml(iterator.next()));
+        while (iterator.hasNext()) {
+            writer.print("<br/>");
+            writer.print(Utils.escapeHtml(iterator.next()));
+        }
+        writer.print("</div>");
+    }
+
+    protected void writeStackTrace(Throwable throwable) {
+        writer.print("<div class=\"stacktrace\">");
+        writer.print(Utils.stackTrace(throwable, true)[0]);
+        writer.print("</div>");
+    }
+
+    /**
+     * Writes a TH element with the specified contents and CSS class names.
+     * 
+     * @param html
+     *            the HTML contents
+     * @param cssClasses
+     *            the space-delimited CSS classes or null if there are no
+     *            classes to apply
+     */
+    protected void writeTableHeader(String html, String cssClasses) {
+        writeTag("th", html, cssClasses);
+    }
+
+    /**
+     * Writes a TD element with the specified contents.
+     * 
+     * @param html
+     *            the HTML contents
+     */
+    protected void writeTableData(String html) {
+        writeTableData(html, null);
+    }
+
+    /**
+     * Writes a TD element with the specified contents and CSS class names.
+     * 
+     * @param html
+     *            the HTML contents
+     * @param cssClasses
+     *            the space-delimited CSS classes or null if there are no
+     *            classes to apply
+     */
+    protected void writeTableData(String html, String cssClasses) {
+        writeTag("td", html, cssClasses);
+    }
+
+    /**
+     * Writes an arbitrary HTML element with the specified contents and CSS
+     * class names.
+     * 
+     * @param tag
+     *            the tag name
+     * @param html
+     *            the HTML contents
+     * @param cssClasses
+     *            the space-delimited CSS classes or null if there are no
+     *            classes to apply
+     */
+    protected void writeTag(String tag, String html, String cssClasses) {
+        writer.print("<");
+        writer.print(tag);
+        if (cssClasses != null) {
+            writer.print(" class=\"");
+            writer.print(cssClasses);
+            writer.print("\"");
+        }
+        writer.print(">");
+        writer.print(html);
+        writer.print("</");
+        writer.print(tag);
+        writer.print(">");
+    }
+
+    /**
+     * Groups {@link TestResult}s by suite.
+     */
+    protected static class SuiteResult {
+        private final String suiteName;
+        private final List<TestResult> testResults = Lists.newArrayList();
+
+        public SuiteResult(ISuite suite) {
+            suiteName = suite.getName();
+            for (ISuiteResult suiteResult : suite.getResults().values()) {
+                testResults.add(new TestResult(suiteResult.getTestContext()));
+            }
+        }
+
+        public String getSuiteName() {
+            return suiteName;
+        }
+
+        /**
+         * @return the test results (possibly empty)
+         */
+        public List<TestResult> getTestResults() {
+            return testResults;
+        }
+    }
+
+    /**
+     * Groups {@link ClassResult}s by test, type (configuration or test), and
+     * status.
+     */
+    protected static class TestResult {
+        /**
+         * Orders test results by class name and then by method name (in
+         * lexicographic order).
+         */
+        protected static final Comparator<ITestResult> RESULT_COMPARATOR = new Comparator<ITestResult>() {
+            @Override
+            public int compare(ITestResult o1, ITestResult o2) {
+                int result = o1.getTestClass().getName()
+                        .compareTo(o2.getTestClass().getName());
+                if (result == 0) {
+                    result = o1.getMethod().getMethodName()
+                            .compareTo(o2.getMethod().getMethodName());
+                }
+                return result;
+            }
+        };
+
+        private final String testName;
+        private final List<ClassResult> failedConfigurationResults;
+        private final List<ClassResult> failedTestResults;
+        private final List<ClassResult> skippedConfigurationResults;
+        private final List<ClassResult> skippedTestResults;
+        private final List<ClassResult> passedTestResults;
+        private final int failedTestCount;
+        private final int skippedTestCount;
+        private final int passedTestCount;
+        private final long duration;
+        private final String includedGroups;
+        private final String excludedGroups;
+
+        public TestResult(ITestContext context) {
+            testName = context.getName();
+
+            Set<ITestResult> failedConfigurations = context
+                    .getFailedConfigurations().getAllResults();
+            Set<ITestResult> failedTests = context.getFailedTests()
+                    .getAllResults();
+            Set<ITestResult> skippedConfigurations = context
+                    .getSkippedConfigurations().getAllResults();
+            Set<ITestResult> skippedTests = context.getSkippedTests()
+                    .getAllResults();
+            Set<ITestResult> passedTests = context.getPassedTests()
+                    .getAllResults();
+
+            failedConfigurationResults = groupResults(failedConfigurations);
+            failedTestResults = groupResults(failedTests);
+            skippedConfigurationResults = groupResults(skippedConfigurations);
+            skippedTestResults = groupResults(skippedTests);
+            passedTestResults = groupResults(passedTests);
+
+            failedTestCount = failedTests.size();
+            skippedTestCount = skippedTests.size();
+            passedTestCount = passedTests.size();
+
+            duration = context.getEndDate().getTime()
+                    - context.getStartDate().getTime();
+
+            includedGroups = formatGroups(context.getIncludedGroups());
+            excludedGroups = formatGroups(context.getExcludedGroups());
+        }
+
+        /**
+         * Groups test results by method and then by class.
+         */
+        protected List<ClassResult> groupResults(Set<ITestResult> results) {
+            List<ClassResult> classResults = Lists.newArrayList();
+            if (!results.isEmpty()) {
+                List<MethodResult> resultsPerClass = Lists.newArrayList();
+                List<ITestResult> resultsPerMethod = Lists.newArrayList();
+
+                List<ITestResult> resultsList = Lists.newArrayList(results);
+                Collections.sort(resultsList, RESULT_COMPARATOR);
+                Iterator<ITestResult> resultsIterator = resultsList.iterator();
+                assert resultsIterator.hasNext();
+
+                ITestResult result = resultsIterator.next();
+                resultsPerMethod.add(result);
+
+                String previousClassName = result.getTestClass().getName();
+                String previousMethodName = result.getMethod().getMethodName();
+                while (resultsIterator.hasNext()) {
+                    result = resultsIterator.next();
+
+                    String className = result.getTestClass().getName();
+                    if (!previousClassName.equals(className)) {
+                        // Different class implies different method
+                        assert !resultsPerMethod.isEmpty();
+                        resultsPerClass.add(new MethodResult(resultsPerMethod));
+                        resultsPerMethod = Lists.newArrayList();
+
+                        assert !resultsPerClass.isEmpty();
+                        classResults.add(new ClassResult(previousClassName,
+                                resultsPerClass));
+                        resultsPerClass = Lists.newArrayList();
+
+                        previousClassName = className;
+                        previousMethodName = result.getMethod().getMethodName();
+                    } else {
+                        String methodName = result.getMethod().getMethodName();
+                        if (!previousMethodName.equals(methodName)) {
+                            assert !resultsPerMethod.isEmpty();
+                            resultsPerClass.add(new MethodResult(resultsPerMethod));
+                            resultsPerMethod = Lists.newArrayList();
+
+                            previousMethodName = methodName;
+                        }
+                    }
+                    resultsPerMethod.add(result);
+                }
+                assert !resultsPerMethod.isEmpty();
+                resultsPerClass.add(new MethodResult(resultsPerMethod));
+                assert !resultsPerClass.isEmpty();
+                classResults.add(new ClassResult(previousClassName,
+                        resultsPerClass));
+            }
+            return classResults;
+        }
+
+        public String getTestName() {
+            return testName;
+        }
+
+        /**
+         * @return the results for failed configurations (possibly empty)
+         */
+        public List<ClassResult> getFailedConfigurationResults() {
+            return failedConfigurationResults;
+        }
+
+        /**
+         * @return the results for failed tests (possibly empty)
+         */
+        public List<ClassResult> getFailedTestResults() {
+            return failedTestResults;
+        }
+
+        /**
+         * @return the results for skipped configurations (possibly empty)
+         */
+        public List<ClassResult> getSkippedConfigurationResults() {
+            return skippedConfigurationResults;
+        }
+
+        /**
+         * @return the results for skipped tests (possibly empty)
+         */
+        public List<ClassResult> getSkippedTestResults() {
+            return skippedTestResults;
+        }
+
+        /**
+         * @return the results for passed tests (possibly empty)
+         */
+        public List<ClassResult> getPassedTestResults() {
+            return passedTestResults;
+        }
+
+        public int getFailedTestCount() {
+            return failedTestCount;
+        }
+
+        public int getSkippedTestCount() {
+            return skippedTestCount;
+        }
+
+        public int getPassedTestCount() {
+            return passedTestCount;
+        }
+
+        public long getDuration() {
+            return duration;
+        }
+
+        public String getIncludedGroups() {
+            return includedGroups;
+        }
+
+        public String getExcludedGroups() {
+            return excludedGroups;
+        }
+
+        /**
+         * Formats an array of groups for display.
+         */
+        protected String formatGroups(String[] groups) {
+            if (groups.length == 0) {
+                return "";
+            }
+
+            StringBuilder builder = new StringBuilder();
+            builder.append(groups[0]);
+            for (int i = 1; i < groups.length; i++) {
+                builder.append(", ").append(groups[i]);
+            }
+            return builder.toString();
+        }
+    }
+
+    /**
+     * Groups {@link MethodResult}s by class.
+     */
+    protected static class ClassResult {
+        private final String className;
+        private final List<MethodResult> methodResults;
+
+        /**
+         * @param className
+         *            the class name
+         * @param methodResults
+         *            the non-null, non-empty {@link MethodResult} list
+         */
+        public ClassResult(String className, List<MethodResult> methodResults) {
+            this.className = className;
+            this.methodResults = methodResults;
+        }
+
+        public String getClassName() {
+            return className;
+        }
+
+        /**
+         * @return the non-null, non-empty {@link MethodResult} list
+         */
+        public List<MethodResult> getMethodResults() {
+            return methodResults;
+        }
+    }
+
+    /**
+     * Groups test results by method.
+     */
+    protected static class MethodResult {
+        private final List<ITestResult> results;
+
+        /**
+         * @param results
+         *            the non-null, non-empty result list
+         */
+        public MethodResult(List<ITestResult> results) {
+            this.results = results;
+        }
+
+        /**
+         * @return the non-null, non-empty result list
+         */
+        public List<ITestResult> getResults() {
+            return results;
+        }
+    }
+}
diff --git a/src/main/java/org/testng/reporters/ExitCodeListener.java b/src/main/java/org/testng/reporters/ExitCodeListener.java
new file mode 100755
index 0000000..e05ba98
--- /dev/null
+++ b/src/main/java/org/testng/reporters/ExitCodeListener.java
@@ -0,0 +1,19 @@
+package org.testng.reporters;
+
+import org.testng.TestNG;
+
+/**
+ * A very simple <code>ITestListener</code> used by the TestNG runner to
+ * find out the exit code.
+ *
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ */
+public class ExitCodeListener extends TestNG.ExitCodeListener {
+  public ExitCodeListener() {
+    super();
+  }
+
+  public ExitCodeListener(TestNG runner) {
+    super(runner);
+  }
+}
diff --git a/src/main/java/org/testng/reporters/FailedReporter.java b/src/main/java/org/testng/reporters/FailedReporter.java
new file mode 100755
index 0000000..2165aa4
--- /dev/null
+++ b/src/main/java/org/testng/reporters/FailedReporter.java
@@ -0,0 +1,241 @@
+package org.testng.reporters;
+
+import org.testng.IReporter;
+import org.testng.ISuite;
+import org.testng.ISuiteResult;
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.collections.Sets;
+import org.testng.internal.MethodHelper;
+import org.testng.internal.Utils;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlInclude;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This reporter is responsible for creating testng-failed.xml
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ */
+public class FailedReporter extends TestListenerAdapter implements IReporter {
+  public static final String TESTNG_FAILED_XML = "testng-failed.xml";
+
+  private XmlSuite m_xmlSuite;
+
+  public FailedReporter() {
+  }
+
+  public FailedReporter(XmlSuite xmlSuite) {
+    m_xmlSuite = xmlSuite;
+  }
+
+  @Override
+  public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
+    for (ISuite suite : suites) {
+      generateFailureSuite(suite.getXmlSuite(), suite, outputDirectory);
+    }
+  }
+
+  protected void generateFailureSuite(XmlSuite xmlSuite, ISuite suite, String outputDir) {
+    XmlSuite failedSuite = (XmlSuite) xmlSuite.clone();
+    failedSuite.setName("Failed suite [" + xmlSuite.getName() + "]");
+    m_xmlSuite= failedSuite;
+
+    Map<String, XmlTest> xmlTests= Maps.newHashMap();
+    for(XmlTest xmlT: xmlSuite.getTests()) {
+      xmlTests.put(xmlT.getName(), xmlT);
+    }
+
+    Map<String, ISuiteResult> results = suite.getResults();
+
+    for(Map.Entry<String, ISuiteResult> entry : results.entrySet()) {
+      ISuiteResult suiteResult = entry.getValue();
+      ITestContext testContext = suiteResult.getTestContext();
+
+      generateXmlTest(suite,
+                      xmlTests.get(testContext.getName()),
+                      testContext,
+                      testContext.getFailedTests().getAllResults(),
+                      testContext.getSkippedTests().getAllResults());
+    }
+
+    if(null != failedSuite.getTests() && failedSuite.getTests().size() > 0) {
+      Utils.writeUtf8File(outputDir, TESTNG_FAILED_XML, failedSuite.toXml());
+      Utils.writeUtf8File(suite.getOutputDirectory(), TESTNG_FAILED_XML, failedSuite.toXml());
+    }
+  }
+
+  /**
+   * Do not rely on this method. The class is used as <code>IReporter</code>.
+   *
+   * @see org.testng.TestListenerAdapter#onFinish(org.testng.ITestContext)
+   * @deprecated this class is used now as IReporter
+   */
+  @Deprecated
+  @Override
+  public void onFinish(ITestContext context) {
+    // Delete the previous file
+//    File f = new File(context.getOutputDirectory(), getFileName(context));
+//    f.delete();
+
+    // Calculate the methods we need to rerun :  failed tests and
+    // their dependents
+//    List<ITestResult> failedTests = getFailedTests();
+//    List<ITestResult> skippedTests = getSkippedTests();
+  }
+
+  private void generateXmlTest(ISuite suite,
+                               XmlTest xmlTest,
+                               ITestContext context,
+                               Collection<ITestResult> failedTests,
+                               Collection<ITestResult> skippedTests) {
+    // Note:  we can have skipped tests and no failed tests
+    // if a method depends on nonexistent groups
+    if (skippedTests.size() > 0 || failedTests.size() > 0) {
+      Set<ITestNGMethod> methodsToReRun = Sets.newHashSet();
+
+      // Get the transitive closure of all the failed methods and the methods
+      // they depend on
+      Collection[] allTests = new Collection[] {
+          failedTests, skippedTests
+      };
+
+      for (Collection<ITestResult> tests : allTests) {
+        for (ITestResult failedTest : tests) {
+          ITestNGMethod current = failedTest.getMethod();
+          if (current.isTest()) {
+            methodsToReRun.add(current);
+            ITestNGMethod method = failedTest.getMethod();
+            // Don't count configuration methods
+            if (method.isTest()) {
+              List<ITestNGMethod> methodsDependedUpon =
+                  MethodHelper.getMethodsDependedUpon(method, context.getAllTestMethods());
+
+              for (ITestNGMethod m : methodsDependedUpon) {
+                if (m.isTest()) {
+                  methodsToReRun.add(m);
+                }
+              }
+            }
+          }
+        }
+      }
+
+      //
+      // Now we have all the right methods.  Go through the list of
+      // all the methods that were run and only pick those that are
+      // in the methodToReRun map.  Since the methods are already
+      // sorted, we don't need to sort them again.
+      //
+      List<ITestNGMethod> result = Lists.newArrayList();
+      for (ITestNGMethod m : context.getAllTestMethods()) {
+        if (methodsToReRun.contains(m)) {
+          result.add(m);
+        }
+      }
+
+      methodsToReRun.clear();
+      Collection<ITestNGMethod> invoked= suite.getInvokedMethods();
+      for(ITestNGMethod tm: invoked) {
+        if(!tm.isTest()) {
+          methodsToReRun.add(tm);
+        }
+      }
+
+      result.addAll(methodsToReRun);
+      createXmlTest(context, result, xmlTest);
+    }
+  }
+
+  /**
+   * Generate testng-failed.xml
+   */
+  private void createXmlTest(ITestContext context, List<ITestNGMethod> methods, XmlTest srcXmlTest) {
+    XmlTest xmlTest = new XmlTest(m_xmlSuite);
+    xmlTest.setName(context.getName() + "(failed)");
+    xmlTest.setBeanShellExpression(srcXmlTest.getExpression());
+    xmlTest.setIncludedGroups(srcXmlTest.getIncludedGroups());
+    xmlTest.setExcludedGroups(srcXmlTest.getExcludedGroups());
+    xmlTest.setParallel(srcXmlTest.getParallel());
+    xmlTest.setParameters(srcXmlTest.getLocalParameters());
+    xmlTest.setJUnit(srcXmlTest.isJUnit());
+    List<XmlClass> xmlClasses = createXmlClasses(methods, srcXmlTest);
+    xmlTest.setXmlClasses(xmlClasses);
+  }
+
+  /**
+   * @param methods The methods we want to represent
+   * @param srcXmlTest 
+   * @return A list of XmlClass objects (each representing a <class> tag) based
+   * on the parameter methods
+   */
+  private List<XmlClass> createXmlClasses(List<ITestNGMethod> methods, XmlTest srcXmlTest) {
+    List<XmlClass> result = Lists.newArrayList();
+    Map<Class, Set<ITestNGMethod>> methodsMap= Maps.newHashMap();
+
+    for (ITestNGMethod m : methods) {
+      Object[] instances= m.getInstances();
+      Class clazz= instances == null || instances.length == 0 || instances[0] == null
+          ? m.getRealClass()
+          : instances[0].getClass();
+      Set<ITestNGMethod> methodList= methodsMap.get(clazz);
+      if(null == methodList) {
+        methodList= new HashSet<>();
+        methodsMap.put(clazz, methodList);
+      }
+      methodList.add(m);
+    }
+
+    // Ideally, we should preserve each parameter in each class but putting them
+    // all in the same bag for now
+    Map<String, String> parameters = Maps.newHashMap();
+    for (XmlClass c : srcXmlTest.getClasses()) {
+      parameters.putAll(c.getLocalParameters());
+    }
+
+    int index = 0;
+    for(Map.Entry<Class, Set<ITestNGMethod>> entry: methodsMap.entrySet()) {
+      Class clazz= entry.getKey();
+      Set<ITestNGMethod> methodList= entry.getValue();
+      // @author Borojevic
+      // Need to check all the methods, not just @Test ones.
+      XmlClass xmlClass= new XmlClass(clazz.getName(), index++, false /* don't load classes */);
+      List<XmlInclude> methodNames= Lists.newArrayList(methodList.size());
+      int ind = 0;
+      for(ITestNGMethod m: methodList) {
+        methodNames.add(new XmlInclude(m.getMethod().getName(), m.getFailedInvocationNumbers(),
+            ind++));
+      }
+      xmlClass.setIncludedMethods(methodNames);
+      xmlClass.setParameters(parameters);
+      result.add(xmlClass);
+
+    }
+
+    return result;
+  }
+
+  /**
+   * TODO:  we might want to make that more flexible in the future, but for
+   * now, hardcode the file name
+   */
+  private String getFileName(ITestContext context) {
+    return TESTNG_FAILED_XML;
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[FailedReporter] " + s);
+  }
+}
diff --git a/src/main/java/org/testng/reporters/FileStringBuffer.java b/src/main/java/org/testng/reporters/FileStringBuffer.java
new file mode 100644
index 0000000..6c980ca
--- /dev/null
+++ b/src/main/java/org/testng/reporters/FileStringBuffer.java
@@ -0,0 +1,185 @@
+package org.testng.reporters;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.Writer;
+import java.util.Random;
+
+/**
+ * A string buffer that flushes its content to a temporary file whenever the internal
+ * string buffer becomes larger than MAX. If the buffer never reaches that size, no file
+ * is ever created and everything happens in memory, so the overhead compared to
+ * StringBuffer/StringBuilder is minimal.
+ *
+ * Note: calling toString() will force the entire string to be loaded in memory, use
+ * toWriter() if you need to avoid this.
+ *
+ * This class is not multi thread safe.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ *
+ * @since Nov 9, 2012
+ */
+public class FileStringBuffer implements IBuffer {
+  private static int MAX = 100000;
+  private static final boolean VERBOSE = System.getProperty("fileStringBuffer") != null;
+
+  private File m_file;
+  private StringBuilder m_sb = new StringBuilder();
+  private final int m_maxCharacters;
+
+  public FileStringBuffer() {
+    this(MAX);
+  }
+
+  public FileStringBuffer(int maxCharacters) {
+    m_maxCharacters = maxCharacters;
+  }
+
+  @Override
+  public FileStringBuffer append(CharSequence s) {
+    if (s == null) {
+      throw new IllegalArgumentException("CharSequence (Argument 0 of FileStringBuffer#append) should not be null");
+    }
+//    m_sb.append(s);
+    if (m_sb.length() > m_maxCharacters) {
+      flushToFile();
+    }
+    if (s.length() < MAX) {
+      // Small string, add it to our internal buffer
+      m_sb.append(s);
+    } else {
+      // Big string, add it to the temporary file directly
+      flushToFile();
+      try {
+        copy(new StringReader(s.toString()), new FileWriter(m_file, true /* append */));
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+    }
+    return this;
+  }
+
+  @Override
+  public void toWriter(Writer fw) {
+    if (fw == null) {
+      throw new IllegalArgumentException("Writer (Argument 0 of FileStringBuffer#toWriter) should not be null");
+    }
+    try {
+      BufferedWriter bw = new BufferedWriter(fw);
+      if (m_file == null) {
+        bw.write(m_sb.toString());
+        bw.close();
+      } else {
+        flushToFile();
+        copy(new FileReader(m_file), bw);
+      }
+    } catch(IOException ex) {
+      ex.printStackTrace();
+    }
+  }
+
+  private static void copy(Reader input, Writer output)
+      throws IOException {
+    char[] buf = new char[MAX];
+    while (true) {
+      int length = input.read(buf);
+      if (length < 0) break;
+      output.write(buf, 0, length);
+    }
+
+    try {
+      input.close();
+    } catch (IOException ignore) {
+    }
+    try {
+      output.close();
+    } catch (IOException ignore) {
+    }
+  }
+
+  private void flushToFile() {
+    if (m_sb.length() == 0) return;
+
+    if (m_file == null) {
+      try {
+        m_file = File.createTempFile("testng", "fileStringBuffer");
+        m_file.deleteOnExit();
+        p("Created temp file " + m_file);
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+    }
+
+    p("Size " + m_sb.length() + ", flushing to " + m_file);
+    try (FileWriter fw = new FileWriter(m_file, true /* append */)) {
+      fw.append(m_sb);
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+    m_sb = new StringBuilder();
+  }
+
+  private static void p(String s) {
+    if (VERBOSE) {
+      System.out.println("[FileStringBuffer] " + s);
+    }
+  }
+
+  @Override
+  public String toString() {
+    String result = null;
+    if (m_file != null) {
+      flushToFile();
+      try {
+        result = Files.readFile(m_file);
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+    } else {
+      result = m_sb.toString();
+    }
+    return result;
+  }
+
+  private static void save(File expected, String s) throws IOException {
+    expected.delete();
+    try (FileWriter expectedWriter = new FileWriter(expected)) {
+      expectedWriter.append(s);
+    }
+  }
+
+  public static void main(String[] args) throws IOException {
+    String s = "abcdefghijklmnopqrstuvwxyz";
+    FileStringBuffer fsb = new FileStringBuffer(10);
+    StringBuilder control = new StringBuilder();
+    Random r = new Random();
+    for (int i = 0; i < 1000; i++) {
+      int start = Math.abs(r.nextInt() % 26);
+      int length = Math.abs(r.nextInt() % (26 - start));
+      String fragment = s.substring(start, start + length);
+      p("... Appending " + fragment);
+      fsb.append(fragment);
+      control.append(fragment);
+    }
+
+    File expected = new File("/tmp/expected");
+    expected.delete();
+    FileWriter expectedWriter = new FileWriter(expected);
+    expectedWriter.append(control);
+    expectedWriter.close();
+
+    File actual = new File("/tmp/actual");
+    actual.delete();
+    FileWriter actualWriter = new FileWriter(actual);
+    fsb.toWriter(actualWriter);
+    actualWriter.close();
+//    Assert.assertEquals(fsb.toString(), control.toString());
+  }
+
+}
diff --git a/src/main/java/org/testng/reporters/Files.java b/src/main/java/org/testng/reporters/Files.java
new file mode 100644
index 0000000..dc66104
--- /dev/null
+++ b/src/main/java/org/testng/reporters/Files.java
@@ -0,0 +1,80 @@
+package org.testng.reporters;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+
+public class Files {
+
+  public static String readFile(File f) throws IOException {
+    try (InputStream is = new FileInputStream(f)) {
+      return readFile(is);
+    }
+  }
+
+  public static String readFile(InputStream is) throws IOException {
+    StringBuilder result = new StringBuilder();
+    BufferedReader br = new BufferedReader(new InputStreamReader(is));
+    String line = br.readLine();
+    while (line != null) {
+      result.append(line + "\n");
+      line = br.readLine();
+    }
+    return result.toString();
+  }
+
+  public static void writeFile(String string, File f) throws IOException {
+    f.getParentFile().mkdirs();
+    FileWriter fw = new FileWriter(f);
+    BufferedWriter bw = new BufferedWriter(fw);
+    bw.write(string);
+    bw.close();
+    fw.close();
+  }
+
+  public static void copyFile(InputStream from, File to) throws IOException {
+    if (! to.getParentFile().exists()) {
+      to.getParentFile().mkdirs();
+    }
+
+    try (OutputStream os = new FileOutputStream(to)) {
+      byte[] buffer = new byte[65536];
+      int count = from.read(buffer);
+      while (count > 0) {
+        os.write(buffer, 0, count);
+        count = from.read(buffer);
+      }
+    }
+  }
+
+  public static String streamToString(InputStream is) throws IOException {
+    if (is != null) {
+      Writer writer = new StringWriter();
+
+      char[] buffer = new char[1024];
+      try {
+        Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
+        int n;
+        while ((n = reader.read(buffer)) != -1) {
+          writer.write(buffer, 0, n);
+        }
+      } finally {
+        is.close();
+      }
+      return writer.toString();
+    } else {
+      return "";
+    }
+  }
+}
diff --git a/src/main/java/org/testng/reporters/HtmlHelper.java b/src/main/java/org/testng/reporters/HtmlHelper.java
new file mode 100755
index 0000000..332206f
--- /dev/null
+++ b/src/main/java/org/testng/reporters/HtmlHelper.java
@@ -0,0 +1,29 @@
+package org.testng.reporters;
+
+import org.testng.internal.Utils;
+
+import java.io.File;
+import java.io.IOException;
+
+public class HtmlHelper {
+  private static final String CSS_FILE_NAME = "testng.css";
+  private static final String MY_CSS_FILE_NAME = "my-testng.css";
+
+  static public String getCssString() {
+    return getCssString("..");
+  }
+
+  static public String getCssString(String directory) {
+    return
+      "<link href=\"" + directory + "/" + CSS_FILE_NAME + "\" rel=\"stylesheet\" type=\"text/css\" />\n" +
+      "<link href=\"" + directory + "/"+ MY_CSS_FILE_NAME + "\" rel=\"stylesheet\" type=\"text/css\" />\n";
+  }
+
+  public static File generateStylesheet(String outputDirectory) throws IOException {
+    File stylesheetFile = new File(outputDirectory, CSS_FILE_NAME);
+    if (!stylesheetFile.exists()) {
+      Utils.writeResourceToFile(stylesheetFile, CSS_FILE_NAME, TestHTMLReporter.class);
+    }
+    return stylesheetFile;
+  }
+}
diff --git a/src/main/java/org/testng/reporters/IBuffer.java b/src/main/java/org/testng/reporters/IBuffer.java
new file mode 100644
index 0000000..f0c891c
--- /dev/null
+++ b/src/main/java/org/testng/reporters/IBuffer.java
@@ -0,0 +1,8 @@
+package org.testng.reporters;
+
+import java.io.Writer;
+
+public interface IBuffer {
+  IBuffer append(CharSequence string);
+  void toWriter(Writer fw);
+}
diff --git a/src/main/java/org/testng/reporters/JUnitReportReporter.java b/src/main/java/org/testng/reporters/JUnitReportReporter.java
new file mode 100644
index 0000000..6fb4d9c
--- /dev/null
+++ b/src/main/java/org/testng/reporters/JUnitReportReporter.java
@@ -0,0 +1,267 @@
+package org.testng.reporters;
+
+import org.testng.IReporter;
+import org.testng.ISuite;
+import org.testng.ISuiteResult;
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+import org.testng.collections.ListMultiMap;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.collections.Sets;
+import org.testng.internal.Utils;
+import org.testng.xml.XmlSuite;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+public class JUnitReportReporter implements IReporter {
+
+  @Override
+  public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites,
+      String defaultOutputDirectory) {
+
+    Map<Class<?>, Set<ITestResult>> results = Maps.newHashMap();
+    Map<Class<?>, Set<ITestResult>> failedConfigurations = Maps.newHashMap();
+    ListMultiMap<Object, ITestResult> befores = Maps.newListMultiMap();
+    ListMultiMap<Object, ITestResult> afters = Maps.newListMultiMap();
+    for (ISuite suite : suites) {
+      Map<String, ISuiteResult> suiteResults = suite.getResults();
+      for (ISuiteResult sr : suiteResults.values()) {
+        ITestContext tc = sr.getTestContext();
+        addResults(tc.getPassedTests().getAllResults(), results);
+        addResults(tc.getFailedTests().getAllResults(), results);
+        addResults(tc.getSkippedTests().getAllResults(), results);
+        addResults(tc.getFailedConfigurations().getAllResults(), failedConfigurations);
+        for (ITestResult tr : tc.getPassedConfigurations().getAllResults()) {
+          if (tr.getMethod().isBeforeMethodConfiguration()) {
+            befores.put(tr.getInstance(), tr);
+          }
+          if (tr.getMethod().isAfterMethodConfiguration()) {
+            afters.put(tr.getInstance(), tr);
+          }
+        }
+      }
+    }
+
+    // A list of iterators for all the passed configuration, explanation below
+//    ListMultiMap<Class<?>, ITestResult> beforeConfigurations = Maps.newListMultiMap();
+//    ListMultiMap<Class<?>, ITestResult> afterConfigurations = Maps.newListMultiMap();
+//    for (Map.Entry<Class<?>, Set<ITestResult>> es : passedConfigurations.entrySet()) {
+//      for (ITestResult tr : es.getValue()) {
+//        ITestNGMethod method = tr.getMethod();
+//        if (method.isBeforeMethodConfiguration()) {
+//          beforeConfigurations.put(method.getRealClass(), tr);
+//        }
+//        if (method.isAfterMethodConfiguration()) {
+//          afterConfigurations.put(method.getRealClass(), tr);
+//        }
+//      }
+//    }
+//    Map<Object, Iterator<ITestResult>> befores = Maps.newHashMap();
+//    for (Map.Entry<Class<?>, List<ITestResult>> es : beforeConfigurations.getEntrySet()) {
+//      List<ITestResult> tr = es.getValue();
+//      for (ITestResult itr : es.getValue()) {
+//      }
+//    }
+//    Map<Class<?>, Iterator<ITestResult>> afters = Maps.newHashMap();
+//    for (Map.Entry<Class<?>, List<ITestResult>> es : afterConfigurations.getEntrySet()) {
+//      afters.put(es.getKey(), es.getValue().iterator());
+//    }
+
+    for (Map.Entry<Class<?>, Set<ITestResult>> entry : results.entrySet()) {
+      Class<?> cls = entry.getKey();
+      Properties p1 = new Properties();
+      p1.setProperty("name", cls.getName());
+      Date timeStamp = Calendar.getInstance().getTime();
+      p1.setProperty(XMLConstants.ATTR_TIMESTAMP, timeStamp.toGMTString());
+
+      List<TestTag> testCases = Lists.newArrayList();
+      int failures = 0;
+      int errors = 0;
+      int testCount = 0;
+      float totalTime = 0;
+
+      for (ITestResult tr: entry.getValue()) {
+        TestTag testTag = new TestTag();
+
+        boolean isSuccess = tr.getStatus() == ITestResult.SUCCESS;
+        if (! isSuccess) {
+          if (tr.getThrowable() instanceof AssertionError) {
+            failures++;
+          } else {
+            errors++;
+          }
+        }
+
+        Properties p2 = new Properties();
+        p2.setProperty("classname", cls.getName());
+        p2.setProperty("name", getTestName(tr));
+        long time = tr.getEndMillis() - tr.getStartMillis();
+
+        time += getNextConfiguration(befores, tr);
+        time += getNextConfiguration(afters, tr);
+
+        p2.setProperty("time", "" + formatTime(time));
+        Throwable t = getThrowable(tr, failedConfigurations);
+        if (! isSuccess && t != null) {
+          StringWriter sw = new StringWriter();
+          PrintWriter pw = new PrintWriter(sw);
+          t.printStackTrace(pw);
+          testTag.message = t.getMessage();
+          testTag.type = t.getClass().getName();
+          testTag.stackTrace = sw.toString();
+          testTag.errorTag = tr.getThrowable() instanceof AssertionError ? "failure" : "error";
+        }
+        totalTime += time;
+        testCount++;
+        testTag.properties = p2;
+        testCases.add(testTag);
+      }
+
+      p1.setProperty("failures", "" + failures);
+      p1.setProperty("errors", "" + errors);
+      p1.setProperty("name", cls.getName());
+      p1.setProperty("tests", "" + testCount);
+      p1.setProperty("time", "" + formatTime(totalTime));
+      try {
+        p1.setProperty(XMLConstants.ATTR_HOSTNAME, InetAddress.getLocalHost().getHostName());
+      } catch (UnknownHostException e) {
+        // ignore
+      }
+
+      //
+      // Now that we have all the information we need, generate the file
+      //
+      XMLStringBuffer xsb = new XMLStringBuffer();
+      xsb.addComment("Generated by " + getClass().getName());
+
+      xsb.push("testsuite", p1);
+      for (TestTag testTag : testCases) {
+        if (testTag.stackTrace == null) {
+          xsb.addEmptyElement("testcase", testTag.properties);
+        }
+        else {
+          xsb.push("testcase", testTag.properties);
+
+          Properties p = new Properties();
+          if (testTag.message != null) {
+            p.setProperty("message", testTag.message);
+          }
+          p.setProperty("type", testTag.type);
+          xsb.push(testTag.errorTag, p);
+          xsb.addCDATA(testTag.stackTrace);
+          xsb.pop(testTag.errorTag);
+
+          xsb.pop("testcase");
+        }
+      }
+      xsb.pop("testsuite");
+
+      String outputDirectory = defaultOutputDirectory + File.separator + "junitreports";
+      Utils.writeUtf8File(outputDirectory, getFileName(cls), xsb.toXML());
+    }
+
+//    System.out.println(xsb.toXML());
+//    System.out.println("");
+
+  }
+
+  /**
+   * Add the time of the configuration method to this test method.
+   *
+   * The only problem with this method is that the timing of a test method
+   * might not be added to the time of the same configuration method that ran before
+   * it but since they should all be equivalent, this should never be an issue.
+   */
+  private long getNextConfiguration(ListMultiMap<Object, ITestResult> configurations,
+      ITestResult tr)
+  {
+    long result = 0;
+
+    List<ITestResult> confResults = configurations.get(tr.getInstance());
+    Map<ITestNGMethod, ITestResult> seen = Maps.newHashMap();
+    if (confResults != null) {
+      for (ITestResult r : confResults) {
+        if (! seen.containsKey(r.getMethod())) {
+          result += r.getEndMillis() - r.getStartMillis();
+          seen.put(r.getMethod(), r);
+        }
+      }
+      confResults.removeAll(seen.values());
+    }
+
+    return result;
+  }
+
+  protected String getFileName(Class cls) {
+    return "TEST-" + cls.getName() + ".xml";
+  }
+
+  protected String getTestName(ITestResult tr) {
+    return tr.getMethod().getMethodName();
+  }
+
+  private String formatTime(float time) {
+    DecimalFormatSymbols symbols = new DecimalFormatSymbols();
+    // JUnitReports wants points here, regardless of the locale
+    symbols.setDecimalSeparator('.');
+    DecimalFormat format = new DecimalFormat("#.###", symbols);
+    format.setMinimumFractionDigits(3);
+    return format.format(time / 1000.0f);
+  }
+
+  private Throwable getThrowable(ITestResult tr,
+      Map<Class<?>, Set<ITestResult>> failedConfigurations) {
+    Throwable result = tr.getThrowable();
+    if (result == null && tr.getStatus() == ITestResult.SKIP) {
+      // Attempt to grab the stack trace from the configuration failure
+      for (Set<ITestResult> failures : failedConfigurations.values()) {
+        for (ITestResult failure : failures) {
+          // Naive implementation for now, eventually, we need to try to find
+          // out if it's this failure that caused the skip since (maybe by
+          // seeing if the class of the configuration method is assignable to
+          // the class of the test method, although that's not 100% fool proof
+          if (failure.getThrowable() != null) {
+            return failure.getThrowable();
+          }
+        }
+      }
+    }
+
+    return result;
+  }
+
+  static class TestTag {
+    public Properties properties;
+    public String message;
+    public String type;
+    public String stackTrace;
+    public String errorTag;
+  }
+
+  private void addResults(Set<ITestResult> allResults, Map<Class<?>, Set<ITestResult>> out) {
+    for (ITestResult tr : allResults) {
+      Class<?> cls = tr.getMethod().getTestClass().getRealClass();
+      Set<ITestResult> l = out.get(cls);
+      if (l == null) {
+        l = Sets.newHashSet();
+        out.put(cls, l);
+      }
+      l.add(tr);
+    }
+  }
+
+}
diff --git a/src/main/java/org/testng/reporters/JUnitXMLReporter.java b/src/main/java/org/testng/reporters/JUnitXMLReporter.java
new file mode 100755
index 0000000..4d97bed
--- /dev/null
+++ b/src/main/java/org/testng/reporters/JUnitXMLReporter.java
@@ -0,0 +1,317 @@
+package org.testng.reporters;
+
+
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.collections.Sets;
+import org.testng.internal.IResultListener2;
+import org.testng.internal.Utils;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * A JUnit XML report generator (replacing the original JUnitXMLReporter that was
+ * based on XML APIs).
+ *
+ * @author <a href='mailto:the[dot]mindstorm[at]gmail[dot]com'>Alex Popescu</a>
+ */
+public class JUnitXMLReporter implements IResultListener2 {
+  private static final Pattern ENTITY= Pattern.compile("&[a-zA-Z]+;.*");
+  private static final Pattern LESS= Pattern.compile("<");
+  private static final Pattern GREATER= Pattern.compile(">");
+  private static final Pattern SINGLE_QUOTE = Pattern.compile("'");
+  private static final Pattern QUOTE = Pattern.compile("\"");
+  private static final Map<String, Pattern> ATTR_ESCAPES= Maps.newHashMap();
+
+  static {
+    ATTR_ESCAPES.put("&lt;", LESS);
+    ATTR_ESCAPES.put("&gt;", GREATER);
+    ATTR_ESCAPES.put("&apos;", SINGLE_QUOTE);
+    ATTR_ESCAPES.put("&quot;", QUOTE);
+  }
+
+
+  /**
+   * keep lists of all the results
+   */
+  private int m_numPassed= 0;
+  private int m_numFailed= 0;
+  private int m_numSkipped= 0;
+  private int m_numFailedButIgnored= 0;
+  private List<ITestResult> m_allTests =
+      Collections.synchronizedList(Lists.<ITestResult>newArrayList());
+  private List<ITestResult> m_configIssues =
+      Collections.synchronizedList(Lists.<ITestResult>newArrayList());
+  private Map<String, String> m_fileNameMap = Maps.newHashMap();
+  private int m_fileNameIncrementer = 0;
+
+  @Override
+  public void onTestStart(ITestResult result) {
+  }
+
+  @Override
+  public void beforeConfiguration(ITestResult tr) {
+  }
+
+  /**
+   * Invoked each time a test succeeds.
+   */
+  @Override
+  public void onTestSuccess(ITestResult tr) {
+    m_allTests.add(tr);
+    m_numPassed++;
+  }
+
+  @Override
+  public void onTestFailedButWithinSuccessPercentage(ITestResult tr) {
+    m_allTests.add(tr);
+    m_numFailedButIgnored++;
+  }
+
+  /**
+   * Invoked each time a test fails.
+   */
+  @Override
+  public void onTestFailure(ITestResult tr) {
+    m_allTests.add(tr);
+    m_numFailed++;
+  }
+
+  /**
+   * Invoked each time a test is skipped.
+   */
+  @Override
+  public void onTestSkipped(ITestResult tr) {
+    m_allTests.add(tr);
+    m_numSkipped++;
+  }
+
+  /**
+   * Invoked after the test class is instantiated and before
+   * any configuration method is called.
+   *
+   */
+  @Override
+  public void onStart(ITestContext context) {
+
+  }
+
+  /**
+   * Invoked after all the tests have run and all their
+   * Configuration methods have been called.
+   *
+   */
+  @Override
+  public void onFinish(ITestContext context) {
+	generateReport(context);
+    resetAll();
+  }
+
+  /**
+   * @see org.testng.IConfigurationListener#onConfigurationFailure(org.testng.ITestResult)
+   */
+  @Override
+  public void onConfigurationFailure(ITestResult itr) {
+    m_configIssues.add(itr);
+  }
+
+  /**
+   * @see org.testng.IConfigurationListener#onConfigurationSkip(org.testng.ITestResult)
+   */
+  @Override
+  public void onConfigurationSkip(ITestResult itr) {
+    m_configIssues.add(itr);
+  }
+
+  /**
+   * @see org.testng.IConfigurationListener#onConfigurationSuccess(org.testng.ITestResult)
+   */
+  @Override
+  public void onConfigurationSuccess(ITestResult itr) {
+  }
+
+  /**
+   * generate the XML report given what we know from all the test results
+   */
+  protected void generateReport(ITestContext context) {
+
+      XMLStringBuffer document= new XMLStringBuffer();
+      document.addComment("Generated by " + getClass().getName());
+
+      Properties attrs= new Properties();
+      attrs.setProperty(XMLConstants.ATTR_ERRORS, "0");
+      attrs.setProperty(XMLConstants.ATTR_FAILURES, "" + m_numFailed);
+      try {
+        attrs.setProperty(XMLConstants.ATTR_HOSTNAME, InetAddress.getLocalHost().getHostName());
+      } catch (UnknownHostException e) {
+        // ignore
+      }
+      Set<String> packages = getPackages(context);
+      if (packages.size() > 0) {
+        attrs.setProperty(XMLConstants.ATTR_NAME, context.getCurrentXmlTest().getName());
+//        attrs.setProperty(XMLConstants.ATTR_PACKAGE, packages.iterator().next());
+      }
+
+      attrs.setProperty(XMLConstants.ATTR_TESTS, "" + m_allTests.size());
+      attrs.setProperty(XMLConstants.ATTR_TIME, ""
+          + ((context.getEndDate().getTime() - context.getStartDate().getTime()) / 1000.0));
+
+      Date timeStamp = Calendar.getInstance().getTime();
+      attrs.setProperty(XMLConstants.ATTR_TIMESTAMP, timeStamp.toGMTString());
+
+      document.push(XMLConstants.TESTSUITE, attrs);
+//      document.addEmptyElement(XMLConstants.PROPERTIES);
+
+      createElementFromTestResults(document, m_configIssues);
+      createElementFromTestResults(document, m_allTests);
+
+      document.pop();
+      Utils.writeUtf8File(context.getOutputDirectory(),generateFileName(context) + ".xml", document.toXML());
+  }
+
+  private void createElementFromTestResults(XMLStringBuffer document, List<ITestResult> results) {
+    synchronized(results) {
+      for(ITestResult tr : results) {
+        createElement(document, tr);
+      }
+    }
+  }
+
+  private Set<String> getPackages(ITestContext context) {
+    Set<String> result = Sets.newHashSet();
+    for (ITestNGMethod m : context.getAllTestMethods()) {
+      Package pkg = m.getMethod().getDeclaringClass().getPackage();
+      if (pkg != null) {
+        result.add(pkg.getName());
+      }
+    }
+    return result;
+  }
+
+  private void createElement(XMLStringBuffer doc, ITestResult tr) {
+    Properties attrs= new Properties();
+    long elapsedTimeMillis= tr.getEndMillis() - tr.getStartMillis();
+    String name= tr.getMethod().isTest() ? tr.getName() : Utils.detailedMethodName(tr.getMethod(), false);
+    attrs.setProperty(XMLConstants.ATTR_NAME, name);
+    attrs.setProperty(XMLConstants.ATTR_CLASSNAME, tr.getTestClass().getRealClass().getName());
+    attrs.setProperty(XMLConstants.ATTR_TIME, "" + (((double) elapsedTimeMillis) / 1000));
+
+    if((ITestResult.FAILURE == tr.getStatus()) || (ITestResult.SKIP == tr.getStatus())) {
+      doc.push(XMLConstants.TESTCASE, attrs);
+
+      if(ITestResult.FAILURE == tr.getStatus()) {
+        createFailureElement(doc, tr);
+      }
+      else if(ITestResult.SKIP == tr.getStatus()) {
+        createSkipElement(doc, tr);
+      }
+
+      doc.pop();
+    }
+    else {
+      doc.addEmptyElement(XMLConstants.TESTCASE, attrs);
+    }
+  }
+
+  private void createFailureElement(XMLStringBuffer doc, ITestResult tr) {
+    Properties attrs= new Properties();
+    Throwable t= tr.getThrowable();
+    if(t != null) {
+      attrs.setProperty(XMLConstants.ATTR_TYPE, t.getClass().getName());
+      String message= t.getMessage();
+      if((message != null) && (message.length() > 0)) {
+        attrs.setProperty(XMLConstants.ATTR_MESSAGE, encodeAttr(message)); // ENCODE
+      }
+      doc.push(XMLConstants.FAILURE, attrs);
+      doc.addCDATA(Utils.stackTrace(t, false)[0]);
+      doc.pop();
+    }
+    else {
+      doc.addEmptyElement(XMLConstants.FAILURE); // THIS IS AN ERROR
+    }
+  }
+
+  private void createSkipElement(XMLStringBuffer doc, ITestResult tr) {
+    doc.addEmptyElement("skipped");
+  }
+
+  private String encodeAttr(String attr) {
+    String result= replaceAmpersand(attr, ENTITY);
+    for(Map.Entry<String, Pattern> e: ATTR_ESCAPES.entrySet()) {
+      result= e.getValue().matcher(result).replaceAll(e.getKey());
+    }
+
+    return result;
+  }
+
+  private String replaceAmpersand(String str, Pattern pattern) {
+    int start = 0;
+    int idx = str.indexOf('&', start);
+    if(idx == -1) {
+      return str;
+    }
+    StringBuffer result= new StringBuffer();
+    while(idx != -1) {
+      result.append(str.substring(start, idx));
+      if(pattern.matcher(str.substring(idx)).matches()) {
+        // do nothing it is an entity;
+        result.append("&");
+      }
+      else {
+        result.append("&amp;");
+      }
+      start= idx + 1;
+      idx= str.indexOf('&', start);
+    }
+    result.append(str.substring(start));
+
+    return result.toString();
+  }
+
+
+  /**
+	 * Reset all member variables for next test.
+	 * */
+	private void resetAll() {
+		m_allTests = Collections.synchronizedList(Lists.<ITestResult>newArrayList());
+		m_configIssues = Collections.synchronizedList(Lists.<ITestResult>newArrayList());
+		m_numFailed = 0;
+		m_numFailedButIgnored = 0;
+		m_numPassed = 0;
+		m_numSkipped = 0;
+	}
+
+	/**
+	 * @author Borojevic Created this method to guarantee unique file names for
+	 *         reports.<br>
+	 *         Also, this will guarantee that the old reports are overwritten
+	 *         when tests are run again.
+	 * @param context
+	 *            test context
+	 * @return unique name for the file associated with this test context.
+	 * */
+	private String generateFileName(ITestContext context) {
+		String fileName = null;
+		String keyToSearch = context.getSuite().getName() + context.getName();
+		if (m_fileNameMap.get(keyToSearch) == null) {
+			fileName = context.getName();
+		} else {
+			fileName = context.getName() + m_fileNameIncrementer++;
+		}
+
+		m_fileNameMap.put(keyToSearch, fileName);
+		return fileName;
+	}
+}
diff --git a/src/main/java/org/testng/reporters/JqReporter.java b/src/main/java/org/testng/reporters/JqReporter.java
new file mode 100644
index 0000000..6e0cb15
--- /dev/null
+++ b/src/main/java/org/testng/reporters/JqReporter.java
@@ -0,0 +1,235 @@
+package org.testng.reporters;
+
+import org.testng.IReporter;
+import org.testng.IResultMap;
+import org.testng.ISuite;
+import org.testng.ISuiteResult;
+import org.testng.ITestContext;
+import org.testng.ITestResult;
+import org.testng.collections.ListMultiMap;
+import org.testng.collections.Maps;
+import org.testng.internal.Utils;
+import org.testng.xml.XmlSuite;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+public class JqReporter implements IReporter {
+  private static final String C = "class";
+  private static final String D = "div";
+  private static final String S = "span";
+
+  private int m_testCount = 0;
+  private String m_outputDirectory;
+  private Map<String, String> m_testMap = Maps.newHashMap();
+
+  @Override
+  public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites,
+      String outputDirectory) {
+    m_outputDirectory = "/Users/cedric/java/misc/jquery";
+
+    XMLStringBuffer xsb = new XMLStringBuffer("  ");
+    xsb.push(D, "id", "suites");
+    generateSuites(xmlSuites, suites, xsb);
+    xsb.pop(D);
+
+    String all;
+    try {
+      all = Files.readFile(new File("/Users/cedric/java/misc/jquery/head"));
+      Utils.writeFile(m_outputDirectory, "index2.html", all + xsb.toXML());
+    } catch (IOException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }
+  }
+
+  private XMLStringBuffer generateSuites(List<XmlSuite> xmlSuites,
+      List<ISuite> suites, XMLStringBuffer main) {
+     for (ISuite suite : suites) {
+      if (suite.getResults().size() == 0) {
+        continue;
+      }
+
+      XMLStringBuffer xsb = new XMLStringBuffer(main.getCurrentIndent());
+      XMLStringBuffer header = new XMLStringBuffer(main.getCurrentIndent());
+
+      xsb.push(D, C, "suite-content");
+      Map<String, ISuiteResult> results = suite.getResults();
+      XMLStringBuffer xs1 = new XMLStringBuffer(xsb.getCurrentIndent());
+      XMLStringBuffer xs2 = new XMLStringBuffer(xsb.getCurrentIndent());
+      XMLStringBuffer xs3 = new XMLStringBuffer(xsb.getCurrentIndent());
+      int failed = 0;
+      int skipped = 0;
+      int passed = 0;
+      for (ISuiteResult result : results.values()) {
+        ITestContext context = result.getTestContext();
+        failed += context.getFailedTests().size();
+        generateTests("failed", context.getFailedTests(), context, xs1);
+        skipped += context.getSkippedTests().size();
+        generateTests("skipped", context.getSkippedTests(), context, xs2);
+        passed += context.getPassedTests().size();
+        generateTests("passed", context.getPassedTests(), context, xs3);
+      }
+      xsb.addOptional(D, "Failed" + " tests", C, "result-banner " + "failed");
+      xsb.addString(xs1.toXML());
+      xsb.addOptional(D, "Skipped" + " tests", C, "result-banner " + "skipped");
+      xsb.addString(xs2.toXML());
+      xsb.addOptional(D, "Passed" + " tests", C, "result-banner " + "passed");
+      xsb.addString(xs3.toXML());
+
+
+      header.push(D, C, "suite");
+      header.push(D, C, "suite-header");
+      header.addOptional(S, suite.getName(), C, "suite-name");
+      header.push(D, C, "stats");
+      int total = failed + skipped + passed;
+      String stats = String.format("%s, %d failed, %d skipped, %d passed",
+          pluralize(total, "method"), failed, skipped, passed);
+      header.push("ul");
+
+      // Method stats
+      header.push("li");
+      header.addOptional(S, stats, C, "method-stats");
+      header.pop("li");
+
+      // Tests
+      header.push("li");
+      header.addOptional(S, String.format("%s ", pluralize(results.values().size(), "test"),
+          C, "test-stats"));
+      header.pop("li");
+
+      // List of tests
+      header.push("ul");
+      for (ISuiteResult tr : results.values()) {
+        String testName = tr.getTestContext().getName();
+        header.push("li");
+        header.addOptional("a", testName, "href", "#" + m_testMap.get(testName));
+        header.pop("li");
+      }
+      header.pop("ul");
+
+      header.pop("ul");
+      header.pop(D);
+
+      header.pop(D);
+
+      main.addString(header.toXML());
+      main.addString(xsb.toXML());
+    }
+
+    return main;
+  }
+
+  private String capitalize(String s) {
+    return Character.toUpperCase(s.charAt(0)) + s.substring(1);
+  }
+
+  private void generateTests(String tagClass, IResultMap tests, ITestContext context,
+      XMLStringBuffer xsb) {
+
+    if (tests.getAllMethods().isEmpty()) return;
+
+    xsb.push(D, C, "test" + (tagClass != null ? " " + tagClass : ""));
+    ListMultiMap<Class<?>, ITestResult> map = Maps.newListMultiMap();
+    for (ITestResult m : tests.getAllResults()) {
+      map.put(m.getTestClass().getRealClass(), m);
+    }
+
+    String testName = "test-" + (m_testCount++);
+    m_testMap.put(context.getName(), testName);
+    xsb.push(D, C, "test-name");
+    xsb.push("a", "name", testName);
+    xsb.addString(context.getName());
+    xsb.pop("a");
+
+    // Expand icon
+    xsb.push("a", C, "expand", "href", "#");
+    xsb.addEmptyElement("img", "src", getStatusImage(tagClass));
+    xsb.pop("a");
+
+    xsb.pop(D);
+
+    xsb.push(D, C, "test-content");
+    for (Class<?> c : map.keySet()) {
+      xsb.push(D, C, C);
+      xsb.push(D, C, "class-header");
+
+      // Passed/failed icon
+      xsb.addEmptyElement("img", "src", getImage(tagClass));
+
+      xsb.addOptional(S, c.getName(), C, "class-name");
+
+      xsb.pop(D);
+      xsb.push(D, C, "class-content");
+      List<ITestResult> l = map.get(c);
+      for (ITestResult m : l) {
+        generateMethod(tagClass, m, context, xsb);
+      }
+      xsb.pop(D);
+      xsb.pop(D);
+    }
+    xsb.pop(D);
+
+    xsb.pop(D);
+  }
+
+  private static String getStatusImage(String status) {
+    return "up.png";
+//    if ("passed".equals(status)) return "down.png";
+//    else return "up.png";
+  }
+
+  private static String getImage(String tagClass) {
+    return tagClass + ".png";
+  }
+
+  private void generateMethod(String tagClass, ITestResult tr,
+      ITestContext context, XMLStringBuffer xsb) {
+    long time = tr.getEndMillis() - tr.getStartMillis();
+    xsb.push(D, C, "method");
+    xsb.push(D, C, "method-content");
+    xsb.addOptional(S, tr.getMethod().getMethodName(), C, "method-name");
+
+    // Parameters?
+    if (tr.getParameters().length > 0) {
+      StringBuilder sb = new StringBuilder();
+      boolean first = true;
+      for (Object p : tr.getParameters()) {
+        if (!first) sb.append(", ");
+        first = false;
+        sb.append(Utils.toString(p));
+      }
+      xsb.addOptional(S, "(" + sb.toString() + ")", C, "parameters");
+    }
+
+    // Exception?
+    if (tr.getThrowable() != null) {
+      StringBuilder stackTrace = new StringBuilder();
+      for (StackTraceElement str : tr.getThrowable().getStackTrace()) {
+        stackTrace.append(str.toString()).append("<br>");
+      }
+      xsb.addOptional(D, stackTrace.toString() + "\n",
+          C, "stack-trace");
+    }
+
+    xsb.addOptional(S, " " + Long.toString(time) + " ms", C, "method-time");
+    xsb.pop(D);
+    xsb.pop(D);
+  }
+
+  /**
+   * Overridable by subclasses to create different directory names (e.g. with timestamps).
+   * @param outputDirectory the output directory specified by the user
+   */
+  protected String generateOutputDirectoryName(String outputDirectory) {
+    return outputDirectory;
+  }
+
+  private String pluralize(int count, String singular) {
+    return Integer.toString(count) + " "
+        + (count > 1 ? (singular.endsWith("s") ? singular + "es" : singular + "s") : singular);
+  }
+
+}
diff --git a/src/main/java/org/testng/reporters/SuiteHTMLReporter.java b/src/main/java/org/testng/reporters/SuiteHTMLReporter.java
new file mode 100755
index 0000000..fff8ddf
--- /dev/null
+++ b/src/main/java/org/testng/reporters/SuiteHTMLReporter.java
@@ -0,0 +1,735 @@
+package org.testng.reporters;
+
+import static org.testng.internal.Utils.isStringNotEmpty;
+
+import org.testng.IInvokedMethod;
+import org.testng.IReporter;
+import org.testng.ISuite;
+import org.testng.ISuiteResult;
+import org.testng.ITestClass;
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+import org.testng.Reporter;
+import org.testng.collections.Maps;
+import org.testng.internal.Utils;
+import org.testng.xml.XmlSuite;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class implements an HTML reporter for suites.
+ *
+ * @author cbeust
+ * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
+ */
+public class SuiteHTMLReporter implements IReporter {
+  public static final String METHODS_CHRONOLOGICAL = "methods.html";
+  public static final String METHODS_ALPHABETICAL = "methods-alphabetical.html";
+  public static final String GROUPS = "groups.html";
+  public static final String CLASSES = "classes.html";
+  public static final String REPORTER_OUTPUT = "reporter-output.html";
+  public static final String METHODS_NOT_RUN = "methods-not-run.html";
+  public static final String TESTNG_XML = "testng.xml.html";
+
+  private Map<String, ITestClass> m_classes = Maps.newHashMap();
+  private String m_outputDirectory;
+
+  @Override
+  public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
+    m_outputDirectory = generateOutputDirectoryName(outputDirectory + File.separator + "old");
+
+    try {
+      HtmlHelper.generateStylesheet(outputDirectory);
+    } catch (IOException e) {
+      //  TODO Propagate the exception properly.
+      e.printStackTrace();
+    }
+
+    for (ISuite suite : suites) {
+
+      //
+      // Generate the various reports
+      //
+      XmlSuite xmlSuite = suite.getXmlSuite();
+      if (xmlSuite.getTests().size() == 0) {
+        continue;
+      }
+      generateTableOfContents(xmlSuite, suite);
+      generateSuites(xmlSuite, suite);
+      generateIndex(xmlSuite, suite);
+      generateMain(xmlSuite, suite);
+      generateMethodsAndGroups(xmlSuite, suite);
+      generateMethodsChronologically(xmlSuite, suite, METHODS_CHRONOLOGICAL, false);
+      generateMethodsChronologically(xmlSuite, suite, METHODS_ALPHABETICAL, true);
+      generateClasses(xmlSuite, suite);
+      generateReporterOutput(xmlSuite, suite);
+      generateExcludedMethodsReport(xmlSuite, suite);
+      generateXmlFile(xmlSuite, suite);
+    }
+
+    generateIndex(suites);
+  }
+
+  /**
+   * Overridable by subclasses to create different directory names (e.g. with timestamps).
+   * @param outputDirectory the output directory specified by the user
+   */
+  protected String generateOutputDirectoryName(String outputDirectory) {
+    return outputDirectory;
+  }
+
+  private void generateXmlFile(XmlSuite xmlSuite, ISuite suite) {
+    String content = xmlSuite.toXml().replaceAll("<", "&lt;").replaceAll(">", "&gt;")
+          .replaceAll(" ", "&nbsp;").replaceAll("\n", "<br/>");
+
+    StringBuffer sb = new StringBuffer("<html>");
+
+    sb.append("<head><title>").append("testng.xml for ")
+      .append(xmlSuite.getName()).append("</title></head><body><tt>")
+      .append(content)
+      .append("</tt></body></html>");
+
+    Utils.writeFile(getOutputDirectory(xmlSuite), TESTNG_XML, sb.toString());
+  }
+
+  /**
+   * Generate the main index.html file that lists all the suites
+   * and their result
+   */
+  private void generateIndex(List<ISuite> suites) {
+    StringBuffer sb = new StringBuffer();
+    String title = "Test results";
+    sb.append("<html>\n<head><title>" + title + "</title>")
+      .append(HtmlHelper.getCssString("."))
+      .append("</head><body>\n")
+      .append("<h2><p align='center'>").append(title).append("</p></h2>\n")
+      .append("<table border='1' width='100%' class='main-page'>")
+      .append("<tr><th>Suite</th><th>Passed</th><th>Failed</th><th>Skipped</th><th>testng.xml</th></tr>\n");
+
+    int totalFailedTests = 0;
+    int totalPassedTests = 0;
+    int totalSkippedTests = 0;
+
+    StringBuffer suiteBuf= new StringBuffer();
+    for (ISuite suite : suites) {
+      if (suite.getResults().size() == 0) {
+        continue;
+      }
+
+      String name = suite.getName();
+
+      int failedTests= 0;
+      int passedTests= 0;
+      int skippedTests= 0;
+
+      Map<String, ISuiteResult> results = suite.getResults();
+      for (ISuiteResult result : results.values()) {
+        ITestContext context = result.getTestContext();
+        failedTests += context.getFailedTests().size();
+        totalFailedTests += context.getFailedTests().size();
+        passedTests += context.getPassedTests().size();
+        totalPassedTests += context.getPassedTests().size();
+        skippedTests += context.getSkippedTests().size();
+        totalSkippedTests += context.getSkippedTests().size();
+      }
+
+      String cls = failedTests > 0 ? "invocation-failed"
+          : (passedTests > 0  ? "invocation-passed" : "invocation-failed");
+      suiteBuf.append("<tr align='center' class='").append(cls).append("'>")
+        .append("<td><a href='").append(name).append("/index.html'>")
+        .append(name).append("</a></td>\n");
+      suiteBuf.append("<td>" + passedTests + "</td>")
+        .append("<td>" + failedTests + "</td>")
+        .append("<td>" + skippedTests + "</td>")
+        .append("<td><a href='").append(name).append("/").append(TESTNG_XML).append("'>Link").append("</a></td>")
+        .append("</tr>");
+
+    }
+
+    String cls= totalFailedTests > 0 ? "invocation-failed"
+        : (totalPassedTests > 0 ? "invocation-passed" : "invocation-failed");
+    sb.append("<tr align='center' class='").append(cls).append("'>")
+      .append("<td><em>Total</em></td>")
+      .append("<td><em>").append(totalPassedTests).append("</em></td>")
+      .append("<td><em>").append(totalFailedTests).append("</em></td>")
+      .append("<td><em>").append(totalSkippedTests).append("</em></td>")
+      .append("<td>&nbsp;</td>")
+      .append("</tr>\n");
+    sb.append(suiteBuf);
+    sb.append("</table>").append("</body></html>\n");
+
+    Utils.writeFile(m_outputDirectory, "index.html", sb.toString());
+  }
+
+  private void generateExcludedMethodsReport(XmlSuite xmlSuite, ISuite suite) {
+      Collection<ITestNGMethod> excluded = suite.getExcludedMethods();
+      StringBuffer sb2 = new StringBuffer("<h2>Methods that were not run</h2><table>\n");
+      for (ITestNGMethod method : excluded) {
+        Method m = method.getMethod();
+        if (m != null) {
+          sb2.append("<tr><td>")
+          .append(m.getDeclaringClass().getName() + "." + m.getName());
+          String description = method.getDescription();
+          if(isStringNotEmpty(description)) {
+            sb2.append("<br/>").append(SP2).append("<i>").append(description).append("</i>");
+          }
+          sb2.append("</td></tr>\n");
+        }
+      }
+      sb2.append("</table>");
+
+      Utils.writeFile(getOutputDirectory(xmlSuite), METHODS_NOT_RUN, sb2.toString());
+  }
+
+  private void generateReporterOutput(XmlSuite xmlSuite, ISuite suite) {
+    StringBuffer sb = new StringBuffer();
+
+    //
+    // Reporter output
+    //
+    sb.append("<h2>Reporter output</h2>")
+      .append("<table>");
+    List<String> output = Reporter.getOutput();
+    for (String line : output) {
+      sb.append("<tr><td>").append(line).append("</td></tr>\n");
+    }
+
+    sb.append("</table>");
+
+    Utils.writeFile(getOutputDirectory(xmlSuite), REPORTER_OUTPUT, sb.toString());
+  }
+
+  private void generateClasses(XmlSuite xmlSuite, ISuite suite) {
+    StringBuffer sb = new StringBuffer();
+    sb.append("<table border='1'>\n")
+    .append("<tr>\n")
+    .append("<th>Class name</th>\n")
+    .append("<th>Method name</th>\n")
+    .append("<th>Groups</th>\n")
+    .append("</tr>")
+    ;
+    for (ITestClass tc : m_classes.values()) {
+      sb.append(generateClass(tc));
+    }
+
+    sb.append("</table>\n");
+
+    Utils.writeFile(getOutputDirectory(xmlSuite), CLASSES, sb.toString());
+  }
+
+  private final static String SP = "&nbsp;";
+  private final static String SP2 = SP + SP + SP + SP;
+  private final static String SP3 = SP2 + SP2;
+  private final static String SP4 = SP3 + SP3;
+
+  private String generateClass(ITestClass cls) {
+    StringBuffer sb = new StringBuffer();
+
+    sb.append("<tr>\n")
+      .append("<td>").append(cls.getRealClass().getName()).append("</td>\n")
+      .append("<td>&nbsp;</td>")
+      .append("<td>&nbsp;</td>")
+      .append("</tr>\n")
+      ;
+
+    String[] tags = new String[] {
+        "@Test",
+        "@BeforeClass",
+        "@BeforeMethod",
+        "@AfterMethod",
+        "@AfterClass"
+    };
+    ITestNGMethod[][] methods = new ITestNGMethod[][] {
+      cls.getTestMethods(),
+      cls.getBeforeClassMethods(),
+      cls.getBeforeTestMethods(),
+      cls.getAfterTestMethods(),
+      cls.getAfterClassMethods()
+    };
+
+    for (int i = 0; i < tags.length; i++) {
+      sb.append("<tr>\n")
+      .append("<td align='center' colspan='3'>").append(tags[i]).append("</td>\n")
+      .append("</tr>\n")
+      .append(dumpMethods(methods[i]))
+      ;
+    }
+//    sb.append("<hr width='100%'/>")
+//    .append("<h3>").append(cls.getRealClass().getName()).append("</h3>\n");
+//
+//    sb.append("<div>").append(SP3).append("Test methods\n")
+//      .append(dumpMethods(cls.getTestMethods())).append("</div>\n")
+//      .append("<div>").append(SP3).append("@BeforeClass\n")
+//      .append(dumpMethods(cls.getBeforeClassMethods())).append("</div>\n")
+//      .append("<div>").append(SP3).append("@BeforeMethod\n")
+//      .append(dumpMethods(cls.getBeforeTestMethods())).append("</div>\n")
+//      .append("<div>").append(SP3).append("@AfterMethod\n")
+//      .append(dumpMethods(cls.getAfterTestMethods())).append("</div>\n")
+//      .append("<div>").append(SP3).append("@AfterClass\n")
+//      .append(dumpMethods(cls.getAfterClassMethods())).append("</div>\n")
+//     ;
+
+    String result = sb.toString();
+    return result;
+  }
+
+  private String dumpMethods(ITestNGMethod[] testMethods) {
+    StringBuffer sb = new StringBuffer();
+    if(null == testMethods || testMethods.length == 0) {
+      return "";
+    }
+
+    for (ITestNGMethod m : testMethods) {
+      sb.append("<tr>\n");
+      sb.append("<td>&nbsp;</td>\n")
+        .append("<td>").append(m.getMethodName()).append("</td>\n")
+        ;
+      String[] groups = m.getGroups();
+      if (groups != null && groups.length > 0) {
+        sb.append("<td>");
+        for (String g : groups) {
+          sb.append(g).append(" ");
+        }
+        sb.append("</td>\n");
+      }
+      else {
+        sb.append("<td>&nbsp;</td>");
+      }
+      sb.append("</tr>\n");
+    }
+
+//    StringBuffer sb = new StringBuffer("<br/>");  //"<table bgcolor=\"#c0c0c0\"/>");
+//    for (ITestNGMethod tm : testMethods) {
+//      sb
+//      .append(SP4).append(tm.getMethodName()).append("()\n")
+//      .append(dumpGroups(tm.getGroups()))
+//      .append("<br/>");
+//      ;
+//    }
+
+
+    String result = sb.toString();
+    return result;
+  }
+
+  private String dumpGroups(String[] groups) {
+    StringBuffer sb = new StringBuffer();
+
+    if (null != groups && groups.length > 0) {
+      sb.append(SP4).append("<em>[");
+
+      for (String g : groups) {
+        sb.append(g).append(" ");
+      }
+
+      sb.append("]</em><br/>\n");
+    }
+
+    String result = sb.toString();
+    return result;
+  }
+
+  /**
+   * Generate information about the methods that were run
+   */
+  public static final String AFTER= "&lt;&lt;";
+  public static final String BEFORE = "&gt;&gt;";
+  private void generateMethodsChronologically(XmlSuite xmlSuite, ISuite suite,
+      String outputFileName, boolean alphabetical)
+  {
+    try (BufferedWriter bw = Utils.openWriter(getOutputDirectory(xmlSuite), outputFileName)) {
+      bw.append("<h2>Methods run, sorted chronologically</h2>");
+      bw.append("<h3>" + BEFORE + " means before, " + AFTER + " means after</h3><p/>");
+
+      long startDate = -1;
+      bw.append("<br/><em>").append(suite.getName()).append("</em><p/>");
+      bw.append("<small><i>(Hover the method name to see the test class name)</i></small><p/>\n");
+
+      Collection<IInvokedMethod> invokedMethods = suite.getAllInvokedMethods();
+      if (alphabetical) {
+	@SuppressWarnings({"unchecked"})
+	Comparator<? super ITestNGMethod>  alphabeticalComparator = new Comparator(){
+	  @Override
+	  public int compare(Object o1, Object o2) {
+	    IInvokedMethod m1 = (IInvokedMethod) o1;
+	    IInvokedMethod m2 = (IInvokedMethod) o2;
+	    return m1.getTestMethod().getMethodName().compareTo(m2.getTestMethod().getMethodName());
+	  }
+	};
+	Collections.sort((List) invokedMethods, alphabeticalComparator);
+      }
+
+      SimpleDateFormat format = new SimpleDateFormat("yy/MM/dd HH:mm:ss");
+      boolean addedHeader = false;
+      for (IInvokedMethod iim : invokedMethods) {
+	ITestNGMethod tm = iim.getTestMethod();
+	if (!addedHeader) {
+	  bw.append("<table border=\"1\">\n")
+	    .append("<tr>")
+	    .append("<th>Time</th>")
+	    .append("<th>Delta (ms)</th>")
+	    .append("<th>Suite<br>configuration</th>")
+	    .append("<th>Test<br>configuration</th>")
+	    .append("<th>Class<br>configuration</th>")
+	    .append("<th>Groups<br>configuration</th>")
+	    .append("<th>Method<br>configuration</th>")
+	    .append("<th>Test<br>method</th>")
+	    .append("<th>Thread</th>")
+	    .append("<th>Instances</th>")
+	    .append("</tr>\n");
+	  addedHeader = true;
+	}
+	String methodName = tm.toString();
+	boolean bc = tm.isBeforeClassConfiguration();
+	boolean ac = tm.isAfterClassConfiguration();
+	boolean bt = tm.isBeforeTestConfiguration();
+	boolean at = tm.isAfterTestConfiguration();
+	boolean bs = tm.isBeforeSuiteConfiguration();
+	boolean as = tm.isAfterSuiteConfiguration();
+	boolean bg = tm.isBeforeGroupsConfiguration();
+	boolean ag = tm.isAfterGroupsConfiguration();
+	boolean setUp = tm.isBeforeMethodConfiguration();
+	boolean tearDown = tm.isAfterMethodConfiguration();
+	boolean isClassConfiguration = bc || ac;
+	boolean isGroupsConfiguration = bg || ag;
+	boolean isTestConfiguration = bt || at;
+	boolean isSuiteConfiguration = bs || as;
+	boolean isSetupOrTearDown = setUp || tearDown;
+	String configurationClassMethod = isClassConfiguration ? (bc ? BEFORE : AFTER) + methodName : SP;
+	String configurationTestMethod = isTestConfiguration ? (bt ? BEFORE : AFTER) + methodName : SP;
+	String configurationGroupsMethod = isGroupsConfiguration ? (bg ? BEFORE : AFTER) + methodName : SP;
+	String configurationSuiteMethod = isSuiteConfiguration ? (bs ? BEFORE : AFTER) + methodName : SP;
+	String setUpOrTearDownMethod = isSetupOrTearDown ? (setUp ? BEFORE : AFTER) + methodName : SP;
+	String testMethod = tm.isTest() ? methodName : SP;
+
+	StringBuffer instances = new StringBuffer();
+	for (long o : tm.getInstanceHashCodes()) {
+	  instances.append(o).append(" ");
+	}
+
+	if (startDate == -1) {
+	  startDate = iim.getDate();
+	}
+	String date = format.format(iim.getDate());
+	bw.append("<tr bgcolor=\"" + createColor(tm) + "\">")
+	  .append("  <td>").append(date).append("</td> ")
+	  .append("  <td>").append(Long.toString(iim.getDate() - startDate)).append("</td> ")
+	  .append(td(configurationSuiteMethod))
+	  .append(td(configurationTestMethod))
+	  .append(td(configurationClassMethod))
+	  .append(td(configurationGroupsMethod))
+	  .append(td(setUpOrTearDownMethod))
+	  .append(td(testMethod))
+	  .append("  <td>").append(tm.getId()).append("</td> ")
+	  .append("  <td>").append(instances).append("</td> ")
+	  .append("</tr>\n")
+	  ;
+      }
+      bw.append("</table>\n");
+    } catch (IOException e) {
+      Utils.log("[SuiteHTMLReporter]", 1, "Error writing to " + outputFileName + ": " + e.getMessage());
+    }
+  }
+
+  /**
+   * Generate a HTML color based on the class of the method
+   */
+  private String createColor(ITestNGMethod tm) {
+    // real class can be null if this client is remote (not serializable)
+    long color = tm.getRealClass() != null ? tm.getRealClass().hashCode() & 0xffffff: 0xffffff;
+    long[] rgb = {
+        ((color & 0xff0000) >> 16) & 0xff,
+        ((color & 0x00ff00) >> 8) & 0xff,
+        color & 0xff
+    };
+    // Not too dark
+    for (int i = 0; i < rgb.length; i++) {
+      if (rgb[i] < 0x60) {
+        rgb[i] += 0x60;
+      }
+    }
+    long adjustedColor = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
+    String result = Long.toHexString(adjustedColor);
+
+    return result;
+  }
+
+  private String td(String s) {
+    StringBuffer result = new StringBuffer();
+    String prefix = "";
+
+    if (s.startsWith(BEFORE)) {
+      prefix = BEFORE;
+    }
+    else if (s.startsWith(AFTER)) {
+      prefix = AFTER;
+    }
+
+    if (! s.equals(SP)) {
+      result.append("<td title=\"").append(s).append("\">");
+      int open = s.lastIndexOf("(");
+      int start = s.substring(0, open).lastIndexOf(".");
+//      int end = s.lastIndexOf(")");
+      if (start >= 0) {
+        result.append(prefix + s.substring(start + 1, open));
+      }
+      else {
+        result.append(prefix + s);
+      }
+      result.append("</td> \n");
+    }
+    else {
+      result.append("<td>").append(SP).append("</td>");
+    }
+
+    return result.toString();
+  }
+
+  private void ppp(String s) {
+    System.out.println("[SuiteHTMLReporter] " + s);
+  }
+
+  /**
+   * Generate information about methods and groups
+   */
+  private void generateMethodsAndGroups(XmlSuite xmlSuite, ISuite suite) {
+    StringBuffer sb = new StringBuffer();
+
+    Map<String, Collection<ITestNGMethod>> groups = suite.getMethodsByGroups();
+
+    sb.append("<h2>Groups used for this test run</h2>");
+    if (groups.size() > 0) {
+      sb.append("<table border=\"1\">\n")
+        .append("<tr> <td align=\"center\"><b>Group name</b></td>")
+        .append("<td align=\"center\"><b>Methods</b></td></tr>");
+
+      String[] groupNames = groups.keySet().toArray(new String[groups.size()]);
+      Arrays.sort(groupNames);
+      for (String group : groupNames) {
+        Collection<ITestNGMethod> methods = groups.get(group);
+        sb.append("<tr><td>").append(group).append("</td>");
+        StringBuffer methodNames = new StringBuffer();
+        Map<ITestNGMethod, ITestNGMethod> uniqueMethods = Maps.newHashMap();
+        for (ITestNGMethod tm : methods) {
+          uniqueMethods.put(tm, tm);
+        }
+        for (ITestNGMethod tm : uniqueMethods.values()) {
+          methodNames.append(tm.toString()).append("<br/>");
+        }
+        sb.append("<td>" + methodNames.toString() + "</td></tr>\n");
+      }
+
+      sb.append("</table>\n");
+    }
+    Utils.writeFile(getOutputDirectory(xmlSuite), GROUPS, sb.toString());
+  }
+
+  private void generateIndex(XmlSuite xmlSuite, ISuite sr) {
+    StringBuffer index = new StringBuffer()
+    .append("<html><head><title>Results for " + sr.getName() + "</title></head>\n")
+    .append("<frameset cols=\"26%,74%\">\n")
+    .append("<frame src=\"toc.html\" name=\"navFrame\">\n")
+    .append("<frame src=\"main.html\" name=\"mainFrame\">\n")
+    .append("</frameset>\n")
+    .append("</html>\n")
+    ;
+
+    Utils.writeFile(getOutputDirectory(xmlSuite), "index.html", index.toString());
+  }
+
+  private String makeTitle(ISuite suite) {
+    return "Results for<br/><em>" + suite.getName() + "</em>";
+  }
+
+  private void generateMain(XmlSuite xmlSuite, ISuite sr) {
+    StringBuffer index = new StringBuffer()
+    .append("<html><head><title>Results for " + sr.getName() + "</title></head>\n")
+    .append("<body>Select a result on the left-hand pane.</body>")
+    .append("</html>\n")
+    ;
+
+    Utils.writeFile(getOutputDirectory(xmlSuite), "main.html", index.toString());
+  }
+
+  /**
+   *
+   */
+  private void generateTableOfContents(XmlSuite xmlSuite, ISuite suite) {
+    StringBuffer tableOfContents = new StringBuffer();
+
+    //
+    // Generate methods and groups hyperlinks
+    //
+    Map<String, ISuiteResult> suiteResults = suite.getResults();
+    int groupCount = suite.getMethodsByGroups().size();
+    int methodCount = 0;
+    for (ISuiteResult sr : suiteResults.values()) {
+      ITestNGMethod[] methods = sr.getTestContext().getAllTestMethods();
+      methodCount += Utils.calculateInvokedMethodCount(methods);
+
+      // Collect testClasses
+      for (ITestNGMethod tm : methods) {
+        ITestClass tc = tm.getTestClass();
+        m_classes.put(tc.getRealClass().getName(), tc);
+      }
+    }
+
+    String name = "Results for " + suite.getName();
+    tableOfContents
+        .append("<html>\n")
+        .append("<head>\n")
+        .append("<title>" + name + "</title>\n")
+        .append(HtmlHelper.getCssString())
+        .append("</head>\n")
+        ;
+    tableOfContents
+        .append("<body>\n")
+        .append("<h3><p align=\"center\">" + makeTitle(suite) + "</p></h3>\n")
+        .append("<table border='1' width='100%'>\n")
+        .append("<tr valign='top'>\n")
+          .append("<td>")
+            .append(suiteResults.size()).append(" ").append(pluralize(suiteResults.size(), "test"))
+          .append("</td>\n")
+          .append("<td>")
+              .append("<a target='mainFrame' href='").append(CLASSES).append("'>")
+              .append(m_classes.size() + " " + pluralize(m_classes.size(), "class"))
+              .append("</a>")
+          .append("</td>\n")
+          .append("<td>" + methodCount + " " + pluralize(methodCount, "method") + ":<br/>\n")
+            .append("&nbsp;&nbsp;<a target='mainFrame' href='").append(METHODS_CHRONOLOGICAL).append("'>").append("chronological</a><br/>\n")
+            .append("&nbsp;&nbsp;<a target='mainFrame' href='").append(METHODS_ALPHABETICAL).append("\'>").append("alphabetical</a><br/>\n")
+            .append("&nbsp;&nbsp;<a target='mainFrame' href='").append(METHODS_NOT_RUN).append("'>not run (" + suite.getExcludedMethods().size() + ")</a>")
+          .append("</td>\n")
+        .append("</tr>\n")
+
+        .append("<tr>\n")
+        .append("<td><a target='mainFrame' href='").append(GROUPS).append("'>").append(groupCount + pluralize(groupCount, " group") + "</a></td>\n")
+        .append("<td><a target='mainFrame' href='").append(REPORTER_OUTPUT).append("'>reporter output</a></td>\n")
+        .append("<td><a target='mainFrame' href='").append(TESTNG_XML).append("'>testng.xml</a></td>\n")
+        .append("</tr>")
+        .append("</table>");
+
+      //
+      // Generate results for individual tests
+      //
+
+      // Order the results so we can show the failures first, then the skip and
+      // finally the successes
+      Map<String, ISuiteResult> redResults = Maps.newHashMap();
+      Map<String, ISuiteResult> yellowResults = Maps.newHashMap();
+      Map<String, ISuiteResult> greenResults = Maps.newHashMap();
+
+      for (Map.Entry<String, ISuiteResult> entry : suiteResults.entrySet()) {
+        String suiteName = entry.getKey();
+        ISuiteResult sr = entry.getValue();
+        ITestContext tc = sr.getTestContext();
+        int failed = tc.getFailedTests().size();
+        int skipped = tc.getSkippedTests().size();
+        int passed = tc.getPassedTests().size();
+
+        if (failed > 0) {
+          redResults.put(suiteName, sr);
+        }
+        else if (skipped > 0) {
+          yellowResults.put(suiteName, sr);
+        }
+        else if (passed > 0) {
+          greenResults.put(suiteName, sr);
+        }
+        else {
+          redResults.put(suiteName, sr);
+        }
+      }
+
+
+      ISuiteResult[][] results = new ISuiteResult[][] {
+        sortResults(redResults.values()), sortResults(yellowResults.values()), sortResults(greenResults.values())
+      };
+
+      String[] colors = {"failed", "skipped", "passed"};
+      for (int i = 0; i < colors.length; i++) {
+        ISuiteResult[] r = results[i];
+        for (ISuiteResult sr: r) {
+          String suiteName = sr.getTestContext().getName();
+          generateSuiteResult(suiteName, sr, colors[i], tableOfContents, m_outputDirectory);
+        }
+      }
+
+    tableOfContents.append("</body></html>");
+    Utils.writeFile(getOutputDirectory(xmlSuite), "toc.html", tableOfContents.toString());
+  }
+
+  private String pluralize(int count, String singular) {
+    return count > 1 ? (singular.endsWith("s") ? singular + "es" : singular + "s") : singular;
+  }
+
+  private String getOutputDirectory(XmlSuite xmlSuite) {
+    return m_outputDirectory + File.separatorChar + xmlSuite.getName();
+  }
+
+  private ISuiteResult[] sortResults(Collection<ISuiteResult> r) {
+    ISuiteResult[] result = r.toArray(new ISuiteResult[r.size()]);
+    Arrays.sort(result);
+    return result;
+  }
+
+  private void generateSuiteResult(String suiteName,
+                                   ISuiteResult sr,
+                                   String cssClass,
+                                   StringBuffer tableOfContents,
+                                   String outputDirectory)
+  {
+    ITestContext tc = sr.getTestContext();
+    int passed = tc.getPassedTests().size();
+    int failed = tc.getFailedTests().size();
+    int skipped = tc.getSkippedTests().size();
+    String baseFile = tc.getName();
+    tableOfContents
+      .append("\n<table width='100%' class='test-").append(cssClass).append("'>\n")
+      .append("<tr><td>\n")
+      .append("<table style='width: 100%'><tr>")
+      .append("<td valign='top'>")
+      .append(suiteName).append(" (").append(passed).append("/").append(failed).append("/").append(skipped).append(")")
+      .append("</td>")
+      .append("<td valign='top' align='right'>\n")
+      .append("  <a href='" + baseFile + ".html' target='mainFrame'>Results</a>\n")
+//      .append("  <a href=\"" + baseFile + ".out\" target=\"mainFrame\"\">Output</a>\n")
+//      .append("&nbsp;&nbsp;<a href=\"file://" + baseFile + ".properties\" target=\"mainFrame\"\">Property file</a><br>\n")
+      .append("</td>")
+      .append("</tr></table>\n")
+      .append("</td></tr><p/>\n")
+      ;
+
+    tableOfContents.append("</table>\n");
+  }
+
+  /**
+   * Writes a property file for each suite result.
+   *
+   * @param xmlSuite
+   * @param suite
+   */
+  private void generateSuites(XmlSuite xmlSuite, ISuite suite) {
+    Map<String, ISuiteResult> suiteResults = suite.getResults();
+
+    for (ISuiteResult sr : suiteResults.values()) {
+      ITestContext testContext = sr.getTestContext();
+      StringBuffer sb = new StringBuffer();
+
+      for (ISuiteResult suiteResult : suiteResults.values()) {
+        sb.append(suiteResult.toString());
+      }
+      Utils.writeFile(getOutputDirectory(xmlSuite), testContext.getName() + ".properties", sb.toString());
+    }
+  }
+}
diff --git a/src/main/java/org/testng/reporters/TestHTMLReporter.java b/src/main/java/org/testng/reporters/TestHTMLReporter.java
new file mode 100755
index 0000000..83c252e
--- /dev/null
+++ b/src/main/java/org/testng/reporters/TestHTMLReporter.java
@@ -0,0 +1,382 @@
+package org.testng.reporters;
+
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+import org.testng.Reporter;
+import org.testng.TestListenerAdapter;
+import org.testng.internal.Utils;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+
+
+/**
+ * This class implements an HTML reporter for individual tests.
+ *
+ * @author Cedric Beust, May 2, 2004
+ * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
+ */
+public class TestHTMLReporter extends TestListenerAdapter {
+  private static final Comparator<ITestResult> NAME_COMPARATOR= new NameComparator();
+  private static final Comparator<ITestResult> CONFIGURATION_COMPARATOR= new ConfigurationComparator();
+
+  private ITestContext m_testContext = null;
+
+  /////
+  // implements ITestListener
+  //
+  @Override
+  public void onStart(ITestContext context) {
+    m_testContext = context;
+  }
+
+  @Override
+  public void onFinish(ITestContext context) {
+    generateLog(m_testContext,
+                null /* host */,
+                m_testContext.getOutputDirectory(),
+                getConfigurationFailures(),
+                getConfigurationSkips(),
+                getPassedTests(),
+                getFailedTests(),
+                getSkippedTests(),
+                getFailedButWithinSuccessPercentageTests());
+  }
+  //
+  // implements ITestListener
+  /////
+
+  private static String getOutputFile(ITestContext context) {
+    return context.getName() + ".html";
+  }
+
+  public static void generateTable(StringBuffer sb, String title,
+      Collection<ITestResult> tests, String cssClass, Comparator<ITestResult> comparator)
+  {
+    sb.append("<table width='100%' border='1' class='invocation-").append(cssClass).append("'>\n")
+      .append("<tr><td colspan='4' align='center'><b>").append(title).append("</b></td></tr>\n")
+      .append("<tr>")
+      .append("<td><b>Test method</b></td>\n")
+      .append("<td width=\"30%\"><b>Exception</b></td>\n")
+      .append("<td width=\"10%\"><b>Time (seconds)</b></td>\n")
+      .append("<td><b>Instance</b></td>\n")
+      .append("</tr>\n");
+
+    if (tests instanceof List) {
+      Collections.sort((List<ITestResult>) tests, comparator);
+    }
+
+    // User output?
+    String id = "";
+    Throwable tw = null;
+
+    for (ITestResult tr : tests) {
+      sb.append("<tr>\n");
+
+      // Test method
+      ITestNGMethod method = tr.getMethod();
+
+      String name = method.getMethodName();
+      sb.append("<td title='").append(tr.getTestClass().getName()).append(".")
+        .append(name)
+        .append("()'>")
+        .append("<b>").append(name).append("</b>");
+
+      // Test class
+      String testClass = tr.getTestClass().getName();
+      if (testClass != null) {
+        sb.append("<br>").append("Test class: " + testClass);
+
+        // Test name
+        String testName = tr.getTestName();
+        if (testName != null) {
+          sb.append(" (").append(testName).append(")");
+        }
+      }
+
+      // Method description
+      if (! Utils.isStringEmpty(method.getDescription())) {
+        sb.append("<br>").append("Test method: ").append(method.getDescription());
+      }
+
+      Object[] parameters = tr.getParameters();
+      if (parameters != null && parameters.length > 0) {
+        sb.append("<br>Parameters: ");
+        for (int j = 0; j < parameters.length; j++) {
+          if (j > 0) {
+            sb.append(", ");
+          }
+          sb.append(parameters[j] == null ? "null" : parameters[j].toString());
+        }
+      }
+
+      //
+      // Output from the method, created by the user calling Reporter.log()
+      //
+      {
+        List<String> output = Reporter.getOutput(tr);
+        if (null != output && output.size() > 0) {
+          sb.append("<br/>");
+          // Method name
+          String divId = "Output-" + tr.hashCode();
+          sb.append("\n<a href=\"#").append(divId).append("\"")
+            .append(" onClick='toggleBox(\"").append(divId).append("\", this, \"Show output\", \"Hide output\");'>")
+            .append("Show output</a>\n")
+            .append("\n<a href=\"#").append(divId).append("\"")
+            .append(" onClick=\"toggleAllBoxes();\">Show all outputs</a>\n")
+            ;
+
+          // Method output
+          sb.append("<div class='log' id=\"").append(divId).append("\">\n");
+          for (String s : output) {
+            sb.append(s).append("<br/>\n");
+          }
+          sb.append("</div>\n");
+        }
+      }
+
+      sb.append("</td>\n");
+
+
+      // Exception
+      tw = tr.getThrowable();
+      String stackTrace = "";
+      String fullStackTrace = "";
+
+      id = "stack-trace" + tr.hashCode();
+      sb.append("<td>");
+
+      if (null != tw) {
+        String[] stackTraces = Utils.stackTrace(tw, true);
+        fullStackTrace = stackTraces[1];
+        stackTrace = "<div><pre>" + stackTraces[0]  + "</pre></div>";
+
+        sb.append(stackTrace);
+        // JavaScript link
+        sb.append("<a href='#' onClick='toggleBox(\"")
+        .append(id).append("\", this, \"Click to show all stack frames\", \"Click to hide stack frames\")'>")
+        .append("Click to show all stack frames").append("</a>\n")
+        .append("<div class='stack-trace' id='" + id + "'>")
+        .append("<pre>" + fullStackTrace + "</pre>")
+        .append("</div>")
+        ;
+      }
+
+      sb.append("</td>\n");
+
+      // Time
+      long time = (tr.getEndMillis() - tr.getStartMillis()) / 1000;
+      String strTime = Long.toString(time);
+      sb.append("<td>").append(strTime).append("</td>\n");
+
+      // Instance
+      Object instance = tr.getInstance();
+      sb.append("<td>").append(instance).append("</td>");
+
+      sb.append("</tr>\n");
+    }
+
+    sb.append("</table><p>\n");
+
+  }
+
+  private static String arrayToString(String[] array) {
+    StringBuffer result = new StringBuffer("");
+    for (String element : array) {
+      result.append(element).append(" ");
+    }
+
+    return result.toString();
+  }
+
+  private static String HEAD =
+    "\n<style type=\"text/css\">\n" +
+    ".log { display: none;} \n" +
+    ".stack-trace { display: none;} \n" +
+    "</style>\n" +
+    "<script type=\"text/javascript\">\n" +
+      "<!--\n" +
+      "function flip(e) {\n" +
+      "  current = e.style.display;\n" +
+      "  if (current == 'block') {\n" +
+      "    e.style.display = 'none';\n" +
+      "    return 0;\n" +
+      "  }\n" +
+      "  else {\n" +
+      "    e.style.display = 'block';\n" +
+      "    return 1;\n" +
+      "  }\n" +
+      "}\n" +
+      "\n" +
+      "function toggleBox(szDivId, elem, msg1, msg2)\n" +
+      "{\n" +
+      "  var res = -1;" +
+      "  if (document.getElementById) {\n" +
+      "    res = flip(document.getElementById(szDivId));\n" +
+      "  }\n" +
+      "  else if (document.all) {\n" +
+      "    // this is the way old msie versions work\n" +
+      "    res = flip(document.all[szDivId]);\n" +
+      "  }\n" +
+      "  if(elem) {\n" +
+      "    if(res == 0) elem.innerHTML = msg1; else elem.innerHTML = msg2;\n" +
+      "  }\n" +
+      "\n" +
+      "}\n" +
+      "\n" +
+      "function toggleAllBoxes() {\n" +
+      "  if (document.getElementsByTagName) {\n" +
+      "    d = document.getElementsByTagName('div');\n" +
+      "    for (i = 0; i < d.length; i++) {\n" +
+      "      if (d[i].className == 'log') {\n" +
+      "        flip(d[i]);\n" +
+      "      }\n" +
+      "    }\n" +
+      "  }\n" +
+      "}\n" +
+      "\n" +
+      "// -->\n" +
+      "</script>\n" +
+      "\n";
+
+  public static void generateLog(ITestContext testContext,
+      String host,
+      String outputDirectory,
+      Collection<ITestResult> failedConfs,
+      Collection<ITestResult> skippedConfs,
+      Collection<ITestResult> passedTests,
+      Collection<ITestResult> failedTests,
+      Collection<ITestResult> skippedTests,
+      Collection<ITestResult> percentageTests)
+  {
+    StringBuffer sb = new StringBuffer();
+    sb.append("<html>\n<head>\n")
+      .append("<title>TestNG:  ").append(testContext.getName()).append("</title>\n")
+      .append(HtmlHelper.getCssString())
+      .append(HEAD)
+      .append("</head>\n")
+      .append("<body>\n");
+
+    Date startDate = testContext.getStartDate();
+    Date endDate = testContext.getEndDate();
+    long duration = (endDate.getTime() - startDate.getTime()) / 1000;
+    int passed =
+      testContext.getPassedTests().size() +
+      testContext.getFailedButWithinSuccessPercentageTests().size();
+    int failed = testContext.getFailedTests().size();
+    int skipped = testContext.getSkippedTests().size();
+    String hostLine = Utils.isStringEmpty(host) ? "" : "<tr><td>Remote host:</td><td>" + host
+        + "</td>\n</tr>";
+
+    sb
+    .append("<h2 align='center'>").append(testContext.getName()).append("</h2>")
+    .append("<table border='1' align=\"center\">\n")
+    .append("<tr>\n")
+//    .append("<td>Property file:</td><td>").append(m_testRunner.getPropertyFileName()).append("</td>\n")
+//    .append("</tr><tr>\n")
+    .append("<td>Tests passed/Failed/Skipped:</td><td>").append(passed).append("/").append(failed).append("/").append(skipped).append("</td>\n")
+    .append("</tr><tr>\n")
+    .append("<td>Started on:</td><td>").append(testContext.getStartDate().toString()).append("</td>\n")
+    .append("</tr>\n")
+    .append(hostLine)
+    .append("<tr><td>Total time:</td><td>").append(duration).append(" seconds (").append(endDate.getTime() - startDate.getTime())
+      .append(" ms)</td>\n")
+    .append("</tr><tr>\n")
+    .append("<td>Included groups:</td><td>").append(arrayToString(testContext.getIncludedGroups())).append("</td>\n")
+    .append("</tr><tr>\n")
+    .append("<td>Excluded groups:</td><td>").append(arrayToString(testContext.getExcludedGroups())).append("</td>\n")
+    .append("</tr>\n")
+    .append("</table><p/>\n")
+    ;
+
+    sb.append("<small><i>(Hover the method name to see the test class name)</i></small><p/>\n");
+    if (failedConfs.size() > 0) {
+      generateTable(sb, "FAILED CONFIGURATIONS", failedConfs, "failed", CONFIGURATION_COMPARATOR);
+    }
+    if (skippedConfs.size() > 0) {
+      generateTable(sb, "SKIPPED CONFIGURATIONS", skippedConfs, "skipped", CONFIGURATION_COMPARATOR);
+    }
+    if (failedTests.size() > 0) {
+      generateTable(sb, "FAILED TESTS", failedTests, "failed", NAME_COMPARATOR);
+    }
+    if (percentageTests.size() > 0) {
+      generateTable(sb, "FAILED TESTS BUT WITHIN SUCCESS PERCENTAGE",
+          percentageTests, "percent", NAME_COMPARATOR);
+    }
+    if (passedTests.size() > 0) {
+      generateTable(sb, "PASSED TESTS", passedTests, "passed", NAME_COMPARATOR);
+    }
+    if (skippedTests.size() > 0) {
+      generateTable(sb, "SKIPPED TESTS", skippedTests, "skipped", NAME_COMPARATOR);
+    }
+
+    sb.append("</body>\n</html>");
+
+    Utils.writeFile(outputDirectory, getOutputFile(testContext), sb.toString());
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[TestHTMLReporter] " + s);
+  }
+
+  private static class NameComparator implements Comparator<ITestResult>, Serializable {
+    private static final long serialVersionUID = 381775815838366907L;
+    public int compare(ITestResult o1, ITestResult o2) {
+      String c1 = o1.getMethod().getMethodName();
+      String c2 = o2.getMethod().getMethodName();
+      return c1.compareTo(c2);
+    }
+
+  }
+
+  private static class ConfigurationComparator implements Comparator<ITestResult>, Serializable {
+    private static final long serialVersionUID = 5558550850685483455L;
+
+    public int compare(ITestResult o1, ITestResult o2) {
+      ITestNGMethod tm1= o1.getMethod();
+      ITestNGMethod tm2= o2.getMethod();
+      return annotationValue(tm2) - annotationValue(tm1);
+    }
+
+    private static int annotationValue(ITestNGMethod method) {
+      if(method.isBeforeSuiteConfiguration()) {
+        return 10;
+      }
+      if(method.isBeforeTestConfiguration()) {
+        return 9;
+      }
+      if(method.isBeforeClassConfiguration()) {
+        return 8;
+      }
+      if(method.isBeforeGroupsConfiguration()) {
+        return 7;
+      }
+      if(method.isBeforeMethodConfiguration()) {
+        return 6;
+      }
+      if(method.isAfterMethodConfiguration()) {
+        return 5;
+      }
+      if(method.isAfterGroupsConfiguration()) {
+        return 4;
+      }
+      if(method.isAfterClassConfiguration()) {
+        return 3;
+      }
+      if(method.isAfterTestConfiguration()) {
+        return 2;
+      }
+      if(method.isAfterSuiteConfiguration()) {
+        return 1;
+      }
+
+      return 0;
+    }
+  }
+
+}
diff --git a/src/main/java/org/testng/reporters/TextReporter.java b/src/main/java/org/testng/reporters/TextReporter.java
new file mode 100644
index 0000000..ed14400
--- /dev/null
+++ b/src/main/java/org/testng/reporters/TextReporter.java
@@ -0,0 +1,183 @@
+package org.testng.reporters;
+
+import static org.testng.internal.Utils.isStringNotBlank;
+
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+import org.testng.internal.Utils;
+
+import java.util.List;
+
+/**
+ * A simple reporter that collects the results and prints them on standard out.
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
+ */
+public class TextReporter extends TestListenerAdapter {
+  private int m_verbose = 0;
+  private String m_testName = null;
+
+
+  public TextReporter(String testName, int verbose) {
+    m_testName = testName;
+    m_verbose = verbose;
+  }
+
+  @Override
+  public void onFinish(ITestContext context) {
+    if (m_verbose >= 2) {
+      logResults();
+    }
+  }
+
+  private ITestNGMethod[] resultsToMethods(List<ITestResult> results) {
+    ITestNGMethod[] result = new ITestNGMethod[results.size()];
+    int i = 0;
+    for (ITestResult tr : results) {
+      result[i++] = tr.getMethod();
+    }
+
+    return result;
+  }
+
+  private void logResults() {
+    //
+    // Log Text
+    //
+    for(Object o : getConfigurationFailures()) {
+      ITestResult tr = (ITestResult) o;
+      Throwable ex = tr.getThrowable();
+      String stackTrace= "";
+      if (ex != null) {
+        if (m_verbose >= 2) {
+          stackTrace= Utils.stackTrace(ex, false)[0];
+        }
+      }
+
+      logResult("FAILED CONFIGURATION",
+          Utils.detailedMethodName(tr.getMethod(), false),
+          tr.getMethod().getDescription(),
+          stackTrace,
+          tr.getParameters(),
+          tr.getMethod().getMethod().getParameterTypes()
+      );
+    }
+
+    for(Object o : getConfigurationSkips()) {
+      ITestResult tr = (ITestResult) o;
+      logResult("SKIPPED CONFIGURATION",
+          Utils.detailedMethodName(tr.getMethod(), false),
+          tr.getMethod().getDescription(),
+          null,
+          tr.getParameters(),
+          tr.getMethod().getMethod().getParameterTypes()
+      );
+    }
+
+    for(Object o : getPassedTests()) {
+      ITestResult tr = (ITestResult) o;
+      logResult("PASSED", tr, null);
+    }
+
+    for(Object o : getFailedTests()) {
+      ITestResult tr = (ITestResult) o;
+      Throwable ex = tr.getThrowable();
+      String stackTrace= "";
+      if (ex != null) {
+        if (m_verbose >= 2) {
+          stackTrace= Utils.stackTrace(ex, false)[0];
+        }
+      }
+
+      logResult("FAILED", tr, stackTrace);
+    }
+
+    for(Object o : getSkippedTests()) {
+      ITestResult tr = (ITestResult) o;
+      Throwable throwable = tr.getThrowable();
+      logResult("SKIPPED", tr, throwable != null ? Utils.stackTrace(throwable, false)[0] : null);
+    }
+
+    ITestNGMethod[] ft = resultsToMethods(getFailedTests());
+    StringBuffer logBuf= new StringBuffer("\n===============================================\n");
+    logBuf.append("    ").append(m_testName).append("\n");
+    logBuf.append("    Tests run: ").append(Utils.calculateInvokedMethodCount(getAllTestMethods()))
+        .append(", Failures: ").append(Utils.calculateInvokedMethodCount(ft))
+        .append(", Skips: ").append(Utils.calculateInvokedMethodCount(resultsToMethods(getSkippedTests())));
+    int confFailures= getConfigurationFailures().size();
+    int confSkips= getConfigurationSkips().size();
+    if(confFailures > 0 || confSkips > 0) {
+      logBuf.append("\n").append("    Configuration Failures: ").append(confFailures)
+          .append(", Skips: ").append(confSkips);
+    }
+    logBuf.append("\n===============================================\n");
+    logResult("", logBuf.toString());
+  }
+
+  private String getName() {
+    return m_testName;
+  }
+
+  private void logResult(String status, ITestResult tr, String stackTrace) {
+    logResult(status, tr.getName(), tr.getMethod().getDescription(), stackTrace,
+        tr.getParameters(), tr.getMethod().getMethod().getParameterTypes());
+  }
+
+  private void logResult(String status, String message) {
+    StringBuffer buf= new StringBuffer();
+    if(isStringNotBlank(status)) {
+      buf.append(status).append(": ");
+    }
+    buf.append(message);
+
+    System.out.println(buf);
+  }
+
+  private void logResult(String status, String name,
+          String description, String stackTrace,
+          Object[] params, Class[] paramTypes) {
+    StringBuffer msg= new StringBuffer(name);
+
+    if(null != params && params.length > 0) {
+      msg.append("(");
+
+      // The error might be a data provider parameter mismatch, so make
+      // a special case here
+      if (params.length != paramTypes.length) {
+        msg.append(name + ": Wrong number of arguments were passed by " +
+                "the Data Provider: found " + params.length + " but " +
+                "expected " + paramTypes.length
+                + ")");
+      }
+      else {
+        for(int i= 0; i < params.length; i++) {
+          if(i > 0) {
+            msg.append(", ");
+          }
+          msg.append(Utils.toString(params[i], paramTypes[i]));
+        }
+
+        msg.append(")");
+      }
+    }
+    if (! Utils.isStringEmpty(description)) {
+      msg.append("\n");
+      for (int i = 0; i < status.length() + 2; i++) {
+        msg.append(" ");
+      }
+      msg.append(description);
+    }
+    if ( ! Utils.isStringEmpty(stackTrace)) {
+      msg.append("\n").append(stackTrace);
+    }
+
+    logResult(status, msg.toString());
+  }
+
+  public void ppp(String s) {
+    System.out.println("[TextReporter " + getName() + "] " + s);
+  }
+}
diff --git a/src/main/java/org/testng/reporters/VerboseReporter.java b/src/main/java/org/testng/reporters/VerboseReporter.java
new file mode 100644
index 0000000..7323148
--- /dev/null
+++ b/src/main/java/org/testng/reporters/VerboseReporter.java
@@ -0,0 +1,317 @@
+package org.testng.reporters;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+import org.testng.internal.Utils;
+
+/**
+ * Reporter printing out detailed messages about what TestNG
+ * is going to run and what is the status of what has been just run.
+ *
+ * To see messages from this reporter, either run Ant in verbose mode ('ant -v')
+ * or set verbose level to 5 or higher
+ *
+ * @author Lukas Jungmann
+ * @since 6.4
+ */
+public class VerboseReporter extends TestListenerAdapter {
+
+    /**
+     * Default prefix for messages printed out by this reporter
+     *
+     */
+    public static final String LISTENER_PREFIX = "[VerboseTestNG] ";
+    private String suiteName;
+    private final String prefix;
+
+    private enum Status {
+
+        SUCCESS(ITestResult.SUCCESS), FAILURE(ITestResult.FAILURE), SKIP(ITestResult.SKIP),
+        SUCCESS_PERCENTAGE_FAILURE(ITestResult.SUCCESS_PERCENTAGE_FAILURE), STARTED(ITestResult.STARTED);
+        private int status;
+
+        private Status(int i) {
+            status = i;
+        }
+    }
+
+    /**
+     * Default constructor
+     */
+    public VerboseReporter() {
+        this(LISTENER_PREFIX);
+    }
+
+    /**
+     * Create VerboseReporter with custom prefix
+     *
+     * @param prefix prefix for messages printed out by this reporter
+     */
+    public VerboseReporter(String prefix) {
+        this.prefix = prefix;
+    }
+
+    @Override
+    public void beforeConfiguration(ITestResult tr) {
+        super.beforeConfiguration(tr);
+        logTestResult(Status.STARTED, tr, true);
+    }
+
+    @Override
+    public void onConfigurationFailure(ITestResult tr) {
+        super.onConfigurationFailure(tr);
+        logTestResult(Status.FAILURE, tr, true);
+    }
+
+    @Override
+    public void onConfigurationSkip(ITestResult tr) {
+        super.onConfigurationSkip(tr);
+        logTestResult(Status.SKIP, tr, true);
+    }
+
+    @Override
+    public void onConfigurationSuccess(ITestResult tr) {
+        super.onConfigurationSuccess(tr);
+        logTestResult(Status.SUCCESS, tr, true);
+    }
+
+    @Override
+    public void onTestStart(ITestResult tr) {
+        logTestResult(Status.STARTED, tr, false);
+    }
+
+    @Override
+    public void onTestFailure(ITestResult tr) {
+        super.onTestFailure(tr);
+        logTestResult(Status.FAILURE, tr, false);
+    }
+
+    @Override
+    public void onTestFailedButWithinSuccessPercentage(ITestResult tr) {
+        super.onTestFailedButWithinSuccessPercentage(tr);
+        logTestResult(Status.SUCCESS_PERCENTAGE_FAILURE, tr, false);
+    }
+
+    @Override
+    public void onTestSkipped(ITestResult tr) {
+        super.onTestSkipped(tr);
+        logTestResult(Status.SKIP, tr, false);
+    }
+
+    @Override
+    public void onTestSuccess(ITestResult tr) {
+        super.onTestSuccess(tr);
+        logTestResult(Status.SUCCESS, tr, false);
+    }
+
+    @Override
+    public void onStart(ITestContext ctx) {
+        suiteName = ctx.getName();//ctx.getSuite().getXmlSuite().getFileName();
+        log("RUNNING: Suite: \"" + suiteName + "\" containing \"" + ctx.getAllTestMethods().length + "\" Tests (config: " + ctx.getSuite().getXmlSuite().getFileName() + ")");
+    }
+
+    @Override
+    public void onFinish(ITestContext context) {
+        logResults();
+        suiteName = null;
+    }
+
+    private ITestNGMethod[] resultsToMethods(List<ITestResult> results) {
+        ITestNGMethod[] result = new ITestNGMethod[results.size()];
+        int i = 0;
+        for (ITestResult tr : results) {
+            result[i++] = tr.getMethod();
+        }
+        return result;
+    }
+
+    /**
+     * Print out test summary
+     */
+    private void logResults() {
+        //
+        // Log test summary
+        //
+        ITestNGMethod[] ft = resultsToMethods(getFailedTests());
+        StringBuilder sb = new StringBuilder("\n===============================================\n");
+        sb.append("    ").append(suiteName).append("\n");
+        sb.append("    Tests run: ").append(Utils.calculateInvokedMethodCount(getAllTestMethods()));
+        sb.append(", Failures: ").append(Utils.calculateInvokedMethodCount(ft));
+        sb.append(", Skips: ").append(Utils.calculateInvokedMethodCount(resultsToMethods(getSkippedTests())));
+        int confFailures = getConfigurationFailures().size();
+        int confSkips = getConfigurationSkips().size();
+        if (confFailures > 0 || confSkips > 0) {
+            sb.append("\n").append("    Configuration Failures: ").append(confFailures);
+            sb.append(", Skips: ").append(confSkips);
+        }
+        sb.append("\n===============================================");
+        log(sb.toString());
+    }
+
+    /**
+     * Log meaningful message for passed in arguments.
+     * Message itself is of form:
+     * $status: "$suiteName" - $methodDeclaration ($actualArguments) finished in $x ms ($run of $totalRuns)
+     *
+     * @param st status of passed in itr
+     * @param itr test result to be described
+     * @param isConfMethod is itr describing configuration method
+     */
+    private void logTestResult(Status st, ITestResult itr, boolean isConfMethod) {
+        StringBuilder sb = new StringBuilder();
+        StringBuilder succRate = null;
+        String stackTrace = "";
+        switch (st) {
+            case STARTED:
+                sb.append("INVOKING");
+                break;
+            case SKIP:
+                sb.append("SKIPPED");
+                stackTrace = itr.getThrowable() != null
+                        ? Utils.stackTrace(itr.getThrowable(), false)[0] : "";
+                break;
+            case FAILURE:
+                sb.append("FAILED");
+                stackTrace = itr.getThrowable() != null
+                        ? Utils.stackTrace(itr.getThrowable(), false)[0] : "";
+                break;
+            case SUCCESS:
+                sb.append("PASSED");
+                break;
+            case SUCCESS_PERCENTAGE_FAILURE:
+                sb.append("PASSED with failures");
+                break;
+            default:
+                //not happen
+                throw new RuntimeException("Unsupported test status:" + itr.getStatus());
+        }
+        if (isConfMethod) {
+            sb.append(" CONFIGURATION: ");
+        } else {
+            sb.append(": ");
+        }
+        ITestNGMethod tm = itr.getMethod();
+        int identLevel = sb.length();
+        sb.append(getMethodDeclaration(tm));
+        Object[] params = itr.getParameters();
+        Class[] paramTypes = tm.getMethod().getParameterTypes();
+        if (null != params && params.length > 0) {
+            // The error might be a data provider parameter mismatch, so make
+            // a special case here
+            if (params.length != paramTypes.length) {
+                sb.append("Wrong number of arguments were passed by the Data Provider: found ");
+                sb.append(params.length);
+                sb.append(" but expected ");
+                sb.append(paramTypes.length);
+            } else {
+                sb.append("(value(s): ");
+                for (int i = 0; i < params.length; i++) {
+                    if (i > 0) {
+                        sb.append(", ");
+                    }
+                    sb.append(Utils.toString(params[i], paramTypes[i]));
+                }
+                sb.append(")");
+
+            }
+        }
+        if (Status.STARTED != st) {
+            sb.append(" finished in ");
+            sb.append(itr.getEndMillis() - itr.getStartMillis());
+            sb.append(" ms");
+            if (!Utils.isStringEmpty(tm.getDescription())) {
+                sb.append("\n");
+                for (int i = 0; i < identLevel; i++) {
+                    sb.append(" ");
+                }
+                sb.append(tm.getDescription());
+            }
+            if (tm.getInvocationCount() > 1) {
+                sb.append(" (");
+                sb.append(tm.getCurrentInvocationCount());
+                sb.append(" of ");
+                sb.append(tm.getInvocationCount());
+                sb.append(")");
+            }
+            if (!Utils.isStringEmpty(stackTrace)) {
+                sb.append("\n").append(stackTrace.substring(0, stackTrace.lastIndexOf(System.getProperty("line.separator"))));
+            }
+        } else {
+            if (!isConfMethod && tm.getInvocationCount() > 1) {
+                sb.append(" success: ");
+                sb.append(tm.getSuccessPercentage());
+                sb.append("%");
+            }
+        }
+        log(sb.toString());
+    }
+
+    protected void log(String message) {
+        //prefix all output lines
+        System.out.println(message.replaceAll("(?m)^", prefix));
+    }
+
+    /**
+     *
+     * @param method method to be described
+     * @return FQN of a class + method declaration for a method passed in
+     *      ie. test.triangle.CheckCount.testCheckCount(java.lang.String)
+     */
+    private String getMethodDeclaration(ITestNGMethod method) {
+        //see Utils.detailedMethodName
+        //perhaps should rather adopt the original method instead
+        Method m = method.getMethod();
+        StringBuilder buf = new StringBuilder();
+        buf.append("\"");
+        if (suiteName != null) {
+            buf.append(suiteName);
+        } else {
+            buf.append("UNKNOWN");
+        }
+        buf.append("\"");
+        buf.append(" - ");
+        if (method.isBeforeSuiteConfiguration()) {
+            buf.append("@BeforeSuite ");
+        } else if (method.isBeforeTestConfiguration()) {
+            buf.append("@BeforeTest ");
+        } else if (method.isBeforeClassConfiguration()) {
+            buf.append("@BeforeClass ");
+        } else if (method.isBeforeGroupsConfiguration()) {
+            buf.append("@BeforeGroups ");
+        } else if (method.isBeforeMethodConfiguration()) {
+            buf.append("@BeforeMethod ");
+        } else if (method.isAfterMethodConfiguration()) {
+            buf.append("@AfterMethod ");
+        } else if (method.isAfterGroupsConfiguration()) {
+            buf.append("@AfterGroups ");
+        } else if (method.isAfterClassConfiguration()) {
+            buf.append("@AfterClass ");
+        } else if (method.isAfterTestConfiguration()) {
+            buf.append("@AfterTest ");
+        } else if (method.isAfterSuiteConfiguration()) {
+            buf.append("@AfterSuite ");
+        }
+        buf.append(m.getDeclaringClass().getName());
+        buf.append(".");
+        buf.append(m.getName());
+        buf.append("(");
+        int i = 0;
+        for (Class<?> p : m.getParameterTypes()) {
+            if (i++ > 0) {
+                buf.append(", ");
+            }
+            buf.append(p.getName());
+        }
+        buf.append(")");
+        return buf.toString();
+    }
+
+    @Override
+    public String toString() {
+        return "VerboseReporter{" + "suiteName=" + suiteName + '}';
+    }
+}
diff --git a/src/main/java/org/testng/reporters/XMLConstants.java b/src/main/java/org/testng/reporters/XMLConstants.java
new file mode 100755
index 0000000..08cdcca
--- /dev/null
+++ b/src/main/java/org/testng/reporters/XMLConstants.java
@@ -0,0 +1,69 @@
+package org.testng.reporters;
+
+/**
+ * interface groups the XML constants
+ * tries to emulate what's in org.apache.tools.ant.taskdefs.optional.junit.XMLConstants
+ * to be compatible with junitreport
+ */
+public interface XMLConstants {
+  /** the testsuites element for the aggregate document */
+  String TESTSUITES = "testsuites";
+
+  /** the testsuite element */
+  String TESTSUITE = "testsuite";
+
+  /** the testcase element */
+  String TESTCASE = "testcase";
+
+  /** the error element */
+  String ERROR = "error";
+
+  /** the failure element */
+  String FAILURE = "failure";
+
+  /** the system-err element */
+  String SYSTEM_ERR = "system-err";
+
+  /** the system-out element */
+  String SYSTEM_OUT = "system-out";
+
+  /** package attribute for the aggregate document */
+  String ATTR_PACKAGE = "package";
+
+  /** name attribute for property, testcase and testsuite elements */
+  String ATTR_NAME = "name";
+
+  /** time attribute for testcase and testsuite elements */
+  String ATTR_TIME = "time";
+
+  /** errors attribute for testsuite elements */
+  String ATTR_ERRORS = "errors";
+
+  /** failures attribute for testsuite elements */
+  String ATTR_FAILURES = "failures";
+
+  /** tests attribute for testsuite elements */
+  String ATTR_TESTS = "tests";
+
+  /** type attribute for failure and error elements */
+  String ATTR_TYPE = "type";
+
+  /** message attribute for failure elements */
+  String ATTR_MESSAGE = "message";
+
+  /** the properties element */
+  String PROPERTIES = "properties";
+
+  /** the property element */
+  String PROPERTY = "property";
+
+  /** value attribute for property elements */
+  String ATTR_VALUE = "value";
+
+  /** classname attribute for testcase elements */
+  String ATTR_CLASSNAME = "classname";
+
+  String ATTR_HOSTNAME = "hostname";
+
+  String ATTR_TIMESTAMP = "timestamp";
+}
\ No newline at end of file
diff --git a/src/main/java/org/testng/reporters/XMLReporter.java b/src/main/java/org/testng/reporters/XMLReporter.java
new file mode 100755
index 0000000..0afd2c0
--- /dev/null
+++ b/src/main/java/org/testng/reporters/XMLReporter.java
@@ -0,0 +1,273 @@
+package org.testng.reporters;
+
+import org.testng.IReporter;
+import org.testng.ISuite;
+import org.testng.ISuiteResult;
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+import org.testng.Reporter;
+import org.testng.internal.Utils;
+import org.testng.xml.XmlSuite;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TimeZone;
+
+/**
+ * The main entry for the XML generation operation
+ * 
+ * @author Cosmin Marginean, Mar 16, 2007
+ */
+public class XMLReporter implements IReporter {
+  public static final String FILE_NAME = "testng-results.xml";
+
+  private final XMLReporterConfig config = new XMLReporterConfig();
+  private XMLStringBuffer rootBuffer;
+
+  @Override
+  public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites,
+      String outputDirectory) {
+    if (Utils.isStringEmpty(config.getOutputDirectory())) {
+      config.setOutputDirectory(outputDirectory);
+    }
+
+    // Calculate passed/failed/skipped
+    int passed = 0;
+    int failed = 0;
+    int skipped = 0;
+    for (ISuite s : suites) {
+      for (ISuiteResult sr : s.getResults().values()) {
+        ITestContext testContext = sr.getTestContext();
+        passed += testContext.getPassedTests().size();
+        failed += testContext.getFailedTests().size();
+        skipped += testContext.getSkippedTests().size();
+      }
+    }
+
+    rootBuffer = new XMLStringBuffer();
+    Properties p = new Properties();
+    p.put("passed", passed);
+    p.put("failed", failed);
+    p.put("skipped", skipped);
+    p.put("total", passed + failed + skipped);
+    rootBuffer.push(XMLReporterConfig.TAG_TESTNG_RESULTS, p);
+    writeReporterOutput(rootBuffer);
+    for (ISuite suite : suites) {
+      writeSuite(suite.getXmlSuite(), suite);
+    }
+    rootBuffer.pop();
+    Utils.writeUtf8File(config.getOutputDirectory(), FILE_NAME, rootBuffer, null /* no prefix */);
+  }
+
+  private void writeReporterOutput(XMLStringBuffer xmlBuffer) {
+    // TODO: Cosmin - maybe a <line> element isn't indicated for each line
+    xmlBuffer.push(XMLReporterConfig.TAG_REPORTER_OUTPUT);
+    List<String> output = Reporter.getOutput();
+    for (String line : output) {
+      if (line != null) {
+        xmlBuffer.push(XMLReporterConfig.TAG_LINE);
+        xmlBuffer.addCDATA(line);
+        xmlBuffer.pop();
+      }
+    }
+    xmlBuffer.pop();
+  }
+
+  private void writeSuite(XmlSuite xmlSuite, ISuite suite) {
+    switch (config.getFileFragmentationLevel()) {
+    case XMLReporterConfig.FF_LEVEL_NONE:
+      writeSuiteToBuffer(rootBuffer, suite);
+      break;
+    case XMLReporterConfig.FF_LEVEL_SUITE:
+    case XMLReporterConfig.FF_LEVEL_SUITE_RESULT:
+      File suiteFile = referenceSuite(rootBuffer, suite);
+      writeSuiteToFile(suiteFile, suite);
+    }
+  }
+
+  private void writeSuiteToFile(File suiteFile, ISuite suite) {
+    XMLStringBuffer xmlBuffer = new XMLStringBuffer();
+    writeSuiteToBuffer(xmlBuffer, suite);
+    File parentDir = suiteFile.getParentFile();
+    if (parentDir.exists() || suiteFile.getParentFile().mkdirs()) {
+      Utils.writeFile(parentDir.getAbsolutePath(), FILE_NAME, xmlBuffer.toXML());
+    }
+  }
+
+  private File referenceSuite(XMLStringBuffer xmlBuffer, ISuite suite) {
+    String relativePath = suite.getName() + File.separatorChar + FILE_NAME;
+    File suiteFile = new File(config.getOutputDirectory(), relativePath);
+    Properties attrs = new Properties();
+    attrs.setProperty(XMLReporterConfig.ATTR_URL, relativePath);
+    xmlBuffer.addEmptyElement(XMLReporterConfig.TAG_SUITE, attrs);
+    return suiteFile;
+  }
+
+  private void writeSuiteToBuffer(XMLStringBuffer xmlBuffer, ISuite suite) {
+    xmlBuffer.push(XMLReporterConfig.TAG_SUITE, getSuiteAttributes(suite));
+    writeSuiteGroups(xmlBuffer, suite);
+
+    Map<String, ISuiteResult> results = suite.getResults();
+    XMLSuiteResultWriter suiteResultWriter = new XMLSuiteResultWriter(config);
+    for (Map.Entry<String, ISuiteResult> result : results.entrySet()) {
+      suiteResultWriter.writeSuiteResult(xmlBuffer, result.getValue());
+    }
+
+    xmlBuffer.pop();
+  }
+
+  private void writeSuiteGroups(XMLStringBuffer xmlBuffer, ISuite suite) {
+    xmlBuffer.push(XMLReporterConfig.TAG_GROUPS);
+    Map<String, Collection<ITestNGMethod>> methodsByGroups = suite.getMethodsByGroups();
+    for (Map.Entry<String, Collection<ITestNGMethod>> entry : methodsByGroups.entrySet()) {
+      Properties groupAttrs = new Properties();
+      groupAttrs.setProperty(XMLReporterConfig.ATTR_NAME, entry.getKey());
+      xmlBuffer.push(XMLReporterConfig.TAG_GROUP, groupAttrs);
+      Set<ITestNGMethod> groupMethods = getUniqueMethodSet(entry.getValue());
+      for (ITestNGMethod groupMethod : groupMethods) {
+        Properties methodAttrs = new Properties();
+        methodAttrs.setProperty(XMLReporterConfig.ATTR_NAME, groupMethod.getMethodName());
+        methodAttrs.setProperty(XMLReporterConfig.ATTR_METHOD_SIG, groupMethod.toString());
+        methodAttrs.setProperty(XMLReporterConfig.ATTR_CLASS, groupMethod.getRealClass().getName());
+        xmlBuffer.addEmptyElement(XMLReporterConfig.TAG_METHOD, methodAttrs);
+      }
+      xmlBuffer.pop();
+    }
+    xmlBuffer.pop();
+  }
+
+  private Properties getSuiteAttributes(ISuite suite) {
+    Properties props = new Properties();
+    props.setProperty(XMLReporterConfig.ATTR_NAME, suite.getName());
+
+    // Calculate the duration
+    Map<String, ISuiteResult> results = suite.getResults();
+    Date minStartDate = new Date();
+    Date maxEndDate = null;
+    // TODO: We could probably optimize this in order not to traverse this twice
+    for (Map.Entry<String, ISuiteResult> result : results.entrySet()) {
+      ITestContext testContext = result.getValue().getTestContext();
+      Date startDate = testContext.getStartDate();
+      Date endDate = testContext.getEndDate();
+      if (minStartDate.after(startDate)) {
+        minStartDate = startDate;
+      }
+      if (maxEndDate == null || maxEndDate.before(endDate)) {
+        maxEndDate = endDate != null ? endDate : startDate;
+      }
+    }
+
+    // The suite could be completely empty
+    if (maxEndDate == null) {
+      maxEndDate = minStartDate;
+    }
+    addDurationAttributes(config, props, minStartDate, maxEndDate);
+    return props;
+  }
+
+  /**
+   * Add started-at, finished-at and duration-ms attributes to the <suite> tag
+   */
+  public static void addDurationAttributes(XMLReporterConfig config, Properties attributes,
+      Date minStartDate, Date maxEndDate) {
+    SimpleDateFormat format = new SimpleDateFormat(config.getTimestampFormat());
+    TimeZone utc = TimeZone.getTimeZone("UTC");
+    format.setTimeZone(utc);
+    String startTime = format.format(minStartDate);
+    String endTime = format.format(maxEndDate);
+    long duration = maxEndDate.getTime() - minStartDate.getTime();
+
+    attributes.setProperty(XMLReporterConfig.ATTR_STARTED_AT, startTime);
+    attributes.setProperty(XMLReporterConfig.ATTR_FINISHED_AT, endTime);
+    attributes.setProperty(XMLReporterConfig.ATTR_DURATION_MS, Long.toString(duration));
+  }
+
+  private Set<ITestNGMethod> getUniqueMethodSet(Collection<ITestNGMethod> methods) {
+    Set<ITestNGMethod> result = new LinkedHashSet<>();
+    for (ITestNGMethod method : methods) {
+      result.add(method);
+    }
+    return result;
+  }
+
+  // TODO: This is not the smartest way to implement the config
+  public int getFileFragmentationLevel() {
+    return config.getFileFragmentationLevel();
+  }
+
+  public void setFileFragmentationLevel(int fileFragmentationLevel) {
+    config.setFileFragmentationLevel(fileFragmentationLevel);
+  }
+
+  public int getStackTraceOutputMethod() {
+    return config.getStackTraceOutputMethod();
+  }
+
+  public void setStackTraceOutputMethod(int stackTraceOutputMethod) {
+    config.setStackTraceOutputMethod(stackTraceOutputMethod);
+  }
+
+  public String getOutputDirectory() {
+    return config.getOutputDirectory();
+  }
+
+  public void setOutputDirectory(String outputDirectory) {
+    config.setOutputDirectory(outputDirectory);
+  }
+
+  public boolean isGenerateGroupsAttribute() {
+    return config.isGenerateGroupsAttribute();
+  }
+
+  public void setGenerateGroupsAttribute(boolean generateGroupsAttribute) {
+    config.setGenerateGroupsAttribute(generateGroupsAttribute);
+  }
+
+  public boolean isSplitClassAndPackageNames() {
+    return config.isSplitClassAndPackageNames();
+  }
+
+  public void setSplitClassAndPackageNames(boolean splitClassAndPackageNames) {
+    config.setSplitClassAndPackageNames(splitClassAndPackageNames);
+  }
+
+  public String getTimestampFormat() {
+    return config.getTimestampFormat();
+  }
+
+  public void setTimestampFormat(String timestampFormat) {
+    config.setTimestampFormat(timestampFormat);
+  }
+
+  public boolean isGenerateDependsOnMethods() {
+    return config.isGenerateDependsOnMethods();
+  }
+
+  public void setGenerateDependsOnMethods(boolean generateDependsOnMethods) {
+    config.setGenerateDependsOnMethods(generateDependsOnMethods);
+  }
+
+  public void setGenerateDependsOnGroups(boolean generateDependsOnGroups) {
+    config.setGenerateDependsOnGroups(generateDependsOnGroups);
+  }
+
+  public boolean isGenerateDependsOnGroups() {
+    return config.isGenerateDependsOnGroups();
+  }
+
+  public void setGenerateTestResultAttributes(boolean generateTestResultAttributes) {
+    config.setGenerateTestResultAttributes(generateTestResultAttributes);
+  }
+
+  public boolean isGenerateTestResultAttributes() {
+    return config.isGenerateTestResultAttributes();
+  }
+
+}
diff --git a/src/main/java/org/testng/reporters/XMLReporterConfig.java b/src/main/java/org/testng/reporters/XMLReporterConfig.java
new file mode 100755
index 0000000..af25cfe
--- /dev/null
+++ b/src/main/java/org/testng/reporters/XMLReporterConfig.java
@@ -0,0 +1,235 @@
+package org.testng.reporters;
+
+import org.testng.ITestResult;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Hani Suleiman Date: Mar 27, 2007 Time: 9:16:28 AM
+ */
+public class XMLReporterConfig {
+
+  public static final String TAG_TEST = "test";
+  public static final String TAG_TEST_METHOD = "test-method";
+  public static final String TAG_EXCEPTION = "exception";
+  public static final String TAG_MESSAGE = "message";
+  public static final String TAG_SHORT_STACKTRACE = "short-stacktrace";
+  public static final String TAG_FULL_STACKTRACE = "full-stacktrace";
+  public static final String TAG_TESTNG_RESULTS = "testng-results";
+  public static final String TAG_SUITE = "suite";
+  public static final String TAG_GROUPS = "groups";
+  public static final String TAG_GROUP = "group";
+  public static final String TAG_CLASS = "class";
+  public static final String TAG_METHOD = "method";
+  public static final String TAG_PARAMS = "params";
+  public static final String TAG_PARAM = "param";
+  public static final String TAG_PARAM_VALUE = "value";
+  public static final String TAG_REPORTER_OUTPUT = "reporter-output";
+  public static final String TAG_LINE = "line";
+  public static final String TAG_ATTRIBUTES = "attributes";
+  public static final String TAG_ATTRIBUTE = "attribute";
+
+  public static final String ATTR_URL = "url";
+  public static final String ATTR_NAME = "name";
+  public static final String ATTR_STATUS = "status";
+  public static final String ATTR_DESC = "description";
+  public static final String ATTR_METHOD_SIG = "signature";
+  public static final String ATTR_GROUPS = "groups";
+  public static final String ATTR_CLASS = "class";
+  public static final String ATTR_TEST_INSTANCE_NAME = "test-instance-name";
+  public static final String ATTR_INDEX = "index";
+  public static final String ATTR_IS_NULL = "is-null";
+  public static final String ATTR_PACKAGE = "package";
+  public static final String ATTR_STARTED_AT = "started-at";
+  public static final String ATTR_FINISHED_AT = "finished-at";
+  public static final String ATTR_DURATION_MS = "duration-ms";
+  public static final String ATTR_IS_CONFIG = "is-config";
+  public static final String ATTR_DEPENDS_ON_METHODS = "depends-on-methods";
+  public static final String ATTR_DEPENDS_ON_GROUPS = "depends-on-groups";
+  public static final String ATTR_DATA_PROVIDER = "data-provider";
+
+  public static final String TEST_PASSED = "PASS";
+  public static final String TEST_FAILED = "FAIL";
+  public static final String TEST_SKIPPED = "SKIP";
+
+  private static Map<String, Integer> STATUSES = new HashMap<String, Integer>() {{
+    put(TEST_PASSED, ITestResult.SUCCESS);
+    put(TEST_FAILED, ITestResult.FAILURE);
+    put(TEST_SKIPPED, ITestResult.SKIP);
+  }};
+
+  public static Integer getStatus(String status) {
+    return STATUSES.get(status);
+  }
+
+  /**
+   * Indicates that no file fragmentation should be performed. This value
+   * indicates the XML generator to write all the results in one big file. Not
+   * recommended for large test suites.
+   */
+  public static final int FF_LEVEL_NONE = 1;
+  /**
+   * Will cause the XML generator to create separate files for each of the
+   * suites. A separate directory will be generated for each suite having the
+   * name of the suite and containing a <code>suite.xml</code> file that will be
+   * referenced in the main file with an <code>url</code> attribute
+   */
+  public static final int FF_LEVEL_SUITE = 2;
+  /**
+   * It behaves like <code>FF_LEVEL_SUITE</code>, except that it will also
+   * create a file for each <code>ISuiteResult</code>
+   */
+  public static final int FF_LEVEL_SUITE_RESULT = 3;
+
+  /**
+   * No stacktrace will be written in the output file
+   */
+  public static final int STACKTRACE_NONE = 0;
+  /**
+   * Write only a short version of the stacktrace
+   */
+  public static final int STACKTRACE_SHORT = 1;
+  /**
+   * Write only the full version of the stacktrace
+   */
+  public static final int STACKTRACE_FULL = 2;
+  /**
+   * Write both types of stacktrace
+   */
+  public static final int STACKTRACE_BOTH = 3;
+
+  // note: We're hardcoding the 'Z' because Java doesn't support all the
+  // intricacies of ISO-8601.
+  static final String FMT_DEFAULT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+
+  /**
+   * Indicates the way that the file fragmentation should be performed. Set this
+   * property to one of the FF_LEVEL_* values for the desired output structure
+   */
+  private int fileFragmentationLevel = FF_LEVEL_NONE;
+
+  /**
+   * Stack trace output method for the failed tests using one of the
+   * STACKTRACE_* constants.
+   */
+  private int stackTraceOutputMethod = STACKTRACE_FULL;
+
+  /**
+   * The root output directory where the XMLs will be written. This will default
+   * for now to the default TestNG output directory
+   */
+  private String outputDirectory;
+
+  /**
+   * Indicates whether the <code>groups</code> attribute should be generated for
+   * a <code>test-method</code> element. Defaults to false due to the fact that
+   * this might be considered redundant because of the group generation in the
+   * suite file.
+   */
+  private boolean generateGroupsAttribute = false;
+
+  /**
+   * When <code>true</code> it will generate the &lt;class&lt; element with a
+   * <code>name</code> and a <code>package</code> attribute. Otherwise, the
+   * fully qualified name will be used for the <code>name</code> attribute.
+   */
+  private boolean splitClassAndPackageNames = false;
+
+  /**
+   * Indicates whether the <code>depends-on-methods</code> attribute should be
+   * generated for a <code>test-method</code> element
+   */
+  private boolean generateDependsOnMethods = true;
+
+  /**
+   * Indicates whether the <code>depends-on-groups</code> attribute should be
+   * generated for a <code>test-method</code> element
+   */
+  private boolean generateDependsOnGroups = true;
+
+  /**
+   * Indicates whether {@link ITestResult} attributes should be generated for
+   * each <code>test-method</code> element
+   */
+  private boolean generateTestResultAttributes = false;
+
+  /**
+   * The output format for timestamps
+   */
+  private String timestampFormat = FMT_DEFAULT;
+
+  public int getFileFragmentationLevel() {
+    return fileFragmentationLevel;
+  }
+
+  public void setFileFragmentationLevel(int fileFragmentationLevel) {
+    this.fileFragmentationLevel = fileFragmentationLevel;
+  }
+
+  public int getStackTraceOutputMethod() {
+    return stackTraceOutputMethod;
+  }
+
+  public void setStackTraceOutputMethod(int stackTraceOutputMethod) {
+    this.stackTraceOutputMethod = stackTraceOutputMethod;
+  }
+
+  public String getOutputDirectory() {
+    return outputDirectory;
+  }
+
+  public void setOutputDirectory(String outputDirectory) {
+    this.outputDirectory = outputDirectory;
+  }
+
+  public boolean isGenerateGroupsAttribute() {
+    return generateGroupsAttribute;
+  }
+
+  public void setGenerateGroupsAttribute(boolean generateGroupsAttribute) {
+    this.generateGroupsAttribute = generateGroupsAttribute;
+  }
+
+  public boolean isSplitClassAndPackageNames() {
+    return splitClassAndPackageNames;
+  }
+
+  public void setSplitClassAndPackageNames(boolean splitClassAndPackageNames) {
+    this.splitClassAndPackageNames = splitClassAndPackageNames;
+  }
+
+  public String getTimestampFormat() {
+    return timestampFormat;
+  }
+
+  public void setTimestampFormat(String timestampFormat) {
+    this.timestampFormat = timestampFormat;
+  }
+
+  public boolean isGenerateDependsOnMethods() {
+    return generateDependsOnMethods;
+  }
+
+  public void setGenerateDependsOnMethods(boolean generateDependsOnMethods) {
+    this.generateDependsOnMethods = generateDependsOnMethods;
+  }
+
+  public boolean isGenerateDependsOnGroups() {
+    return generateDependsOnGroups;
+  }
+
+  public void setGenerateDependsOnGroups(boolean generateDependsOnGroups) {
+    this.generateDependsOnGroups = generateDependsOnGroups;
+  }
+
+  public void setGenerateTestResultAttributes(boolean generateTestResultAttributes) {
+    this.generateTestResultAttributes = generateTestResultAttributes;
+  }
+
+  public boolean isGenerateTestResultAttributes() {
+    return generateTestResultAttributes;
+  }
+}
diff --git a/src/main/java/org/testng/reporters/XMLStringBuffer.java b/src/main/java/org/testng/reporters/XMLStringBuffer.java
new file mode 100755
index 0000000..4e2b051
--- /dev/null
+++ b/src/main/java/org/testng/reporters/XMLStringBuffer.java
@@ -0,0 +1,386 @@
+package org.testng.reporters;
+
+import java.io.Writer;
+import java.util.Properties;
+import java.util.Stack;
+import java.util.regex.Pattern;
+
+import org.testng.internal.Nullable;
+
+/**
+ * This class allows you to generate an XML text document by pushing
+ * and popping tags from a stack maintained internally.
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a> Jul 21, 2003
+ */
+public class XMLStringBuffer {
+  /** End of line, value of 'line.separator' system property or '\n' */
+  private static final String EOL = System.getProperty("line.separator", "\n");
+
+  /** Tab space indent for XML document */
+  private static final String DEFAULT_INDENT_INCREMENT = "  ";
+
+  /** The buffer to hold the xml document */
+  private IBuffer m_buffer;
+
+  /** The stack of tags to make sure XML document is well formed. */
+  private final Stack<Tag> m_tagStack = new Stack<>();
+
+  /** A string of space character representing the current indentation. */
+  private String m_currentIndent = "";
+
+  public XMLStringBuffer() {
+    init(Buffer.create(), "", "1.0", "UTF-8");
+  }
+
+  /**
+   * @param start A string of spaces indicating the indentation at which
+   * to start the generation. This constructor will not insert an <?xml
+   * prologue.
+   */
+  public XMLStringBuffer(String start) {
+    init(Buffer.create(), start);
+  }
+
+  /**
+   * @param buffer The StringBuffer to use internally to represent the
+   * document.
+   * @param start A string of spaces indicating the indentation at which
+   * to start the generation.
+   */
+  public XMLStringBuffer(IBuffer buffer, String start) {
+    init(buffer, start);
+  }
+
+  private void init(IBuffer buffer, String start) {
+    init(buffer, start, null, null);
+  }
+
+  /**
+  *
+  * @param start A string of spaces indicating the indentation at which
+  * to start the generation.
+  */
+  private void init(IBuffer buffer, String start, @Nullable String version, @Nullable String encoding) {
+    m_buffer = buffer;
+    m_currentIndent = start;
+    if (version != null) {
+      setXmlDetails(version, encoding);
+    }
+  }
+
+ /**
+   * Set the xml version and encoding for this document.
+   *
+   * @param v the XML version
+   * @param enc the XML encoding
+   */
+  public void setXmlDetails(String v, String enc) {
+    if (m_buffer.toString().length() != 0) {
+      throw new IllegalStateException("Buffer should be empty: '" + m_buffer.toString() + "'");
+    }
+    m_buffer.append("<?xml version=\"" + v + "\" encoding=\"" + enc + "\"?>").append(EOL);
+  }
+
+  /**
+   * Set the doctype for this document.
+   *
+   * @param docType The DOCTYPE string, without the "&lt;!DOCTYPE " "&gt;"
+   */
+  public void setDocType(String docType) {
+    m_buffer.append("<!DOCTYPE " + docType + ">" + EOL);
+  }
+
+  /**
+   * Push a new tag.  Its value is stored and will be compared against the parameter
+   * passed to pop().
+   *
+   * @param tagName The name of the tag.
+   * @param schema The schema to use (can be null or an empty string).
+   * @param attributes A Properties file representing the attributes (or null)
+   */
+  public void push(String tagName, @Nullable String schema, @Nullable Properties attributes) {
+    XMLUtils.xmlOpen(m_buffer, m_currentIndent, tagName + schema, attributes);
+    m_tagStack.push(new Tag(m_currentIndent, tagName, attributes));
+    m_currentIndent += DEFAULT_INDENT_INCREMENT;
+  }
+
+  /**
+   * Push a new tag.  Its value is stored and will be compared against the parameter
+   * passed to pop().
+   *
+   * @param tagName The name of the tag.
+   * @param schema The schema to use (can be null or an empty string).
+   */
+  public void push(String tagName, @Nullable String schema) {
+    push(tagName, schema, null);
+  }
+
+  /**
+   * Push a new tag.  Its value is stored and will be compared against the parameter
+   * passed to pop().
+   *
+   * @param tagName The name of the tag.
+   * @param attributes A Properties file representing the attributes (or null)
+   */
+  public void push(String tagName, @Nullable Properties attributes) {
+    push(tagName, "", attributes);
+  }
+
+  public void push(String tagName, String... attributes) {
+    push(tagName, createProperties(attributes));
+  }
+
+  private Properties createProperties(String[] attributes) {
+    Properties result = new Properties();
+    if (attributes == null) {
+      return result;
+    }
+    if (attributes.length % 2 != 0) {
+      throw new IllegalArgumentException("Arguments 'attributes' length must be even. Actual: " + attributes.length);
+    }
+    for (int i = 0; i < attributes.length; i += 2) {
+      result.put(attributes[i], attributes[i + 1]);
+    }
+    return result;
+  }
+
+  /**
+   * Push a new tag.  Its value is stored and will be compared against the parameter
+   * passed to pop().
+   *
+   * @param tagName The name of the tag.
+   */
+  public void push(String tagName) {
+    push(tagName, "");
+  }
+
+  /**
+   * Pop the last pushed element without verifying it if matches the previously
+   * pushed tag.
+   */
+  public void pop() {
+    pop(null);
+  }
+
+  /**
+   * Pop the last pushed element and throws an AssertionError if it doesn't
+   * match the corresponding tag that was pushed earlier.
+   *
+   * @param tagName The name of the tag this pop() is supposed to match.
+   */
+  public void pop(String tagName) {
+    m_currentIndent = m_currentIndent.substring(DEFAULT_INDENT_INCREMENT.length());
+    Tag t = m_tagStack.pop();
+    if (null != tagName) {
+      if (!tagName.equals(t.tagName)) {
+        // TODO Is it normal to throw an Error here?
+        throw new AssertionError(
+            "Popping the wrong tag: " + t.tagName + " but expected " + tagName);
+      }
+    }
+    XMLUtils.xmlClose(m_buffer, m_currentIndent, t.tagName,
+        XMLUtils.extractComment(tagName, t.properties));
+  }
+
+  /**
+   * Add a required element to the current tag.  An opening and closing tag
+   * will be generated even if value is null.
+   * @param tagName The name of the tag
+   * @param value The value for this tag
+   */
+  public void addRequired(String tagName, @Nullable String value) {
+    addRequired(tagName, value, (Properties) null);
+  }
+
+  /**
+   * Add a required element to the current tag.  An opening and closing tag
+   * will be generated even if value is null.
+   * @param tagName The name of the tag
+   * @param value The value for this tag
+   * @param attributes A Properties file containing the attributes (or null)
+   */
+  public void addRequired(String tagName, @Nullable String value, @Nullable Properties attributes) {
+    XMLUtils.xmlRequired(m_buffer, m_currentIndent, tagName, value, attributes);
+  }
+  public void addRequired(String tagName, @Nullable String value, String... attributes) {
+    addRequired(tagName, value, createProperties(attributes));
+  }
+
+  /**
+   * Add an optional String element to the current tag.  If value is null, nothing is
+   * added.
+   * @param tagName The name of the tag
+   * @param value The value for this tag
+   * @param attributes A Properties file containing the attributes (or null)
+   */
+  public void addOptional(String tagName, @Nullable String value, @Nullable Properties attributes) {
+    if (value != null) {
+      XMLUtils.xmlOptional(m_buffer, m_currentIndent, tagName, value, attributes);
+    }
+  }
+
+  public void addOptional(String tagName, @Nullable String value, String... attributes) {
+    if (value != null) {
+      XMLUtils.xmlOptional(m_buffer, m_currentIndent, tagName, value, createProperties(attributes));
+    }
+  }
+
+  /**
+   * Add an optional String element to the current tag.  If value is null, nothing is
+   * added.
+   * @param tagName The name of the tag
+   * @param value The value for this tag
+   */
+  public void addOptional(String tagName, @Nullable String value) {
+    addOptional(tagName, value, (Properties) null);
+  }
+
+  /**
+   * Add an optional Boolean element to the current tag.  If value is null, nothing is
+   * added.
+   * @param tagName The name of the tag
+   * @param value The value for this tag
+   * @param attributes A Properties file containing the attributes (or null)
+   */
+  public void addOptional(String tagName, @Nullable Boolean value, @Nullable Properties attributes) {
+    if (null != value) {
+      XMLUtils.xmlOptional(m_buffer, m_currentIndent, tagName, value.toString(), attributes);
+    }
+  }
+
+  /**
+   * Add an optional Boolean element to the current tag.  If value is null, nothing is
+   * added.
+   * @param tagName The name of the tag
+   * @param value The value for this tag
+   */
+  public void addOptional(String tagName, @Nullable Boolean value) {
+    addOptional(tagName, value, null);
+  }
+
+  /**
+   * Add an empty element tag (e.g. <foo/>)
+   *
+   * @param tagName The name of the tag
+   *
+   */
+  public void addEmptyElement(String tagName) {
+    addEmptyElement(tagName, (Properties) null);
+  }
+
+  /**
+   * Add an empty element tag (e.g. <foo/>)
+   * @param tagName The name of the tag
+   * @param attributes A Properties file containing the attributes (or null)
+   */
+  public void addEmptyElement(String tagName, @Nullable Properties attributes) {
+    m_buffer.append(m_currentIndent).append("<").append(tagName);
+    XMLUtils.appendAttributes(m_buffer, attributes);
+    m_buffer.append("/>").append(EOL);
+  }
+
+  public void addEmptyElement(String tagName, String... attributes) {
+    addEmptyElement(tagName, createProperties(attributes));
+  }
+
+  public void addComment(String comment) {
+    m_buffer.append(m_currentIndent).append("<!-- " + comment.replaceAll("[-]{2,}", "-") + " -->\n");
+  }
+
+  public void addString(String s) {
+    m_buffer.append(s);
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[XMLStringBuffer] " + s);
+  }
+
+  /**
+   * Add a CDATA tag.
+   */
+  public void addCDATA(String content) {
+    if (content == null) {
+      content = "null";
+    }
+    if (content.contains("]]>")) {
+      String[] subStrings = content.split("]]>");
+      m_buffer.append(m_currentIndent).append("<![CDATA[").append(subStrings[0]).append("]]]]>");
+      for (int i = 1; i < subStrings.length - 1; i++) {
+        m_buffer.append("<![CDATA[>").append(subStrings[i]).append("]]]]>");
+      }
+      m_buffer.append("<![CDATA[>").append(subStrings[subStrings.length - 1]).append("]]>");
+      if (content.endsWith("]]>")) {
+        m_buffer.append("<![CDATA[]]]]>").append("<![CDATA[>]]>");
+      }
+      m_buffer.append(EOL);
+    } else {
+      m_buffer.append(m_currentIndent).append("<![CDATA[").append(content).append("]]>" + EOL);
+    }
+  }
+
+  /**
+   *
+   * @return The StringBuffer used to create the document.
+   */
+  public IBuffer getStringBuffer() {
+    return m_buffer;
+  }
+
+  private static final Pattern INVALID_XML_CHARS =
+      Pattern.compile("[^\\u0009\\u000A\\u000D\\u0020-\\uD7FF\\uE000-\\uFFFD\uD800\uDC00-\uDBFF\uDFFF]");
+
+  /**
+   * @return The String representation of the XML for this XMLStringBuffer.
+   */
+  public String toXML() {
+    return INVALID_XML_CHARS.matcher(m_buffer.toString()).replaceAll("");
+  }
+
+  public static void main(String[] argv) {
+    IBuffer result = Buffer.create();
+    XMLStringBuffer sb = new XMLStringBuffer(result, "");
+
+    sb.push("family");
+    Properties p = new Properties();
+    p.setProperty("prop1", "value1");
+    p.setProperty("prop2", "value2");
+    sb.addRequired("cedric", "true", p);
+    sb.addRequired("alois", "true");
+    sb.addOptional("anne-marie", (String) null);
+    sb.pop();
+
+    System.out.println(result.toString());
+
+    assert ("<family>" + EOL + "<cedric>true</cedric>" + EOL + "<alois>true</alois>" + EOL + "</family>"  + EOL)
+      .equals(result.toString());
+  }
+
+  public String getCurrentIndent() {
+    return m_currentIndent;
+  }
+
+  public void toWriter(Writer fw) {
+    m_buffer.toWriter(fw);
+  }
+}
+
+
+////////////////////////
+
+class Tag {
+  public final String tagName;
+  public final String indent;
+  public final Properties properties;
+
+  public Tag(String ind, String n, Properties p) {
+    tagName = n;
+    indent = ind;
+    properties = p;
+  }
+
+  @Override
+  public String toString() {
+    return tagName;
+  }
+}
diff --git a/src/main/java/org/testng/reporters/XMLSuiteResultWriter.java b/src/main/java/org/testng/reporters/XMLSuiteResultWriter.java
new file mode 100755
index 0000000..c8ebebd
--- /dev/null
+++ b/src/main/java/org/testng/reporters/XMLSuiteResultWriter.java
@@ -0,0 +1,341 @@
+package org.testng.reporters;
+
+import org.testng.IResultMap;
+import org.testng.ISuiteResult;
+import org.testng.ITestContext;
+import org.testng.ITestResult;
+import org.testng.Reporter;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.collections.Sets;
+import org.testng.internal.ConstructorOrMethod;
+import org.testng.internal.Utils;
+import org.testng.util.Strings;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * Utility writing an ISuiteResult to an XMLStringBuffer. Depending on the settings in the <code>config</code> property
+ * it might generate an additional XML file with the actual content and only reference the file with an <code>url</code>
+ * attribute in the passed XMLStringBuffer.
+ *
+ * @author Cosmin Marginean, Mar 16, 2007
+ */
+
+public class XMLSuiteResultWriter {
+
+  private XMLReporterConfig config;
+
+  public XMLSuiteResultWriter(XMLReporterConfig config) {
+    this.config = config;
+  }
+
+  /**
+   * Writes the specified ISuiteResult in the given XMLStringBuffer. Please consider that depending on the settings in
+   * the <code>config</code> property it might generate an additional XML file with the actual content and only
+   * reference the file with an <code>url</code> attribute in the passed XMLStringBuffer.
+   *
+   * @param xmlBuffer   The XML buffer where to write or reference the suite result
+   * @param suiteResult The <code>ISuiteResult</code> to serialize
+   */
+  public void writeSuiteResult(XMLStringBuffer xmlBuffer, ISuiteResult suiteResult) {
+    if (XMLReporterConfig.FF_LEVEL_SUITE_RESULT != config.getFileFragmentationLevel()) {
+      writeAllToBuffer(xmlBuffer, suiteResult);
+    } else {
+      String parentDir =
+              config.getOutputDirectory() + File.separatorChar + suiteResult.getTestContext().getSuite().getName();
+      File file = referenceSuiteResult(xmlBuffer, parentDir, suiteResult);
+      XMLStringBuffer suiteXmlBuffer = new XMLStringBuffer();
+      writeAllToBuffer(suiteXmlBuffer, suiteResult);
+      Utils.writeUtf8File(file.getAbsoluteFile().getParent(), file.getName(), suiteXmlBuffer.toXML());
+    }
+  }
+
+  private void writeAllToBuffer(XMLStringBuffer xmlBuffer, ISuiteResult suiteResult) {
+    xmlBuffer.push(XMLReporterConfig.TAG_TEST, getSuiteResultAttributes(suiteResult));
+    Set<ITestResult> testResults = Sets.newHashSet();
+    ITestContext testContext = suiteResult.getTestContext();
+    addAllTestResults(testResults, testContext.getPassedTests());
+    addAllTestResults(testResults, testContext.getFailedTests());
+    addAllTestResults(testResults, testContext.getSkippedTests());
+    addAllTestResults(testResults, testContext.getPassedConfigurations());
+    addAllTestResults(testResults, testContext.getSkippedConfigurations());
+    addAllTestResults(testResults, testContext.getFailedConfigurations());
+    addAllTestResults(testResults, testContext.getFailedButWithinSuccessPercentageTests());
+    addTestResults(xmlBuffer, testResults);
+    xmlBuffer.pop();
+  }
+
+  @SuppressWarnings("unchecked")
+  private void addAllTestResults(Set<ITestResult> testResults, IResultMap resultMap) {
+    if (resultMap != null) {
+      // Sort the results chronologically before adding them
+      List<ITestResult> allResults = new ArrayList<>();
+      allResults.addAll(resultMap.getAllResults());
+
+      Collections.sort(new ArrayList(allResults), new Comparator<ITestResult>() {
+        @Override
+        public int compare(ITestResult o1, ITestResult o2) {
+          return (int) (o1.getStartMillis() - o2.getStartMillis());
+        }
+      });
+
+      testResults.addAll(allResults);
+    }
+  }
+
+  private File referenceSuiteResult(XMLStringBuffer xmlBuffer, String parentDir, ISuiteResult suiteResult) {
+    Properties attrs = new Properties();
+    String suiteResultName = suiteResult.getTestContext().getName() + ".xml";
+    attrs.setProperty(XMLReporterConfig.ATTR_URL, suiteResultName);
+    xmlBuffer.addEmptyElement(XMLReporterConfig.TAG_TEST, attrs);
+    return new File(parentDir + File.separatorChar + suiteResultName);
+  }
+
+  private Properties getSuiteResultAttributes(ISuiteResult suiteResult) {
+    Properties attributes = new Properties();
+    ITestContext tc = suiteResult.getTestContext();
+    attributes.setProperty(XMLReporterConfig.ATTR_NAME, tc.getName());
+    XMLReporter.addDurationAttributes(config, attributes, tc.getStartDate(), tc.getEndDate());
+    return attributes;
+  }
+
+  private void addTestResults(XMLStringBuffer xmlBuffer, Set<ITestResult> testResults) {
+    Map<String, List<ITestResult>> testsGroupedByClass = buildTestClassGroups(testResults);
+    for (Map.Entry<String, List<ITestResult>> result : testsGroupedByClass.entrySet()) {
+      Properties attributes = new Properties();
+      String className = result.getKey();
+      if (config.isSplitClassAndPackageNames()) {
+        int dot = className.lastIndexOf('.');
+        attributes.setProperty(XMLReporterConfig.ATTR_NAME,
+                dot > -1 ? className.substring(dot + 1, className.length()) : className);
+        attributes.setProperty(XMLReporterConfig.ATTR_PACKAGE, dot > -1 ? className.substring(0, dot) : "[default]");
+      } else {
+        attributes.setProperty(XMLReporterConfig.ATTR_NAME, className);
+      }
+
+      xmlBuffer.push(XMLReporterConfig.TAG_CLASS, attributes);
+      List<ITestResult> sortedResults = result.getValue();
+      Collections.sort( sortedResults );
+      for (ITestResult testResult : sortedResults) {
+        addTestResult(xmlBuffer, testResult);
+      }
+      xmlBuffer.pop();
+    }
+  }
+
+  private Map<String, List<ITestResult>> buildTestClassGroups(Set<ITestResult> testResults) {
+    Map<String, List<ITestResult>> map = Maps.newHashMap();
+    for (ITestResult result : testResults) {
+      String className = result.getTestClass().getName();
+      List<ITestResult> list = map.get(className);
+      if (list == null) {
+        list = Lists.newArrayList();
+        map.put(className, list);
+      }
+      list.add(result);
+    }
+    return map;
+  }
+
+  private void addTestResult(XMLStringBuffer xmlBuffer, ITestResult testResult) {
+    Properties attribs = getTestResultAttributes(testResult);
+    attribs.setProperty(XMLReporterConfig.ATTR_STATUS, getStatusString(testResult.getStatus()));
+    xmlBuffer.push(XMLReporterConfig.TAG_TEST_METHOD, attribs);
+    addTestMethodParams(xmlBuffer, testResult);
+    addTestResultException(xmlBuffer, testResult);
+    addTestResultOutput(xmlBuffer, testResult);
+    if (config.isGenerateTestResultAttributes()) {
+      addTestResultAttributes(xmlBuffer, testResult);
+    }
+    xmlBuffer.pop();
+  }
+
+  private String getStatusString(int testResultStatus) {
+    switch (testResultStatus) {
+      case ITestResult.SUCCESS:
+        return "PASS";
+      case ITestResult.FAILURE:
+        return "FAIL";
+      case ITestResult.SKIP:
+        return "SKIP";
+      case ITestResult.SUCCESS_PERCENTAGE_FAILURE:
+        return "SUCCESS_PERCENTAGE_FAILURE";
+    }
+    return null;
+  }
+
+  private Properties getTestResultAttributes(ITestResult testResult) {
+    Properties attributes = new Properties();
+    if (!testResult.getMethod().isTest()) {
+      attributes.setProperty(XMLReporterConfig.ATTR_IS_CONFIG, "true");
+    }
+    attributes.setProperty(XMLReporterConfig.ATTR_NAME, testResult.getMethod().getMethodName());
+    String testInstanceName = testResult.getTestName();
+    if (null != testInstanceName) {
+      attributes.setProperty(XMLReporterConfig.ATTR_TEST_INSTANCE_NAME, testInstanceName);
+    }
+    String description = testResult.getMethod().getDescription();
+    if (!Utils.isStringEmpty(description)) {
+      attributes.setProperty(XMLReporterConfig.ATTR_DESC, description);
+    }
+
+    attributes.setProperty(XMLReporterConfig.ATTR_METHOD_SIG, removeClassName(testResult.getMethod().toString()));
+
+    SimpleDateFormat format = new SimpleDateFormat(config.getTimestampFormat());
+    String startTime = format.format(testResult.getStartMillis());
+    String endTime = format.format(testResult.getEndMillis());
+    attributes.setProperty(XMLReporterConfig.ATTR_STARTED_AT, startTime);
+    attributes.setProperty(XMLReporterConfig.ATTR_FINISHED_AT, endTime);
+    long duration = testResult.getEndMillis() - testResult.getStartMillis();
+    String strDuration = Long.toString(duration);
+    attributes.setProperty(XMLReporterConfig.ATTR_DURATION_MS, strDuration);
+
+    if (config.isGenerateGroupsAttribute()) {
+      String groupNamesStr = Utils.arrayToString(testResult.getMethod().getGroups());
+      if (!Utils.isStringEmpty(groupNamesStr)) {
+        attributes.setProperty(XMLReporterConfig.ATTR_GROUPS, groupNamesStr);
+      }
+    }
+
+    if (config.isGenerateDependsOnMethods()) {
+      String dependsOnStr = Utils.arrayToString(testResult.getMethod().getMethodsDependedUpon());
+      if (!Utils.isStringEmpty(dependsOnStr)) {
+        attributes.setProperty(XMLReporterConfig.ATTR_DEPENDS_ON_METHODS, dependsOnStr);
+      }
+    }
+
+    if (config.isGenerateDependsOnGroups()) {
+      String dependsOnStr = Utils.arrayToString(testResult.getMethod().getGroupsDependedUpon());
+      if (!Utils.isStringEmpty(dependsOnStr)) {
+        attributes.setProperty(XMLReporterConfig.ATTR_DEPENDS_ON_GROUPS, dependsOnStr);
+      }
+    }
+
+    ConstructorOrMethod cm = testResult.getMethod().getConstructorOrMethod();
+    Test testAnnotation;
+    if (cm.getMethod() != null) {
+      testAnnotation = cm.getMethod().getAnnotation(Test.class);
+      if (testAnnotation != null) {
+        String dataProvider = testAnnotation.dataProvider();
+        if (!Strings.isNullOrEmpty(dataProvider)) {
+          attributes.setProperty(XMLReporterConfig.ATTR_DATA_PROVIDER, dataProvider);
+        }
+      }
+    }
+
+    return attributes;
+  }
+
+  private String removeClassName(String methodSignature) {
+    int firstParanthesisPos = methodSignature.indexOf("(");
+    int dotAferClassPos = methodSignature.substring(0, firstParanthesisPos).lastIndexOf(".");
+    return methodSignature.substring(dotAferClassPos + 1, methodSignature.length());
+  }
+
+  public void addTestMethodParams(XMLStringBuffer xmlBuffer, ITestResult testResult) {
+    Object[] parameters = testResult.getParameters();
+    if ((parameters != null) && (parameters.length > 0)) {
+      xmlBuffer.push(XMLReporterConfig.TAG_PARAMS);
+      for (int i = 0; i < parameters.length; i++) {
+        addParameter(xmlBuffer, parameters[i], i);
+      }
+      xmlBuffer.pop();
+    }
+  }
+
+  private void addParameter(XMLStringBuffer xmlBuffer, Object parameter, int i) {
+    Properties attrs = new Properties();
+    attrs.setProperty(XMLReporterConfig.ATTR_INDEX, String.valueOf(i));
+    xmlBuffer.push(XMLReporterConfig.TAG_PARAM, attrs);
+    if (parameter == null) {
+      Properties valueAttrs = new Properties();
+      valueAttrs.setProperty(XMLReporterConfig.ATTR_IS_NULL, "true");
+      xmlBuffer.addEmptyElement(XMLReporterConfig.TAG_PARAM_VALUE, valueAttrs);
+    } else {
+      xmlBuffer.push(XMLReporterConfig.TAG_PARAM_VALUE);
+      xmlBuffer.addCDATA(parameter.toString());
+      xmlBuffer.pop();
+    }
+    xmlBuffer.pop();
+  }
+
+  private void addTestResultException(XMLStringBuffer xmlBuffer, ITestResult testResult) {
+    Throwable exception = testResult.getThrowable();
+    if (exception != null) {
+      Properties exceptionAttrs = new Properties();
+      exceptionAttrs.setProperty(XMLReporterConfig.ATTR_CLASS, exception.getClass().getName());
+      xmlBuffer.push(XMLReporterConfig.TAG_EXCEPTION, exceptionAttrs);
+
+      if (!Utils.isStringEmpty(exception.getMessage())) {
+        xmlBuffer.push(XMLReporterConfig.TAG_MESSAGE);
+        xmlBuffer.addCDATA(exception.getMessage());
+        xmlBuffer.pop();
+      }
+
+      String[] stackTraces = Utils.stackTrace(exception, false);
+      if ((config.getStackTraceOutputMethod() & XMLReporterConfig.STACKTRACE_SHORT) == XMLReporterConfig
+              .STACKTRACE_SHORT) {
+        xmlBuffer.push(XMLReporterConfig.TAG_SHORT_STACKTRACE);
+        xmlBuffer.addCDATA(stackTraces[0]);
+        xmlBuffer.pop();
+      }
+      if ((config.getStackTraceOutputMethod() & XMLReporterConfig.STACKTRACE_FULL) == XMLReporterConfig
+              .STACKTRACE_FULL) {
+        xmlBuffer.push(XMLReporterConfig.TAG_FULL_STACKTRACE);
+        xmlBuffer.addCDATA(stackTraces[1]);
+        xmlBuffer.pop();
+      }
+
+      xmlBuffer.pop();
+    }
+  }
+
+  private void addTestResultOutput(XMLStringBuffer xmlBuffer, ITestResult testResult) {
+    // TODO: Cosmin - maybe a <line> element isn't indicated for each line
+    xmlBuffer.push(XMLReporterConfig.TAG_REPORTER_OUTPUT);
+    List<String> output = Reporter.getOutput(testResult);
+    for (String line : output) {
+      if (line != null) {
+        xmlBuffer.push(XMLReporterConfig.TAG_LINE);
+        xmlBuffer.addCDATA(line);
+        xmlBuffer.pop();
+      }
+    }
+    xmlBuffer.pop();
+  }
+
+  private void addTestResultAttributes(XMLStringBuffer xmlBuffer, ITestResult testResult) {
+    if (testResult.getAttributeNames() != null && testResult.getAttributeNames().size() > 0) {
+      xmlBuffer.push(XMLReporterConfig.TAG_ATTRIBUTES);
+      for (String attrName: testResult.getAttributeNames()) {
+        if (attrName == null) {
+          continue;
+        }
+        Object attrValue = testResult.getAttribute(attrName);
+
+        Properties attributeAttrs = new Properties();
+        attributeAttrs.setProperty(XMLReporterConfig.ATTR_NAME, attrName);
+        if (attrValue == null) {
+          attributeAttrs.setProperty(XMLReporterConfig.ATTR_IS_NULL, "true");
+          xmlBuffer.addEmptyElement(XMLReporterConfig.TAG_ATTRIBUTE, attributeAttrs);
+        } else {
+          xmlBuffer.push(XMLReporterConfig.TAG_ATTRIBUTE, attributeAttrs);
+          xmlBuffer.addCDATA(attrValue.toString());
+          xmlBuffer.pop();
+        }
+      }
+      xmlBuffer.pop();
+    }
+  }
+
+}
diff --git a/src/main/java/org/testng/reporters/XMLUtils.java b/src/main/java/org/testng/reporters/XMLUtils.java
new file mode 100755
index 0000000..afe61af
--- /dev/null
+++ b/src/main/java/org/testng/reporters/XMLUtils.java
@@ -0,0 +1,145 @@
+package org.testng.reporters;
+
+import org.testng.internal.Nullable;
+
+import java.text.CharacterIterator;
+import java.text.StringCharacterIterator;
+import java.util.Map.Entry;
+import java.util.Properties;
+
+/**
+ * Static helpers for XML.
+ *
+ * @author Cedric Beust Jul 21, 2003
+ *
+ */
+public final class XMLUtils {
+
+  /** Platform specific end of line */
+  private static final String EOL = System.getProperty("line.separator");
+
+  private XMLUtils() {
+    // Hide constructor
+  }
+
+  /**
+   * Generate tag.
+   * An opening and closing tag will be generated even if value is null.
+   * @param name name of the tag
+   * @param content content for this tag (or null)
+   * @param attributes tag attributes (or null)
+   */
+  static public String xml(String indent,
+                           String name,
+                           @Nullable String content,
+                           @Nullable Properties attributes) {
+    IBuffer result = Buffer.create();
+    xmlOpen(result, indent, name, attributes, true /* no newline */);
+    if (content != null) {
+      result.append(content);
+    }
+    xmlClose(result, "", name, XMLUtils.extractComment(name, attributes));
+
+    return result.toString();
+  }
+
+  public static String extractComment(String tag, Properties properties) {
+    if (properties == null || "span".equals(tag)) return null;
+
+    String[] attributes = new String[] { "id", "name", "class" };
+    for (String a : attributes) {
+      String comment = properties.getProperty(a);
+      if (comment != null) {
+        return " <!-- " + comment.replaceAll("[-]{2,}", "-") + " -->";
+      }
+    }
+
+    return null;
+  }
+
+  public static void xmlOptional(IBuffer result, String sp,
+      String elementName, Boolean value, Properties attributes) {
+    if (null != value) {
+      xmlRequired(result, sp, elementName, value.toString(), attributes);
+    }
+  }
+
+  public static void xmlOptional(IBuffer result, String sp,
+      String elementName, @Nullable String value, Properties attributes) {
+    if (null != value) {
+      xmlRequired(result, sp, elementName, value, attributes);
+    }
+  }
+
+  public static void xmlRequired(IBuffer result, String sp,
+      String elementName, @Nullable String value, @Nullable Properties attributes) {
+    result.append(xml(sp, elementName, value, attributes));
+  }
+
+  public static void xmlOpen(IBuffer result, String indent, String tag,
+      Properties attributes) {
+    xmlOpen(result, indent, tag, attributes, false /* no newline */);
+  }
+
+  /**
+   * Appends the attributes to result. The attributes are added on a single line
+   * as: key1="value1" key2="value2" ... (a space is added before the first key)
+   *
+   * @param result
+   *          the buffer to append attributes to.
+   * @param attributes
+   *          the attributes to append (may be null).
+   */
+  public static void appendAttributes(IBuffer result, Properties attributes) {
+    if (null != attributes) {
+      for (Object element : attributes.entrySet()) {
+        Entry entry = (Entry) element;
+        String key = entry.getKey().toString();
+        String value = escape(entry.getValue().toString());
+        result.append(" ").append(key).append("=\"").append(value).append("\"");
+      }
+    }
+  }
+
+  public static void xmlOpen(IBuffer result, String indent, String tag,
+      Properties attributes, boolean noNewLine) {
+    result.append(indent).append("<").append(tag);
+    appendAttributes(result, attributes);
+    result.append(">");
+    if (!noNewLine) {
+      result.append(EOL);
+    }
+  }
+
+  public static void xmlClose(IBuffer result, String indent, String tag, String comment) {
+    result.append(indent).append("</").append(tag).append(">")
+        .append(comment != null ? comment : "")
+        .append(EOL);
+  }
+
+  public static String escape(String input) {
+    if (input == null) {
+      return null;
+    }
+    StringBuilder result = new StringBuilder();
+    StringCharacterIterator iterator = new StringCharacterIterator(input);
+    char character = iterator.current();
+    while (character != CharacterIterator.DONE) {
+      if (character == '<') {
+        result.append("&lt;");
+      } else if (character == '>') {
+        result.append("&gt;");
+      } else if (character == '\"') {
+        result.append("&quot;");
+      } else if (character == '\'') {
+        result.append("&#039;");
+      } else if (character == '&') {
+        result.append("&amp;");
+      } else {
+        result.append(character);
+      }
+      character = iterator.next();
+    }
+    return result.toString();
+  }
+}
diff --git a/src/main/java/org/testng/reporters/jq/BannerPanel.java b/src/main/java/org/testng/reporters/jq/BannerPanel.java
new file mode 100644
index 0000000..e55f213
--- /dev/null
+++ b/src/main/java/org/testng/reporters/jq/BannerPanel.java
@@ -0,0 +1,24 @@
+package org.testng.reporters.jq;
+
+import org.testng.reporters.XMLStringBuffer;
+
+public class BannerPanel extends BasePanel {
+
+  public BannerPanel(Model model) {
+    super(model);
+  }
+
+  @Override
+  public void generate(XMLStringBuffer xsb) {
+    xsb.push(D, C, "top-banner-root");
+    xsb.addRequired(S, "Test results", C, "top-banner-title-font");
+    xsb.addEmptyElement("br");
+    int failedCount = getModel().getAllFailedResults().size();
+    String testResult = failedCount > 0 ? ", " + pluralize(failedCount, "failed test") : "";
+    String subTitle = pluralize(getModel().getSuites().size(), "suite")
+        + testResult;
+    xsb.addRequired(S, subTitle, C, "top-banner-font-1");
+    xsb.pop(D);
+  }
+
+}
diff --git a/src/main/java/org/testng/reporters/jq/BaseMultiSuitePanel.java b/src/main/java/org/testng/reporters/jq/BaseMultiSuitePanel.java
new file mode 100644
index 0000000..f83f837
--- /dev/null
+++ b/src/main/java/org/testng/reporters/jq/BaseMultiSuitePanel.java
@@ -0,0 +1,41 @@
+package org.testng.reporters.jq;
+
+import org.testng.ISuite;
+import org.testng.reporters.XMLStringBuffer;
+
+abstract public class BaseMultiSuitePanel extends BasePanel implements INavigatorPanel {
+
+  abstract String getHeader(ISuite suite);
+  abstract String getContent(ISuite suite, XMLStringBuffer xsb);
+
+  public BaseMultiSuitePanel(Model model) {
+    super(model);
+  }
+
+  @Override
+  public void generate(XMLStringBuffer xsb) {
+    for (ISuite s : getSuites()) {
+      xsb.push(D, C, "panel", "panel-name", getPanelName(s));
+      xsb.push(D, C, "main-panel-header rounded-window-top");
+      xsb.addOptional(S, getHeader(s), C, "header-content");
+      xsb.pop(D);
+
+      xsb.push(D, C, "main-panel-content rounded-window-bottom");
+      xsb.addString(getContent(s, xsb));
+      xsb.pop(D);
+
+      xsb.pop(D);
+    }
+  }
+
+  @Override
+  public String getClassName() {
+    return null;
+  }
+
+  @Override
+  public String getPanelName(ISuite suite) {
+    return getPrefix() + suiteToTag(suite);
+  }
+
+}
diff --git a/src/main/java/org/testng/reporters/jq/BasePanel.java b/src/main/java/org/testng/reporters/jq/BasePanel.java
new file mode 100644
index 0000000..3d72922
--- /dev/null
+++ b/src/main/java/org/testng/reporters/jq/BasePanel.java
@@ -0,0 +1,36 @@
+package org.testng.reporters.jq;
+
+import org.testng.ISuite;
+
+import java.util.List;
+
+abstract public class BasePanel implements IPanel {
+  public static final String C = "class";
+  public static final String D = "div";
+  public static final String S = "span";
+
+  private Model m_model;
+
+  public BasePanel(Model model) {
+    m_model = model;
+  }
+
+  protected Model getModel() {
+    return m_model;
+  }
+
+  protected List<ISuite> getSuites() {
+    return getModel().getSuites();
+  }
+
+  protected static String pluralize(int count, String singular) {
+    return Integer.toString(count) + " " + (count == 0 || count > 1
+        ? (singular.endsWith("s") ? singular + "es" : singular + "s")
+        : singular);
+  }
+
+  protected static String suiteToTag(ISuite suite) {
+    return suite.getName().replace(" ", "_");
+  }
+
+}
diff --git a/src/main/java/org/testng/reporters/jq/ChronologicalPanel.java b/src/main/java/org/testng/reporters/jq/ChronologicalPanel.java
new file mode 100644
index 0000000..af5d491
--- /dev/null
+++ b/src/main/java/org/testng/reporters/jq/ChronologicalPanel.java
@@ -0,0 +1,101 @@
+package org.testng.reporters.jq;
+
+import org.testng.IInvokedMethod;
+import org.testng.ISuite;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+import org.testng.reporters.XMLStringBuffer;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class ChronologicalPanel extends BaseMultiSuitePanel {
+
+  public ChronologicalPanel(Model model) {
+    super(model);
+  }
+
+  @Override
+  public String getPrefix() {
+    return "chronological-";
+  }
+
+  @Override
+  public String getHeader(ISuite suite) {
+    return "Methods in chronological order";
+  }
+
+  @Override
+  public String getContent(ISuite suite, XMLStringBuffer main) {
+    XMLStringBuffer xsb = new XMLStringBuffer(main.getCurrentIndent());
+    List<IInvokedMethod> invokedMethods = suite.getAllInvokedMethods();
+
+    Collections.sort(invokedMethods, new Comparator<IInvokedMethod>() {
+      @Override
+      public int compare(IInvokedMethod arg0, IInvokedMethod arg1) {
+        return (int)
+            (arg0.getTestResult().getStartMillis() - arg1.getTestResult().getStartMillis());
+      }
+    });
+
+    String currentClass = "";
+    long start = 0;
+    for (IInvokedMethod im : invokedMethods) {
+      ITestNGMethod m = im.getTestMethod();
+//    for (ITestResult tr : results) {
+//      ITestNGMethod m = tr.getMethod();
+      String cls = "test-method";
+      if (m.isBeforeSuiteConfiguration()) {
+        cls = "configuration-suite before";
+      } else if (m.isAfterSuiteConfiguration()) {
+        cls = "configuration-suite after";
+      } else if (m.isBeforeTestConfiguration()) {
+        cls = "configuration-test before";
+      } else if (m.isAfterTestConfiguration()) {
+        cls = "configuration-test after";
+      } else if (m.isBeforeClassConfiguration()) {
+        cls = "configuration-class before";
+      } else if (m.isAfterClassConfiguration()) {
+        cls = "configuration-class after";
+      } else if (m.isBeforeMethodConfiguration()) {
+        cls = "configuration-method before";
+      } else if (m.isAfterMethodConfiguration()) {
+        cls = "configuration-method after";
+      }
+      ITestResult tr = im.getTestResult();
+      String methodName = Model.getTestResultName(tr);
+
+      if (!m.getTestClass().getName().equals(currentClass)) {
+        if (!"".equals(currentClass)) {
+          xsb.pop(D);
+        }
+        xsb.push(D, C, "chronological-class");
+        xsb.addRequired(D, m.getTestClass().getName(), C, "chronological-class-name");
+        currentClass = m.getTestClass().getName();
+      }
+      xsb.push(D, C, cls);
+      if (tr.getStatus() == ITestResult.FAILURE) {
+        xsb.push("img", "src", Model.getImage("failed"));
+        xsb.pop("img");
+      }
+
+      // No need to check for skipped methods since by definition, they were never
+      // invoked.
+
+      xsb.addRequired(S, methodName, C, "method-name");
+      if (start == 0) {
+        start = tr.getStartMillis();
+      }
+      xsb.addRequired(S, Long.toString(tr.getStartMillis() - start)  + " ms", C, "method-start");
+      xsb.pop(D);
+    }
+    return xsb.toXML();
+  }
+
+  @Override
+  public String getNavigatorLink(ISuite suite) {
+    return "Chronological view";
+  }
+
+}
diff --git a/src/main/java/org/testng/reporters/jq/GroupPanel.java b/src/main/java/org/testng/reporters/jq/GroupPanel.java
new file mode 100644
index 0000000..a003420
--- /dev/null
+++ b/src/main/java/org/testng/reporters/jq/GroupPanel.java
@@ -0,0 +1,49 @@
+package org.testng.reporters.jq;
+
+import org.testng.ISuite;
+import org.testng.reporters.XMLStringBuffer;
+
+import java.util.Collections;
+import java.util.List;
+
+public class GroupPanel extends BaseMultiSuitePanel {
+  public GroupPanel(Model model) {
+    super(model);
+  }
+
+  @Override
+  public String getPrefix() {
+    return "group-";
+  }
+
+  @Override
+  public String getHeader(ISuite suite) {
+    return "Groups for " + suite.getName();
+  }
+
+  @Override
+  public String getContent(ISuite suite, XMLStringBuffer main) {
+    XMLStringBuffer xsb = new XMLStringBuffer(main.getCurrentIndent());
+    List<String> sortedGroups = getModel().getGroups(suite.getName());
+    Collections.sort(sortedGroups);
+    for (String group : sortedGroups) {
+      xsb.push(D, C, "test-group");
+      xsb.addRequired(S, group, C, "test-group-name");
+      xsb.addEmptyElement("br");
+      List<String> sortedMethods = getModel().getMethodsInGroup(group);
+      for (String method : sortedMethods) {
+        xsb.push(D, C, "method-in-group");
+        xsb.addRequired(S, method, C, "method-in-group-name");
+        xsb.addEmptyElement("br");
+        xsb.pop(D);
+      }
+      xsb.pop(D);
+    }
+    return xsb.toXML();
+  }
+
+  @Override
+  public String getNavigatorLink(ISuite suite) {
+    return pluralize(getModel().getGroups(suite.getName()).size(), "group");
+  }
+}
diff --git a/src/main/java/org/testng/reporters/jq/INavigatorPanel.java b/src/main/java/org/testng/reporters/jq/INavigatorPanel.java
new file mode 100644
index 0000000..181953b
--- /dev/null
+++ b/src/main/java/org/testng/reporters/jq/INavigatorPanel.java
@@ -0,0 +1,13 @@
+package org.testng.reporters.jq;
+
+import org.testng.ISuite;
+
+/**
+ * Panels that are accessible from the navigator.
+ */
+public interface INavigatorPanel extends IPanel {
+  String getPanelName(ISuite suite);
+  String getNavigatorLink(ISuite suite);
+  String getClassName();
+  String getPrefix();
+}
diff --git a/src/main/java/org/testng/reporters/jq/IPanel.java b/src/main/java/org/testng/reporters/jq/IPanel.java
new file mode 100644
index 0000000..0fd1ca4
--- /dev/null
+++ b/src/main/java/org/testng/reporters/jq/IPanel.java
@@ -0,0 +1,7 @@
+package org.testng.reporters.jq;
+
+import org.testng.reporters.XMLStringBuffer;
+
+public interface IPanel {
+  void generate(XMLStringBuffer xsb);
+}
diff --git a/src/main/java/org/testng/reporters/jq/IgnoredMethodsPanel.java b/src/main/java/org/testng/reporters/jq/IgnoredMethodsPanel.java
new file mode 100644
index 0000000..41f35e3
--- /dev/null
+++ b/src/main/java/org/testng/reporters/jq/IgnoredMethodsPanel.java
@@ -0,0 +1,54 @@
+package org.testng.reporters.jq;
+
+import org.testng.ISuite;
+import org.testng.ITestNGMethod;
+import org.testng.collections.Maps;
+import org.testng.collections.SetMultiMap;
+import org.testng.reporters.XMLStringBuffer;
+
+public class IgnoredMethodsPanel extends BaseMultiSuitePanel {
+
+  public IgnoredMethodsPanel(Model model) {
+    super(model);
+  }
+
+
+  @Override
+  public String getPrefix() {
+    return "ignored-methods-";
+  }
+
+  @Override
+  public String getHeader(ISuite suite) {
+    return pluralize(suite.getExcludedMethods().size(), "ignored method");
+  }
+
+  @Override
+  public String getContent(ISuite suite, XMLStringBuffer main) {
+    XMLStringBuffer xsb = new XMLStringBuffer(main.getCurrentIndent());
+    SetMultiMap<Class<?>, ITestNGMethod> map = Maps.newSetMultiMap();
+
+    for (ITestNGMethod method : suite.getExcludedMethods()) {
+      map.put(method.getTestClass().getRealClass(), method);
+    }
+
+    for (Class<?> c : map.keySet()) {
+      xsb.push(D, C, "ignored-class-div");
+      xsb.addRequired(S, c.getName(), C, "ignored-class-name");
+      xsb.push(D, C, "ignored-methods-div");
+      for (ITestNGMethod m : map.get(c)) {
+        xsb.addRequired(S, m.getMethodName(), C, "ignored-method-name");
+        xsb.addEmptyElement("br");
+      }
+      xsb.pop(D);
+      xsb.pop(D);
+    }
+    return xsb.toXML();
+  }
+
+  @Override
+  public String getNavigatorLink(ISuite suite) {
+    return "Ignored methods";
+  }
+
+}
diff --git a/src/main/java/org/testng/reporters/jq/Main.java b/src/main/java/org/testng/reporters/jq/Main.java
new file mode 100644
index 0000000..ad2c4e0
--- /dev/null
+++ b/src/main/java/org/testng/reporters/jq/Main.java
@@ -0,0 +1,95 @@
+package org.testng.reporters.jq;
+
+import static org.testng.reporters.jq.BasePanel.C;
+import static org.testng.reporters.jq.BasePanel.D;
+
+import org.testng.IReporter;
+import org.testng.ISuite;
+import org.testng.internal.Utils;
+import org.testng.reporters.Files;
+import org.testng.reporters.XMLStringBuffer;
+import org.testng.xml.XmlSuite;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+
+public class Main implements IReporter {
+  private static final String[] RESOURCES = new String[] {
+    "jquery-1.7.1.min.js", "testng-reports.css", "testng-reports.js",
+    "passed.png", "failed.png", "skipped.png", "navigator-bullet.png",
+    "bullet_point.png", "collapseall.gif"
+  };
+
+  private Model m_model;
+  private String m_outputDirectory;
+
+  @Override
+  public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites,
+      String outputDirectory) {
+    m_model = new Model(suites);
+    m_outputDirectory = outputDirectory;
+
+    XMLStringBuffer xsb = new XMLStringBuffer("    ");
+
+    // Generate the top banner
+    new BannerPanel(m_model).generate(xsb);
+
+    // All the panels selectable from the navigator
+    List<INavigatorPanel> panels = Arrays.<INavigatorPanel>asList(
+        new TestNgXmlPanel(m_model),
+        new TestPanel(m_model),
+        new GroupPanel(m_model),
+        new TimesPanel(m_model),
+        new ReporterPanel(m_model),
+        new IgnoredMethodsPanel(m_model),
+        new ChronologicalPanel(m_model));
+
+    // Generate the navigator on the left hand side
+    new NavigatorPanel(m_model, panels).generate(xsb);
+
+    xsb.push(D, C, "wrapper");
+    xsb.push(D, "class", "main-panel-root");
+
+    //
+    // Generate the main suite panel
+    //
+    new SuitePanel(m_model).generate(xsb);
+
+    // Generate all the navigator panels
+    for (INavigatorPanel panel : panels) {
+      panel.generate(xsb);
+    }
+
+    xsb.pop(D); // main-panel-root
+    xsb.pop(D); // wrapper
+
+    xsb.addString("  </body>\n");
+    xsb.addString("</html>\n");
+
+    String all;
+    try {
+      try (InputStream header = getClass().getResourceAsStream("/header")) {
+        if (header == null) {
+          throw new RuntimeException("Couldn't find resource header");
+        }
+        for (String fileName : RESOURCES) {
+          try (InputStream is = getClass().getResourceAsStream("/" + fileName)) {
+            if (is == null) {
+              throw new AssertionError("Couldn't find resource: " + fileName);
+            }
+            Files.copyFile(is, new File(m_outputDirectory, fileName));
+          }
+        }
+        all = Files.readFile(header);
+        Utils.writeUtf8File(m_outputDirectory, "index.html", xsb, all); 
+      }
+    } catch (IOException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }
+  }
+
+}
diff --git a/src/main/java/org/testng/reporters/jq/Model.java b/src/main/java/org/testng/reporters/jq/Model.java
new file mode 100644
index 0000000..bf1daa2
--- /dev/null
+++ b/src/main/java/org/testng/reporters/jq/Model.java
@@ -0,0 +1,212 @@
+package org.testng.reporters.jq;
+
+import org.testng.IResultMap;
+import org.testng.ISuite;
+import org.testng.ISuiteResult;
+import org.testng.ITestContext;
+import org.testng.ITestResult;
+import org.testng.collections.ListMultiMap;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.collections.SetMultiMap;
+import org.testng.internal.Utils;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class Model {
+  private ListMultiMap<ISuite, ITestResult> m_model = Maps.newListMultiMap();
+  private List<ISuite> m_suites = null;
+  private Map<String, String> m_testTags = Maps.newHashMap();
+  private Map<ITestResult, String> m_testResultMap = Maps.newHashMap();
+  private Map<ISuite, ResultsByClass> m_failedResultsByClass = Maps.newHashMap();
+  private Map<ISuite, ResultsByClass> m_skippedResultsByClass = Maps.newHashMap();
+  private Map<ISuite, ResultsByClass> m_passedResultsByClass = Maps.newHashMap();
+  private List<ITestResult> m_allFailedResults = Lists.newArrayList();
+  // Each suite is mapped to failed.png, skipped.png or nothing (which means passed.png)
+  private Map<String, String> m_statusBySuiteName = Maps.newHashMap();
+  private SetMultiMap<String, String> m_groupsBySuiteName = Maps.newSetMultiMap();
+  private SetMultiMap<String, String> m_methodsByGroup = Maps.newSetMultiMap();
+
+  public Model(List<ISuite> suites) {
+    m_suites = suites;
+    init();
+  }
+
+  public List<ISuite> getSuites() {
+    return m_suites;
+  }
+
+  private void init() {
+    int testCounter = 0;
+    for (ISuite suite : m_suites) {
+      List<ITestResult> passed = Lists.newArrayList();
+      List<ITestResult> failed = Lists.newArrayList();
+      List<ITestResult> skipped = Lists.newArrayList();
+      for (ISuiteResult sr : suite.getResults().values()) {
+        ITestContext context = sr.getTestContext();
+        m_testTags.put(context.getName(), "test-" + testCounter++);
+        failed.addAll(context.getFailedTests().getAllResults());
+        skipped.addAll(context.getSkippedTests().getAllResults());
+        passed.addAll(context.getPassedTests().getAllResults());
+        IResultMap[] map = new IResultMap[] {
+            context.getFailedTests(),
+            context.getSkippedTests(),
+            context.getPassedTests()
+        };
+        for (IResultMap m : map) {
+          for (ITestResult tr : m.getAllResults()) {
+            m_testResultMap.put(tr, getTestResultName(tr));
+          }
+        }
+      }
+
+      // Process them in the order passed, skipped and failed, so that the failed
+      // icon overrides all the others and the skipped icon overrides passed.
+
+      // Passed
+      {
+        ResultsByClass rbc = new ResultsByClass();
+        for (ITestResult tr : passed) {
+          rbc.addResult(tr.getTestClass().getRealClass(), tr);
+          updateGroups(suite, tr);
+        }
+        m_passedResultsByClass.put(suite, rbc);
+      }
+
+      // Skipped
+      {
+        ResultsByClass rbc = new ResultsByClass();
+        for (ITestResult tr : skipped) {
+          m_statusBySuiteName.put(suite.getName(), "skipped");
+          rbc.addResult(tr.getTestClass().getRealClass(), tr);
+          updateGroups(suite, tr);
+        }
+        m_skippedResultsByClass.put(suite, rbc);
+      }
+
+      // Failed
+      {
+        ResultsByClass rbc = new ResultsByClass();
+        for (ITestResult tr : failed) {
+          m_statusBySuiteName.put(suite.getName(), "failed");
+          rbc.addResult(tr.getTestClass().getRealClass(), tr);
+          m_allFailedResults.add(tr);
+          updateGroups(suite, tr);
+        }
+        m_failedResultsByClass.put(suite, rbc);
+      }
+
+      m_model.putAll(suite, failed);
+      m_model.putAll(suite, skipped);
+      m_model.putAll(suite, passed);
+    }
+  }
+
+  private void updateGroups(ISuite suite, ITestResult tr) {
+    String[] groups = tr.getMethod().getGroups();
+    m_groupsBySuiteName.putAll(suite.getName(),
+        Arrays.asList(groups));
+    for (String group : groups) {
+      m_methodsByGroup.put(group, tr.getMethod().getMethodName());
+    }
+  }
+
+  public ResultsByClass getFailedResultsByClass(ISuite suite) {
+    return m_failedResultsByClass.get(suite);
+  }
+
+  public ResultsByClass getSkippedResultsByClass(ISuite suite) {
+    return m_skippedResultsByClass.get(suite);
+  }
+
+  public ResultsByClass getPassedResultsByClass(ISuite suite) {
+    return m_passedResultsByClass.get(suite);
+  }
+
+  public String getTag(ITestResult tr) {
+    return m_testResultMap.get(tr);
+  }
+
+  public List<ITestResult> getTestResults(ISuite suite) {
+    return nonnullList(m_model.get(suite));
+   }
+
+  public static String getTestResultName(ITestResult tr) {
+    StringBuilder result = new StringBuilder(tr.getMethod().getMethodName());
+    Object[] parameters = tr.getParameters();
+    if (parameters.length > 0) {
+      result.append("(");
+      StringBuilder p = new StringBuilder();
+      for (int i = 0; i < parameters.length; i++) {
+        if (i > 0) p.append(", ");
+        p.append(Utils.toString(parameters[i]));
+      }
+      if (p.length() > 100) {
+        String s = p.toString().substring(0, 100);
+        s = s + "...";
+        result.append(s);
+      } else {
+        result.append(p.toString());
+      }
+      result.append(")");
+    }
+
+    return result.toString();
+  }
+
+  public List<ITestResult> getAllFailedResults() {
+    return m_allFailedResults;
+  }
+
+  public static String getImage(String tagClass) {
+    return tagClass + ".png";
+  }
+
+  public String getStatusForSuite(String suiteName) {
+    String result = m_statusBySuiteName.get(suiteName);
+    return result != null ? result : "passed";
+  }
+
+  public <T> Set<T> nonnullSet(Set<T> l) {
+    return l != null ? l : Collections.<T>emptySet();
+  }
+
+  public <T> List<T> nonnullList(List<T> l) {
+    return l != null ? l : Collections.<T>emptyList();
+  }
+
+  public List<String> getGroups(String name) {
+    List<String> result = Lists.newArrayList(nonnullSet(m_groupsBySuiteName.get(name)));
+    Collections.sort(result);
+    return result;
+  }
+
+  public List<String> getMethodsInGroup(String groupName) {
+    List<String> result = Lists.newArrayList(nonnullSet(m_methodsByGroup.get(groupName)));
+    Collections.sort(result);
+    return result;
+  }
+
+  public List<ITestResult> getAllTestResults(ISuite suite) {
+    return getAllTestResults(suite, true /* tests only */);
+  }
+
+  public List<ITestResult> getAllTestResults(ISuite suite, boolean testsOnly) {
+    List<ITestResult> result = Lists.newArrayList();
+    for (ISuiteResult sr : suite.getResults().values()) {
+      result.addAll(sr.getTestContext().getPassedTests().getAllResults());
+      result.addAll(sr.getTestContext().getFailedTests().getAllResults());
+      result.addAll(sr.getTestContext().getSkippedTests().getAllResults());
+      if (! testsOnly) {
+        result.addAll(sr.getTestContext().getPassedConfigurations().getAllResults());
+        result.addAll(sr.getTestContext().getFailedConfigurations().getAllResults());
+        result.addAll(sr.getTestContext().getSkippedConfigurations().getAllResults());
+      }
+    }
+    return result;
+  }
+}
diff --git a/src/main/java/org/testng/reporters/jq/NavigatorPanel.java b/src/main/java/org/testng/reporters/jq/NavigatorPanel.java
new file mode 100644
index 0000000..b74d546
--- /dev/null
+++ b/src/main/java/org/testng/reporters/jq/NavigatorPanel.java
@@ -0,0 +1,253 @@
+package org.testng.reporters.jq;
+
+import org.testng.ISuite;
+import org.testng.ISuiteResult;
+import org.testng.ITestContext;
+import org.testng.ITestResult;
+import org.testng.collections.Lists;
+import org.testng.reporters.XMLStringBuffer;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class NavigatorPanel extends BasePanel {
+
+  private List<INavigatorPanel> m_panels;
+
+  public NavigatorPanel(Model model, List<INavigatorPanel> panels) {
+    super(model);
+    m_panels = panels;
+  }
+
+  @Override
+  public void generate(XMLStringBuffer main) {
+    main.push(D, C, "navigator-root");
+    main.push(D, C, "navigator-suite-header");
+    main.addRequired(S, "All suites");
+    main.push("a", C, "collapse-all-link", "href", "#", "title", "Collapse/expand all the suites");
+    main.push("img", "src", "collapseall.gif", C, "collapse-all-icon");
+    main.pop("img");
+    main.pop("a");
+    main.pop(D);
+    for (ISuite suite : getSuites()) {
+      if (suite.getResults().size() == 0) {
+        continue;
+      }
+
+      String suiteName = "suite-" + suiteToTag(suite);
+
+      XMLStringBuffer header = new XMLStringBuffer(main.getCurrentIndent());
+
+      Map<String, ISuiteResult> results = suite.getResults();
+      int failed = 0;
+      int skipped = 0;
+      int passed = 0;
+      for (ISuiteResult result : results.values()) {
+        ITestContext context = result.getTestContext();
+        failed += context.getFailedTests().size();
+        skipped += context.getSkippedTests().size();
+        passed += context.getPassedTests().size();
+      }
+
+      // Suite name in big font
+      header.push(D, C, "suite");
+      header.push(D, C, "rounded-window");
+      // Extra div so the highlighting logic will only highlight this line and not
+      // the entire container
+      header.push(D, C, "suite-header light-rounded-window-top");
+      header.push("a", "href", "#",
+          "panel-name", suiteName,
+          C, "navigator-link");
+      header.addOptional(S, suite.getName(),
+          C, "suite-name border-" + getModel().getStatusForSuite(suite.getName()));
+      header.pop("a");
+      header.pop(D);
+
+      header.push(D, C, "navigator-suite-content");
+
+      generateInfo(header, suite);
+      generateResult(header, failed, skipped, passed, suite, suiteName);
+
+      header.pop("ul");
+
+      header.pop(D); // suite-section-content
+      header.pop(D); // suite-header
+      header.pop(D); // suite
+
+      header.pop(D); // result-section
+
+      header.pop(D); // navigator-suite-content
+
+      main.addString(header.toXML());
+    }
+    main.pop(D);
+  }
+
+  private void generateResult(XMLStringBuffer header, int failed, int skipped, int passed,
+      ISuite suite, String suiteName) {
+    //
+    // Results
+    //
+    header.push(D, C, "result-section");
+
+    header.push(D, C, "suite-section-title");
+    header.addRequired(S, "Results");
+    header.pop(D);
+
+    // Method stats
+    int total = failed + skipped + passed;
+    String stats = String.format("%s, %s %s %s",
+        pluralize(total, "method"),
+        maybe(failed, "failed", ", "),
+        maybe(skipped, "skipped", ", "),
+        maybe(passed, "passed", ""));
+    header.push(D, C, "suite-section-content");
+    header.push("ul");
+    header.push("li");
+    header.addOptional(S, stats, C, "method-stats");
+    header.pop("li");
+
+    generateMethodList("Failed methods", new ResultsByStatus(suite, "failed", ITestResult.FAILURE),
+        suiteName, header);
+    generateMethodList("Skipped methods", new ResultsByStatus(suite, "skipped", ITestResult.SKIP),
+        suiteName, header);
+    generateMethodList("Passed methods", new ResultsByStatus(suite, "passed", ITestResult.SUCCESS),
+        suiteName, header);
+    }
+
+  private void generateInfo(XMLStringBuffer header, ISuite suite) {
+    //
+    // Info
+    //
+    header.push(D, C, "suite-section-title");
+    header.addRequired(S, "Info");
+    header.pop(D);
+
+    header.push(D, C, "suite-section-content");
+
+    header.push("ul");
+
+    // All the panels
+    for (INavigatorPanel panel : m_panels) {
+      addLinkTo(header, panel, suite);
+    }
+
+    header.pop("ul");
+    header.pop(D); // suite-section-content
+  }
+
+  private void addLinkTo(XMLStringBuffer header, INavigatorPanel panel, ISuite suite) {
+    String text = panel.getNavigatorLink(suite);
+    header.push("li");
+    header.push("a", "href", "#",
+        "panel-name", panel.getPanelName(suite),
+        C, "navigator-link ");
+    String className = panel.getClassName();
+    if (className != null) {
+      header.addOptional(S, text, C, className);
+    } else {
+      header.addOptional(S, text);
+    }
+    header.pop("a");
+    header.pop("li");
+  }
+
+  private static String maybe(int count, String s, String sep) {
+    return count > 0 ? count + " " + s + sep: "";
+  }
+
+  private List<ITestResult> getMethodsByStatus(ISuite suite, int status) {
+    List<ITestResult> result = Lists.newArrayList();
+    List<ITestResult> testResults = getModel().getTestResults(suite);
+    for (ITestResult tr : testResults) {
+      if (tr.getStatus() == status) {
+        result.add(tr);
+      }
+    }
+    Collections.sort(result, ResultsByClass.METHOD_NAME_COMPARATOR);
+
+    return result;
+  }
+
+  private static interface IResultProvider {
+    List<ITestResult> getResults();
+    String getType();
+  }
+
+  private abstract static class BaseResultProvider implements IResultProvider {
+    protected ISuite m_suite;
+    protected String m_type;
+    public BaseResultProvider(ISuite suite, String type) {
+      m_suite = suite;
+      m_type = type;
+    }
+
+    @Override
+    public String getType() {
+      return m_type;
+    }
+  }
+
+  private class ResultsByStatus extends BaseResultProvider {
+    private final int m_status;
+
+    public ResultsByStatus(ISuite suite, String type, int status) {
+      super(suite, type);
+      m_status = status;
+    }
+
+    @Override
+    public List<ITestResult> getResults() {
+      return getMethodsByStatus(m_suite, m_status);
+    }
+  }
+
+  private void generateMethodList(String name, IResultProvider provider,
+      String suiteName, XMLStringBuffer main) {
+    XMLStringBuffer xsb = new XMLStringBuffer(main.getCurrentIndent());
+    String type = provider.getType();
+    String image = Model.getImage(type);
+
+    xsb.push("li");
+
+    // The methods themselves
+    xsb.addRequired(S, name, C, "method-list-title " + type);
+
+    // The mark up to show the (hide)/(show) links
+    xsb.push(S, C, "show-or-hide-methods " + type);
+    xsb.addRequired("a", " (hide)", "href", "#", C, "hide-methods " + type + " " + suiteName,
+        "panel-name", suiteName);
+    xsb.addRequired("a", " (show)", "href", "#",C, "show-methods " + type + " " + suiteName,
+        "panel-name", suiteName);
+    xsb.pop(S);
+
+    // List of methods
+    xsb.push(D, C, "method-list-content " + type + " " + suiteName);
+    int count = 0;
+    List<ITestResult> testResults = provider.getResults();
+    if (testResults != null) {
+      Collections.sort(testResults, ResultsByClass.METHOD_NAME_COMPARATOR);
+      for (ITestResult tr : testResults) {
+        String testName = Model.getTestResultName(tr);
+        xsb.push(S);
+        xsb.addEmptyElement("img", "src", image, "width", "3%");
+        xsb.addRequired("a", testName, "href", "#",
+            "hash-for-method", getModel().getTag(tr),
+            "panel-name", suiteName,
+            "title", tr.getTestClass().getName(),
+            C, "method navigator-link");
+        xsb.pop(S);
+        xsb.addEmptyElement("br");
+        count++;
+      }
+    }
+    xsb.pop(D);
+    xsb.pop("li");
+
+    if (count > 0) {
+      main.addString(xsb.toXML());
+    }
+  }
+
+}
diff --git a/src/main/java/org/testng/reporters/jq/ReporterPanel.java b/src/main/java/org/testng/reporters/jq/ReporterPanel.java
new file mode 100644
index 0000000..fa2aa4d
--- /dev/null
+++ b/src/main/java/org/testng/reporters/jq/ReporterPanel.java
@@ -0,0 +1,54 @@
+package org.testng.reporters.jq;
+
+import org.testng.ISuite;
+import org.testng.ITestResult;
+import org.testng.Reporter;
+import org.testng.reporters.XMLStringBuffer;
+
+import java.util.List;
+
+/**
+ * Display the reporter output for each test result.
+ */
+public class ReporterPanel extends BaseMultiSuitePanel {
+
+  public ReporterPanel(Model model) {
+    super(model);
+  }
+
+
+  @Override
+  public String getPrefix() {
+    return "reporter-";
+  }
+
+  @Override
+  public String getHeader(ISuite suite) {
+    return "Reporter output for " + suite.getName();
+  }
+
+  @Override
+  public String getContent(ISuite suite, XMLStringBuffer main) {
+    XMLStringBuffer xsb = new XMLStringBuffer(main.getCurrentIndent());
+    for (ITestResult tr : getModel().getAllTestResults(suite)) {
+      List<String> lines = Reporter.getOutput(tr);
+      if (! lines.isEmpty()) {
+        xsb.push(D, C, "reporter-method-div");
+        xsb.addRequired(S, Model.getTestResultName(tr), C, "reporter-method-name");
+        xsb.push(D, C, "reporter-method-output-div");
+        for (String output : lines) {
+          xsb.addRequired(S, output, C, "reporter-method-output");
+        }
+        xsb.pop(D);
+        xsb.pop(D);
+      }
+    }
+    return xsb.toXML();
+  }
+
+  @Override
+  public String getNavigatorLink(ISuite suite) {
+    return "Reporter output";
+  }
+
+}
diff --git a/src/main/java/org/testng/reporters/jq/ResultsByClass.java b/src/main/java/org/testng/reporters/jq/ResultsByClass.java
new file mode 100644
index 0000000..de242fe
--- /dev/null
+++ b/src/main/java/org/testng/reporters/jq/ResultsByClass.java
@@ -0,0 +1,39 @@
+package org.testng.reporters.jq;
+
+import org.testng.ITestResult;
+import org.testng.collections.ListMultiMap;
+import org.testng.collections.Maps;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class ResultsByClass {
+  public static final Comparator<ITestResult> METHOD_NAME_COMPARATOR =
+      new Comparator<ITestResult>() {
+
+    @Override
+    public int compare(ITestResult arg0, ITestResult arg1) {
+      return arg0.getMethod().getMethodName().compareTo(
+          arg1.getMethod().getMethodName());
+    }
+
+  };
+
+  private ListMultiMap<Class<?>, ITestResult> m_results = Maps.newListMultiMap();
+
+  public void addResult(Class<?> c, ITestResult tr) {
+    m_results.put(c, tr);
+  }
+
+  public List<ITestResult> getResults(Class<?> c) {
+    List<ITestResult> result = m_results.get(c);
+    Collections.sort(result, METHOD_NAME_COMPARATOR);
+    return result;
+  }
+
+  public List<Class<?>> getClasses() {
+    // TODO do not use deprecated method
+    return m_results.getKeys();
+  }
+}
diff --git a/src/main/java/org/testng/reporters/jq/SuitePanel.java b/src/main/java/org/testng/reporters/jq/SuitePanel.java
new file mode 100644
index 0000000..f132507
--- /dev/null
+++ b/src/main/java/org/testng/reporters/jq/SuitePanel.java
@@ -0,0 +1,104 @@
+package org.testng.reporters.jq;
+
+import org.testng.ISuite;
+import org.testng.ITestResult;
+import org.testng.internal.Utils;
+import org.testng.reporters.XMLStringBuffer;
+import org.testng.util.Strings;
+
+import java.util.List;
+
+public class SuitePanel extends BasePanel {
+  private static final String PASSED = "passed";
+  private static final String SKIPPED = "skipped";
+  private static final String FAILED = "failed";
+
+  public SuitePanel(Model model) {
+    super(model);
+  }
+
+  @Override
+  public void generate(XMLStringBuffer xsb) {
+    for (ISuite suite : getSuites()) {
+      generateSuitePanel(suite, xsb);
+    }
+  }
+
+  private void generateSuitePanel(ISuite suite, XMLStringBuffer xsb) {
+    String divName = suiteToTag(suite);
+    xsb.push(D, C, "panel " + divName, "panel-name", "suite-" + divName);
+    String[] statuses = new String[] { FAILED, SKIPPED, PASSED };
+    ResultsByClass[] results = new ResultsByClass[] {
+        getModel().getFailedResultsByClass(suite),
+        getModel().getSkippedResultsByClass(suite),
+        getModel().getPassedResultsByClass(suite),
+    };
+
+    for (int i = 0; i < results.length; i++) {
+      ResultsByClass byClass = results[i];
+      for (Class<?> c : byClass.getClasses()) {
+        generateClassPanel(c, byClass.getResults(c), xsb, statuses[i], suite);
+      }
+    }
+    xsb.pop(D);
+  }
+
+  private void generateClassPanel(Class c, List<ITestResult> results, XMLStringBuffer xsb,
+      String status, ISuite suite) {
+    xsb.push(D, C, "suite-" + suiteToTag(suite) + "-class-" + status);
+    xsb.push(D, C, "main-panel-header rounded-window-top");
+
+    // Passed/failed icon
+    xsb.addEmptyElement("img", "src", Model.getImage(status));
+    xsb.addOptional(S, c.getName(), C, "class-name");
+    xsb.pop(D);
+
+    xsb.push(D, C, "main-panel-content rounded-window-bottom");
+
+    for (ITestResult tr : results) {
+      generateMethod(tr, xsb);
+    }
+    xsb.pop(D);
+    xsb.pop(D);
+  }
+
+  private void generateMethod(ITestResult tr, XMLStringBuffer xsb) {
+    xsb.push(D, C, "method");
+    xsb.push(D, C, "method-content");
+    xsb.push("a", "name", Model.getTestResultName(tr));
+    xsb.pop("a");
+    xsb.addOptional(S, tr.getMethod().getMethodName(), C, "method-name");
+
+    // Parameters?
+    if (tr.getParameters().length > 0) {
+      StringBuilder sb = new StringBuilder();
+      boolean first = true;
+      for (Object p : tr.getParameters()) {
+        if (!first) sb.append(", ");
+        first = false;
+        sb.append(Utils.toString(p));
+      }
+      xsb.addOptional(S, "(" + sb.toString() + ")", C, "parameters");
+    }
+
+    // Exception?
+    if (tr.getStatus() != ITestResult.SUCCESS && tr.getThrowable() != null) {
+      StringBuilder stackTrace = new StringBuilder();
+      stackTrace.append(Utils.stackTrace(tr.getThrowable(), true)[0]);
+      xsb.addOptional(D, stackTrace.toString() + "\n",
+          C, "stack-trace");
+    }
+
+    // Description?
+    String description = tr.getMethod().getDescription();
+    if (! Strings.isNullOrEmpty(description)) {
+        xsb.push("em");
+        xsb.addString("(" + description + ")");
+        xsb.pop("em");
+    }
+//    long time = tr.getEndMillis() - tr.getStartMillis();
+//    xsb.addOptional(S, " " + Long.toString(time) + " ms", C, "method-time");
+    xsb.pop(D);
+    xsb.pop(D);
+  }
+}
diff --git a/src/main/java/org/testng/reporters/jq/TestNgXmlPanel.java b/src/main/java/org/testng/reporters/jq/TestNgXmlPanel.java
new file mode 100644
index 0000000..2cf98fb
--- /dev/null
+++ b/src/main/java/org/testng/reporters/jq/TestNgXmlPanel.java
@@ -0,0 +1,40 @@
+package org.testng.reporters.jq;
+
+import org.testng.ISuite;
+import org.testng.internal.Utils;
+import org.testng.reporters.XMLStringBuffer;
+
+public class TestNgXmlPanel extends BaseMultiSuitePanel {
+
+  public TestNgXmlPanel(Model model) {
+    super(model);
+  }
+
+
+  @Override
+  public String getPrefix() {
+    return "test-xml-";
+  }
+
+  @Override
+  public String getHeader(ISuite suite) {
+    return suite.getXmlSuite().getFileName();
+  }
+
+  @Override
+  public String getContent(ISuite suite, XMLStringBuffer main) {
+    XMLStringBuffer xsb = new XMLStringBuffer(main.getCurrentIndent());
+    xsb.push("pre");
+    xsb.addString(Utils.escapeHtml(suite.getXmlSuite().toXml()));
+    xsb.pop("pre");
+    return xsb.toXML();
+  }
+
+  @Override
+  public String getNavigatorLink(ISuite suite) {
+    String fqName = suite.getXmlSuite().getFileName();
+    if (fqName == null) fqName = "/[unset file name]";
+    return fqName.substring(fqName.lastIndexOf("/") + 1);
+  }
+
+}
diff --git a/src/main/java/org/testng/reporters/jq/TestPanel.java b/src/main/java/org/testng/reporters/jq/TestPanel.java
new file mode 100644
index 0000000..676bf57
--- /dev/null
+++ b/src/main/java/org/testng/reporters/jq/TestPanel.java
@@ -0,0 +1,53 @@
+package org.testng.reporters.jq;
+
+import org.testng.ISuite;
+import org.testng.reporters.XMLStringBuffer;
+import org.testng.xml.XmlTest;
+
+/**
+ * Display the list of <test> tags.
+ */
+public class TestPanel extends BaseMultiSuitePanel {
+
+  public TestPanel(Model model) {
+    super(model);
+  }
+
+
+  @Override
+  public String getPrefix() {
+    return "testlist-";
+  }
+
+  @Override
+  public String getHeader(ISuite suite) {
+    return "Tests for " + suite.getName();
+  }
+
+  @Override
+  public String getContent(ISuite suite, XMLStringBuffer main) {
+    XMLStringBuffer xsb = new XMLStringBuffer(main.getCurrentIndent());
+
+    xsb.push("ul");
+    for (XmlTest test : suite.getXmlSuite().getTests()) {
+      xsb.push("li");
+      int count = test.getXmlClasses().size();
+      String name = test.getName() + " (" + pluralize(count, "class") + ")";
+      xsb.addRequired(S, name, C, "test-name");
+      xsb.pop("li");
+    }
+    xsb.pop("ul");
+
+    return xsb.toXML();
+  }
+
+  @Override
+  public String getNavigatorLink(ISuite suite) {
+    return pluralize(suite.getXmlSuite().getTests().size(), "test");
+  }
+
+  @Override
+  public String getClassName() {
+    return "test-stats";
+  }
+}
diff --git a/src/main/java/org/testng/reporters/jq/TimesPanel.java b/src/main/java/org/testng/reporters/jq/TimesPanel.java
new file mode 100644
index 0000000..b29d809
--- /dev/null
+++ b/src/main/java/org/testng/reporters/jq/TimesPanel.java
@@ -0,0 +1,122 @@
+package org.testng.reporters.jq;
+
+import org.testng.ISuite;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+import org.testng.collections.Maps;
+import org.testng.reporters.XMLStringBuffer;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+public class TimesPanel extends BaseMultiSuitePanel {
+  private Map<String, Long> m_totalTime = Maps.newHashMap();
+
+  public TimesPanel(Model model) {
+    super(model);
+  }
+
+
+  @Override
+  public String getPrefix() {
+    return "times-";
+  }
+
+  @Override
+  public String getHeader(ISuite suite) {
+    return "Times for " + suite.getName();
+  }
+
+  private String js(ISuite suite) {
+    String functionName = "tableData_" + suiteToTag(suite);
+    StringBuilder result = new StringBuilder(
+        "suiteTableInitFunctions.push('" + functionName + "');\n"
+          + "function " + functionName + "() {\n"
+          + "var data = new google.visualization.DataTable();\n"
+          + "data.addColumn('number', 'Number');\n"
+          + "data.addColumn('string', 'Method');\n"
+          + "data.addColumn('string', 'Class');\n"
+          + "data.addColumn('number', 'Time (ms)');\n");
+
+    List<ITestResult> allTestResults = getModel().getAllTestResults(suite);
+    result.append(
+      "data.addRows(" + allTestResults.size() + ");\n");
+
+    Collections.sort(allTestResults, new Comparator<ITestResult>() {
+      @Override
+      public int compare(ITestResult o1, ITestResult o2) {
+        long t1 = o1.getEndMillis() - o1.getStartMillis();
+        long t2 = o2.getEndMillis() - o2.getStartMillis();
+        return (int) (t2 - t1);
+      }
+    });
+
+    int index = 0;
+    for (ITestResult tr : allTestResults) {
+      ITestNGMethod m = tr.getMethod();
+      long time = tr.getEndMillis() - tr.getStartMillis();
+      result
+          .append("data.setCell(" + index + ", "
+              + "0, " + index + ")\n")
+          .append("data.setCell(" + index + ", "
+              + "1, '" + m.getMethodName() + "')\n")
+          .append("data.setCell(" + index + ", "
+              + "2, '" + m.getTestClass().getName() + "')\n")
+          .append("data.setCell(" + index + ", "
+              + "3, " + time + ");\n");
+      Long total = m_totalTime.get(suite.getName());
+      if (total == null) {
+        total = 0L;
+      }
+      m_totalTime.put(suite.getName(), total + time);
+      index++;
+    }
+
+    result.append(
+        "window.suiteTableData['" + suiteToTag(suite) + "']" +
+        		"= { tableData: data, tableDiv: 'times-div-" + suiteToTag(suite) + "'}\n"
+        + "return data;\n" +
+        "}\n");
+
+    return result.toString();
+  }
+
+  @Override
+  public String getContent(ISuite suite, XMLStringBuffer main) {
+    XMLStringBuffer xsb = new XMLStringBuffer(main.getCurrentIndent());
+    xsb.push(D, C, "times-div");
+    xsb.push("script", "type", "text/javascript");
+    xsb.addString(js(suite));
+    xsb.pop("script");
+    Long time = m_totalTime.get(suite.getName());
+    if (time != null) {
+      xsb.addRequired(S, String.format("Total running time: %s", prettyDuration(time)),
+          C, "suite-total-time");
+    }
+    xsb.push(D, "id", "times-div-" + suiteToTag(suite));
+    xsb.pop(D);
+    xsb.pop(D);
+    return xsb.toXML();
+  }
+
+  private String prettyDuration(long totalTime) {
+    String result;
+    if (totalTime < 1000) {
+      result = totalTime + " ms";
+    } else if (totalTime < 1000 * 60) {
+      result = (totalTime / 1000) + " seconds";
+    } else if (totalTime < 1000 * 60 * 60) {
+      result = (totalTime / 1000 / 60) + " minutes";
+    } else {
+      result = (totalTime / 1000 / 60 / 60) + " hours";
+    }
+    return result;
+  }
+
+  @Override
+  public String getNavigatorLink(ISuite suite) {
+    return "Times";
+  }
+}
diff --git a/src/main/java/org/testng/reporters/util/StackTraceTools.java b/src/main/java/org/testng/reporters/util/StackTraceTools.java
new file mode 100755
index 0000000..06170d8
--- /dev/null
+++ b/src/main/java/org/testng/reporters/util/StackTraceTools.java
@@ -0,0 +1,44 @@
+package org.testng.reporters.util;
+
+import org.testng.ITestNGMethod;
+
+/**
+ * Functionality to allow tools to analyse and subdivide stack traces.
+ *
+ * @author Paul Mendelson
+ * @since 5.3
+ * @version $Revision: 173 $
+ */
+public class StackTraceTools {
+  // ~ Methods --------------------------------------------------------------
+
+  /** Finds topmost position of the test method in the stack, or top of stack if <code>method</code> is not in it. */
+  public static int getTestRoot(StackTraceElement[] stack,ITestNGMethod method) {
+    if(stack!=null) {
+      String cname = method.getTestClass().getName();
+      for(int x=stack.length-1; x>=0; x--) {
+        if(cname.equals(stack[x].getClassName())
+            && method.getMethodName().equals(stack[x].getMethodName())) {
+          return x;
+        }
+      }
+      return stack.length-1;
+    } else {
+      return -1;
+    }
+  }
+
+  /** Finds topmost position of the test method in the stack, or top of stack if <code>method</code> is not in it. */
+  public static StackTraceElement[] getTestNGInstrastructure(StackTraceElement[] stack,ITestNGMethod method) {
+    int slot=StackTraceTools.getTestRoot(stack, method);
+    if(slot>=0) {
+      StackTraceElement[] r=new StackTraceElement[stack.length-slot];
+      for(int x=0; x<r.length; x++) {
+        r[x]=stack[x+slot];
+      }
+      return r;
+    } else {
+      return new StackTraceElement[0];
+    }
+  }
+}
diff --git a/src/main/java/org/testng/util/RetryAnalyzerCount.java b/src/main/java/org/testng/util/RetryAnalyzerCount.java
new file mode 100755
index 0000000..03949ff
--- /dev/null
+++ b/src/main/java/org/testng/util/RetryAnalyzerCount.java
@@ -0,0 +1,52 @@
+package org.testng.util;

+

+import java.util.concurrent.atomic.AtomicInteger;

+

+import org.testng.IRetryAnalyzer;

+import org.testng.ITestResult;

+

+/**

+ * An implementation of IRetryAnalyzer that allows you to specify

+ * the maximum number of times you want your test to be retried.

+ *

+ * @author tocman@gmail.com (Jeremie Lenfant-Engelmann)

+ */

+public abstract class RetryAnalyzerCount implements IRetryAnalyzer {

+

+  // Default retry once.

+  AtomicInteger count = new AtomicInteger(1);

+

+  /**

+   * Set the max number of time the method needs to be retried.

+   */

+  protected void setCount(int count) {

+    this.count.set(count);

+  }

+

+  /**

+   * Return the current counter value

+   */

+  protected int getCount(){

+      return this.count.get();

+  }

+

+  /**

+   * Retries the test if count is not 0.

+   * @param result The result of the test.

+   */

+  @Override

+  public boolean retry(ITestResult result) {

+    if (count.getAndDecrement() > 0) {

+      return retryMethod(result);

+    }

+    return false;

+  }

+

+  /**

+   * The method implemented by the class that test if the test

+   * must be retried or not.

+   * @param result The result of the test.

+   * @return true if the test must be retried, false otherwise.

+   */

+  public abstract boolean retryMethod(ITestResult result);

+}

diff --git a/src/main/java/org/testng/util/Strings.java b/src/main/java/org/testng/util/Strings.java
new file mode 100644
index 0000000..9a4bc99
--- /dev/null
+++ b/src/main/java/org/testng/util/Strings.java
@@ -0,0 +1,39 @@
+package org.testng.util;
+
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+
+import java.util.List;
+import java.util.Map;
+
+public class Strings {
+  public static boolean isNullOrEmpty(String string) {
+    return string == null || string.length() == 0; // string.isEmpty() in Java 6
+  }
+
+  private static List<String> ESCAPE_HTML_LIST = Lists.newArrayList(
+    "&", "&amp;",
+    "<", "&lt;",
+    ">", "&gt;"
+  );
+  
+  private static final Map<String, String> ESCAPE_HTML_MAP = Maps.newLinkedHashMap();
+
+  static {
+    for (int i = 0; i < ESCAPE_HTML_LIST.size(); i += 2) {
+      ESCAPE_HTML_MAP.put(ESCAPE_HTML_LIST.get(i), ESCAPE_HTML_LIST.get(i + 1));
+    }
+  }
+
+  public static String escapeHtml(String text) {
+    String result = text;
+    for (Map.Entry<String, String> entry : ESCAPE_HTML_MAP.entrySet()) {
+      result = result.replace(entry.getKey(), entry.getValue());
+    }
+    return result;
+  }
+
+  public static void main(String[] args) {
+    System.out.println(escapeHtml("10 < 20 && 30 > 20"));
+  }
+}
diff --git a/src/main/java/org/testng/xml/IFileParser.java b/src/main/java/org/testng/xml/IFileParser.java
new file mode 100644
index 0000000..e18bc48
--- /dev/null
+++ b/src/main/java/org/testng/xml/IFileParser.java
@@ -0,0 +1,11 @@
+package org.testng.xml;
+
+import org.testng.TestNGException;
+
+import java.io.InputStream;
+
+public interface IFileParser<T> {
+
+  T parse(String filePath, InputStream is, boolean loadClasses) throws TestNGException;
+
+}
diff --git a/src/main/java/org/testng/xml/IPostProcessor.java b/src/main/java/org/testng/xml/IPostProcessor.java
new file mode 100644
index 0000000..b682fee
--- /dev/null
+++ b/src/main/java/org/testng/xml/IPostProcessor.java
@@ -0,0 +1,14 @@
+package org.testng.xml;
+
+import java.util.Collection;
+
+/**
+ * Used by Parser to perform changes on an XML suite after it's been parsed.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public interface IPostProcessor {
+
+  Collection<XmlSuite> process(Collection<XmlSuite> suites);
+
+}
diff --git a/src/main/java/org/testng/xml/ISuiteParser.java b/src/main/java/org/testng/xml/ISuiteParser.java
new file mode 100644
index 0000000..c737898
--- /dev/null
+++ b/src/main/java/org/testng/xml/ISuiteParser.java
@@ -0,0 +1,6 @@
+package org.testng.xml;
+
+public interface ISuiteParser extends IFileParser<XmlSuite> {
+
+  boolean accept(String fileName);
+}
diff --git a/src/main/java/org/testng/xml/LaunchSuite.java b/src/main/java/org/testng/xml/LaunchSuite.java
new file mode 100755
index 0000000..7f456ed
--- /dev/null
+++ b/src/main/java/org/testng/xml/LaunchSuite.java
@@ -0,0 +1,439 @@
+package org.testng.xml;
+
+
+import static org.testng.internal.Utils.isStringNotBlank;
+
+import org.testng.collections.Lists;
+import org.testng.internal.Utils;
+import org.testng.log4testng.Logger;
+import org.testng.remote.RemoteTestNG;
+import org.testng.reporters.XMLStringBuffer;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * This class is used to encapsulate a launch. Various synthetic XML files are created
+ * depending on whether the user is trying to launch a suite, a class, a method, etc... 
+ */
+public abstract class LaunchSuite {
+  /** This class's log4testng Logger. */
+  private static final Logger LOGGER = Logger.getLogger(LaunchSuite.class);
+
+  protected boolean m_temporary;
+
+  /**
+   * Constructs a <code>LaunchSuite</code>
+   *
+   * @param isTemp the temporary status
+   */
+  protected LaunchSuite(boolean isTemp) {
+    m_temporary = isTemp;
+  }
+
+  /**
+   * Returns the temporary state.
+   * @return the temporary state.
+   */
+  public boolean isTemporary() {
+    return m_temporary;
+  }
+
+  /**
+   * Saves the suite file in the specified directory and returns the file
+   * pathname.
+   *
+   * @param directory the directory where the suite file is to be saved.
+   * @return the file pathname of the saved file.
+   */
+  public abstract File save(File directory);
+
+  public abstract XMLStringBuffer getSuiteBuffer();
+
+  /**
+   * <code>ExistingSuite</code> is a non-temporary LaunchSuite based on an existing
+   * file.
+   */
+  public static class ExistingSuite extends LaunchSuite {
+
+    /**
+     * The existing suite path (either relative to the project root or an absolute path)
+     */
+    private File m_suitePath;
+
+    /**
+     * Constructs a <code>ExistingSuite</code> based on an existing file
+     *
+     * @param path the path to the existing Launch suite.
+     */
+    public ExistingSuite(File path) {
+      super(false);
+
+      m_suitePath = path;
+    }
+
+    @Override
+    public XMLStringBuffer getSuiteBuffer() {
+      throw new UnsupportedOperationException("Not implemented yet");
+    }
+
+    /**
+     * Trying to run an existing XML file: copy its content to where the plug-in
+     * expects it.
+     */
+    @Override
+    public File save(File directory) {
+      if (RemoteTestNG.isDebug()) {
+        File result = new File(directory, RemoteTestNG.DEBUG_SUITE_FILE);
+        Utils.copyFile(m_suitePath, result);
+        return result;
+      } else {
+        return m_suitePath;
+      }
+    }
+  }
+
+  /**
+   * <code>CustomizedSuite</code> TODO cquezel JavaDoc.
+   */
+  private abstract static class CustomizedSuite extends LaunchSuite {
+    protected String m_projectName;
+    protected String m_suiteName;
+
+    /** The annotation type. May be null. */
+    protected Map<String, String> m_parameters;
+
+    /** The string buffer used to write the XML file. */
+    private XMLStringBuffer m_suiteBuffer;
+
+    /**
+     * Constructs a <code>CustomizedSuite</code> TODO cquezel JavaDoc.
+     *
+     * @param projectName
+     * @param className
+     * @param parameters
+     * @param annotationType
+     */
+    private CustomizedSuite(final String projectName,
+        final String className,
+        final Map<String, String> parameters,
+        final String annotationType)
+    {
+      super(true);
+
+      m_projectName = projectName;
+      m_suiteName = className;
+      m_parameters = parameters;
+    }
+
+    /**
+     * TODO cquezel JavaDoc
+     *
+     * @return
+     */
+    protected XMLStringBuffer createContentBuffer() {
+      XMLStringBuffer suiteBuffer = new XMLStringBuffer();
+      suiteBuffer.setDocType("suite SYSTEM \"" + Parser.TESTNG_DTD_URL + "\"");
+
+      Properties attrs = new Properties();
+      attrs.setProperty("parallel", XmlSuite.ParallelMode.NONE.toString());
+      attrs.setProperty("name", m_suiteName);
+      suiteBuffer.push("suite", attrs);
+
+      if (m_parameters != null) {
+        for (Map.Entry<String, String> entry : m_parameters.entrySet()) {
+          Properties paramAttrs = new Properties();
+          paramAttrs.setProperty("name", entry.getKey());
+          paramAttrs.setProperty("value", entry.getValue());
+          suiteBuffer.push("parameter", paramAttrs);
+          suiteBuffer.pop("parameter");
+        }
+      }
+
+      initContentBuffer(suiteBuffer);
+
+      suiteBuffer.pop("suite");
+
+      return suiteBuffer;
+    }
+
+    /**
+     * TODO cquezel JavaDoc
+     *
+     * @return
+     */
+    @Override
+    public XMLStringBuffer getSuiteBuffer() {
+      if (null == m_suiteBuffer) {
+        m_suiteBuffer = createContentBuffer();
+      }
+
+      return m_suiteBuffer;
+    }
+
+    /**
+     * Initializes the content of the xml string buffer.
+     *
+     * @param suiteBuffer the string buffer to initialize.
+     */
+    protected abstract void initContentBuffer(XMLStringBuffer suiteBuffer);
+
+    /**
+     * {@inheritDoc} This implementation saves the suite to the "temp-testng-customsuite.xml"
+     * file in the specified directory.
+     */
+    @Override
+    public File save(File directory) {
+      final File suiteFile = new File(directory, "temp-testng-customsuite.xml");
+
+      saveSuiteContent(suiteFile, getSuiteBuffer());
+
+      return suiteFile;
+    }
+
+    /**
+     * Saves the content of the string buffer to the specified file.
+     *
+     * @param file the file to write to.
+     * @param content the content to write to the file.
+     */
+    protected void saveSuiteContent(final File file, final XMLStringBuffer content) {
+
+      try {
+        OutputStreamWriter fw = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8"));
+        try {
+          fw.write(content.getStringBuffer().toString());
+        }
+        finally {
+          fw.close();
+        }
+      }
+      catch (IOException ioe) {
+        // TODO CQ is this normal to swallow exception here
+        LOGGER.error("IO Exception", ioe);
+      }
+    }
+  }
+
+  /**
+   * A <code>MethodsSuite</code> is a suite made up of methods.
+   */
+  static class MethodsSuite extends CustomizedSuite {
+    protected Collection<String> m_methodNames;
+    protected String m_className;
+    protected int m_logLevel;
+
+    /**
+     * Constructs a <code>MethodsSuite</code> TODO cquezel JavaDoc.
+     *
+     * @param projectName
+     * @param className
+     * @param methodNames
+     * @param parameters
+     * @param annotationType (may be null)
+     * @param logLevel
+     */
+    MethodsSuite(final String projectName,
+        final String className,
+        final Collection<String> methodNames,
+        final Map<String, String> parameters,
+        final String annotationType,
+        final int logLevel) {
+      super(projectName, className, parameters, annotationType);
+
+      m_className = className;
+      m_methodNames = methodNames;
+      m_logLevel = logLevel;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void initContentBuffer(XMLStringBuffer suiteBuffer) {
+      Properties testAttrs = new Properties();
+      testAttrs.setProperty("name", m_className);
+      testAttrs.setProperty("verbose", String.valueOf(m_logLevel));
+
+      suiteBuffer.push("test", testAttrs);
+
+      suiteBuffer.push("classes");
+
+      Properties classAttrs = new Properties();
+      classAttrs.setProperty("name", m_className);
+
+      if ((null != m_methodNames) && (m_methodNames.size() > 0)) {
+        suiteBuffer.push("class", classAttrs);
+
+        suiteBuffer.push("methods");
+
+        for (Object methodName : m_methodNames) {
+          Properties methodAttrs = new Properties();
+          methodAttrs.setProperty("name", (String) methodName);
+          suiteBuffer.addEmptyElement("include", methodAttrs);
+        }
+
+        suiteBuffer.pop("methods");
+        suiteBuffer.pop("class");
+      }
+      else {
+        suiteBuffer.addEmptyElement("class", classAttrs);
+      }
+      suiteBuffer.pop("classes");
+      suiteBuffer.pop("test");
+    }
+  }
+
+  static class ClassesAndMethodsSuite extends CustomizedSuite {
+    protected Map<String, Collection<String>> m_classes;
+    protected int m_logLevel;
+
+    ClassesAndMethodsSuite(final String projectName,
+        final Map<String, Collection<String>> classes,
+        final Map<String, String> parameters,
+        final String annotationType,
+        final int logLevel) {
+      super(projectName, "Custom suite", parameters, annotationType);
+      m_classes = classes;
+      m_logLevel = logLevel;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void initContentBuffer(XMLStringBuffer suiteBuffer) {
+      Properties testAttrs = new Properties();
+      testAttrs.setProperty("name", m_projectName);
+      testAttrs.setProperty("verbose", String.valueOf(m_logLevel));
+
+      suiteBuffer.push("test", testAttrs);
+
+      suiteBuffer.push("classes");
+
+      for(Map.Entry<String, Collection<String>> entry : m_classes.entrySet()) {
+        Properties classAttrs = new Properties();
+        classAttrs.setProperty("name", entry.getKey());
+
+        Collection<String> methodNames= sanitize(entry.getValue());
+        if ((null != methodNames) && (methodNames.size() > 0)) {
+          suiteBuffer.push("class", classAttrs);
+
+          suiteBuffer.push("methods");
+
+          for (String methodName : methodNames) {
+            Properties methodAttrs = new Properties();
+            methodAttrs.setProperty("name", methodName);
+            suiteBuffer.addEmptyElement("include", methodAttrs);
+          }
+
+          suiteBuffer.pop("methods");
+          suiteBuffer.pop("class");
+        }
+        else {
+          suiteBuffer.addEmptyElement("class", classAttrs);
+        }
+      }
+      suiteBuffer.pop("classes");
+      suiteBuffer.pop("test");
+    }
+
+    private Collection<String> sanitize(Collection<String> source) {
+      if(null == source) {
+        return null;
+      }
+
+      List<String> result= Lists.newArrayList();
+      for(String name: source) {
+        if(isStringNotBlank(name)) {
+          result.add(name);
+        }
+      }
+
+      return result;
+    }
+  }
+
+  /**
+   * <code>ClassListSuite</code> TODO cquezel JavaDoc.
+   */
+  static class ClassListSuite extends CustomizedSuite {
+    protected Collection<String> m_packageNames;
+    protected Collection<String> m_classNames;
+    protected Collection<String> m_groupNames;
+    protected int m_logLevel;
+
+    ClassListSuite(final String projectName,
+        final Collection<String> packageNames,
+        final Collection<String> classNames,
+        final Collection<String> groupNames,
+        final Map<String, String> parameters,
+        final String annotationType,
+        final int logLevel) {
+      super(projectName, "Custom suite", parameters, annotationType);
+
+      m_packageNames = packageNames;
+      m_classNames = classNames;
+      m_groupNames = groupNames;
+      m_logLevel = logLevel;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void initContentBuffer(XMLStringBuffer suiteBuffer) {
+      Properties testAttrs = new Properties();
+      testAttrs.setProperty("name", m_projectName);
+      testAttrs.setProperty("verbose", String.valueOf(m_logLevel));
+
+      suiteBuffer.push("test", testAttrs);
+
+      if (null != m_groupNames) {
+        suiteBuffer.push("groups");
+        suiteBuffer.push("run");
+
+        for (String groupName : m_groupNames) {
+          Properties includeAttrs = new Properties();
+          includeAttrs.setProperty("name", groupName);
+          suiteBuffer.addEmptyElement("include", includeAttrs);
+        }
+
+        suiteBuffer.pop("run");
+        suiteBuffer.pop("groups");
+      }
+
+      // packages belongs to suite according to the latest DTD
+      if ((m_packageNames != null) && (m_packageNames.size() > 0)) {
+        suiteBuffer.push("packages");
+
+        for (String packageName : m_packageNames) {
+          Properties packageAttrs = new Properties();
+          packageAttrs.setProperty("name", packageName);
+          suiteBuffer.addEmptyElement("package", packageAttrs);
+        }
+        suiteBuffer.pop("packages");
+      }
+
+      if ((m_classNames != null) && (m_classNames.size() > 0)) {
+        suiteBuffer.push("classes");
+
+        for (String className : m_classNames) {
+          Properties classAttrs = new Properties();
+          classAttrs.setProperty("name", className);
+          suiteBuffer.addEmptyElement("class", classAttrs);
+        }
+
+        suiteBuffer.pop("classes");
+      }
+      suiteBuffer.pop("test");
+    }
+  }
+}
diff --git a/src/main/java/org/testng/xml/Parameters.java b/src/main/java/org/testng/xml/Parameters.java
new file mode 100644
index 0000000..3c79901
--- /dev/null
+++ b/src/main/java/org/testng/xml/Parameters.java
@@ -0,0 +1,37 @@
+package org.testng.xml;
+
+import org.testng.collections.ListMultiMap;
+import org.testng.collections.Maps;
+
+import java.util.List;
+
+/**
+ * A holder class for parameters defined just in this tag and all parameters
+ * including the ones inherited from enclosing tags.
+ */
+public class Parameters {
+
+  private ListMultiMap<String, String> m_localParameters = Maps.newListMultiMap();
+  private ListMultiMap<String, String> m_allParameters = Maps.newListMultiMap();
+
+  public List<String> getLocalParameter(String name) {
+    return m_localParameters.get(name);
+  }
+
+  public List<String> getAllValues(String name) {
+    return m_allParameters.get(name);
+  }
+
+  public List<String> getValue(String name) {
+    return m_localParameters.get(name);
+  }
+
+  public void addLocalParameter(String name, String value) {
+    m_localParameters.put(name, value);
+    m_allParameters.put(name, value);
+  }
+
+  public void addAllParameter(String name, String value) {
+    m_allParameters.put(name, value);
+  }
+}
diff --git a/src/main/java/org/testng/xml/Parser.java b/src/main/java/org/testng/xml/Parser.java
new file mode 100755
index 0000000..5be433c
--- /dev/null
+++ b/src/main/java/org/testng/xml/Parser.java
@@ -0,0 +1,249 @@
+package org.testng.xml;
+
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+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;
+import java.util.ServiceLoader;
+
+/**
+ * <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 ISuiteParser DEFAULT_FILE_PARSER = new SuiteXmlParser();
+  private static final List<ISuiteParser> PARSERS = Lists.newArrayList(DEFAULT_FILE_PARSER);
+  static {
+    ServiceLoader<ISuiteParser> suiteParserLoader = ServiceLoader.load(ISuiteParser.class);
+    for (ISuiteParser parser : suiteParserLoader) {
+      PARSERS.add(parser);
+    }
+  }
+
+  /** 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 IPostProcessor m_postProcessor;
+
+  private boolean m_loadClasses = true;
+
+  /**
+   * 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.
+   */
+  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;
+  }
+
+  public void setPostProcessor(IPostProcessor processor) {
+    m_postProcessor = processor;
+  }
+
+  /**
+   * If false, don't try to load the classes during the parsing.
+   */
+  public void setLoadClasses(boolean loadClasses) {
+    m_loadClasses = loadClasses;
+  }
+
+  /**
+   * 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;
+//  }
+
+  private static IFileParser getParser(String fileName) {
+    for (ISuiteParser parser : PARSERS) {
+      if (parser.accept(fileName)) {
+        return parser;
+      }
+    }
+
+    return DEFAULT_FILE_PARSER;
+  }
+
+  /**
+   * 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;
+
+    List<String> toBeParsed = Lists.newArrayList();
+    List<String> toBeAdded = Lists.newArrayList();
+    List<String> toBeRemoved = Lists.newArrayList();
+
+    if (m_fileName != null) {
+      File mainFile = new File(m_fileName);
+      toBeParsed.add(mainFile.getCanonicalPath());
+    }
+
+    /*
+     * Keeps a track of parent XmlSuite for each child suite
+     */
+    Map<String, XmlSuite> childToParentMap = Maps.newHashMap();
+    while (toBeParsed.size() > 0) {
+
+      for (String currentFile : toBeParsed) {
+        File currFile = new File(currentFile);
+        File parentFile = currFile.getParentFile();
+        InputStream inputStream = m_inputStream != null
+            ? m_inputStream
+            : new FileInputStream(currentFile);
+
+        IFileParser<XmlSuite> fileParser = getParser(currentFile);
+        XmlSuite currentXmlSuite = fileParser.parse(currentFile, inputStream, m_loadClasses);
+        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);
+
+    boolean postProcess = true;
+
+    if (postProcess && m_postProcessor != null) {
+      return m_postProcessor.process(resultList);
+    } else {
+      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;
+  }
+
+
+
+}
+
diff --git a/src/main/java/org/testng/xml/ResultContentHandler.java b/src/main/java/org/testng/xml/ResultContentHandler.java
new file mode 100644
index 0000000..e1851c3
--- /dev/null
+++ b/src/main/java/org/testng/xml/ResultContentHandler.java
@@ -0,0 +1,152 @@
+package org.testng.xml;
+
+import static org.testng.reporters.XMLReporterConfig.ATTR_DESC;
+import static org.testng.reporters.XMLReporterConfig.ATTR_DURATION_MS;
+import static org.testng.reporters.XMLReporterConfig.ATTR_NAME;
+import static org.testng.reporters.XMLReporterConfig.ATTR_STATUS;
+import static org.testng.reporters.XMLReporterConfig.TAG_CLASS;
+import static org.testng.reporters.XMLReporterConfig.TAG_PARAMS;
+import static org.testng.reporters.XMLReporterConfig.TAG_SUITE;
+import static org.testng.reporters.XMLReporterConfig.TAG_TEST;
+import static org.testng.reporters.XMLReporterConfig.TAG_TEST_METHOD;
+
+import org.testng.ITestResult;
+import org.testng.collections.Lists;
+import org.testng.remote.strprotocol.GenericMessage;
+import org.testng.remote.strprotocol.IRemoteSuiteListener;
+import org.testng.remote.strprotocol.IRemoteTestListener;
+import org.testng.remote.strprotocol.MessageHelper;
+import org.testng.remote.strprotocol.SuiteMessage;
+import org.testng.remote.strprotocol.TestMessage;
+import org.testng.remote.strprotocol.TestResultMessage;
+import org.testng.reporters.XMLReporterConfig;
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.util.List;
+
+/**
+ * Parses testng-result.xml, create TestResultMessages and send them back to the listener
+ * as we encounter them.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public class ResultContentHandler extends DefaultHandler {
+  private int m_suiteMethodCount = 0;
+  private int m_testMethodCount = 0;
+  private SuiteMessage m_currentSuite;
+  private TestMessage m_currentTest;
+  private String m_className;
+  private int m_passed;
+  private int m_failed;
+  private int m_skipped;
+  private int m_invocationCount;
+  private int m_currentInvocationCount;
+  private TestResultMessage m_currentTestResult;
+  private IRemoteSuiteListener m_suiteListener;
+  private IRemoteTestListener m_testListener;
+  private List<String> m_params = null;
+
+  public ResultContentHandler(IRemoteSuiteListener suiteListener,
+      IRemoteTestListener testListener, boolean resolveClasses /* ignored */) {
+    m_suiteListener = suiteListener;
+    m_testListener = testListener;
+  }
+
+  @Override
+  public void startElement (String uri, String localName,
+      String qName, Attributes attributes) {
+    p("Start " + qName);
+    if (TAG_SUITE.equals(qName)) {
+      m_suiteListener.onInitialization(new GenericMessage(MessageHelper.GENERIC_SUITE_COUNT));
+      m_suiteMethodCount = 0;
+      m_currentSuite = new SuiteMessage(attributes.getValue(ATTR_NAME),
+          true /* start */, m_suiteMethodCount);
+      m_suiteListener.onStart(m_currentSuite);
+    } else if (TAG_TEST.equals(qName)) {
+      m_passed = m_failed = m_skipped = 0;
+      m_currentTest = new TestMessage(true /* start */, m_currentSuite.getSuiteName(),
+          attributes.getValue(ATTR_NAME), m_testMethodCount,
+          m_passed, m_failed, m_skipped, 0);
+      m_testListener.onStart(m_currentTest);
+    } else if (TAG_CLASS.equals(qName)) {
+      m_className = attributes.getValue(ATTR_NAME);
+    } else if (TAG_TEST_METHOD.equals(qName)) {
+      Integer status = XMLReporterConfig.getStatus(attributes.getValue(ATTR_STATUS));
+      m_currentTestResult = new TestResultMessage(status, m_currentSuite.getSuiteName(),
+          m_currentTest.getTestName(), m_className, attributes.getValue(ATTR_NAME),
+          attributes.getValue(ATTR_DESC),
+          attributes.getValue(ATTR_DESC),
+          new String[0], /* no parameters, filled later */
+          0, Long.parseLong(attributes.getValue(ATTR_DURATION_MS)),
+          "" /* stack trace, filled later */,
+          m_invocationCount, m_currentInvocationCount);
+      m_suiteMethodCount++;
+      m_testMethodCount++;
+      if (status == ITestResult.SUCCESS) m_passed++;
+      else if (status == ITestResult.FAILURE) m_failed++;
+      else if (status == ITestResult.SKIP) m_skipped++;
+    } else if (TAG_PARAMS.equals(qName)) {
+      m_params = Lists.newArrayList();
+    }
+  }
+
+  @Override
+  public void characters(char[] ch, int start, int length) {
+    if (m_params != null) {
+      String string = new String(ch, start, length);
+      String parameter = string;
+      if (parameter.trim().length() != 0) {
+        m_params.add(parameter);
+      }
+    }
+  }
+
+  @Override
+  public void endElement (String uri, String localName, String qName) {
+    if (TAG_SUITE.equals(qName)) {
+      m_suiteListener.onFinish(new SuiteMessage(null, false /* end */, m_suiteMethodCount));
+      m_currentSuite = null;
+    } else if (TAG_TEST.equals(qName)) {
+      m_currentTest = new TestMessage(false /* start */, m_currentSuite.getSuiteName(),
+          null, m_testMethodCount,
+          m_passed, m_failed, m_skipped, 0);
+      m_testMethodCount = 0;
+      m_testListener.onFinish(m_currentTest);
+    } else if (TAG_CLASS.equals(qName)) {
+      m_className = null;
+    } else if (TAG_TEST_METHOD.equals(qName)) {
+      switch(m_currentTestResult.getResult()) {
+      case ITestResult.SUCCESS:
+        m_testListener.onTestSuccess(m_currentTestResult);
+        break;
+      case ITestResult.FAILURE:
+        m_testListener.onTestFailure(m_currentTestResult);
+        break;
+      case ITestResult.SKIP:
+        m_testListener.onTestSkipped(m_currentTestResult);
+        break;
+      default:
+       p("Ignoring test status:" + m_currentTestResult.getResult());
+      }
+    }
+    else if (TAG_PARAMS.equals(qName)) {
+      String[] params = new String[m_params.size()];
+      for (int i = 0; i < m_params.size(); i++) {
+        // The parameters are encoded  as type:value. Since we only care about the
+        // value (and we don't receive the type anyway), use a dummy character in
+        // its place
+        params[i] = "@:" + m_params.get(i);
+      }
+      m_currentTestResult.setParameters(params);
+      m_params = null;
+    }
+  }
+
+  private static void p(String string) {
+    if (false) {
+      System.out.println("[ResultContentHandler] " + string);
+    }
+  }
+}
+
diff --git a/src/main/java/org/testng/xml/ResultXMLParser.java b/src/main/java/org/testng/xml/ResultXMLParser.java
new file mode 100644
index 0000000..b3f5d14
--- /dev/null
+++ b/src/main/java/org/testng/xml/ResultXMLParser.java
@@ -0,0 +1,103 @@
+package org.testng.xml;
+
+import org.testng.TestNGException;
+import org.testng.remote.strprotocol.GenericMessage;
+import org.testng.remote.strprotocol.IRemoteSuiteListener;
+import org.testng.remote.strprotocol.IRemoteTestListener;
+import org.testng.remote.strprotocol.SuiteMessage;
+import org.testng.remote.strprotocol.TestMessage;
+import org.testng.remote.strprotocol.TestResultMessage;
+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;
+
+/**
+ * Parses testng-result.xml.
+ *
+ * @see ResultContentHandler
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public class ResultXMLParser extends XMLParser<Object> {
+  private IRemoteTestListener m_testListener;
+  private IRemoteSuiteListener m_suiteListener;
+
+  public ResultXMLParser(IRemoteSuiteListener suiteListener, IRemoteTestListener testListener) {
+    m_suiteListener = suiteListener;
+    m_testListener = testListener;
+  }
+
+  public void parse() {
+  }
+
+  @Override
+  public Object parse(String currentFile, InputStream inputStream, boolean loadClasses) {
+    ResultContentHandler handler = new ResultContentHandler(m_suiteListener, m_testListener,
+        loadClasses);
+
+    try {
+      parse(inputStream, handler);
+
+      return null;
+    } catch (SAXException | IOException e) {
+      throw new TestNGException(e);
+    }
+  }
+
+  public static void main(String[] args) throws FileNotFoundException {
+    IRemoteSuiteListener l1 = new IRemoteSuiteListener() {
+
+      @Override
+      public void onInitialization(GenericMessage genericMessage) {
+      }
+
+      @Override
+      public void onStart(SuiteMessage suiteMessage) {
+      }
+
+      @Override
+      public void onFinish(SuiteMessage suiteMessage) {
+      }
+
+    };
+
+    IRemoteTestListener l2 = new IRemoteTestListener() {
+
+      @Override
+      public void onStart(TestMessage tm) {
+      }
+
+      @Override
+      public void onFinish(TestMessage tm) {
+      }
+
+      @Override
+      public void onTestStart(TestResultMessage trm) {
+      }
+
+      @Override
+      public void onTestSuccess(TestResultMessage trm) {
+      }
+
+      @Override
+      public void onTestFailure(TestResultMessage trm) {
+      }
+
+      @Override
+      public void onTestSkipped(TestResultMessage trm) {
+      }
+
+      @Override
+      public void onTestFailedButWithinSuccessPercentage(TestResultMessage trm) {
+      }
+
+    };
+    ResultXMLParser parser = new ResultXMLParser(l1, l2);
+    String fileName = "/Users/cbeust/java/testng/test-output/testng-results.xml";
+    parser.parse(fileName, new FileInputStream(new File(fileName)), false /* don't load classes */);
+  }
+}
diff --git a/src/main/java/org/testng/xml/SuiteGenerator.java b/src/main/java/org/testng/xml/SuiteGenerator.java
new file mode 100755
index 0000000..c887084
--- /dev/null
+++ b/src/main/java/org/testng/xml/SuiteGenerator.java
@@ -0,0 +1,73 @@
+package org.testng.xml;
+
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Factory to create custom suites.
+ * @author Hani Suleiman
+ *         Date: Jul 25, 2005
+ *         Time: 1:12:18 PM
+ */
+public class SuiteGenerator {
+  private static final Collection<String> EMPTY_CLASS_LIST= Collections.emptyList();
+
+  public static LaunchSuite createProxiedXmlSuite(final File xmlSuitePath) {
+    return new LaunchSuite.ExistingSuite(xmlSuitePath);
+  }
+
+  public static LaunchSuite createSuite(String projectName, Collection<String> packageNames,
+      Map<String, Collection<String>> classAndMethodNames, Collection<String> groupNames,
+      Map<String, String> parameters, String annotationType, int logLevel) {
+
+    LaunchSuite result;
+    Collection<String> classes = classAndMethodNames != null ? classAndMethodNames.keySet()
+        : EMPTY_CLASS_LIST;
+    if ((null != groupNames) && !groupNames.isEmpty()) {
+      //
+      // Create a suite from groups
+      //
+      result = new LaunchSuite.ClassListSuite(projectName, packageNames, classes, groupNames,
+          parameters, annotationType, logLevel);
+    } else if (packageNames != null && packageNames.size() > 0) {
+      //
+      // Create a suite from packages
+      //
+      result = new LaunchSuite.ClassListSuite(projectName, packageNames, classes, groupNames,
+          parameters, annotationType, logLevel);
+    } else {
+      //
+      // Default suite creation
+      //
+      result = new LaunchSuite.ClassesAndMethodsSuite(projectName, classAndMethodNames, parameters,
+          annotationType, logLevel);
+    }
+
+    return result;
+  }
+
+  /**
+   * @deprecated use {@link #createSuite(String, java.util.Collection, java.util.Map,
+   * java.util.Collection, java.util.Map, String, int)} instead.
+   */
+  @Deprecated
+  public static LaunchSuite createCustomizedSuite(String projectName,
+      Collection<String> packageNames, Collection<String> classNames,
+      Collection<String> methodNames, Collection<String> groupNames,
+      Map<String, String> parameters, String annotationType, int logLevel) {
+    if ((null != groupNames) && !groupNames.isEmpty()) {
+      return new LaunchSuite.ClassListSuite(projectName, packageNames, classNames, groupNames,
+          parameters, annotationType, logLevel);
+    } else if ((classNames != null && classNames.size() > 1) || packageNames != null
+        && packageNames.size() > 0) {
+      return new LaunchSuite.ClassListSuite(projectName, packageNames, classNames, groupNames,
+          parameters, annotationType, logLevel);
+    } else {
+      return new LaunchSuite.MethodsSuite(projectName, classNames.iterator().next(), methodNames,
+          parameters, annotationType, logLevel);
+    }
+  }
+}
diff --git a/src/main/java/org/testng/xml/SuiteXmlParser.java b/src/main/java/org/testng/xml/SuiteXmlParser.java
new file mode 100644
index 0000000..5f47188
--- /dev/null
+++ b/src/main/java/org/testng/xml/SuiteXmlParser.java
@@ -0,0 +1,28 @@
+package org.testng.xml;
+
+import org.testng.TestNGException;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class SuiteXmlParser extends XMLParser<XmlSuite> implements ISuiteParser {
+
+  @Override
+  public XmlSuite parse(String currentFile, InputStream inputStream, boolean loadClasses) {
+    TestNGContentHandler contentHandler = new TestNGContentHandler(currentFile, loadClasses);
+
+    try {
+      parse(inputStream, contentHandler);
+
+      return contentHandler.getSuite();
+    } catch (SAXException | IOException e) {
+      throw new TestNGException(e);
+    }
+  }
+
+  @Override
+  public boolean accept(String fileName) {
+    return fileName.endsWith(".xml");
+  }
+}
diff --git a/src/main/java/org/testng/xml/TestNGContentHandler.java b/src/main/java/org/testng/xml/TestNGContentHandler.java
new file mode 100755
index 0000000..a4bf2fb
--- /dev/null
+++ b/src/main/java/org/testng/xml/TestNGContentHandler.java
@@ -0,0 +1,814 @@
+package org.testng.xml;
+
+import static org.testng.internal.Utils.isStringBlank;
+
+import org.testng.ITestObjectFactory;
+import org.testng.TestNGException;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.internal.Utils;
+import org.testng.log4testng.Logger;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+/**
+ * Suite definition parser utility.
+ *
+ * @author Cedric Beust
+ * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a>
+ */
+public class TestNGContentHandler extends DefaultHandler {
+  private XmlSuite m_currentSuite = null;
+  private XmlTest m_currentTest = null;
+  private List<String> m_currentDefines = null;
+  private List<String> m_currentRuns = null;
+  private List<XmlClass> m_currentClasses = null;
+  private int m_currentTestIndex = 0;
+  private int m_currentClassIndex = 0;
+  private int m_currentIncludeIndex = 0;
+  private List<XmlPackage> m_currentPackages = null;
+  private XmlPackage m_currentPackage = null;
+  private List<XmlSuite> m_suites = Lists.newArrayList();
+  private List<String> m_currentIncludedGroups = null;
+  private List<String> m_currentExcludedGroups = null;
+  private Map<String, String> m_currentTestParameters = null;
+  private Map<String, String> m_currentSuiteParameters = null;
+  private Map<String, String> m_currentClassParameters = null;
+  private Include m_currentInclude;
+  private List<String> m_currentMetaGroup = null;
+  private String m_currentMetaGroupName;
+
+  enum Location {
+    SUITE,
+    TEST,
+    CLASS,
+    INCLUDE,
+    EXCLUDE
+  }
+  private Stack<Location> m_locations = new Stack<>();
+
+  private XmlClass m_currentClass = null;
+  private ArrayList<XmlInclude> m_currentIncludedMethods = null;
+  private List<String> m_currentExcludedMethods = null;
+  private ArrayList<XmlMethodSelector> m_currentSelectors = null;
+  private XmlMethodSelector m_currentSelector = null;
+  private String m_currentLanguage = null;
+  private String m_currentExpression = null;
+  private List<String> m_suiteFiles = Lists.newArrayList();
+  private boolean m_enabledTest;
+  private List<String> m_listeners;
+
+  private String m_fileName;
+  private boolean m_loadClasses;
+  private boolean m_validate = false;
+  private boolean m_hasWarn = false;
+
+  public TestNGContentHandler(String fileName, boolean loadClasses) {
+    m_fileName = fileName;
+    m_loadClasses = loadClasses;
+  }
+
+  static private void ppp(String s) {
+    System.out.println("[TestNGContentHandler] " + s);
+  }
+
+  /*
+   * (non-Javadoc)
+   *
+   * @see org.xml.sax.EntityResolver#resolveEntity(java.lang.String,
+   *      java.lang.String)
+   */
+  @Override
+  public InputSource resolveEntity(String systemId, String publicId)
+      throws IOException, SAXException {
+    InputSource result = null;
+    if (Parser.DEPRECATED_TESTNG_DTD_URL.equals(publicId)
+        || Parser.TESTNG_DTD_URL.equals(publicId)) {
+      m_validate = true;
+      InputStream is = getClass().getClassLoader().getResourceAsStream(Parser.TESTNG_DTD);
+      if (null == is) {
+        is = Thread.currentThread().getContextClassLoader().getResourceAsStream(Parser.TESTNG_DTD);
+        if (null == is) {
+          System.out.println("WARNING: couldn't find in classpath " + publicId
+              + "\n" + "Fetching it from the Web site.");
+          result = super.resolveEntity(systemId, publicId);
+        }
+        else {
+          result = new InputSource(is);
+        }
+      }
+      else {
+        result = new InputSource(is);
+      }
+    }
+    else {
+      result = super.resolveEntity(systemId, publicId);
+    }
+
+    return result;
+  }
+
+  /**
+   * Parse <suite-file>
+   */
+  private void xmlSuiteFile(boolean start, Attributes attributes) {
+    if (start) {
+      String path = attributes.getValue("path");
+      pushLocation(Location.SUITE);
+      m_suiteFiles.add(path);
+    }
+    else {
+      m_currentSuite.setSuiteFiles(m_suiteFiles);
+      popLocation(Location.SUITE);
+    }
+  }
+
+  /**
+   * Parse <suite>
+   */
+  private void xmlSuite(boolean start, Attributes attributes) {
+    if (start) {
+      pushLocation(Location.SUITE);
+      String name = attributes.getValue("name");
+      if (isStringBlank(name)) {
+        throw new TestNGException("The <suite> tag must define the name attribute");
+      }
+      m_currentSuite = new XmlSuite();
+      m_currentSuite.setFileName(m_fileName);
+      m_currentSuite.setName(name);
+      m_currentSuiteParameters = Maps.newHashMap();
+
+      String verbose = attributes.getValue("verbose");
+      if (null != verbose) {
+        m_currentSuite.setVerbose(Integer.parseInt(verbose));
+      }
+      String jUnit = attributes.getValue("junit");
+      if (null != jUnit) {
+        m_currentSuite.setJUnit(Boolean.valueOf(jUnit));
+      }
+      String parallel = attributes.getValue("parallel");
+      if (parallel != null) {
+        XmlSuite.ParallelMode mode = XmlSuite.ParallelMode.getValidParallel(parallel);
+        if (mode != null) {
+          m_currentSuite.setParallel(mode);
+        } else {
+          Utils.log("Parser", 1, "[WARN] Unknown value of attribute 'parallel' at suite level: '" + parallel + "'.");
+        }
+      }
+      String parentModule = attributes.getValue("parent-module");
+      if (parentModule != null) {
+        m_currentSuite.setParentModule(parentModule);
+      }
+      String guiceStage = attributes.getValue("guice-stage");
+      if (guiceStage != null) {
+        m_currentSuite.setGuiceStage(guiceStage);
+      }
+      String configFailurePolicy = attributes.getValue("configfailurepolicy");
+      if (null != configFailurePolicy) {
+        if (XmlSuite.SKIP.equals(configFailurePolicy) || XmlSuite.CONTINUE.equals(configFailurePolicy)) {
+          m_currentSuite.setConfigFailurePolicy(configFailurePolicy);
+        }
+      }
+      String groupByInstances = attributes.getValue("group-by-instances");
+      if (groupByInstances!= null) {
+        m_currentSuite.setGroupByInstances(Boolean.valueOf(groupByInstances));
+      }
+      String skip = attributes.getValue("skipfailedinvocationcounts");
+      if (skip != null) {
+        m_currentSuite.setSkipFailedInvocationCounts(Boolean.valueOf(skip));
+      }
+      String threadCount = attributes.getValue("thread-count");
+      if (null != threadCount) {
+        m_currentSuite.setThreadCount(Integer.parseInt(threadCount));
+      }
+      String dataProviderThreadCount = attributes.getValue("data-provider-thread-count");
+      if (null != dataProviderThreadCount) {
+        m_currentSuite.setDataProviderThreadCount(Integer.parseInt(dataProviderThreadCount));
+      }
+      String timeOut = attributes.getValue("time-out");
+      if (null != timeOut) {
+        m_currentSuite.setTimeOut(timeOut);
+      }
+      String objectFactory = attributes.getValue("object-factory");
+      if (null != objectFactory && m_loadClasses) {
+        try {
+          m_currentSuite.setObjectFactory((ITestObjectFactory)Class.forName(objectFactory).newInstance());
+        }
+        catch(Exception e) {
+          Utils.log("Parser", 1, "[ERROR] Unable to create custom object factory '" + objectFactory + "' :" + e);
+        }
+      }
+      String preserveOrder = attributes.getValue("preserve-order");
+      if (preserveOrder != null) {
+        m_currentSuite.setPreserveOrder(preserveOrder);
+      }
+      String allowReturnValues = attributes.getValue("allow-return-values");
+      if (allowReturnValues != null) {
+        m_currentSuite.setAllowReturnValues(Boolean.valueOf(allowReturnValues));
+      }
+    }
+    else {
+      m_currentSuite.setParameters(m_currentSuiteParameters);
+      m_suites.add(m_currentSuite);
+      m_currentSuiteParameters = null;
+      popLocation(Location.SUITE);
+    }
+  }
+
+  /**
+   * Parse <define>
+   */
+  private void xmlDefine(boolean start, Attributes attributes) {
+    if (start) {
+      String name = attributes.getValue("name");
+      m_currentDefines = Lists.newArrayList();
+      m_currentMetaGroup = Lists.newArrayList();
+      m_currentMetaGroupName = name;
+    }
+    else {
+      m_currentTest.addMetaGroup(m_currentMetaGroupName, m_currentMetaGroup);
+      m_currentDefines = null;
+    }
+  }
+
+  /**
+   * Parse <script>
+   */
+  private void xmlScript(boolean start, Attributes attributes) {
+    if (start) {
+//      ppp("OPEN SCRIPT");
+      m_currentLanguage = attributes.getValue("language");
+      m_currentExpression = "";
+    }
+    else {
+//      ppp("CLOSE SCRIPT:@@" + m_currentExpression + "@@");
+      m_currentSelector.setExpression(m_currentExpression);
+      m_currentSelector.setLanguage(m_currentLanguage);
+      if (m_locations.peek() == Location.TEST) {
+        m_currentTest.setBeanShellExpression(m_currentExpression);
+      }
+      m_currentLanguage = null;
+      m_currentExpression = null;
+    }
+  }
+
+  /**
+   * Parse <test>
+   */
+  private void xmlTest(boolean start, Attributes attributes) {
+    if (start) {
+      m_currentTest = new XmlTest(m_currentSuite, m_currentTestIndex++);
+      pushLocation(Location.TEST);
+      m_currentTestParameters = Maps.newHashMap();
+      final String testName= attributes.getValue("name");
+      if(isStringBlank(testName)) {
+        throw new TestNGException("The <test> tag must define the name attribute");
+      }
+      m_currentTest.setName(attributes.getValue("name"));
+      String verbose = attributes.getValue("verbose");
+      if (null != verbose) {
+        m_currentTest.setVerbose(Integer.parseInt(verbose));
+      }
+      String jUnit = attributes.getValue("junit");
+      if (null != jUnit) {
+        m_currentTest.setJUnit(Boolean.valueOf(jUnit));
+      }
+      String skip = attributes.getValue("skipfailedinvocationcounts");
+      if (skip != null) {
+        m_currentTest.setSkipFailedInvocationCounts(Boolean.valueOf(skip));
+      }
+      String groupByInstances = attributes.getValue("group-by-instances");
+      if (groupByInstances!= null) {
+        m_currentTest.setGroupByInstances(Boolean.valueOf(groupByInstances));
+      }
+      String preserveOrder = attributes.getValue("preserve-order");
+      if (preserveOrder != null) {
+        m_currentTest.setPreserveOrder(preserveOrder);
+      }
+      String parallel = attributes.getValue("parallel");
+      if (parallel != null) {
+        XmlSuite.ParallelMode mode = XmlSuite.ParallelMode.getValidParallel(parallel);
+        if (mode != null) {
+          m_currentTest.setParallel(mode);
+        } else {
+          Utils.log("Parser", 1, "[WARN] Unknown value of attribute 'parallel' for test '"
+            + m_currentTest.getName() + "': '" + parallel + "'");
+        }
+      }
+      String threadCount = attributes.getValue("thread-count");
+      if(null != threadCount) {
+        m_currentTest.setThreadCount(Integer.parseInt(threadCount));
+      }
+      String timeOut = attributes.getValue("time-out");
+      if (null != timeOut) {
+        m_currentTest.setTimeOut(Long.parseLong(timeOut));
+      }
+      m_enabledTest= true;
+      String enabledTestString = attributes.getValue("enabled");
+      if(null != enabledTestString) {
+        m_enabledTest = Boolean.valueOf(enabledTestString);
+      }
+    }
+    else {
+      if (null != m_currentTestParameters && m_currentTestParameters.size() > 0) {
+        m_currentTest.setParameters(m_currentTestParameters);
+      }
+      if (null != m_currentClasses) {
+        m_currentTest.setXmlClasses(m_currentClasses);
+      }
+      m_currentClasses = null;
+      m_currentTest = null;
+      m_currentTestParameters = null;
+      popLocation(Location.TEST);
+      if(!m_enabledTest) {
+        List<XmlTest> tests= m_currentSuite.getTests();
+        tests.remove(tests.size() - 1);
+      }
+    }
+  }
+
+  /**
+   * Parse <classes>
+   */
+  public void xmlClasses(boolean start, Attributes attributes) {
+    if (start) {
+      m_currentClasses = Lists.newArrayList();
+      m_currentClassIndex = 0;
+    }
+    else {
+      m_currentTest.setXmlClasses(m_currentClasses);
+      m_currentClasses = null;
+    }
+  }
+
+  /**
+   * Parse <listeners>
+   */
+  public void xmlListeners(boolean start, Attributes attributes) {
+    if (start) {
+      m_listeners = Lists.newArrayList();
+    }
+    else {
+      if (null != m_listeners) {
+        m_currentSuite.setListeners(m_listeners);
+        m_listeners = null;
+      }
+    }
+  }
+
+  /**
+   * Parse <listener>
+   */
+  public void xmlListener(boolean start, Attributes attributes) {
+    if (start) {
+      String listener = attributes.getValue("class-name");
+      m_listeners.add(listener);
+    }
+  }
+
+  /**
+   * Parse <packages>
+   */
+  public void xmlPackages(boolean start, Attributes attributes) {
+    if (start) {
+      m_currentPackages = Lists.newArrayList();
+    }
+    else {
+      if (null != m_currentPackages) {
+        switch(m_locations.peek()) {
+          case TEST:
+            m_currentTest.setXmlPackages(m_currentPackages);
+            break;
+          case SUITE:
+            m_currentSuite.setXmlPackages(m_currentPackages);
+            break;
+          case CLASS:
+            throw new UnsupportedOperationException("CLASS");
+        }
+      }
+
+      m_currentPackages = null;
+      m_currentPackage = null;
+    }
+  }
+
+  /**
+   * Parse <method-selectors>
+   */
+  public void xmlMethodSelectors(boolean start, Attributes attributes) {
+    if (start) {
+      m_currentSelectors = new ArrayList<>();
+    }
+    else {
+      switch(m_locations.peek()) {
+        case TEST:
+          m_currentTest.setMethodSelectors(m_currentSelectors);
+          break;
+        default:
+          m_currentSuite.setMethodSelectors(m_currentSelectors);
+          break;
+      }
+
+      m_currentSelectors = null;
+    }
+  }
+
+  /**
+   * Parse <selector-class>
+   */
+  public void xmlSelectorClass(boolean start, Attributes attributes) {
+    if (start) {
+      m_currentSelector.setName(attributes.getValue("name"));
+      String priority = attributes.getValue("priority");
+      if (priority == null) {
+        priority = "0";
+      }
+      m_currentSelector.setPriority(Integer.parseInt(priority));
+    }
+    else {
+      // do nothing
+    }
+  }
+
+  /**
+   * Parse <method-selector>
+   */
+  public void xmlMethodSelector(boolean start, Attributes attributes) {
+    if (start) {
+      m_currentSelector = new XmlMethodSelector();
+    }
+    else {
+      m_currentSelectors.add(m_currentSelector);
+      m_currentSelector = null;
+    }
+  }
+
+  private void xmlMethod(boolean start, Attributes attributes) {
+    if (start) {
+      m_currentIncludedMethods = new ArrayList<>();
+      m_currentExcludedMethods = Lists.newArrayList();
+      m_currentIncludeIndex = 0;
+    }
+    else {
+      m_currentClass.setIncludedMethods(m_currentIncludedMethods);
+      m_currentClass.setExcludedMethods(m_currentExcludedMethods);
+      m_currentIncludedMethods = null;
+      m_currentExcludedMethods = null;
+    }
+  }
+
+  /**
+   * Parse <run>
+   */
+  public void xmlRun(boolean start, Attributes attributes) throws SAXException {
+    if (start) {
+      m_currentRuns = Lists.newArrayList();
+    }
+    else {
+      if (m_currentTest != null) {
+        m_currentTest.setIncludedGroups(m_currentIncludedGroups);
+        m_currentTest.setExcludedGroups(m_currentExcludedGroups);
+      } else {
+        m_currentSuite.setIncludedGroups(m_currentIncludedGroups);
+        m_currentSuite.setExcludedGroups(m_currentExcludedGroups);
+      }
+      m_currentRuns = null;
+    }
+  }
+
+
+  /**
+   * Parse <group>
+   */
+  public void xmlGroup(boolean start, Attributes attributes) throws SAXException {
+    if (start) {
+      m_currentTest.addXmlDependencyGroup(attributes.getValue("name"),
+          attributes.getValue("depends-on"));
+    }
+  }
+
+  /**
+   * NOTE: I only invoke xml*methods (e.g. xmlSuite()) if I am acting on both
+   * the start and the end of the tag. This way I can keep the treatment of
+   * this tag in one place. If I am only doing something when the tag opens,
+   * the code is inlined below in the startElement() method.
+   */
+  @Override
+  public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+    if (!m_validate && !m_hasWarn) {
+      Logger.getLogger(TestNGContentHandler.class).warn("It is strongly recommended to add " +
+              "\"<!DOCTYPE suite SYSTEM \"http://testng.org/testng-1.0.dtd\" >\" at the top of your file, " +
+              "otherwise TestNG may fail or not work as expected.");
+      m_hasWarn = true;
+    }
+    String name = attributes.getValue("name");
+
+    // ppp("START ELEMENT uri:" + uri + " sName:" + localName + " qName:" + qName +
+    // " " + attributes);
+    if ("suite".equals(qName)) {
+      xmlSuite(true, attributes);
+    }
+    else if ("suite-file".equals(qName)) {
+      xmlSuiteFile(true, attributes);
+    }
+    else if ("test".equals(qName)) {
+      xmlTest(true, attributes);
+    }
+    else if ("script".equals(qName)) {
+      xmlScript(true, attributes);
+    }
+    else if ("method-selector".equals(qName)) {
+      xmlMethodSelector(true, attributes);
+    }
+    else if ("method-selectors".equals(qName)) {
+      xmlMethodSelectors(true, attributes);
+    }
+    else if ("selector-class".equals(qName)) {
+      xmlSelectorClass(true, attributes);
+    }
+    else if ("classes".equals(qName)) {
+      xmlClasses(true, attributes);
+    }
+    else if ("packages".equals(qName)) {
+      xmlPackages(true, attributes);
+    }
+    else if ("listeners".equals(qName)) {
+      xmlListeners(true, attributes);
+    }
+    else if ("listener".equals(qName)) {
+      xmlListener(true, attributes);
+    }
+    else if ("class".equals(qName)) {
+      // If m_currentClasses is null, the XML is invalid and SAX
+      // will complain, but in the meantime, dodge the NPE so SAX
+      // can finish parsing the file.
+      if (null != m_currentClasses) {
+        m_currentClass = new XmlClass(name, m_currentClassIndex++, m_loadClasses);
+        m_currentClass.setXmlTest(m_currentTest);
+        m_currentClassParameters = Maps.newHashMap();
+        m_currentClasses.add(m_currentClass);
+        pushLocation(Location.CLASS);
+      }
+    }
+    else if ("package".equals(qName)) {
+      if (null != m_currentPackages) {
+        m_currentPackage = new XmlPackage();
+        m_currentPackage.setName(name);
+        m_currentPackages.add(m_currentPackage);
+      }
+    }
+    else if ("define".equals(qName)) {
+      xmlDefine(true, attributes);
+    }
+    else if ("run".equals(qName)) {
+      xmlRun(true, attributes);
+    }
+    else if ("group".equals(qName)) {
+      xmlGroup(true, attributes);
+    }
+    else if ("groups".equals(qName)) {
+      m_currentIncludedGroups = Lists.newArrayList();
+      m_currentExcludedGroups = Lists.newArrayList();
+    }
+    else if ("methods".equals(qName)) {
+      xmlMethod(true, attributes);
+    }
+    else if ("include".equals(qName)) {
+      xmlInclude(true, attributes);
+    }
+    else if ("exclude".equals(qName)) {
+      xmlExclude(true, attributes);
+    }
+    else if ("parameter".equals(qName)) {
+      String value = expandValue(attributes.getValue("value"));
+      switch(m_locations.peek()) {
+        case TEST:
+          m_currentTestParameters.put(name, value);
+          break;
+        case SUITE:
+          m_currentSuiteParameters.put(name, value);
+          break;
+        case CLASS:
+          m_currentClassParameters.put(name, value);
+          break;
+        case INCLUDE:
+          m_currentInclude.parameters.put(name, value);
+          break;
+      }
+    }
+  }
+
+  private static class Include {
+    String name;
+    String invocationNumbers;
+    String description;
+    Map<String, String> parameters = Maps.newHashMap();
+
+    public Include(String name, String numbers) {
+      this.name = name;
+      this.invocationNumbers = numbers;
+    }
+  }
+
+  private void xmlInclude(boolean start, Attributes attributes) {
+    if (start) {
+      m_locations.push(Location.INCLUDE);
+      m_currentInclude = new Include(attributes.getValue("name"),
+          attributes.getValue("invocation-numbers"));
+    } else {
+      String name = m_currentInclude.name;
+      if (null != m_currentIncludedMethods) {
+        String in = m_currentInclude.invocationNumbers;
+        XmlInclude include;
+        if (!Utils.isStringEmpty(in)) {
+          include = new XmlInclude(name, stringToList(in), m_currentIncludeIndex++);
+        } else {
+          include = new XmlInclude(name, m_currentIncludeIndex++);
+        }
+        for (Map.Entry<String, String> entry : m_currentInclude.parameters.entrySet()) {
+          include.addParameter(entry.getKey(), entry.getValue());
+        }
+
+        include.setDescription(m_currentInclude.description);
+        m_currentIncludedMethods.add(include);
+      }
+      else if (null != m_currentDefines) {
+        m_currentMetaGroup.add(name);
+      }
+      else if (null != m_currentRuns) {
+        m_currentIncludedGroups.add(name);
+      }
+      else if (null != m_currentPackage) {
+        m_currentPackage.getInclude().add(name);
+      }
+
+      popLocation(Location.INCLUDE);
+      m_currentInclude = null;
+    }
+  }
+
+  private void xmlExclude(boolean start, Attributes attributes) {
+    if (start) {
+      m_locations.push(Location.EXCLUDE);
+      String name = attributes.getValue("name");
+      if (null != m_currentExcludedMethods) {
+        m_currentExcludedMethods.add(name);
+      }
+      else if (null != m_currentRuns) {
+        m_currentExcludedGroups.add(name);
+      }
+      else if (null != m_currentPackage) {
+        m_currentPackage.getExclude().add(name);
+      }
+    } else {
+      popLocation(Location.EXCLUDE);
+    }
+  }
+
+  private void pushLocation(Location l) {
+    m_locations.push(l);
+  }
+
+  private Location popLocation(Location location) {
+    return m_locations.pop();
+  }
+
+  private List<Integer> stringToList(String in) {
+    String[] numbers = in.split(" ");
+    List<Integer> result = Lists.newArrayList();
+    for (String n : numbers) {
+      result.add(Integer.parseInt(n));
+    }
+    return result;
+  }
+
+  @Override
+  public void endElement(String uri, String localName, String qName) throws SAXException {
+    if ("suite".equals(qName)) {
+      xmlSuite(false, null);
+    }
+    else if ("suite-file".equals(qName)) {
+      xmlSuiteFile(false, null);
+    }
+    else if ("test".equals(qName)) {
+      xmlTest(false, null);
+    }
+    else if ("define".equals(qName)) {
+      xmlDefine(false, null);
+    }
+    else if ("run".equals(qName)) {
+      xmlRun(false, null);
+    }
+    else if ("methods".equals(qName)) {
+      xmlMethod(false, null);
+    }
+    else if ("classes".equals(qName)) {
+      xmlClasses(false, null);
+    }
+    else if ("packages".equals(qName)) {
+      xmlPackages(false, null);
+    }
+    else if ("class".equals(qName)) {
+      m_currentClass.setParameters(m_currentClassParameters);
+      m_currentClassParameters = null;
+      popLocation(Location.CLASS);
+    }
+    else if ("listeners".equals(qName)) {
+      xmlListeners(false, null);
+    }
+    else if ("method-selector".equals(qName)) {
+      xmlMethodSelector(false, null);
+    }
+    else if ("method-selectors".equals(qName)) {
+      xmlMethodSelectors(false, null);
+    }
+    else if ("selector-class".equals(qName)) {
+      xmlSelectorClass(false, null);
+    }
+    else if ("script".equals(qName)) {
+      xmlScript(false, null);
+    }
+    else if ("packages".equals(qName)) {
+      xmlPackages(false, null);
+    }
+    else if ("include".equals(qName)) {
+      xmlInclude(false, null);
+    } else if ("exclude".equals(qName)){
+      xmlExclude(false, null);
+    }
+  }
+
+  @Override
+  public void error(SAXParseException e) throws SAXException {
+    if (m_validate) {
+      throw e;
+    }
+  }
+
+  private boolean areWhiteSpaces(char[] ch, int start, int length) {
+    for (int i = start; i < start + length; i++) {
+      char c = ch[i];
+      if (c != '\n' && c != '\t' && c != ' ') {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  @Override
+  public void characters(char ch[], int start, int length) {
+    if (null != m_currentLanguage && ! areWhiteSpaces(ch, start, length)) {
+      m_currentExpression += new String(ch, start, length);
+    }
+  }
+
+  public XmlSuite getSuite() {
+    return m_currentSuite;
+  }
+
+  private static String expandValue(String value)
+  {
+    StringBuffer result = null;
+    int startIndex = 0;
+    int endIndex = 0;
+    int startPosition = 0;
+    String property = null;
+    while ((startIndex = value.indexOf("${", startPosition)) > -1 && (endIndex = value.indexOf("}", startIndex + 3)) > -1) {
+      property = value.substring(startIndex + 2, endIndex);
+      if (result == null) {
+        result = new StringBuffer(value.substring(startPosition, startIndex));
+      } else {
+        result.append(value.substring(startPosition, startIndex));
+      }
+      String propertyValue = System.getProperty(property);
+      if (propertyValue == null) {
+        propertyValue = System.getenv(property);
+      }
+      if (propertyValue != null) {
+        result.append(propertyValue);
+      } else {
+        result.append("${");
+        result.append(property);
+        result.append("}");
+      }
+      startPosition = startIndex + 3 + property.length();
+    }
+    if (result != null) {
+      result.append(value.substring(startPosition));
+      return result.toString();
+    } else {
+      return value;
+    }
+  }
+}
diff --git a/src/main/java/org/testng/xml/XMLParser.java b/src/main/java/org/testng/xml/XMLParser.java
new file mode 100644
index 0000000..9b43997
--- /dev/null
+++ b/src/main/java/org/testng/xml/XMLParser.java
@@ -0,0 +1,128 @@
+package org.testng.xml;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.xml.parsers.FactoryConfigurationError;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.testng.TestNGException;
+import org.testng.internal.ClassHelper;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+abstract public class XMLParser<T> implements IFileParser<T> {
+
+  private final static SAXParser m_saxParser;
+
+  static {
+    SAXParserFactory spf = loadSAXParserFactory();
+
+    if (supportsValidation(spf)) {
+      spf.setNamespaceAware(true);
+      spf.setValidating(true);
+    }
+
+    SAXParser parser = null;
+    try {
+      parser = spf.newSAXParser();
+    } catch (ParserConfigurationException | SAXException e) {
+      e.printStackTrace();
+    }
+    m_saxParser = parser;
+  }
+
+  public void parse(InputStream is, DefaultHandler dh) throws SAXException, IOException {
+    synchronized (m_saxParser) {
+      m_saxParser.parse(is, dh);
+    }
+  }
+
+  /**
+   * Tries to load a <code>SAXParserFactory</code> by trying in order the following:
+   * <tt>com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl</tt> (SUN JDK5)
+   * <tt>org.apache.crimson.jaxp.SAXParserFactoryImpl</tt> (SUN JDK1.4) and
+   * last <code>SAXParserFactory.newInstance()</code>.
+   *
+   * @return a <code>SAXParserFactory</code> implementation
+   * @throws TestNGException thrown if no <code>SAXParserFactory</code> can be loaded
+   */
+  private static SAXParserFactory loadSAXParserFactory() {
+    SAXParserFactory spf = null;
+
+    StringBuffer errorLog= new StringBuffer();
+    try {
+      Class factoryClass= ClassHelper.forName("com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl");
+      spf = (SAXParserFactory) factoryClass.newInstance();
+    }
+    catch(Exception ex) {
+      errorLog.append("JDK5 SAXParserFactory cannot be loaded: " + ex.getMessage());
+    }
+
+    if(null == spf) {
+      // If running with JDK 1.4
+      try {
+        Class factoryClass = ClassHelper.forName("org.apache.crimson.jaxp.SAXParserFactoryImpl");
+        spf = (SAXParserFactory) factoryClass.newInstance();
+      }
+      catch(Exception ex) {
+        errorLog.append("\n").append("JDK1.4 SAXParserFactory cannot be loaded: " + ex.getMessage());
+      }
+    }
+
+    Throwable cause= null;
+    if(null == spf) {
+      try {
+        spf= SAXParserFactory.newInstance();
+      }
+      catch(FactoryConfigurationError fcerr) {
+        cause= fcerr;
+      }
+    }
+
+    if(null == spf) {
+      throw new TestNGException("Cannot initialize a SAXParserFactory\n" + errorLog.toString(), cause);
+    }
+
+    return spf;
+  }
+
+
+  /**
+   * Tests if the current <code>SAXParserFactory</code> supports DTD validation.
+   */
+  private static boolean supportsValidation(SAXParserFactory spf) {
+    try {
+      spf.getFeature("http://xml.org/sax/features/validation");
+      return true;
+    }
+    catch(Exception ex) {
+      return false;
+    }
+  }
+
+//  private static void ppp(String s) {
+//    System.out.println("[Parser] " + s);
+//  }
+
+//  /**
+//   *
+//   * @param argv ignored
+//   * @throws FileNotFoundException if the
+//   * @throws ParserConfigurationException
+//   * @throws SAXException
+//   * @throws IOException
+//   * @since 1.0
+//   */
+//  public static void main(String[] argv)
+//    throws FileNotFoundException, ParserConfigurationException, SAXException, IOException
+//  {
+//    XmlSuite l =
+//      new Parser("c:/eclipse-workspace/testng/test/testng.xml").parse();
+//
+//    System.out.println(l);
+//  }  @Override
+
+}
diff --git a/src/main/java/org/testng/xml/XmlClass.java b/src/main/java/org/testng/xml/XmlClass.java
new file mode 100755
index 0000000..d3b4ba3
--- /dev/null
+++ b/src/main/java/org/testng/xml/XmlClass.java
@@ -0,0 +1,312 @@
+package org.testng.xml;
+
+import org.testng.TestNGException;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.collections.Objects;
+import org.testng.internal.ClassHelper;
+import org.testng.reporters.XMLStringBuffer;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * This class describes the tag <class> in testng.xml.
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public class XmlClass implements Serializable, Cloneable {
+
+  private static final long serialVersionUID = 8885360896966149897L;
+  private List<XmlInclude> m_includedMethods = Lists.newArrayList();
+  private List<String> m_excludedMethods = Lists.newArrayList();
+  private String m_name = null;
+  private Class m_class = null;
+  /** The index of this class in the <test> tag */
+  private int m_index;
+  /** True if the classes need to be loaded */
+  private boolean m_loadClasses = true;
+  private Map<String, String> m_parameters = Maps.newHashMap();
+  private XmlTest m_xmlTest;
+
+  public XmlClass() {
+    init("", null, 0, false /* load classes */);
+  }
+
+  public XmlClass(String name) {
+    init(name, null, 0);
+  }
+
+  public XmlClass(String name, boolean loadClasses) {
+    init(name, null, 0, loadClasses);
+  }
+
+  public XmlClass(Class cls) {
+    init(cls.getName(), cls, 0, true);
+  }
+
+  public XmlClass(Class cls, boolean loadClasses) {
+    init(cls.getName(), cls, 0, loadClasses);
+  }
+
+  public XmlClass(String className, int index) {
+    init(className, null, index, true /* load classes */);
+  }
+
+  public XmlClass(String className, int index, boolean loadClasses) {
+    init(className, null, index, loadClasses);
+  }
+
+  private void init(String className, Class cls, int index) {
+    init(className, cls, index, true /* load classes */);
+  }
+
+  private void init(String className, Class cls, int index,
+      boolean resolveClass) {
+    m_name = className;
+    m_class = cls;
+    m_index = index;
+
+    if (null == m_class && resolveClass) {
+      loadClass();
+    }
+  }
+
+  private void loadClass() {
+    m_class = ClassHelper.forName(m_name);
+
+    if (null == m_class) {
+      throw new TestNGException("Cannot find class in classpath: " + m_name);
+    }
+  }
+
+  /**
+   * @return Returns the className.
+   */
+  public Class getSupportClass() {
+    if (m_class == null) loadClass();
+    return m_class;
+  }
+
+  /**
+   * @param className The className to set.
+   */
+  public void setClass(Class className) {
+    m_class = className;
+  }
+
+  /**
+   * @return Returns the excludedMethods.
+   */
+  public List<String> getExcludedMethods() {
+    return m_excludedMethods;
+  }
+
+  /**
+   * @param excludedMethods The excludedMethods to set.
+   */
+  public void setExcludedMethods(List<String> excludedMethods) {
+    m_excludedMethods = excludedMethods;
+  }
+
+  /**
+   * @return Returns the includedMethods.
+   */
+  public List<XmlInclude> getIncludedMethods() {
+    return m_includedMethods;
+  }
+
+  /**
+   * @param includedMethods The includedMethods to set.
+   */
+  public void setIncludedMethods(List<XmlInclude> includedMethods) {
+    m_includedMethods = includedMethods;
+  }
+
+  /**
+   * @return Returns the name.
+   */
+  public String getName() {
+    return m_name;
+  }
+
+  /**
+   * @param name The name to set.
+   */
+  public void setName(String name) {
+    m_name = name;
+  }
+
+  /**
+   * @return true if the classes need to be loaded.
+   */
+  public boolean loadClasses() {
+    return m_loadClasses;
+  }
+
+  @Override
+  public String toString() {
+    return Objects.toStringHelper(getClass())
+        .add("class", m_name)
+        .toString();
+  }
+
+  public String toXml(String indent) {
+    XMLStringBuffer xsb = new XMLStringBuffer(indent);
+    Properties prop = new Properties();
+    prop.setProperty("name", getName());
+
+    boolean hasMethods = !m_includedMethods.isEmpty() || !m_excludedMethods.isEmpty();
+    boolean hasParameters = !m_parameters.isEmpty();
+    if (hasParameters || hasMethods) {
+      xsb.push("class", prop);
+      XmlUtils.dumpParameters(xsb, m_parameters);
+
+      if (hasMethods) {
+        xsb.push("methods");
+  
+        for (XmlInclude m : getIncludedMethods()) {
+          xsb.getStringBuffer().append(m.toXml(indent + "    "));
+        }
+  
+        for (String m: getExcludedMethods()) {
+          Properties p= new Properties();
+          p.setProperty("name", m);
+          xsb.addEmptyElement("exclude", p);
+        }
+  
+        xsb.pop("methods");
+      }
+
+      xsb.pop("class");
+    }
+    else {
+      xsb.addEmptyElement("class", prop);
+    }
+
+    return xsb.toXML();
+
+  }
+
+  public static String listToString(List<Integer> invocationNumbers) {
+    StringBuilder result = new StringBuilder();
+    int i = 0;
+    for (Integer n : invocationNumbers) {
+      if (i++ > 0) {
+        result.append(" ");
+      }
+      result.append(n);
+    }
+    return result.toString();
+  }
+
+  /**
+   * Clone an XmlClass by copying all its components.
+   */
+  @Override
+  public Object clone() {
+    XmlClass result = new XmlClass(getName(), getIndex(), loadClasses());
+    result.setExcludedMethods(getExcludedMethods());
+    result.setIncludedMethods(getIncludedMethods());
+
+    return result;
+  }
+
+  /**
+   * Note that this attribute does not come from the XML file, it's calculated
+   * internally and represents the order in which this class was found in its
+   * &lt;test&gt; tag.  It's used to calculate the ordering of the classes
+   * when preserve-order is true.
+   */
+  public int getIndex() {
+    return m_index;
+  }
+
+  public void setIndex(int index) {
+    m_index = index;
+  }
+
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((m_class == null) ? 0 : m_class.hashCode());
+    result = prime * result + (m_loadClasses ? 1 : 0);
+    result = prime * result
+        + ((m_excludedMethods == null) ? 0 : m_excludedMethods.hashCode());
+    result = prime * result
+        + ((m_includedMethods == null) ? 0 : m_includedMethods.hashCode());
+    result = prime * result + m_index;
+    result = prime * result + ((m_name == null) ? 0 : m_name.hashCode());
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null)
+      return XmlSuite.f();
+    if (getClass() != obj.getClass())
+      return XmlSuite.f();
+    XmlClass other = (XmlClass) obj;
+    if (other.m_loadClasses != m_loadClasses) {
+      return XmlSuite.f();
+    } else if (!m_excludedMethods.equals(other.m_excludedMethods)) {
+      return XmlSuite.f();
+    }
+    if (m_includedMethods == null) {
+      if (other.m_includedMethods != null)
+        return XmlSuite.f();
+    } else if (!m_includedMethods.equals(other.m_includedMethods))
+      return XmlSuite.f();
+//    if (m_index != other.m_index)
+//      return XmlSuite.f();
+    if (m_name == null) {
+      if (other.m_name != null)
+        return XmlSuite.f();
+    } else if (!m_name.equals(other.m_name))
+      return XmlSuite.f();
+
+    return true;
+  }
+
+  public void setParameters(Map<String, String> parameters) {
+    m_parameters.clear();
+    m_parameters.putAll(parameters);
+  }
+
+  /**
+   * @return The parameters defined in this test tag and the tags above it.
+   */
+  public Map<String, String> getAllParameters() {
+    Map<String, String> result = Maps.newHashMap();
+    Map<String, String> parameters = m_xmlTest.getLocalParameters();
+    result.putAll(parameters);
+    result.putAll(m_parameters);
+    return result;
+  }
+
+  /**
+   * @return The parameters defined in this tag, and only this test tag. To retrieve
+   * the inherited parameters as well, call {@code getAllParameters()}.
+   */
+  public Map<String, String> getLocalParameters() {
+    return m_parameters;
+  }
+
+  /**
+   * @deprecated Use {@code getLocalParameters()} or {@code getAllParameters()}
+   */
+  @Deprecated
+  public Map<String, String> getParameters() {
+    return getAllParameters();
+  }
+
+  public void setXmlTest(XmlTest test) {
+    m_xmlTest = test;
+  }
+}
diff --git a/src/main/java/org/testng/xml/XmlDefine.java b/src/main/java/org/testng/xml/XmlDefine.java
new file mode 100644
index 0000000..985ac4b
--- /dev/null
+++ b/src/main/java/org/testng/xml/XmlDefine.java
@@ -0,0 +1,49 @@
+package org.testng.xml;
+
+import static org.testng.collections.CollectionUtils.hasElements;
+
+import org.testng.collections.Lists;
+import org.testng.reporters.XMLStringBuffer;
+import org.testng.xml.dom.OnElement;
+
+import java.util.List;
+
+public class XmlDefine {
+
+  private String m_name;
+
+  public void setName(String name) {
+    m_name = name;
+  }
+
+  public String getName() {
+    return m_name;
+  }
+
+  public String toXml(String indent) {
+    XMLStringBuffer xsb = new XMLStringBuffer(indent);
+    boolean hasElements = hasElements(m_includes);
+    if (hasElements) {
+      xsb.push("define", "name", m_name);
+    }
+    for (String s : m_includes) {
+      xsb.addEmptyElement("include", "name", s);
+    }
+    if (hasElements) {
+      xsb.pop("define");
+    }
+
+    return xsb.toXML();
+  }
+
+  private List<String> m_includes = Lists.newArrayList();
+
+  @OnElement(tag = "include", attributes = "name")
+  public void onElement(String name) {
+    m_includes.add(name);
+  }
+
+  public List<String> getIncludes() {
+    return m_includes;
+  }
+}
diff --git a/src/main/java/org/testng/xml/XmlDependencies.java b/src/main/java/org/testng/xml/XmlDependencies.java
new file mode 100644
index 0000000..e4b24c2
--- /dev/null
+++ b/src/main/java/org/testng/xml/XmlDependencies.java
@@ -0,0 +1,40 @@
+package org.testng.xml;
+
+import java.util.Map;
+
+import org.testng.collections.Maps;
+import org.testng.reporters.XMLStringBuffer;
+import org.testng.xml.dom.OnElement;
+
+import static org.testng.collections.CollectionUtils.hasElements;
+
+public class XmlDependencies {
+
+  private Map<String, String> m_xmlDependencyGroups = Maps.newHashMap();
+
+  @OnElement(tag = "group", attributes = { "name", "depends-on" })
+  public void onGroup(String name, String dependsOn) {
+    m_xmlDependencyGroups.put(name, dependsOn);
+  }
+
+  public Map<String, String> getDependencies() {
+    return m_xmlDependencyGroups;
+  }
+
+  public String toXml(String indent) {
+    XMLStringBuffer xsb = new XMLStringBuffer(indent);
+    boolean hasElements = hasElements(m_xmlDependencyGroups);
+    if (hasElements) {
+      xsb.push("dependencies");
+    }
+    for (Map.Entry<String, String> entry : m_xmlDependencyGroups.entrySet()) {
+      xsb.addEmptyElement("include", "name", entry.getKey(), "depends-on", entry.getValue());
+    }
+    if (hasElements) {
+      xsb.pop("dependencies");
+    }
+
+    return xsb.toXML();
+  }
+
+}
diff --git a/src/main/java/org/testng/xml/XmlGroups.java b/src/main/java/org/testng/xml/XmlGroups.java
new file mode 100644
index 0000000..0ce68b0
--- /dev/null
+++ b/src/main/java/org/testng/xml/XmlGroups.java
@@ -0,0 +1,78 @@
+package org.testng.xml;
+
+import java.util.List;
+
+import org.testng.collections.Lists;
+import org.testng.reporters.XMLStringBuffer;
+import org.testng.xml.dom.Tag;
+
+import static org.testng.collections.CollectionUtils.hasElements;
+
+public class XmlGroups {
+
+  private List<XmlDefine> m_defines = Lists.newArrayList();
+  private XmlRun m_run;
+  private List<XmlDependencies> m_dependencies = Lists.newArrayList();
+
+  public List<XmlDefine> getDefines() {
+    return m_defines;
+  }
+
+  @Tag(name = "define")
+  public void addDefine(XmlDefine define) {
+    getDefines().add(define);
+  }
+
+  public void setDefines(List<XmlDefine> defines) {
+    m_defines = defines;
+  }
+
+  public XmlRun getRun() {
+    return m_run;
+  }
+
+  public void setRun(XmlRun run) {
+    m_run = run;
+  }
+
+  public List<XmlDependencies> getDependencies() {
+    return m_dependencies;
+  }
+
+//  public void setDependencies(List<XmlDependencies> dependencies) {
+//    m_dependencies = dependencies;
+//  }
+
+  @Tag(name = "dependencies")
+  public void setXmlDependencies(XmlDependencies dependencies) {
+    m_dependencies.add(dependencies);
+  }
+
+  public String toXml(String indent) {
+    XMLStringBuffer xsb = new XMLStringBuffer(indent);
+    String indent2 = indent + "  ";
+
+    boolean hasGroups = hasElements(m_defines) || m_run != null
+        || hasElements(m_dependencies);
+
+    if (hasGroups) {
+      xsb.push("groups");
+    }
+
+    for (XmlDefine d : m_defines) {
+      xsb.getStringBuffer().append(d.toXml(indent2));
+    }
+
+    xsb.getStringBuffer().append(m_run.toXml(indent2));
+
+    for (XmlDependencies d : m_dependencies) {
+      xsb.getStringBuffer().append(d.toXml(indent2));
+    }
+
+    if (hasGroups) {
+      xsb.pop("groups");
+    }
+
+    return xsb.toXML();
+  }
+}
diff --git a/src/main/java/org/testng/xml/XmlInclude.java b/src/main/java/org/testng/xml/XmlInclude.java
new file mode 100644
index 0000000..213ec67
--- /dev/null
+++ b/src/main/java/org/testng/xml/XmlInclude.java
@@ -0,0 +1,161 @@
+package org.testng.xml;
+
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.reporters.XMLStringBuffer;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+public class XmlInclude implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  private String m_name;
+  private List<Integer> m_invocationNumbers = Lists.newArrayList();
+  private int m_index;
+  private String m_description;
+  private Map<String, String> m_parameters = Maps.newHashMap();
+
+  private XmlClass m_xmlClass;
+
+  public XmlInclude() {
+  }
+
+  public XmlInclude(String n) {
+    this(n, Collections.<Integer> emptyList(), 0);
+  }
+
+  public XmlInclude(String n, int index) {
+    this(n, Collections.<Integer> emptyList(), index);
+  }
+
+  public XmlInclude(String n, List<Integer> list, int index) {
+    m_name = n;
+    m_invocationNumbers = list;
+    m_index = index;
+  }
+
+  public void setDescription(String description) {
+    m_description = description;
+  }
+
+  public String getDescription() {
+    return m_description;
+  }
+
+  public String getName() {
+    return m_name;
+  }
+
+  public List<Integer> getInvocationNumbers() {
+    return m_invocationNumbers;
+  }
+
+  public int getIndex() {
+    return m_index;
+  }
+
+  public String toXml(String indent) {
+    XMLStringBuffer xsb = new XMLStringBuffer(indent);
+    Properties p = new Properties();
+    p.setProperty("name", getName());
+    List<Integer> invocationNumbers = getInvocationNumbers();
+    if (invocationNumbers != null && invocationNumbers.size() > 0) {
+      p.setProperty("invocation-numbers",
+          XmlClass.listToString(invocationNumbers).toString());
+    }
+
+    if (!m_parameters.isEmpty()){
+        xsb.push("include", p);
+        XmlUtils.dumpParameters(xsb, m_parameters);
+        xsb.pop("include");
+    } else {
+       xsb.addEmptyElement("include", p);
+    }
+
+    return xsb.toXML();
+  }
+
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + m_index;
+    result = prime * result
+        + ((m_invocationNumbers == null) ? 0 : m_invocationNumbers.hashCode());
+    result = prime * result + (m_parameters == null ? 0 : m_parameters.hashCode());
+    result = prime * result + ((m_name == null) ? 0 : m_name.hashCode());
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj)
+      return true;
+    if (obj == null)
+      return XmlSuite.f();
+    if (getClass() != obj.getClass())
+      return XmlSuite.f();
+    XmlInclude other = (XmlInclude) obj;
+    // if (m_index != other.m_index)
+    // return XmlSuite.f();
+    if (m_invocationNumbers == null) {
+      if (other.m_invocationNumbers != null)
+        return XmlSuite.f();
+    } else if (!m_invocationNumbers.equals(other.m_invocationNumbers))
+      return XmlSuite.f();
+    if (m_name == null) {
+      if (other.m_name != null)
+        return XmlSuite.f();
+    } else if (!m_name.equals(other.m_name))
+      return XmlSuite.f();
+    if (m_parameters == null) {
+      if (other.m_parameters != null) {
+        return XmlSuite.f();
+      }
+    } else if (!m_parameters.equals(other.m_parameters)) {
+      return XmlSuite.f();
+    }
+    return true;
+  }
+
+  public void addParameter(String name, String value) {
+    m_parameters.put(name, value);
+  }
+
+  /**
+   * @deprecated Use {@code getLocalParameters()} or {@code getAllParameters()}
+   */
+  @Deprecated
+  public Map<String, String> getParameters() {
+    return getAllParameters();
+  }
+
+  /**
+   * @return the parameters defined in this test tag, and only this test tag. To retrieve
+   * the inherited parameters as well, call {@code getAllParameters()}.
+   */
+  public Map<String, String> getLocalParameters() {
+    return m_parameters;
+  }
+
+  /**
+   * @return the parameters defined in this tag and the tags above it.
+   */
+  public Map<String, String> getAllParameters() {
+    Map<String, String> result = Maps.newHashMap();
+    if (m_xmlClass != null) {
+      result.putAll(m_xmlClass.getAllParameters());
+    }
+    result.putAll(m_parameters);
+    return result;
+  }
+
+  public void setXmlClass(XmlClass xmlClass) {
+    m_xmlClass = xmlClass;
+  }
+
+}
diff --git a/src/main/java/org/testng/xml/XmlMethodSelector.java b/src/main/java/org/testng/xml/XmlMethodSelector.java
new file mode 100755
index 0000000..2dcbea5
--- /dev/null
+++ b/src/main/java/org/testng/xml/XmlMethodSelector.java
@@ -0,0 +1,161 @@
+package org.testng.xml;
+
+import org.testng.TestNGException;
+import org.testng.reporters.XMLStringBuffer;
+import org.testng.xml.dom.OnElement;
+
+import java.util.Properties;
+
+/**
+ * This class describes the tag <method-selector>  in testng.xml.
+ *
+ * Created on Sep 26, 2005
+ * @author cbeust
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ */
+public class XmlMethodSelector {
+  // Either this:
+  private String m_className;
+  private int m_priority;
+
+  // Or that:
+  private XmlScript m_script = new XmlScript();
+
+  // For YAML
+  public void setClassName(String s) {
+    m_className = s;
+  }
+
+  public String getClassName() {
+    return m_className;
+  }
+
+  // For YAML
+  @OnElement(tag = "selector-class", attributes = { "name", "priority" })
+  public void setElement(String name, String priority) {
+    setName(name);
+    setPriority(Integer.parseInt(priority));
+  }
+
+  public void setName(String name) {
+    m_className = name;
+  }
+
+  public void setScript(XmlScript script) {
+    m_script = script;
+  }
+
+  /**
+   * @return Returns the expression.
+   */
+  public String getExpression() {
+    return m_script.getScript();
+  }
+
+  /**
+   * @param expression The expression to set.
+   */
+  public void setExpression(String expression) {
+    m_script.setScript(expression);
+  }
+
+  /**
+   * @return Returns the language.
+   */
+  public String getLanguage() {
+    return m_script.getLanguage();
+  }
+
+  /**
+   * @param language The language to set.
+   */
+//  @OnElement(tag = "script", attributes = "language")
+  public void setLanguage(String language) {
+    m_script.setLanguage(language);
+//    m_language = language;
+  }
+
+  public int getPriority() {
+    return m_priority;
+  }
+
+  public void setPriority(int priority) {
+    m_priority = priority;
+  }
+
+  private void ppp(String s) {
+    System.out.println("[XmlMethodSelector] " + s);
+  }
+
+  public String toXml(String indent) {
+    XMLStringBuffer xsb = new XMLStringBuffer(indent);
+
+    xsb.push("method-selector");
+
+    if (null != m_className) {
+      Properties clsProp = new Properties();
+      clsProp.setProperty("name", getClassName());
+      if(getPriority() != -1) {
+        clsProp.setProperty("priority", String.valueOf(getPriority()));
+      }
+      xsb.addEmptyElement("selector-class", clsProp);
+    }
+    else if (getLanguage() != null) {
+      Properties scriptProp = new Properties();
+      scriptProp.setProperty("language", getLanguage());
+      xsb.push("script", scriptProp);
+      xsb.addCDATA(getExpression());
+      xsb.pop("script");
+    }
+    else {
+      throw new TestNGException("Invalid Method Selector:  found neither class name nor language");
+    }
+
+    xsb.pop("method-selector");
+
+    return xsb.toXML();
+  }
+
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result
+        + ((m_className == null) ? 0 : m_className.hashCode());
+    result = prime * result
+        + ((getExpression() == null) ? 0 : getExpression().hashCode());
+    result = prime * result
+        + ((getLanguage() == null) ? 0 : getLanguage().hashCode());
+    result = prime * result + m_priority;
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj)
+      return true;
+    if (obj == null)
+      return XmlSuite.f();
+    if (getClass() != obj.getClass())
+      return XmlSuite.f();
+    XmlMethodSelector other = (XmlMethodSelector) obj;
+    if (m_className == null) {
+      if (other.m_className != null)
+        return XmlSuite.f();
+    } else if (!m_className.equals(other.m_className))
+      return XmlSuite.f();
+    if (getExpression() == null) {
+      if (other.getExpression() != null)
+        return XmlSuite.f();
+    } else if (!getExpression().equals(other.getExpression()))
+      return XmlSuite.f();
+    if (getLanguage() == null) {
+      if (other.getLanguage() != null)
+        return XmlSuite.f();
+    } else if (!getLanguage().equals(other.getLanguage()))
+      return XmlSuite.f();
+    if (m_priority != other.m_priority)
+      return XmlSuite.f();
+    return true;
+  }
+}
diff --git a/src/main/java/org/testng/xml/XmlMethodSelectors.java b/src/main/java/org/testng/xml/XmlMethodSelectors.java
new file mode 100644
index 0000000..8f766cc
--- /dev/null
+++ b/src/main/java/org/testng/xml/XmlMethodSelectors.java
@@ -0,0 +1,37 @@
+package org.testng.xml;
+
+import static org.testng.collections.CollectionUtils.hasElements;
+
+import org.testng.collections.Lists;
+import org.testng.reporters.XMLStringBuffer;
+
+import java.util.List;
+
+public class XmlMethodSelectors {
+
+  private List<XmlMethodSelector> m_methodSelectors = Lists.newArrayList();
+
+  public XmlMethodSelectors() {
+  }
+
+  public List<XmlMethodSelector> getMethodSelectors() {
+    return m_methodSelectors;
+  }
+
+  public void setMethodSelector(XmlMethodSelector xms) {
+    m_methodSelectors.add(xms);
+  }
+
+  public String toXml(String indent) {
+    XMLStringBuffer xsb = new XMLStringBuffer(indent);
+    if (hasElements(m_methodSelectors)) {
+      xsb.push("method-selectors");
+      for (XmlMethodSelector selector : m_methodSelectors) {
+        xsb.getStringBuffer().append(selector.toXml(indent + "  "));
+      }
+  
+      xsb.pop("method-selectors");
+    }
+    return xsb.toXML();
+  }
+}
diff --git a/src/main/java/org/testng/xml/XmlPackage.java b/src/main/java/org/testng/xml/XmlPackage.java
new file mode 100755
index 0000000..7abe1ad
--- /dev/null
+++ b/src/main/java/org/testng/xml/XmlPackage.java
@@ -0,0 +1,175 @@
+package org.testng.xml;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Properties;
+
+import org.testng.collections.Lists;
+import org.testng.internal.PackageUtils;
+import org.testng.internal.Utils;
+import org.testng.reporters.XMLStringBuffer;
+
+/**
+ * This class describes the tag <package>  in testng.xml.
+ *
+ * @author Cedric
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ */
+public class XmlPackage implements Serializable {
+  /**
+   *
+   */
+  private static final long serialVersionUID = 1996341670354923204L;
+  private String m_name;
+  private List<String> m_include = Lists.newArrayList();
+  private List<String> m_exclude = Lists.newArrayList();
+  private List<XmlClass> m_xmlClasses= null;
+
+  public XmlPackage() {
+  }
+
+  // For YAML
+  public XmlPackage(String name) {
+    m_name = name;
+  }
+
+  /**
+   * @return the exclude
+   */
+  public List<String> getExclude() {
+    return m_exclude;
+  }
+
+  /**
+   * @param exclude the exclude to set
+   */
+  public void setExclude(List<String> exclude) {
+    m_exclude = exclude;
+  }
+
+  /**
+   * @return the include
+   */
+  public List<String> getInclude() {
+    return m_include;
+  }
+
+  /**
+   * @param include the include to set
+   */
+  public void setInclude(List<String> include) {
+    m_include = include;
+  }
+
+  /**
+   * @return the name
+   */
+  public String getName() {
+    return m_name;
+  }
+
+  /**
+   * @param name the name to set
+   */
+  public void setName(String name) {
+    m_name = name;
+  }
+
+  public List<XmlClass> getXmlClasses() {
+    if(null == m_xmlClasses) {
+      m_xmlClasses= initializeXmlClasses();
+    }
+
+    return m_xmlClasses;
+  }
+
+  private List<XmlClass> initializeXmlClasses() {
+    List<XmlClass> result= Lists.newArrayList();
+    try {
+      String[] classes = PackageUtils.findClassesInPackage(m_name, m_include, m_exclude);
+
+      int index = 0;
+      for(String className: classes) {
+        result.add(new XmlClass(className, index++, false /* don't load classes */));
+      }
+    }
+    catch(IOException ioex) {
+      Utils.log("XmlPackage", 1, ioex.getMessage());
+    }
+
+    return result;
+  }
+
+  public String toXml(String indent) {
+    XMLStringBuffer xsb = new XMLStringBuffer(indent);
+    Properties p = new Properties();
+    p.setProperty("name", getName());
+
+    if (getInclude().isEmpty() && getExclude().isEmpty()) {
+      xsb.addEmptyElement("package", p);
+    } else {
+      xsb.push("package", p);
+
+      for (String m : getInclude()) {
+        Properties includeProp= new Properties();
+        includeProp.setProperty("name", m);
+        xsb.addEmptyElement("include", includeProp);
+      }
+      for (String m: getExclude()) {
+        Properties excludeProp= new Properties();
+        excludeProp.setProperty("name", m);
+        xsb.addEmptyElement("exclude", excludeProp);
+      }
+
+      xsb.pop("package");
+    }
+
+    return xsb.toXML();
+  }
+
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((m_exclude == null) ? 0 : m_exclude.hashCode());
+    result = prime * result + ((m_include == null) ? 0 : m_include.hashCode());
+    result = prime * result + ((m_name == null) ? 0 : m_name.hashCode());
+    result = prime * result
+        + ((m_xmlClasses == null) ? 0 : m_xmlClasses.hashCode());
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj)
+      return true;
+    if (obj == null)
+      return XmlSuite.f();
+    if (getClass() != obj.getClass())
+      return XmlSuite.f();
+    XmlPackage other = (XmlPackage) obj;
+    if (m_exclude == null) {
+      if (other.m_exclude != null)
+        return XmlSuite.f();
+    } else if (!m_exclude.equals(other.m_exclude))
+      return XmlSuite.f();
+    if (m_include == null) {
+      if (other.m_include != null)
+        return XmlSuite.f();
+    } else if (!m_include.equals(other.m_include))
+      return XmlSuite.f();
+    if (m_name == null) {
+      if (other.m_name != null)
+        return XmlSuite.f();
+    } else if (!m_name.equals(other.m_name))
+      return XmlSuite.f();
+    if (m_xmlClasses == null) {
+      if (other.m_xmlClasses != null)
+        return XmlSuite.f();
+    } else if (!m_xmlClasses.equals(other.m_xmlClasses))
+      return XmlSuite.f();
+    return true;
+  }
+
+}
diff --git a/src/main/java/org/testng/xml/XmlRun.java b/src/main/java/org/testng/xml/XmlRun.java
new file mode 100644
index 0000000..e1207c1
--- /dev/null
+++ b/src/main/java/org/testng/xml/XmlRun.java
@@ -0,0 +1,53 @@
+package org.testng.xml;
+
+import static org.testng.collections.CollectionUtils.hasElements;
+
+import org.testng.collections.Lists;
+import org.testng.reporters.XMLStringBuffer;
+import org.testng.xml.dom.OnElement;
+
+import java.util.List;
+
+public class XmlRun {
+
+  public String toXml(String indent) {
+    XMLStringBuffer xsb = new XMLStringBuffer(indent);
+    boolean hasElements = hasElements(m_excludes) || hasElements(m_includes);
+    if (hasElements) {
+      xsb.push("run");
+    }
+    for (String s : m_includes) {
+      xsb.addEmptyElement("include", "name", s);
+    }
+    for (String s : m_excludes) {
+      xsb.addEmptyElement("exclude", "name", s);
+    }
+    if (hasElements) {
+      xsb.pop("run");
+    }
+
+    return xsb.toXML();
+  }
+
+  private List<String> m_excludes = Lists.newArrayList();
+
+  public List<String> getExcludes() {
+    return m_excludes;
+  }
+
+  @OnElement(tag = "exclude", attributes = "name")
+  public void onExclude(String name) {
+    m_excludes.add(name);
+  }
+
+  private List<String> m_includes = Lists.newArrayList();
+
+  public List<String> getIncludes() {
+    return m_includes;
+  }
+
+  @OnElement(tag = "include", attributes = "name")
+  public void onInclude(String name) {
+    m_includes.add(name);
+  }
+}
diff --git a/src/main/java/org/testng/xml/XmlScript.java b/src/main/java/org/testng/xml/XmlScript.java
new file mode 100644
index 0000000..1aaa7bc
--- /dev/null
+++ b/src/main/java/org/testng/xml/XmlScript.java
@@ -0,0 +1,27 @@
+package org.testng.xml;
+
+import org.testng.xml.dom.TagContent;
+
+
+public class XmlScript {
+
+  private String m_language;
+  private String m_script;
+
+  public void setLanguage(String language) {
+    m_language = language;
+  }
+
+  @TagContent(name = "script")
+  public void setScript(String script) {
+    m_script = script;
+  }
+
+  public String getScript() {
+    return m_script;
+  }
+
+  public String getLanguage() {
+    return m_language;
+  }
+}
diff --git a/src/main/java/org/testng/xml/XmlSuite.java b/src/main/java/org/testng/xml/XmlSuite.java
new file mode 100755
index 0000000..678a926
--- /dev/null
+++ b/src/main/java/org/testng/xml/XmlSuite.java
@@ -0,0 +1,1074 @@
+package org.testng.xml;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import org.testng.ITestObjectFactory;
+import org.testng.TestNG;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.reporters.XMLStringBuffer;
+import org.testng.xml.dom.OnElement;
+import org.testng.xml.dom.OnElementList;
+import org.testng.xml.dom.Tag;
+
+import static org.testng.collections.CollectionUtils.hasElements;
+import static org.testng.internal.Utils.isStringNotEmpty;
+
+/**
+ * This class describes the tag &lt;suite&gt; in testng.xml.
+ *
+ * @author <a href = "mailto:cedric&#64;beust.com">Cedric Beust</a>
+ * @author <a href = 'mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ */
+public class XmlSuite implements Serializable, Cloneable {
+  /** Parallel modes */
+  public enum ParallelMode {
+    TESTS, METHODS, CLASSES, INSTANCES, NONE, TRUE, FALSE;
+
+    public static XmlSuite.ParallelMode getValidParallel(String parallel) {
+      if (parallel == null) {
+        return null;
+      }
+      try {
+        return XmlSuite.ParallelMode.valueOf(parallel.toUpperCase());
+      } catch (IllegalArgumentException e) {
+        return null;
+      }
+    }
+
+    @Override
+    public String toString() {
+      return name().toLowerCase();
+    }
+  }
+
+  /** Configuration failure policy options */
+  public static final String SKIP = "skip";
+  public static final String CONTINUE = "continue";
+
+  private String m_test;
+
+  /** The default suite name TODO CQ is this OK as a default name. */
+  private static final String DEFAULT_SUITE_NAME = "Default Suite";
+
+  /** The suite name (defaults to DEFAULT_SUITE_NAME) */
+  private String m_name = DEFAULT_SUITE_NAME;
+
+  /** The suite verbose flag. (0 to 10)*/
+  public static final Integer DEFAULT_VERBOSE = 1;
+  private Integer m_verbose = null;
+
+  public static final ParallelMode DEFAULT_PARALLEL = ParallelMode.FALSE;
+  private ParallelMode m_parallel = DEFAULT_PARALLEL;
+
+  private String m_parentModule = "";
+  private String m_guiceStage = "";
+
+  /** Whether to SKIP or CONTINUE to re-attempt failed configuration methods. */
+  public static final String DEFAULT_CONFIG_FAILURE_POLICY = SKIP;
+  private String m_configFailurePolicy = DEFAULT_CONFIG_FAILURE_POLICY;
+
+  /** JUnit compatibility flag. */
+  public static final Boolean DEFAULT_JUNIT = Boolean.FALSE;
+  private Boolean m_isJUnit = DEFAULT_JUNIT;
+
+  /** mixed mode flag. */
+  public static final Boolean DEFAULT_MIXED = Boolean.FALSE;
+  private Boolean m_isMixed = DEFAULT_MIXED;
+
+  public static final Boolean DEFAULT_SKIP_FAILED_INVOCATION_COUNTS = Boolean.FALSE;
+  private Boolean m_skipFailedInvocationCounts = DEFAULT_SKIP_FAILED_INVOCATION_COUNTS;
+
+  /** The thread count. */
+  public static final Integer DEFAULT_THREAD_COUNT = 5;
+  private int m_threadCount = DEFAULT_THREAD_COUNT;
+
+  /** Thread count for the data provider pool */
+  public static final Integer DEFAULT_DATA_PROVIDER_THREAD_COUNT = 10;
+  private int m_dataProviderThreadCount = DEFAULT_DATA_PROVIDER_THREAD_COUNT;
+
+  /** By default, a method failing will cause all instances of that class to skip */
+  public static final Boolean DEFAULT_GROUP_BY_INSTANCES = false;
+  private Boolean m_groupByInstances = DEFAULT_GROUP_BY_INSTANCES;
+
+  public static final Boolean DEFAULT_ALLOW_RETURN_VALUES = Boolean.FALSE;
+  private Boolean m_allowReturnValues = DEFAULT_ALLOW_RETURN_VALUES;
+
+  /** The packages containing test classes. */
+  private List<XmlPackage> m_xmlPackages = Lists.newArrayList();
+
+  /** BeanShell expression. */
+  private String m_expression = null;
+
+  /** Suite level method selectors. */
+  private List<XmlMethodSelector> m_methodSelectors = Lists.newArrayList();
+
+  /** Tests in suite. */
+  private List<XmlTest> m_tests = Lists.newArrayList();
+
+  /** Suite level parameters. */
+  private Map<String, String> m_parameters = Maps.newHashMap();
+
+  /** Name of the XML file */
+  private String m_fileName;
+
+  /** Time out for methods/tests */
+  private String m_timeOut;
+
+  /** List of child XML suite specified using <suite-file> tags */
+  private List<XmlSuite> m_childSuites = Lists.newArrayList();
+
+  /** Parent XML Suite if this suite was specified in another suite using <suite-file> tag */
+  private XmlSuite m_parentSuite;
+
+  private List<String> m_suiteFiles = Lists.newArrayList();
+
+  private ITestObjectFactory m_objectFactory;
+
+  private List<String> m_listeners = Lists.newArrayList();
+
+  private static final long serialVersionUID = 4999962288272750226L;
+
+  public static final String DEFAULT_PRESERVE_ORDER = "true";
+  private String m_preserveOrder = DEFAULT_PRESERVE_ORDER;
+
+  private List<String> m_includedGroups = Lists.newArrayList();
+  private List<String> m_excludedGroups = Lists.newArrayList();
+  private XmlMethodSelectors m_xmlMethodSelectors;
+
+  /**
+   * @return the fileName
+   */
+  public String getFileName() {
+    return m_fileName;
+  }
+
+  /**
+   * @param fileName the fileName to set
+   */
+  public void setFileName(String fileName) {
+    m_fileName = fileName;
+  }
+
+  /**
+   * Returns the parallel mode.
+   * @return the parallel mode.
+   */
+  public ParallelMode getParallel() {
+    return m_parallel;
+  }
+
+
+  public String getParentModule() {
+    return m_parentModule;
+  }
+
+  public String getGuiceStage() {
+    return m_guiceStage;
+  }
+
+  public ITestObjectFactory getObjectFactory() {
+    return m_objectFactory;
+  }
+
+  public void setObjectFactory(ITestObjectFactory objectFactory) {
+    m_objectFactory = objectFactory;
+  }
+
+  /**
+   * @deprecated Use #setParallel(XmlSuite.ParallelMode) instead
+   */
+  @Deprecated
+  public void setParallel(String parallel) {
+    if (parallel == null) {
+      setParallel((ParallelMode)null);
+    } else {
+      setParallel(XmlSuite.ParallelMode.getValidParallel(parallel));
+    }
+  }
+
+  /**
+   * Sets the parallel mode
+   * @param parallel the parallel mode
+   */
+  public void setParallel(ParallelMode parallel) {
+    if (parallel == null) {
+      m_parallel = DEFAULT_PARALLEL;
+    } else {
+      m_parallel = parallel;
+    }
+  }
+
+  public void setParentModule(String parentModule) {
+    m_parentModule = parentModule;
+  }
+
+  public void setGuiceStage(String guiceStage) {
+    m_guiceStage = guiceStage;
+  }
+
+  /**
+   * Sets the configuration failure policy.
+   * @param configFailurePolicy the config failure policy
+   */
+  public void setConfigFailurePolicy(String configFailurePolicy) {
+    m_configFailurePolicy = configFailurePolicy;
+  }
+
+  /**
+   * Returns the configuration failure policy.
+   * @return the configuration failure policy
+   */
+  public String getConfigFailurePolicy() {
+    return m_configFailurePolicy;
+  }
+
+
+  /**
+   * Returns the verbose.
+   * @return the verbose.
+   */
+  public Integer getVerbose() {
+    return m_verbose != null ? m_verbose : TestNG.DEFAULT_VERBOSE;
+  }
+
+  /**
+   * Set the verbose.
+   * @param verbose The verbose to set.
+   */
+  public void setVerbose(Integer verbose) {
+    m_verbose = verbose;
+  }
+
+  /**
+   * Returns the name.
+   * @return the name.
+   */
+  public String getName() {
+    return m_name;
+  }
+
+  /**
+   * Sets the name.
+   * @param name The name to set.
+   */
+  public void setName(String name) {
+    m_name = name;
+  }
+
+  /**
+   * Returns the test.
+   * @return the test.
+   */
+  public String getTest() {
+    return m_test;
+  }
+
+  /**
+   * Returns the tests.
+   * @return the tests.
+   */
+  public List<XmlTest> getTests() {
+    return m_tests;
+  }
+
+  // For YAML
+  public void setTests(List<XmlTest> tests) {
+    m_tests = tests;
+  }
+
+  /**
+   * Returns the method selectors.
+   *
+   * @return the method selectors.
+   */
+  public List<XmlMethodSelector> getMethodSelectors() {
+    if (m_xmlMethodSelectors != null) {
+      return m_xmlMethodSelectors.getMethodSelectors();
+    } else {
+      // deprecated
+      return m_methodSelectors;
+    }
+  }
+
+  /**
+   * Sets the method selectors.
+   *
+   * @param methodSelectors the method selectors.
+   */
+  public void setMethodSelectors(List<XmlMethodSelector> methodSelectors) {
+    m_methodSelectors = Lists.newArrayList(methodSelectors);
+  }
+
+  /**
+   * Updates the list of parameters that apply to this XML suite. This method
+   * should be invoked any time there is a change in the state of this suite that
+   * would affect the parameter list.<br>
+   * NOTE: Currently being invoked after a parent suite is added or if parameters
+   * for this suite are updated.
+   */
+  private void updateParameters() {
+    /*
+     * Whatever parameters are set by user or via XML, should be updated
+     * using parameters from parent suite, if it exists. Parameters from this
+     * suite override the same named parameters from parent suite.
+     */
+    if (m_parentSuite != null) {
+      Set<String> keySet = m_parentSuite.getParameters().keySet();
+      for (String name : keySet) {
+        if (!m_parameters.containsKey(name)) {
+           m_parameters.put(name, m_parentSuite.getParameter(name));
+        }
+      }
+    }
+  }
+
+  /**
+   * Sets parameters.
+   * @param parameters the parameters.
+   */
+  public void setParameters(Map<String, String> parameters) {
+    m_parameters = parameters;
+    updateParameters();
+  }
+
+  /**
+   * Gets the parameters that apply to tests in this suite.<br>
+   * Set of parameters for a suite is appended with parameters from parent suite.
+   * Also, parameters from this suite override the same named parameters from
+   * parent suite.
+   */
+  public Map<String, String> getParameters() {
+    return m_parameters;
+  }
+
+  /**
+   * @return The parameters defined in this suite and all its XmlTests.
+   */
+  public Map<String, String> getAllParameters() {
+    Map<String, String> result = Maps.newHashMap();
+    for (Map.Entry<String, String> entry : m_parameters.entrySet()) {
+      result.put(entry.getKey(), entry.getValue());
+    }
+
+    for (XmlTest test : getTests()) {
+      Map<String, String> tp = test.getLocalParameters();
+      for (Map.Entry<String, String> entry : tp.entrySet()) {
+        result.put(entry.getKey(), entry.getValue());
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Returns the parameter defined in this suite only.
+   * @param name the parameter name.
+   * @return The parameter defined in this suite only.
+   */
+  public String getParameter(String name) {
+    return m_parameters.get(name);
+  }
+
+  /**
+   * @return The threadCount.
+   */
+  public int getThreadCount() {
+    return m_threadCount;
+  }
+
+  /**
+   * Set the thread count.
+   * @param threadCount The thread count to set.
+   */
+  public void setThreadCount(int threadCount) {
+    m_threadCount = threadCount;
+  }
+
+  /**
+   * @return The JUnit compatibility flag.
+   */
+  public Boolean isJUnit() {
+    return m_isJUnit;
+  }
+
+  /**
+   * Sets the JUnit compatibility flag.
+   *
+   * @param isJUnit the JUnit compatibility flag.
+   */
+  public void setJUnit(Boolean isJUnit) {
+    m_isJUnit = isJUnit;
+  }
+
+  // For YAML
+  public void setJunit(Boolean j) {
+    setJUnit(j);
+  }
+
+  public Boolean skipFailedInvocationCounts() {
+    return m_skipFailedInvocationCounts;
+  }
+
+  public void setSkipFailedInvocationCounts(boolean skip) {
+    m_skipFailedInvocationCounts = skip;
+  }
+
+  /**
+   * Sets the XML packages.
+   *
+   * @param packages the XML packages.
+   */
+  public void setXmlPackages(List<XmlPackage> packages) {
+    m_xmlPackages = Lists.newArrayList(packages);
+  }
+
+  /**
+   * Returns the XML packages.
+   *
+   * @return the XML packages.
+   */
+  public List<XmlPackage> getXmlPackages() {
+    return m_xmlPackages;
+  }
+
+
+  // For YAML
+  public List<XmlPackage> getPackages() {
+    return getXmlPackages();
+  }
+
+  @Tag(name = "method-selectors")
+  public void setMethodSelectors(XmlMethodSelectors xms) {
+    m_xmlMethodSelectors = xms;
+  }
+
+  // For YAML
+  public void setPackages(List<XmlPackage> packages) {
+    setXmlPackages(packages);
+  }
+
+  /**
+   * @return A String representation of this XML suite.
+   */
+  public String toXml() {
+    XMLStringBuffer xsb = new XMLStringBuffer();
+    xsb.setDocType("suite SYSTEM \"" + Parser.TESTNG_DTD_URL + '\"');
+    Properties p = new Properties();
+    p.setProperty("name", getName());
+    if (getVerbose() != null) {
+      XmlUtils.setProperty(p, "verbose", getVerbose().toString(), DEFAULT_VERBOSE.toString());
+    }
+    final ParallelMode parallel= getParallel();
+    if(parallel != null && !DEFAULT_PARALLEL.equals(parallel)) {
+      p.setProperty("parallel", parallel.toString());
+    }
+    XmlUtils.setProperty(p, "group-by-instances", String.valueOf(getGroupByInstances()),
+        DEFAULT_GROUP_BY_INSTANCES.toString());
+    XmlUtils.setProperty(p, "configfailurepolicy", getConfigFailurePolicy(),
+        DEFAULT_CONFIG_FAILURE_POLICY);
+    XmlUtils.setProperty(p, "thread-count", String.valueOf(getThreadCount()),
+        DEFAULT_THREAD_COUNT.toString());
+    XmlUtils.setProperty(p, "data-provider-thread-count", String.valueOf(getDataProviderThreadCount()),
+        DEFAULT_DATA_PROVIDER_THREAD_COUNT.toString());
+    if (! DEFAULT_JUNIT.equals(m_isJUnit)) {
+      p.setProperty("junit", m_isJUnit != null ? m_isJUnit.toString() : "false"); // TESTNG-141
+    }
+    XmlUtils.setProperty(p, "skipfailedinvocationcounts", m_skipFailedInvocationCounts.toString(),
+        DEFAULT_SKIP_FAILED_INVOCATION_COUNTS.toString());
+    if(null != m_objectFactory) {
+      p.setProperty("object-factory", m_objectFactory.getClass().getName());
+    }
+    if (isStringNotEmpty(m_parentModule)) {
+      p.setProperty("parent-module", getParentModule());
+    }
+    if (isStringNotEmpty(m_guiceStage)) {
+      p.setProperty("guice-stage", getGuiceStage());
+    }
+    XmlUtils.setProperty(p, "allow-return-values", String.valueOf(getAllowReturnValues()),
+        DEFAULT_ALLOW_RETURN_VALUES.toString());
+    xsb.push("suite", p);
+
+    XmlUtils.dumpParameters(xsb, m_parameters);
+
+    if (hasElements(m_listeners)) {
+      xsb.push("listeners");
+      for (String listenerName: m_listeners) {
+        Properties listenerProps = new Properties();
+        listenerProps.setProperty("class-name", listenerName);
+        xsb.addEmptyElement("listener", listenerProps);
+      }
+      xsb.pop("listeners");
+    }
+
+    if (hasElements(getXmlPackages())) {
+      xsb.push("packages");
+
+      for (XmlPackage pack : getXmlPackages()) {
+        xsb.getStringBuffer().append(pack.toXml("    "));
+      }
+
+      xsb.pop("packages");
+    }
+
+    if (getXmlMethodSelectors() != null) {
+      xsb.getStringBuffer().append(getXmlMethodSelectors().toXml("  "));
+    } else {
+      // deprecated
+      if (hasElements(getMethodSelectors())) {
+        xsb.push("method-selectors");
+        for (XmlMethodSelector selector : getMethodSelectors()) {
+          xsb.getStringBuffer().append(selector.toXml("  "));
+        }
+
+        xsb.pop("method-selectors");
+      }
+    }
+
+    List<String> suiteFiles = getSuiteFiles();
+    if (suiteFiles.size() > 0) {
+      xsb.push("suite-files");
+      for (String sf : suiteFiles) {
+        Properties prop = new Properties();
+        prop.setProperty("path", sf);
+        xsb.addEmptyElement("suite-file", prop);
+      }
+      xsb.pop("suite-files");
+    }
+
+    List<String> included = getIncludedGroups();
+    List<String> excluded = getExcludedGroups();
+    if (hasElements(included) || hasElements(excluded)) {
+      xsb.push("groups");
+      xsb.push("run");
+      for (String g : included) {
+        xsb.addEmptyElement("include", "name", g);
+      }
+      for (String g : excluded) {
+        xsb.addEmptyElement("exclude", "name", g);
+      }
+      xsb.pop("run");
+      xsb.pop("groups");
+    }
+
+    if (m_xmlGroups != null) {
+      xsb.getStringBuffer().append(m_xmlGroups.toXml("  "));
+    }
+
+    for (XmlTest test : getTests()) {
+      xsb.getStringBuffer().append(test.toXml("  "));
+    }
+
+    xsb.pop("suite");
+
+    return xsb.toXML();
+  }
+
+  @Tag(name = "method-selectors")
+  public void setXmlMethodSelectors(XmlMethodSelectors xms) {
+    m_xmlMethodSelectors = xms;
+  }
+
+  private XmlMethodSelectors getXmlMethodSelectors() {
+    return m_xmlMethodSelectors;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString() {
+    StringBuffer result = new StringBuffer("[Suite: \"").append( m_name).append( "\" ");
+
+    for (XmlTest t : m_tests) {
+      result.append("  ").append( t.toString()).append(' ');
+    }
+
+    for (XmlMethodSelector ms : m_methodSelectors) {
+      result.append(" methodSelector:" + ms);
+    }
+
+    result.append(']');
+
+    return result.toString();
+  }
+
+  /**
+   * Logs to System.out.
+   * @param s the message to log.
+   */
+  private static void ppp(String s) {
+    System.out.println("[XmlSuite] " + s);
+  }
+
+  /**
+   * {@inheritDoc}
+   * Note that this is not a full clone:  XmlTest children are not cloned by this
+   * method.
+   */
+  @Override
+  public Object clone() {
+    XmlSuite result = new XmlSuite();
+    
+    result.setExcludedGroups(getExcludedGroups());
+    result.setIncludedGroups(getIncludedGroups());
+    result.setGroupByInstances(getGroupByInstances());
+    result.setGroups(getGroups());
+    result.setMethodSelectors(getXmlMethodSelectors());
+    result.setPackages(getPackages());
+    result.setParentSuite(getParentSuite());
+    result.setPreserveOrder(getPreserveOrder());
+    result.setSuiteFiles(getSuiteFiles());
+    result.setTests(getTests());
+    result.setXmlMethodSelectors(getXmlMethodSelectors());
+    result.setName(getName());
+    result.setFileName(getFileName());
+    result.setListeners(getListeners());
+    result.setParallel(getParallel());
+    result.setParentModule(getParentModule());
+    result.setGuiceStage(getGuiceStage());
+    result.setConfigFailurePolicy(getConfigFailurePolicy());
+    result.setThreadCount(getThreadCount());
+    result.setDataProviderThreadCount(getDataProviderThreadCount());
+    result.setParameters(getAllParameters());
+    result.setVerbose(getVerbose());
+    result.setXmlPackages(getXmlPackages());
+//    result.setBeanShellExpression(getExpression());
+    result.setMethodSelectors(getMethodSelectors());
+    result.setJUnit(isJUnit()); // TESTNG-141
+    result.setSkipFailedInvocationCounts(skipFailedInvocationCounts());
+    result.setObjectFactory(getObjectFactory());
+    result.setAllowReturnValues(getAllowReturnValues());
+    result.setTimeOut(getTimeOut());
+    return result;
+  }
+
+  /**
+   * Sets the timeout.
+   *
+   * @param timeOut the timeout.
+   */
+  public void setTimeOut(String timeOut) {
+    m_timeOut = timeOut;
+  }
+
+  /**
+   * Returns the timeout.
+   * @return the timeout.
+   */
+  public String getTimeOut() {
+    return m_timeOut;
+  }
+  
+  /**
+   * Returns the timeout as a long value specifying the default value to be used if
+   * no timeout was specified.
+   *
+   * @param def the the default value to be used if no timeout was specified.
+   * @return the timeout as a long value specifying the default value to be used if
+   * no timeout was specified.
+   */
+  public long getTimeOut(long def) {
+    long result = def;
+    if (m_timeOut != null) {
+        result = Long.parseLong(m_timeOut);
+    }
+    
+    return result;
+  }
+
+  /**
+   * Sets the suite files.
+   *
+   * @param files the suite files.
+   */
+  public void setSuiteFiles(List<String> files) {
+    m_suiteFiles = files;
+  }
+  
+  /**
+   * Returns the suite files.
+   * @return the suite files.
+   */
+  public List<String> getSuiteFiles() {
+    return m_suiteFiles;
+  }
+
+  public void setListeners(List<String> listeners) {
+    m_listeners = listeners;
+  }
+
+  public List<String> getListeners() {
+    if (m_parentSuite != null) {
+      List<String> listeners = m_parentSuite.getListeners();
+      for (String listener : listeners) {
+        if (!m_listeners.contains(listener)) {
+           m_listeners.add(listener);
+        }
+      }
+    }
+    return m_listeners;
+  }
+
+  public void setDataProviderThreadCount(int count) {
+    m_dataProviderThreadCount = count;
+  }
+
+  public int getDataProviderThreadCount() {
+    // org.testng.CommandLineArgs.DATA_PROVIDER_THREAD_COUNT
+    String s = System.getProperty("dataproviderthreadcount");
+    if (s != null) {
+      try {
+        int nthreads = Integer.parseInt(s);
+        return nthreads;
+      } catch(NumberFormatException nfe) {
+        System.err.println("Parsing System property 'dataproviderthreadcount': " + nfe);
+      }
+    }
+    return m_dataProviderThreadCount;
+  }
+
+  public void setParentSuite(XmlSuite parentSuite) {
+    m_parentSuite = parentSuite;
+    updateParameters();
+  }
+
+  public XmlSuite getParentSuite() {
+    return m_parentSuite;
+  }
+
+  public List<XmlSuite> getChildSuites() {
+    return m_childSuites;
+  }
+
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+//      result = prime * result
+//          + ((m_childSuites == null) ? 0 : m_childSuites.hashCode());
+    result = prime
+        * result
+        + ((m_configFailurePolicy == null) ? 0 : m_configFailurePolicy
+            .hashCode());
+    result = prime * result + m_dataProviderThreadCount;
+    result = prime * result
+        + ((m_expression == null) ? 0 : m_expression.hashCode());
+    result = prime * result
+        + ((m_fileName == null) ? 0 : m_fileName.hashCode());
+    result = prime * result
+        + ((m_isJUnit == null) ? 0 : m_isJUnit.hashCode());
+    result = prime * result
+        + ((m_listeners == null) ? 0 : m_listeners.hashCode());
+
+    result = prime * result
+        + ((m_methodSelectors == null) ? 0 : m_methodSelectors.hashCode());
+    result = prime * result + ((m_name == null) ? 0 : m_name.hashCode());
+    result = prime * result
+        + ((m_objectFactory == null) ? 0 : m_objectFactory.hashCode());
+    result = prime * result
+        + ((m_parallel == null) ? 0 : m_parallel.hashCode());
+//    result = prime * result
+//        + ((m_parameters == null) ? 0 : m_parameters.hashCode());
+//      result = prime * result
+//          + ((m_parentSuite == null) ? 0 : m_parentSuite.hashCode());
+    result = prime
+        * result
+        + ((m_skipFailedInvocationCounts == null) ? 0
+            : m_skipFailedInvocationCounts.hashCode());
+    result = prime * result
+        + ((m_suiteFiles == null) ? 0 : m_suiteFiles.hashCode());
+    result = prime * result + ((m_test == null) ? 0 : m_test.hashCode());
+    result = prime * result + ((m_tests == null) ? 0 : m_tests.hashCode());
+    result = prime * result + m_threadCount;
+    result = prime * result
+        + ((m_timeOut == null) ? 0 : m_timeOut.hashCode());
+    result = prime * result
+        + ((m_verbose == null) ? 0 : m_verbose.hashCode());
+    result = prime * result
+        + ((m_xmlPackages == null) ? 0 : m_xmlPackages.hashCode());
+    return result;
+  }
+
+  /**
+   * Used to debug equals() bugs.
+   */
+  static boolean f() {
+    return false;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null) {
+      return f();
+    }
+    if (getClass() != obj.getClass()) {
+      return f();
+    }
+    XmlSuite other = (XmlSuite) obj;
+//      if (m_childSuites == null) {
+//        if (other.m_childSuites != null)
+//          return f();
+//      } else if (!m_childSuites.equals(other.m_childSuites))
+//        return f();
+    if (m_configFailurePolicy == null) {
+      if (other.m_configFailurePolicy != null) {
+        return f();
+      }
+    } else if (!m_configFailurePolicy.equals(other.m_configFailurePolicy)) {
+      return f();
+    }
+    if (m_dataProviderThreadCount != other.m_dataProviderThreadCount) {
+      return f();
+    }
+    if (m_expression == null) {
+      if (other.m_expression != null) {
+        return f();
+      }
+    } else if (!m_expression.equals(other.m_expression)) {
+      return f();
+    }
+    if (m_isJUnit == null) {
+      if (other.m_isJUnit != null) {
+        return f();
+      }
+    } else if (!m_isJUnit.equals(other.m_isJUnit)) {
+      return f();
+    }
+    if (m_listeners == null) {
+      if (other.m_listeners != null) {
+        return f();
+      }
+    } else if (!m_listeners.equals(other.m_listeners)) {
+      return f();
+    }
+    if (m_methodSelectors == null) {
+      if (other.m_methodSelectors != null) {
+        return f();
+      }
+    } else if (!m_methodSelectors.equals(other.m_methodSelectors)) {
+      return f();
+    }
+    if (m_name == null) {
+      if (other.m_name != null) {
+        return f();
+      }
+    } else if (!m_name.equals(other.m_name)) {
+      return f();
+    }
+    if (m_objectFactory == null) {
+      if (other.m_objectFactory != null) {
+        return f();
+      }
+    } else if (!m_objectFactory.equals(other.m_objectFactory)) {
+      return f();
+    }
+    if (m_parallel == null) {
+      if (other.m_parallel != null) {
+        return f();
+      }
+    } else if (!m_parallel.equals(other.m_parallel)) {
+      return f();
+    }
+//    if (m_parameters == null) {
+//      if (other.m_parameters != null) {
+//        return f();
+//      }
+//    } else if (!m_parameters.equals(other.m_parameters)) {
+//      return f();
+//    }
+//      if (m_parentSuite == null) {
+//        if (other.m_parentSuite != null)
+//          return f();
+//      } else if (!m_parentSuite.equals(other.m_parentSuite))
+//        return f();
+    if (m_skipFailedInvocationCounts == null) {
+      if (other.m_skipFailedInvocationCounts != null)
+        return f();
+    } else if (!m_skipFailedInvocationCounts
+        .equals(other.m_skipFailedInvocationCounts))
+      return f();
+    if (m_suiteFiles == null) {
+      if (other.m_suiteFiles != null)
+        return f();
+    } else if (!m_suiteFiles.equals(other.m_suiteFiles))
+      return f();
+    if (m_test == null) {
+      if (other.m_test != null)
+        return f();
+    } else if (!m_test.equals(other.m_test))
+      return f();
+    if (m_tests == null) {
+      if (other.m_tests != null)
+        return f();
+    } else if (!m_tests.equals(other.m_tests))
+      return f();
+    if (m_threadCount != other.m_threadCount)
+      return f();
+    if (m_timeOut == null) {
+      if (other.m_timeOut != null)
+        return f();
+    } else if (!m_timeOut.equals(other.m_timeOut))
+      return f();
+    if (m_verbose == null) {
+      if (other.m_verbose != null)
+        return f();
+    } else if (!m_verbose.equals(other.m_verbose))
+      return f();
+    if (m_xmlPackages == null) {
+      if (other.m_xmlPackages != null)
+        return f();
+    } else if (!m_xmlPackages.equals(other.m_xmlPackages))
+      return f();
+    return true;
+  }
+
+
+  /**
+   * The DTD sometimes forces certain attributes to receive a default value. Such
+   * a value is considered equal to another one if that other one is null.
+   */
+  private boolean eq(String o1, String o2, String def) {
+    boolean result = false;
+    if (o1 == null && o2 == null) result = true;
+    else if (o1 != null) {
+      result = o1.equals(o2) || (def.equals(o1) && o2 == null);
+    }
+    else if (o2 != null) {
+      result = o2.equals(o1) || (def.equals(o2) && o1 == null);
+    }
+    return result;
+  }
+
+  public void setPreserveOrder(String f) {
+    m_preserveOrder = f;
+  }
+
+  public String getPreserveOrder() {
+    return m_preserveOrder;
+  }
+
+  /**
+   * @return Returns the includedGroups.
+   * Note: do not modify the returned value, use {@link #addIncludedGroup(String)}.
+   */
+  public List<String> getIncludedGroups() {
+    if (m_xmlGroups != null) {
+      return m_xmlGroups.getRun().getIncludes();
+    } else {
+      // deprecated
+      return m_includedGroups;
+    }
+  }
+
+  public void addIncludedGroup(String g) {
+    m_includedGroups.add(g);
+  }
+
+  /**
+   * @param g
+   */
+  public void setIncludedGroups(List<String> g) {
+    m_includedGroups = g;
+  }
+
+  /**
+   * @param g The excludedGrousps to set.
+   */
+  public void setExcludedGroups(List<String> g) {
+    m_excludedGroups = g;
+  }
+
+  /**
+   * @return Returns the excludedGroups.
+   * Note: do not modify the returned value, use {@link #addExcludedGroup(String)}.
+   */
+  public List<String> getExcludedGroups() {
+    if (m_xmlGroups != null) {
+      return m_xmlGroups.getRun().getExcludes();
+    } else {
+      return m_excludedGroups;
+    }
+  }
+
+  public void addExcludedGroup(String g) {
+    m_excludedGroups.add(g);
+  }
+
+  public Boolean getGroupByInstances() {
+    return m_groupByInstances;
+  }
+
+  public void setGroupByInstances(boolean f) {
+    m_groupByInstances = f;
+  }
+
+  public void addListener(String listener) {
+    m_listeners.add(listener);
+  }
+
+  public Boolean getAllowReturnValues() {
+    return m_allowReturnValues;
+  }
+
+  public void setAllowReturnValues(Boolean allowReturnValues) {
+    m_allowReturnValues = allowReturnValues;
+  }
+
+  private XmlGroups m_xmlGroups;
+
+  public void setGroups(XmlGroups xmlGroups) {
+    m_xmlGroups = xmlGroups;
+  }
+
+  @OnElement(tag = "parameter", attributes = { "name", "value" })
+  public void onParameterElement(String name, String value) {
+    getParameters().put(name, value);
+  }
+
+  @OnElementList(tag = "listeners", attributes = { "class-name" })
+  public void onListenerElement(String className) {
+    addListener(className);
+  }
+
+  @OnElementList(tag = "suite-files", attributes = { "path" })
+  public void onSuiteFilesElement(String path) {
+    getSuiteFiles().add(path);
+  }
+
+  @OnElementList(tag = "packages", attributes = { "name" })
+  public void onPackagesElement(String name) {
+    getPackages().add(new XmlPackage(name));
+  }
+
+//  @OnElementList(tag = "method-selectors", attributes = { "language", "name", "priority" })
+  public void onMethodSelectorElement(String language, String name, String priority) {
+    System.out.println("Language:" + language);
+  }
+
+  public XmlGroups getGroups() {
+    return m_xmlGroups;
+  }
+
+  public void addTest(XmlTest test) {
+    getTests().add(test);
+  }
+
+  public Collection<String> getPackageNames() {
+    List<String> result = Lists.newArrayList();
+    for (XmlPackage p : getPackages()) {
+      result.add(p.getName());
+    }
+    return result;
+  }
+}
+
diff --git a/src/main/java/org/testng/xml/XmlTest.java b/src/main/java/org/testng/xml/XmlTest.java
new file mode 100755
index 0000000..336a46b
--- /dev/null
+++ b/src/main/java/org/testng/xml/XmlTest.java
@@ -0,0 +1,899 @@
+package org.testng.xml;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.UUID;
+
+import org.testng.TestNG;
+import org.testng.TestNGException;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.reporters.XMLStringBuffer;
+import org.testng.xml.dom.ParentSetter;
+
+/**
+ * This class describes the tag &lt;test&gt;  in testng.xml.
+ *
+ * @author <a href = "mailto:cedric&#64;beust.com">Cedric Beust</a>
+ * @author <a href = 'mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ */
+public class XmlTest implements Serializable, Cloneable {
+  private static final long serialVersionUID = 6533504325942417606L;
+
+  public static final int DEFAULT_TIMEOUT_MS = Integer.MAX_VALUE;
+
+  private XmlSuite m_suite;
+  private String m_name;
+  private Integer m_verbose = XmlSuite.DEFAULT_VERBOSE;
+  private Boolean m_isJUnit = XmlSuite.DEFAULT_JUNIT;
+  private int m_threadCount= -1;
+
+  private List<XmlClass> m_xmlClasses = Lists.newArrayList();
+
+  private List<String> m_includedGroups = Lists.newArrayList();
+  private List<String> m_excludedGroups = Lists.newArrayList();
+
+  private Map<String, List<String>> m_metaGroups = Maps.newHashMap();
+  private Map<String, String> m_parameters = Maps.newHashMap();
+  private XmlSuite.ParallelMode m_parallel;
+
+  private List<XmlMethodSelector> m_methodSelectors = Lists.newArrayList();
+  // test level packages
+  private List<XmlPackage> m_xmlPackages = Lists.newArrayList();
+
+  private String m_timeOut;
+  private Boolean m_skipFailedInvocationCounts = XmlSuite.DEFAULT_SKIP_FAILED_INVOCATION_COUNTS;
+  private Map<String, List<Integer>> m_failedInvocationNumbers = null; // lazily initialized
+
+  private String m_preserveOrder = XmlSuite.DEFAULT_PRESERVE_ORDER;
+
+  private int m_index;
+
+  private Boolean m_groupByInstances;
+
+  private Boolean m_allowReturnValues = null;
+
+  private Map<String, String> m_xmlDependencyGroups = Maps.newHashMap();
+
+  /**
+   * Constructs a <code>XmlTest</code> and adds it to suite's list of tests.
+   *
+   * @param suite the parent suite.
+   * @param index the index of this test tag in testng.xml
+   */
+  public XmlTest(XmlSuite suite, int index) {
+    init(suite, index);
+  }
+
+  public XmlTest(XmlSuite suite) {
+    init(suite, 0);
+  }
+
+  private void init(XmlSuite suite, int index) {
+    m_suite = suite;
+    m_suite.getTests().add(this);
+    m_index = index;
+    //no two tests in the same suite should have the same name.
+    //so, make the default test name unique
+    m_name = TestNG.DEFAULT_COMMAND_LINE_TEST_NAME
+      + " " + UUID.randomUUID().toString();
+  }
+
+  // For YAML
+  public XmlTest() {
+  }
+
+  public void setXmlPackages(List<XmlPackage> packages) {
+    m_xmlPackages = Lists.newArrayList(packages);
+  }
+
+  public List<XmlPackage> getXmlPackages() {
+    return m_xmlPackages;
+  }
+
+  // For YAML
+  public List<XmlPackage> getPackages() {
+    return getXmlPackages();
+  }
+
+  // For YAML
+  public void setPackages(List<XmlPackage> p) {
+    setXmlPackages(p);
+  }
+
+  public List<XmlMethodSelector> getMethodSelectors() {
+    return m_methodSelectors;
+  }
+
+  public void setMethodSelectors(List<XmlMethodSelector> methodSelectors) {
+    m_methodSelectors = Lists.newArrayList(methodSelectors);
+  }
+
+  /**
+   * Returns the suite this test is part of.
+   * @return the suite this test is part of.
+   */
+  public XmlSuite getSuite() {
+    return m_suite;
+  }
+
+  /**
+   * @return the includedGroups.
+   * Note: do not modify the returned value, use {@link #addIncludedGroup(String)}.
+   */
+  public List<String> getIncludedGroups() {
+    List<String> result;
+    if (m_xmlGroups != null) {
+      result = m_xmlGroups.getRun().getIncludes();
+      result.addAll(m_suite.getIncludedGroups());
+    } else {
+      // deprecated
+      result = Lists.newArrayList(m_includedGroups);
+      result.addAll(m_suite.getIncludedGroups());
+    }
+    return result;
+  }
+
+  /**
+   * Sets the XML Classes.
+   * @param classes The classes to set.
+   * @deprecated use setXmlClasses
+   */
+  @Deprecated
+  public void setClassNames(List<XmlClass> classes) {
+    m_xmlClasses = classes;
+  }
+
+  /**
+   * @return Returns the classes.
+   */
+  public List<XmlClass> getXmlClasses() {
+    return m_xmlClasses;
+  }
+
+  // For YAML
+  public List<XmlClass> getClasses() {
+    return getXmlClasses();
+  }
+
+  // For YAML
+  public void setClasses(List<XmlClass> c) {
+    setXmlClasses(c);
+  }
+
+  /**
+   * Sets the XML Classes.
+   * @param classes The classes to set.
+   */
+  public void setXmlClasses(List<XmlClass> classes) {
+    m_xmlClasses = classes;
+  }
+
+  /**
+   * @return Returns the name.
+   */
+  public String getName() {
+    return m_name;
+  }
+
+  /**
+   * @param name The name to set.
+   */
+  public void setName(String name) {
+    m_name = name;
+  }
+
+  /**
+   * @param v
+   */
+  public void setVerbose(int v) {
+    m_verbose = v;
+  }
+
+  public int getThreadCount() {
+    return m_threadCount > 0 ? m_threadCount : getSuite().getThreadCount();
+  }
+
+  public void setThreadCount(int threadCount) {
+    m_threadCount = threadCount;
+  }
+
+  /**
+   * @param g
+   */
+  public void setIncludedGroups(List<String> g) {
+    m_includedGroups = g;
+  }
+
+  /**
+   * @param g The excludedGrousps to set.
+   */
+  public void setExcludedGroups(List<String> g) {
+    m_excludedGroups = g;
+  }
+
+  /**
+   * @return Returns the excludedGroups.
+   * Note: do not modify the returned value, use {@link #addExcludedGroup(String)}.
+   */
+  public List<String> getExcludedGroups() {
+    List<String> result = new ArrayList(m_excludedGroups);
+    result.addAll(m_suite.getExcludedGroups());
+    return result;
+  }
+
+  public void addIncludedGroup(String g) {
+    m_includedGroups.add(g);
+  }
+
+  public void addExcludedGroup(String g) {
+    m_excludedGroups.add(g);
+  }
+
+  /**
+   * @return Returns the verbose.
+   */
+  public int getVerbose() {
+    Integer result = m_verbose;
+    if (null == result || XmlSuite.DEFAULT_VERBOSE.equals(m_verbose)) {
+      result = m_suite.getVerbose();
+    }
+
+    if (null != result) {
+      return result;
+    } else {
+      return 1;
+    }
+  }
+
+  public boolean getGroupByInstances() {
+    Boolean result = m_groupByInstances;
+    if (result == null || XmlSuite.DEFAULT_GROUP_BY_INSTANCES.equals(m_groupByInstances)) {
+      result = m_suite.getGroupByInstances();
+    }
+    if (result != null) {
+      return result;
+    } else {
+      return XmlSuite.DEFAULT_GROUP_BY_INSTANCES;
+    }
+  }
+
+  public void setGroupByInstances(boolean f) {
+    m_groupByInstances = f;
+  }
+
+  /**
+   * @return Returns the isJUnit.
+   */
+  public boolean isJUnit() {
+    Boolean result = m_isJUnit;
+    if (null == result || XmlSuite.DEFAULT_JUNIT.equals(result)) {
+      result = m_suite.isJUnit();
+    }
+
+    return result;
+  }
+
+  /**
+   * @param isJUnit The isJUnit to set.
+   */
+  public void setJUnit(boolean isJUnit) {
+    m_isJUnit = isJUnit;
+  }
+
+  // For YAML
+  public void setJunit(boolean isJUnit) {
+    setJUnit(isJUnit);
+  }
+
+  public void setSkipFailedInvocationCounts(boolean skip) {
+    m_skipFailedInvocationCounts = skip;
+  }
+
+  /**
+   * @return Returns the isJUnit.
+   */
+  public boolean skipFailedInvocationCounts() {
+    Boolean result = m_skipFailedInvocationCounts;
+    if (null == result) {
+      result = m_suite.skipFailedInvocationCounts();
+    }
+
+    return result;
+  }
+
+  public void addMetaGroup(String name, List<String> metaGroup) {
+    m_metaGroups.put(name, metaGroup);
+  }
+
+  // For YAML
+  public void setMetaGroups(Map<String, List<String>> metaGroups) {
+    m_metaGroups = metaGroups;
+  }
+
+  /**
+   * @return Returns the metaGroups.
+   */
+  public Map<String, List<String>> getMetaGroups() {
+    if (m_xmlGroups != null) {
+      Map<String, List<String>> result = Maps.newHashMap();
+      List<XmlDefine> defines = m_xmlGroups.getDefines();
+      for (XmlDefine xd : defines) {
+        result.put(xd.getName(), xd.getIncludes());
+      }
+      return result;
+    } else {
+      // deprecated
+      return m_metaGroups;
+    }
+  }
+
+  /**
+   * @param parameters
+   */
+  public void setParameters(Map<String, String> parameters) {
+    m_parameters = parameters;
+  }
+
+  public void addParameter(String key, String value) {
+    m_parameters.put(key, value);
+  }
+
+  public String getParameter(String name) {
+    String result = m_parameters.get(name);
+    if (null == result) {
+      result = m_suite.getParameter(name);
+    }
+
+    return result;
+  }
+
+  /**
+   * @return the parameters defined in this test tag and the tags above it.
+   */
+  public Map<String, String> getAllParameters() {
+    Map<String, String> result = Maps.newHashMap();
+    result.putAll(getSuite().getParameters());
+    result.putAll(m_parameters);
+    return result;
+  }
+
+  /**
+   * @return the parameters defined in this tag, and only this test tag. To retrieve
+   * the inherited parameters as well, call {@code getAllParameters()}.
+   */
+  public Map<String, String> getLocalParameters() {
+    return m_parameters;
+  }
+
+  /**
+   * @deprecated Use {@code getLocalParameters()} or {@code getAllParameters()}
+   */
+  @Deprecated
+  public Map<String, String> getParameters() {
+    return getAllParameters();
+  }
+
+  /**
+   * @return the parameters defined on this <test> tag only
+   */
+  public Map<String, String> getTestParameters() {
+    return m_parameters;
+  }
+
+  public void setParallel(XmlSuite.ParallelMode parallel) {
+    m_parallel = parallel;
+  }
+
+  public XmlSuite.ParallelMode getParallel() {
+    XmlSuite.ParallelMode result;
+    if (null != m_parallel || XmlSuite.DEFAULT_PARALLEL.equals(m_parallel)) {
+      result = m_parallel;
+    }
+    else {
+      result = m_suite.getParallel();
+    }
+
+    return result;
+  }
+
+  public String getTimeOut() {
+    String result = null;
+    if (null != m_timeOut) {
+      result = m_timeOut;
+    }
+    else {
+      result = m_suite.getTimeOut();
+    }
+
+    return result;
+  }
+
+  public long getTimeOut(long def) {
+    long result = def;
+    if (getTimeOut() != null) {
+        result = Long.parseLong(getTimeOut());
+    }
+
+    return result;
+  }
+
+  public void setTimeOut(long timeOut) {
+      m_timeOut = Long.toString(timeOut);
+  }
+
+  private void setTimeOut(String timeOut) {
+      m_timeOut = timeOut;
+  }
+
+  public void setExpression(String expression) {
+    setBeanShellExpression(expression);
+  }
+
+  public void setBeanShellExpression(String expression) {
+    List<XmlMethodSelector> selectors = getMethodSelectors();
+    if (selectors.size() > 0) {
+      selectors.get(0).setExpression(expression);
+    } else if (expression != null) {
+      XmlMethodSelector xms = new XmlMethodSelector();
+      xms.setExpression(expression);
+      xms.setLanguage("BeanShell");
+      getMethodSelectors().add(xms);
+    }
+  }
+
+  public String getExpression() {
+    List<XmlMethodSelector> selectors = getMethodSelectors();
+    if (selectors.size() > 0) {
+      return selectors.get(0).getExpression();
+    } else {
+      return null;
+    }
+  }
+
+  public String toXml(String indent) {
+    XMLStringBuffer xsb = new XMLStringBuffer(indent);
+    Properties p = new Properties();
+    p.setProperty("name", getName());
+    if (m_isJUnit != null) {
+      XmlUtils.setProperty(p, "junit", m_isJUnit.toString(), XmlSuite.DEFAULT_JUNIT.toString());
+    }
+    if (m_parallel != null) {
+      XmlUtils.setProperty(p, "parallel", m_parallel.toString(), XmlSuite.DEFAULT_PARALLEL.toString());
+    }
+    if (m_verbose != null) {
+      XmlUtils.setProperty(p, "verbose", m_verbose.toString(), XmlSuite.DEFAULT_VERBOSE.toString());
+    }
+    if (null != m_timeOut) {
+      p.setProperty("time-out", m_timeOut.toString());
+    }
+    if (m_preserveOrder != null && ! XmlSuite.DEFAULT_PRESERVE_ORDER.equals(m_preserveOrder)) {
+      p.setProperty("preserve-order", m_preserveOrder.toString());
+    }
+    if (m_threadCount != -1) {
+      p.setProperty("thread-count", Integer.toString(m_threadCount));
+    }
+    if (m_groupByInstances != null) {
+      XmlUtils.setProperty(p, "group-by-instances", String.valueOf(getGroupByInstances()),
+          XmlSuite.DEFAULT_GROUP_BY_INSTANCES.toString());
+    }
+
+    xsb.push("test", p);
+
+
+    if (null != getMethodSelectors() && !getMethodSelectors().isEmpty()) {
+      xsb.push("method-selectors");
+      for (XmlMethodSelector selector: getMethodSelectors()) {
+        xsb.getStringBuffer().append(selector.toXml(indent + "    "));
+      }
+
+      xsb.pop("method-selectors");
+    }
+
+    XmlUtils.dumpParameters(xsb, m_parameters);
+
+    // groups
+    if (!m_metaGroups.isEmpty() || !m_includedGroups.isEmpty() || !m_excludedGroups.isEmpty()
+        || !m_xmlDependencyGroups.isEmpty()) {
+      xsb.push("groups");
+
+      // define
+      for (Map.Entry<String, List<String>> entry: m_metaGroups.entrySet()) {
+        String metaGroupName = entry.getKey();
+        List<String> groupNames = entry.getValue();
+
+        Properties metaGroupProp= new Properties();
+        metaGroupProp.setProperty("name",  metaGroupName);
+
+        xsb.push("define", metaGroupProp);
+
+        for (String groupName: groupNames) {
+          Properties includeProps = new Properties();
+          includeProps.setProperty("name", groupName);
+
+          xsb.addEmptyElement("include", includeProps);
+        }
+
+        xsb.pop("define");
+      }
+
+      // run
+      if (!m_includedGroups.isEmpty() || !m_excludedGroups.isEmpty()) {
+        xsb.push("run");
+
+        for (String includeGroupName: m_includedGroups) {
+          Properties includeProps = new Properties();
+          includeProps.setProperty("name", includeGroupName);
+
+          xsb.addEmptyElement("include", includeProps);
+        }
+
+        for (String excludeGroupName: m_excludedGroups) {
+          Properties excludeProps = new Properties();
+          excludeProps.setProperty("name", excludeGroupName);
+
+          xsb.addEmptyElement("exclude", excludeProps);
+        }
+
+        xsb.pop("run");
+      }
+
+      // group dependencies
+      if (m_xmlDependencyGroups != null && ! m_xmlDependencyGroups.isEmpty()) {
+        xsb.push("dependencies");
+        for (Map.Entry<String, String> entry : m_xmlDependencyGroups.entrySet()) {
+          xsb.addEmptyElement("group", "name", entry.getKey(), "depends-on", entry.getValue());
+        }
+        xsb.pop("dependencies");
+      }
+
+      xsb.pop("groups");
+    }
+
+    if (null != m_xmlPackages && !m_xmlPackages.isEmpty()) {
+      xsb.push("packages");
+
+      for (XmlPackage pack: m_xmlPackages) {
+        xsb.getStringBuffer().append(pack.toXml("      "));
+      }
+
+      xsb.pop("packages");
+    }
+
+    // classes
+    if (null != getXmlClasses() && !getXmlClasses().isEmpty()) {
+      xsb.push("classes");
+      for (XmlClass cls : getXmlClasses()) {
+        xsb.getStringBuffer().append(cls.toXml(indent + "    "));
+      }
+      xsb.pop("classes");
+    }
+
+    xsb.pop("test");
+
+    return xsb.toXML();
+  }
+
+  @Override
+  public String toString() {
+//    return toXml("");
+    StringBuilder result = new StringBuilder("[Test: \"")
+            .append(m_name)
+            .append("\"")
+            .append(" verbose:")
+            .append(m_verbose);
+
+    result.append("[parameters:");
+    for (Map.Entry<String, String> entry : m_parameters.entrySet()) {
+      result.append(entry.getKey()).append("=>").append(entry.getValue());
+    }
+
+    result.append("]");
+    result.append("[metagroups:");
+    for (Map.Entry<String, List<String>> entry : m_metaGroups.entrySet()) {
+      result.append(entry.getKey()).append("=");
+      for (String n : entry.getValue()) {
+        result.append(n).append(",");
+      }
+    }
+    result.append("] ");
+
+    result.append("[included: ");
+    for (String g : m_includedGroups) {
+      result.append(g).append(" ");
+    }
+    result.append("]");
+
+    result.append("[excluded: ");
+    for (String g : m_excludedGroups) {
+      result.append(g).append(" ");
+    }
+    result.append("] ");
+
+    result.append(" classes:");
+    for (XmlClass cl : m_xmlClasses) {
+      result.append(cl).append(" ");
+    }
+
+    result.append(" packages:");
+    for (XmlPackage p : m_xmlPackages) {
+      result.append(p).append(" ");
+    }
+
+    result.append("] ");
+
+    return result.toString();
+  }
+
+  static void ppp(String s) {
+    System.out.println("[XmlTest] " + s);
+  }
+
+  /**
+   * Clone the <TT>source</TT> <CODE>XmlTest</CODE> by including:
+   * - test attributes
+   * - groups definitions
+   * - parameters
+   *
+   * The &lt;classes&gt; sub element is ignored for the moment.
+   *
+   * @return a clone of the current XmlTest
+   */
+  @Override
+  public Object clone() {
+    XmlTest result = new XmlTest(getSuite());
+
+    result.setName(getName());
+    result.setIncludedGroups(getIncludedGroups());
+    result.setExcludedGroups(getExcludedGroups());
+    result.setJUnit(isJUnit());
+    result.setParallel(getParallel());
+    result.setVerbose(getVerbose());
+    result.setParameters(getLocalParameters());
+    result.setXmlPackages(getXmlPackages());
+    result.setTimeOut(getTimeOut());
+
+    Map<String, List<String>> metagroups = getMetaGroups();
+    for (Map.Entry<String, List<String>> group: metagroups.entrySet()) {
+      result.addMetaGroup(group.getKey(), group.getValue());
+    }
+
+    return result;
+  }
+
+  /**
+   * Convenience method to cache the ordering numbers for methods.
+   */
+  public List<Integer> getInvocationNumbers(String method) {
+    if (m_failedInvocationNumbers == null) {
+      m_failedInvocationNumbers = Maps.newHashMap();
+      for (XmlClass c : getXmlClasses()) {
+        for (XmlInclude xi : c.getIncludedMethods()) {
+          List<Integer> invocationNumbers = xi.getInvocationNumbers();
+          if (invocationNumbers.size() > 0) {
+            String methodName = c.getName() + "." + xi.getName();
+            m_failedInvocationNumbers.put(methodName, invocationNumbers);
+          }
+        }
+      }
+    }
+
+    List<Integer> result = m_failedInvocationNumbers.get(method);
+    if (result == null) {
+      // Don't use emptyList here since this list might end up receiving values if
+      // the test run fails.
+      return Lists.newArrayList();
+    } else {
+      return result;
+    }
+  }
+
+  public void setPreserveOrder(String preserveOrder) {
+    m_preserveOrder = preserveOrder;
+  }
+
+  public String getPreserveOrder() {
+    String result = m_preserveOrder;
+    if (result == null || XmlSuite.DEFAULT_PRESERVE_ORDER.equals(m_preserveOrder)) {
+      result = m_suite.getPreserveOrder();
+    }
+
+    return result;
+  }
+
+  public void setSuite(XmlSuite result) {
+    m_suite = result;
+  }
+
+  public Boolean getAllowReturnValues() {
+    if (m_allowReturnValues != null) return m_allowReturnValues;
+    else return getSuite().getAllowReturnValues();
+  }
+
+  public void setAllowReturnValues(Boolean allowReturnValues) {
+    m_allowReturnValues = allowReturnValues;
+  }
+
+  /**
+   * Note that this attribute does not come from the XML file, it's calculated
+   * internally and represents the order in which this test tag was found in its
+   * &lt;suite&gt; tag.  It's used to calculate the ordering of the tests
+   * when preserve-test-order is true.
+   */
+  public int getIndex() {
+    return m_index;
+  }
+
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result
+        + ((m_excludedGroups == null) ? 0 : m_excludedGroups.hashCode());
+    result = prime
+        * result
+        + ((m_failedInvocationNumbers == null) ? 0 : m_failedInvocationNumbers
+            .hashCode());
+    result = prime * result
+        + ((m_includedGroups == null) ? 0 : m_includedGroups.hashCode());
+    result = prime * result + ((m_isJUnit == null) ? 0 : m_isJUnit.hashCode());
+    result = prime * result
+        + ((m_metaGroups == null) ? 0 : m_metaGroups.hashCode());
+    result = prime * result
+        + ((m_methodSelectors == null) ? 0 : m_methodSelectors.hashCode());
+    result = prime * result + ((m_name == null) ? 0 : m_name.hashCode());
+    result = prime * result
+        + ((m_parallel == null) ? 0 : m_parallel.hashCode());
+    result = prime * result
+        + ((m_parameters == null) ? 0 : m_parameters.hashCode());
+    result = prime * result
+        + ((m_preserveOrder == null) ? 0 : m_preserveOrder.hashCode());
+    result = prime
+        * result
+        + ((m_skipFailedInvocationCounts == null) ? 0
+            : m_skipFailedInvocationCounts.hashCode());
+    result = prime * result + m_threadCount;
+    result = prime * result + ((m_timeOut == null) ? 0 : m_timeOut.hashCode());
+    result = prime * result + ((m_verbose == null) ? 0 : m_verbose.hashCode());
+    result = prime * result
+        + ((m_xmlClasses == null) ? 0 : m_xmlClasses.hashCode());
+    result = prime * result
+        + ((m_xmlPackages == null) ? 0 : m_xmlPackages.hashCode());
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null)
+      return XmlSuite.f();
+    if (getClass() != obj.getClass())
+      return XmlSuite.f();
+    XmlTest other = (XmlTest) obj;
+    if (m_excludedGroups == null) {
+      if (other.m_excludedGroups != null)
+        return XmlSuite.f();
+    } else if (!m_excludedGroups.equals(other.m_excludedGroups))
+      return XmlSuite.f();
+//    if (m_expression == null) {
+//      if (other.m_expression != null)
+//        return XmlSuite.f();
+//    } else if (!m_expression.equals(other.m_expression))
+//      return XmlSuite.f();
+    if (m_failedInvocationNumbers == null) {
+      if (other.m_failedInvocationNumbers != null)
+        return XmlSuite.f();
+    } else if (!m_failedInvocationNumbers
+        .equals(other.m_failedInvocationNumbers))
+      return XmlSuite.f();
+    if (m_includedGroups == null) {
+      if (other.m_includedGroups != null)
+        return XmlSuite.f();
+    } else if (!m_includedGroups.equals(other.m_includedGroups))
+      return XmlSuite.f();
+    if (m_isJUnit == null) {
+      if (other.m_isJUnit != null && ! other.m_isJUnit.equals(XmlSuite.DEFAULT_JUNIT))
+        return XmlSuite.f();
+    } else if (!m_isJUnit.equals(other.m_isJUnit))
+      return XmlSuite.f();
+    if (m_metaGroups == null) {
+      if (other.m_metaGroups != null)
+        return XmlSuite.f();
+    } else if (!m_metaGroups.equals(other.m_metaGroups))
+      return XmlSuite.f();
+    if (m_methodSelectors == null) {
+      if (other.m_methodSelectors != null)
+        return XmlSuite.f();
+    } else if (!m_methodSelectors.equals(other.m_methodSelectors))
+      return XmlSuite.f();
+    if (m_name == null) {
+      if (other.m_name != null)
+        return XmlSuite.f();
+    } else if (!m_name.equals(other.m_name))
+      return XmlSuite.f();
+    if (m_parallel == null) {
+      if (other.m_parallel != null)
+        return XmlSuite.f();
+    } else if (!m_parallel.equals(other.m_parallel))
+      return XmlSuite.f();
+    if (m_parameters == null) {
+      if (other.m_parameters != null)
+        return XmlSuite.f();
+    } else if (!m_parameters.equals(other.m_parameters))
+      return XmlSuite.f();
+    if (m_preserveOrder == null) {
+      if (other.m_preserveOrder != null)
+        return XmlSuite.f();
+    } else if (!m_preserveOrder.equals(other.m_preserveOrder))
+      return XmlSuite.f();
+    if (m_skipFailedInvocationCounts == null) {
+      if (other.m_skipFailedInvocationCounts != null)
+        return XmlSuite.f();
+    } else if (!m_skipFailedInvocationCounts
+        .equals(other.m_skipFailedInvocationCounts))
+      return XmlSuite.f();
+    if (m_threadCount != other.m_threadCount)
+      return XmlSuite.f();
+    if (m_timeOut == null) {
+      if (other.m_timeOut != null)
+        return XmlSuite.f();
+    } else if (!m_timeOut.equals(other.m_timeOut))
+      return XmlSuite.f();
+    if (m_verbose == null) {
+      if (other.m_verbose != null)
+        return XmlSuite.f();
+    } else if (!m_verbose.equals(other.m_verbose))
+      return XmlSuite.f();
+    if (m_xmlClasses == null) {
+      if (other.m_xmlClasses != null)
+        return XmlSuite.f();
+    } else if (!m_xmlClasses.equals(other.m_xmlClasses))
+      return XmlSuite.f();
+    if (m_xmlPackages == null) {
+      if (other.m_xmlPackages != null)
+        return XmlSuite.f();
+    } else if (!m_xmlPackages.equals(other.m_xmlPackages))
+      return XmlSuite.f();
+
+    return true;
+  }
+
+  public void addXmlDependencyGroup(String group, String dependsOn) {
+    if (! m_xmlDependencyGroups.containsKey(group)) {
+      m_xmlDependencyGroups.put(group, dependsOn);
+    } else {
+      throw new TestNGException("Duplicate group dependency found for group \"" + group + "\""
+          + ", use a space-separated list of groups in the \"depends-on\" attribute");
+    }
+  }
+
+  public Map<String, String> getXmlDependencyGroups() {
+    if (m_xmlGroups != null) {
+      Map<String, String> result = Maps.newHashMap();
+      List<XmlDependencies> deps = m_xmlGroups.getDependencies();
+      for (XmlDependencies d : deps) {
+        result.putAll(d.getDependencies());
+      }
+      return result;
+    } else {
+      // deprecated
+      return m_xmlDependencyGroups;
+    }
+  }
+
+  @ParentSetter
+  public void setXmlSuite(XmlSuite suite) {
+	  m_suite = suite;
+  }
+
+  private XmlGroups m_xmlGroups;
+
+  public void setGroups(XmlGroups xmlGroups) {
+    m_xmlGroups = xmlGroups;
+  }
+}
diff --git a/src/main/java/org/testng/xml/XmlUtils.java b/src/main/java/org/testng/xml/XmlUtils.java
new file mode 100644
index 0000000..47134da
--- /dev/null
+++ b/src/main/java/org/testng/xml/XmlUtils.java
@@ -0,0 +1,32 @@
+package org.testng.xml;
+
+import org.testng.reporters.XMLStringBuffer;
+
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+
+public class XmlUtils {
+
+  /**
+   * Don't add this property if it's equal to its default value.
+   */
+  public static void setProperty(Properties p, String name, String value, String def) {
+    if (! def.equals(value) && value != null) {
+      p.setProperty(name, value);
+    }
+  }
+
+  public static void dumpParameters(XMLStringBuffer xsb, Map<String, String> parameters) {
+    // parameters
+    if (!parameters.isEmpty()) {
+      for(Map.Entry<String, String> para: parameters.entrySet()) {
+        Properties paramProps= new Properties();
+        paramProps.setProperty("name", para.getKey());
+        paramProps.setProperty("value", para.getValue());
+        xsb.addEmptyElement("parameter", paramProps); // BUGFIX: TESTNG-27
+      }
+    }
+  }
+
+}
diff --git a/src/main/java/org/testng/xml/dom/DomUtil.java b/src/main/java/org/testng/xml/dom/DomUtil.java
new file mode 100644
index 0000000..158f776
--- /dev/null
+++ b/src/main/java/org/testng/xml/dom/DomUtil.java
@@ -0,0 +1,277 @@
+package org.testng.xml.dom;
+
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+public class DomUtil {
+
+  private XPath m_xpath;
+  private Document m_document;
+
+  public DomUtil(Document doc) {
+    XPathFactory xpathFactory = XPathFactory.newInstance();
+    m_xpath = xpathFactory.newXPath();
+    m_document = doc;
+  }
+
+  public void populate(final XmlSuite xmlSuite) throws XPathExpressionException {
+    NodeList nodes = m_document.getChildNodes();
+    final Map<String, String> parameters = Maps.newHashMap();
+    for (int i = 0; i < nodes.getLength(); i++) {
+      Node item1 = nodes.item(i);
+
+      Map<String, NodeProcessor> map = Maps.newHashMap();
+      map.put("parameter", new NodeProcessor() {
+        @Override
+        public void process(Node node) {
+          Element e = (Element) node;
+          parameters.put(e.getAttribute("name"), e.getAttribute("value"));
+        }
+      });
+      map.put("test", new NodeProcessor() {
+        @Override
+        public void process(Node node) {
+          XmlTest xmlTest = new XmlTest(xmlSuite);
+          populateTest(xmlTest, node);
+        }
+      });
+      map.put("suite-files", new NodeProcessor() {
+        @Override
+        public void process(Node node) {
+          NodeList item2Children = node.getChildNodes();
+          List<String> suiteFiles = Lists.newArrayList();
+          for (int k = 0; k < item2Children.getLength(); k++) {
+            Node item3 = item2Children.item(k);
+            if (item3 instanceof Element) {
+              Element e = (Element) item3;
+              if ("suite-file".equals(item3.getNodeName())) {
+                suiteFiles.add(e.getAttribute("path"));
+              }
+            }
+          }
+          xmlSuite.setSuiteFiles(suiteFiles);
+        }
+      });
+      parseNodeAndChildren("suite", item1, xmlSuite, map);
+
+//      if ("suite".equals(item1.getNodeName()) && item1.getAttributes() != null) {
+//        populateAttributes(item1, xmlSuite);
+//        NodeList item1Children = item1.getChildNodes();
+//        for (int j = 0; j < item1Children.getLength(); j++) {
+//          Node item2 = item1Children.item(j);
+//          if ("parameter".equals(item2.getNodeName())) {
+//            Element e = (Element) item2;
+//            parameters.put(e.getAttribute("name"), e.getAttribute("value"));
+//          } else if ("test".equals(item2.getNodeName())) {
+//            XmlTest xmlTest = new XmlTest(xmlSuite);
+//            populateTest(xmlTest, item2);
+//          } else if ("suite-files".equals(item2.getNodeName())) {
+//            NodeList item2Children = item2.getChildNodes();
+//            List<String> suiteFiles = Lists.newArrayList();
+//            for (int k = 0; k < item2Children.getLength(); k++) {
+//              Node item3 = item2Children.item(k);
+//              if (item3 instanceof Element) {
+//                Element e = (Element) item3;
+//                if ("suite-file".equals(item3.getNodeName())) {
+//                  suiteFiles.add(e.getAttribute("path"));
+//                }
+//              }
+//            }
+//            xmlSuite.setSuiteFiles(suiteFiles);
+//          }
+//        }
+//      }
+    }
+
+    xmlSuite.setParameters(parameters);
+//    XPathExpression expr = m_xpath.compile("//suite/test");
+//    NodeList tests = (NodeList) expr.evaluate(m_document, XPathConstants.NODESET);
+//    for (int i = 0; i < tests.getLength(); i++) {
+//      Node node = tests.item(i);
+//      System.out.println("<test>:" + node);
+//    }
+  }
+
+  public static interface NodeProcessor {
+    void process(Node node);
+  }
+
+  private void parseNodeAndChildren(String name, Node root, Object object,
+      Map<String, NodeProcessor> processors) throws XPathExpressionException {
+    if (name.equals(root.getNodeName()) && root.getAttributes() != null) {
+      populateAttributes(root, object);
+      NodeList children = root.getChildNodes();
+      for (int j = 0; j < children.getLength(); j++) {
+        Node item2 = children.item(j);
+        String nodeName = item2.getNodeName();
+        NodeProcessor proc = processors.get(nodeName);
+        if (proc != null) {
+          proc.process(item2);
+        } else if (! nodeName.startsWith("#")){
+          throw new RuntimeException("No processor found for " + nodeName);
+        }
+//        if ("parameter".equals(item2.getNodeName())) {
+//          Element e = (Element) item2;
+//          parameters.put(e.getAttribute("name"), e.getAttribute("value"));
+//        }
+      }
+    }
+  }
+
+    public static Iterator<Node> findChildren(Node node, String name) {
+    List<Node> result = Lists.newArrayList();
+    NodeList children = node.getChildNodes();
+    for (int i = 0; i < children.getLength(); i++) {
+      Node n = children.item(i);
+      if (name.equals(n.getNodeName())) {
+        result.add(n);
+      }
+    }
+    return result.iterator();
+  }
+
+  private void populateTest(XmlTest xmlTest, Node item) {
+    Map<String, String> testParameters = Maps.newHashMap();
+    populateAttributes(item, xmlTest);
+    NodeList itemChildren = item.getChildNodes();
+    for (int k = 0; k < itemChildren.getLength(); k++) {
+      Node item2 = itemChildren.item(k);
+      if ("parameter".equals(item2.getNodeName())) {
+        Element e = (Element) item2;
+        testParameters.put(e.getAttribute("name"), e.getAttribute("value"));
+      } else if ("classes".equals(item2.getNodeName())) {
+        NodeList item2Children = item2.getChildNodes();
+        for (int l = 0; l < item2Children.getLength(); l++) {
+          Node item4 = item2Children.item(l);
+          if ("class".equals(item4.getNodeName())) {
+            XmlClass xmlClass = new XmlClass();
+            populateAttributes(item4, xmlClass);
+            xmlTest.getClasses().add(xmlClass);
+
+            // TODO: excluded/included methods
+          }
+        }
+      } else if ("groups".equals(item2.getNodeName())) {
+        NodeList item2Children = item2.getChildNodes();
+        List<String> includes = Lists.newArrayList();
+        List<String> excludes = Lists.newArrayList();
+        for (int l = 0; l < item2Children.getLength(); l++) {
+          Node item3 = item2Children.item(l);
+          if ("run".equals(item3.getNodeName())) {
+            NodeList item3Children = item3.getChildNodes();
+            for (int m = 0; m < item3Children.getLength(); m++) {
+              Node item4 = item3Children.item(m);
+              if ("include".equals(item4.getNodeName())) {
+                includes.add(((Element) item4).getAttribute("name"));
+              } else if ("exclude".equals(item4.getNodeName())) {
+                excludes.add(((Element) item4).getAttribute("name"));
+              }
+            }
+          } else if ("dependencies".equals(item3.getNodeName())) {
+            NodeList item3Children = item3.getChildNodes();
+            for (int m = 0; m < item3Children.getLength(); m++) {
+              Node item4 = item3Children.item(m);
+              if ("group".equals(item4.getNodeName())) {
+                Element e = (Element) item4;
+                xmlTest.addXmlDependencyGroup(e.getAttribute("name"), e.getAttribute("depends-on"));
+              }
+            }
+          } else if ("define".equals(item3.getNodeName())) {
+            xmlDefine(xmlTest, item3);
+          }
+        }
+        xmlTest.setIncludedGroups(includes);
+        xmlTest.setExcludedGroups(excludes);
+      } // TODO: (method-selectors?,packages?) >
+    }
+
+    xmlTest.setParameters(testParameters);
+  }
+
+  /**
+   * Parse the <define> tag.
+   */
+  private void xmlDefine(XmlTest xmlTest, Node item) {
+    NodeList item3Children = item.getChildNodes();
+    List<String> groups = Lists.newArrayList();
+    for (int m = 0; m < item3Children.getLength(); m++) {
+      Node item4 = item3Children.item(m);
+      if ("include".equals(item4.getNodeName())) {
+        Element e = (Element) item4;
+        groups.add(e.getAttribute("name"));
+      }
+    }
+    xmlTest.addMetaGroup(((Element) item).getAttribute("name"), groups);
+  }
+
+  private void populateAttributes(Node node, Object object) {
+    for (int j = 0; j < node.getAttributes().getLength(); j++) {
+      Node item = node.getAttributes().item(j);
+      p(node.getAttributes().item(j).toString());
+      setProperty(object, item.getLocalName(), item.getNodeValue());
+    }
+  }
+
+  private void setProperty(Object object, String name, Object value) {
+    String methodName = toCamelCaseSetter(name);
+    Method foundMethod = null;
+    for (Method m : object.getClass().getDeclaredMethods()) {
+      if (m.getName().equals(methodName)) {
+        foundMethod = m;
+        break;
+      }
+    }
+
+    if (foundMethod == null) {
+      p("Warning: couldn't find setter method " + methodName);
+    } else {
+      try {
+        p("Invoking " + methodName + " with " + value);
+        Class<?> type = foundMethod.getParameterTypes()[0];
+        if (type == Boolean.class || type == boolean.class) {
+          foundMethod.invoke(object, Boolean.parseBoolean(value.toString()));
+        } else if (type == Integer.class || type == int.class) {
+          foundMethod.invoke(object, Integer.parseInt(value.toString()));
+        } else {
+          foundMethod.invoke(object, value.toString());
+        }
+      } catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
+        e.printStackTrace();
+      }
+    }
+  }
+
+  private void p(String string) {
+//    System.out.println("[XPathUtil] " + string);
+  }
+
+  private String toCamelCaseSetter(String name) {
+    StringBuilder result = new StringBuilder("set" + name.substring(0, 1).toUpperCase());
+    for (int i = 1; i < name.length(); i++) {
+      if (name.charAt(i) == '-') {
+        result.append(Character.toUpperCase(name.charAt(i + 1)));
+        i++;
+      } else {
+        result.append(name.charAt(i));
+      }
+    }
+    return result.toString();
+  }
+}
diff --git a/src/main/java/org/testng/xml/dom/DomXmlParser.java b/src/main/java/org/testng/xml/dom/DomXmlParser.java
new file mode 100644
index 0000000..f2513ef
--- /dev/null
+++ b/src/main/java/org/testng/xml/dom/DomXmlParser.java
@@ -0,0 +1,69 @@
+package org.testng.xml.dom;
+
+import org.testng.xml.ISuiteParser;
+import org.testng.xml.XMLParser;
+import org.testng.xml.XmlSuite;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPathExpressionException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class DomXmlParser extends XMLParser<XmlSuite> implements ISuiteParser {
+  @Override
+  public XmlSuite parse(String currentFile, InputStream inputStream, boolean loadClasses) {
+    XmlSuite result = null;
+    try {
+      result = parse2(currentFile, inputStream, loadClasses);
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    return result;
+  }
+
+  @Override
+  public boolean accept(String fileName) {
+    return fileName.endsWith(".xml");
+  }
+
+  public XmlSuite parse2(String currentFile, InputStream inputStream,
+      boolean loadClasses) throws ParserConfigurationException, SAXException,
+      IOException, XPathExpressionException {
+    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+    factory.setNamespaceAware(true); // never forget this!
+    DocumentBuilder builder = factory.newDocumentBuilder();
+    Document doc = builder.parse(inputStream);
+
+    DomUtil xpu = new DomUtil(doc);
+    XmlSuite result = new XmlSuite();
+    xpu.populate(result);
+//    XPathFactory xpathFactory = XPathFactory.newInstance();
+//    XPath xpath = xpathFactory.newXPath();
+//
+//    {
+//      XPathExpression expr = xpath.compile("//suite");
+//      Object result = expr.evaluate(doc, XPathConstants.NODESET);
+//      NodeList nodes = (NodeList) result;
+//      for (int i = 0; i < nodes.getLength(); i++) {
+//        Node node = nodes.item(i);
+//        for (int j = 0; j < node.getAttributes().getLength(); j++) {
+//          System.out.println(node.getAttributes().item(j));
+//        }
+//      }
+//    }
+
+//    {
+//      XPathExpression expr = xpath.compile("//suite/@name");
+//      Object result = expr.evaluate(doc, XPathConstants.STRING);
+//      System.out.println("NAME:" + result);
+//    }
+    System.out.println(result.toXml());
+    return result;
+  }
+}
diff --git a/src/main/java/org/testng/xml/dom/ITagFactory.java b/src/main/java/org/testng/xml/dom/ITagFactory.java
new file mode 100644
index 0000000..c0dbd36
--- /dev/null
+++ b/src/main/java/org/testng/xml/dom/ITagFactory.java
@@ -0,0 +1,6 @@
+package org.testng.xml.dom;
+
+public interface ITagFactory {
+
+  Class<?> getClassForTag(String tag);
+}
diff --git a/src/main/java/org/testng/xml/dom/ITagSetter.java b/src/main/java/org/testng/xml/dom/ITagSetter.java
new file mode 100644
index 0000000..39bd08c
--- /dev/null
+++ b/src/main/java/org/testng/xml/dom/ITagSetter.java
@@ -0,0 +1,7 @@
+package org.testng.xml.dom;
+
+import org.w3c.dom.Node;
+
+public interface ITagSetter<T> {
+  void setProperty(String name, T parent, Node node);
+}
diff --git a/src/main/java/org/testng/xml/dom/OnElement.java b/src/main/java/org/testng/xml/dom/OnElement.java
new file mode 100644
index 0000000..c0b715a
--- /dev/null
+++ b/src/main/java/org/testng/xml/dom/OnElement.java
@@ -0,0 +1,16 @@
+package org.testng.xml.dom;
+
+import static java.lang.annotation.ElementType.METHOD;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target({METHOD})
+public @interface OnElement {
+
+  String tag();
+
+  String[] attributes();
+
+}
diff --git a/src/main/java/org/testng/xml/dom/OnElementList.java b/src/main/java/org/testng/xml/dom/OnElementList.java
new file mode 100644
index 0000000..673160a
--- /dev/null
+++ b/src/main/java/org/testng/xml/dom/OnElementList.java
@@ -0,0 +1,16 @@
+package org.testng.xml.dom;
+
+import static java.lang.annotation.ElementType.METHOD;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target({METHOD})
+public @interface OnElementList {
+
+  String tag();
+
+  String[] attributes();
+
+}
diff --git a/src/main/java/org/testng/xml/dom/ParentSetter.java b/src/main/java/org/testng/xml/dom/ParentSetter.java
new file mode 100644
index 0000000..6536906
--- /dev/null
+++ b/src/main/java/org/testng/xml/dom/ParentSetter.java
@@ -0,0 +1,11 @@
+package org.testng.xml.dom;
+
+import static java.lang.annotation.ElementType.METHOD;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target({METHOD})
+public @interface ParentSetter {
+}
diff --git a/src/main/java/org/testng/xml/dom/Reflect.java b/src/main/java/org/testng/xml/dom/Reflect.java
new file mode 100644
index 0000000..0f71b85
--- /dev/null
+++ b/src/main/java/org/testng/xml/dom/Reflect.java
@@ -0,0 +1,71 @@
+package org.testng.xml.dom;
+
+import org.testng.collections.Lists;
+import org.testng.internal.collections.Pair;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+
+public class Reflect {
+  public static List<Pair<Method, Wrapper>> findMethodsWithAnnotation(
+      Class<?> c, Class<? extends Annotation> ac, Object bean) {
+    List<Pair<Method, Wrapper>> result = Lists.newArrayList();
+    for (Method m : c.getMethods()) {
+      Annotation a = m.getAnnotation(ac);
+      if (a != null) {
+        result.add(Pair.of(m, new Wrapper(a, bean)));
+      }
+    }
+    return result;
+  }
+
+  public static Pair<Method, Wrapper> findSetterForTag(
+      Class<?> c, String tagName, Object bean) {
+
+    // Try to find an annotation
+    List<Class<? extends Annotation>> annotations =
+        Arrays.asList(OnElement.class, OnElementList.class, Tag.class);
+    for (Class<? extends Annotation> annotationClass : annotations) {
+      List<Pair<Method, Wrapper>> methods
+          = findMethodsWithAnnotation(c, annotationClass, bean);
+  
+      for (Pair<Method, Wrapper> pair : methods) {
+        if (pair.second().getTagName().equals(tagName)) {
+          return pair;
+        }
+      }
+    }
+
+    // try fo find an adder or a setter
+    for (String prefix : new String[] { "add", "set" }) {
+      for (Method m : c.getDeclaredMethods()) {
+        String methodName = toCamelCase(tagName, prefix);
+        if (m.getName().equals(methodName)) {
+          return Pair.of(m, null);
+        }
+      }
+    }
+
+    return null;
+  }
+
+  private static String toCamelCase(String name, String prefix) {
+    return prefix + toCapitalizedCamelCase(name);
+  }
+
+  public static String toCapitalizedCamelCase(String name) {
+    StringBuilder result = new StringBuilder(name.substring(0, 1).toUpperCase());
+    for (int i = 1; i < name.length(); i++) {
+      if (name.charAt(i) == '-') {
+        result.append(Character.toUpperCase(name.charAt(i + 1)));
+        i++;
+      } else {
+        result.append(name.charAt(i));
+      }
+    }
+    return result.toString();
+  }
+
+}
diff --git a/src/main/java/org/testng/xml/dom/Tag.java b/src/main/java/org/testng/xml/dom/Tag.java
new file mode 100644
index 0000000..87a68dd
--- /dev/null
+++ b/src/main/java/org/testng/xml/dom/Tag.java
@@ -0,0 +1,14 @@
+package org.testng.xml.dom;
+
+import static java.lang.annotation.ElementType.METHOD;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target({METHOD})
+public @interface Tag {
+
+  String name();
+
+}
diff --git a/src/main/java/org/testng/xml/dom/TagContent.java b/src/main/java/org/testng/xml/dom/TagContent.java
new file mode 100644
index 0000000..efe01f4
--- /dev/null
+++ b/src/main/java/org/testng/xml/dom/TagContent.java
@@ -0,0 +1,14 @@
+package org.testng.xml.dom;
+
+import static java.lang.annotation.ElementType.METHOD;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target({METHOD})
+public @interface TagContent {
+
+  String name();
+
+}
diff --git a/src/main/java/org/testng/xml/dom/TestNGTagFactory.java b/src/main/java/org/testng/xml/dom/TestNGTagFactory.java
new file mode 100644
index 0000000..4e03fd5
--- /dev/null
+++ b/src/main/java/org/testng/xml/dom/TestNGTagFactory.java
@@ -0,0 +1,32 @@
+package org.testng.xml.dom;
+
+import java.util.Map;
+
+import org.testng.collections.Maps;
+
+public class TestNGTagFactory implements ITagFactory {
+
+  private Map<String, Class<?>> m_map = Maps.newHashMap();
+
+  public TestNGTagFactory() {
+//    m_map.put("suite-files", XDom.ChildSuite.class);
+  }
+
+  @Override
+  public Class<?> getClassForTag(String tag) {
+    Class<?> result = m_map.get(tag);
+    if (result != null) {
+      return result;
+    } else {
+      String className = "org.testng.xml.Xml" + Reflect.toCapitalizedCamelCase(tag);
+      try {
+        result = Class.forName(className);
+      } catch (ClassNotFoundException e) {
+        System.out.println("Couldn't find class " + className);
+      }
+    }
+
+    return result;
+  }
+
+}
diff --git a/src/main/java/org/testng/xml/dom/Wrapper.java b/src/main/java/org/testng/xml/dom/Wrapper.java
new file mode 100644
index 0000000..515fad5
--- /dev/null
+++ b/src/main/java/org/testng/xml/dom/Wrapper.java
@@ -0,0 +1,61 @@
+package org.testng.xml.dom;
+
+import org.testng.collections.Lists;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+
+public class Wrapper {
+
+  private OnElement m_onElement;
+  private OnElementList m_onElementList;
+  private Tag m_tag;
+  private TagContent m_tagContent;
+  private Object m_bean;
+
+  public Wrapper(Annotation a, Object bean) {
+    m_bean = bean;
+    if (a instanceof OnElement) m_onElement = (OnElement) a;
+    else if (a instanceof OnElementList) m_onElementList = (OnElementList) a;
+    else if (a instanceof Tag) m_tag = (Tag) a;
+    else if (a instanceof TagContent) m_tagContent = (TagContent) a;
+    else throw new RuntimeException("Illegal annotation " + a);
+  }
+
+  public String getTagName() {
+    if (m_onElement != null) return m_onElement.tag();
+    else if (m_onElementList != null) return m_onElementList.tag();
+    else return m_tag.name();
+  }
+
+  public List<Object[]> getParameters(Element element) {
+    List<Object[]> allParameters = Lists.newArrayList();
+    if (m_onElement != null) {
+      List<Object> result = Lists.newArrayList();
+      for (String attributeName : m_onElement.attributes()) {
+        result.add(element.getAttribute(attributeName));
+      }
+      allParameters.add(result.toArray());
+    } else if (m_tag != null) {
+      List<Object> result = Lists.newArrayList();
+      result.add(m_bean);
+      allParameters.add(result.toArray());
+    } else {
+      NodeList childNodes = element.getChildNodes();
+      for (int i = 0; i < childNodes.getLength(); i++) {
+        if (childNodes.item(i).hasAttributes()) {
+          Element item = (Element) childNodes.item(i);
+          List<Object> result = Lists.newArrayList();
+          for (String attributeName : m_onElementList.attributes()) {
+            result.add(item.getAttribute(attributeName));
+          }
+          allParameters.add(result.toArray());
+        }
+      }
+    }
+
+    return allParameters;
+  }
+}
diff --git a/src/main/java/org/testng/xml/dom/XDom.java b/src/main/java/org/testng/xml/dom/XDom.java
new file mode 100644
index 0000000..a0afb16
--- /dev/null
+++ b/src/main/java/org/testng/xml/dom/XDom.java
@@ -0,0 +1,371 @@
+package org.testng.xml.dom;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPathExpressionException;
+
+import org.testng.Assert;
+import org.testng.collections.ListMultiMap;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.internal.collections.Pair;
+import org.testng.xml.XmlDefine;
+import org.testng.xml.XmlGroups;
+import org.testng.xml.XmlMethodSelector;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+public class XDom {
+//  private static Map<String, Class<?>> m_map = Maps.newHashMap();
+  private Document m_document;
+  private ITagFactory m_tagFactory;
+
+  public XDom(ITagFactory tagFactory, Document document)
+      throws XPathExpressionException,
+      InstantiationException, IllegalAccessException {
+    m_tagFactory = tagFactory;
+    m_document = document;
+  }
+
+  public Object parse() throws XPathExpressionException,
+      InstantiationException, IllegalAccessException, SecurityException,
+      IllegalArgumentException, NoSuchMethodException,
+      InvocationTargetException {
+    Object result = null;
+    NodeList nodes = m_document.getChildNodes();
+    for (int i = 0; i < nodes.getLength(); i++) {
+      Node item = nodes.item(i);
+      if (item.getAttributes() != null) {
+        String nodeName = item.getNodeName();
+
+        System.out.println("Node name:" + nodeName);
+        Class<?> c = m_tagFactory.getClassForTag(nodeName);
+        if (c == null) {
+          throw new RuntimeException("No class found for tag " + nodeName);
+        }
+  
+        result = c.newInstance();
+        populateAttributes(item, result);
+        if (ITagSetter.class.isAssignableFrom(result.getClass())) {
+          throw new RuntimeException("TAG SETTER");
+        }
+        populateChildren(item, result);
+      }
+    }
+    return result;
+  }
+
+  public void populateChildren(Node root, Object result) throws InstantiationException,
+      IllegalAccessException, XPathExpressionException, SecurityException, IllegalArgumentException, NoSuchMethodException, InvocationTargetException {
+    p("populateChildren: " + root.getLocalName());
+    NodeList childNodes = root.getChildNodes();
+    ListMultiMap<String, Object> children = Maps.newListMultiMap();
+    for (int i = 0; i < childNodes.getLength(); i++) {
+      Node item = childNodes.item(i);
+      if (item.getAttributes() != null) {
+        String nodeName = item.getNodeName();
+        if ("suite-files".equals(nodeName)) {
+          System.out.println("BREAK");
+        }
+
+        Class<?> c = m_tagFactory.getClassForTag(nodeName);
+        if (c == null) {
+          System.out.println("Warning: No class found for tag " + nodeName);
+          boolean foundSetter = invokeOnSetter(result, (Element) item, nodeName, null);
+          System.out.println("  found setter:" + foundSetter);
+        } else {
+          Object object = instantiateElement(c, result);
+          if (ITagSetter.class.isAssignableFrom(object.getClass())) {
+            System.out.println("Tag setter:"  + result);
+            ((ITagSetter) object).setProperty(nodeName, result, item);
+          } else {
+            children.put(nodeName, object);
+            populateAttributes(item, object);
+            populateContent(item, object);
+          }
+          boolean foundSetter = invokeOnSetter(result, (Element) item, nodeName, object);
+//          setProperty(result, nodeName, object);
+          populateChildren(item, object);
+        }
+
+//        boolean foundSetter = invokeOnSetter(result, (Element) item, nodeName);
+//        if (! foundSetter) {
+//          boolean foundListSetter = invokeOnListSetter(result, nodeName, item);
+//          if (! foundListSetter) {
+//          }
+//        }
+      }
+    }
+//    System.out.println("Found children:" + children);
+//    for (String s : children.getKeys()) {
+//      setCollectionProperty(result, s, children.get(s), object);
+//    }
+  }
+
+  /**
+   * Try to find a @ParentSetter. If this fails, try to find a constructor that takes the parent as a parameter.
+   * If this fails, use the default constructor.
+   */
+  private Object instantiateElement(Class<?> c, Object parent)
+      throws SecurityException, NoSuchMethodException,
+      IllegalArgumentException, InstantiationException, IllegalAccessException,
+      InvocationTargetException {
+    Object result = null;
+    Method m = findMethodAnnotatedWith(c, ParentSetter.class);
+    if (m != null) {
+        result = c.newInstance();
+    	m.invoke(result, parent);
+    } else {
+	    try {
+	      result = c.getConstructor(parent.getClass()).newInstance(parent);
+	    } catch(NoSuchMethodException ex) {
+	      result = c.newInstance();
+	    }
+    }
+
+    return result;
+  }
+
+//  private List<Pair<Method, ? extends Annotation>>
+//      findMethodsWithAnnotation(Class<?> c, Class<? extends Annotation> ac) {
+//    List<Pair<Method, ? extends Annotation>> result = Lists.newArrayList();
+//    for (Method m : c.getMethods()) {
+//      Annotation a = m.getAnnotation(ac);
+//      if (a != null) {
+//        result.add(Pair.of(m, a));
+//      }
+//    }
+//    return result;
+//  }
+
+  private Method findMethodAnnotatedWith(Class<?> c, Class<? extends Annotation> annotation) {
+	  for (Method m : c.getMethods()) {
+		  if (m.getAnnotation(annotation) != null) {
+			  return m;
+		  }
+	  }
+	  return null;
+  }
+
+private void populateContent(Node item, Object object) {
+    for (int i = 0; i < item.getChildNodes().getLength(); i++) {
+      Node child = item.getChildNodes().item(i);
+      if (child instanceof Text) {
+        setText(object, (Text) child);
+      }
+    }
+  }
+
+  private void setText(Object bean, Text child) {
+    List<Pair<Method, Wrapper>> pairs =
+        Reflect.findMethodsWithAnnotation(bean.getClass(), TagContent.class, bean);
+    for (Pair<Method, Wrapper> pair : pairs) {
+      try {
+        pair.first().invoke(bean, child.getTextContent());
+      } catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException | DOMException e) {
+        e.printStackTrace();
+      }
+    }
+  }
+
+  private boolean invokeOnSetter(Object object, Element element, String nodeName,
+      Object bean) {
+    Pair<Method, Wrapper> pair =
+       Reflect.findSetterForTag(object.getClass(), nodeName, bean);
+
+    List<Object[]> allParameters = null;
+    if (pair != null) {
+      Method m = pair.first();
+      try {
+        if (pair.second() != null) {
+          allParameters = pair.second().getParameters(element);
+        } else {
+          allParameters = Lists.newArrayList();
+          allParameters.add(new Object[] { bean });
+        }
+
+        for (Object[] p : allParameters) {
+          m.invoke(object, p);
+        }
+        return true;
+      } catch (IllegalArgumentException e) {
+        System.out.println("Parameters: " + allParameters);
+        e.printStackTrace();
+      } catch (IllegalAccessException | InvocationTargetException e) {
+        e.printStackTrace();
+      }
+    }
+
+    return false;
+  }
+
+  private void populateAttributes(Node node, Object object) throws XPathExpressionException {
+    for (int j = 0; j < node.getAttributes().getLength(); j++) {
+      Node item = node.getAttributes().item(j);
+      setProperty(object, item.getLocalName(), item.getNodeValue());
+    }
+  }
+
+  private void setProperty(Object object, String name, Object value) {
+    Pair<Method, Wrapper> setter = Reflect.findSetterForTag(object.getClass(), name,
+        value);
+
+    if (setter != null) {
+      Method foundMethod = setter.first();
+      try {
+        Class<?> type = foundMethod.getParameterTypes()[0];
+        if (type == Boolean.class || type == boolean.class) {
+          foundMethod.invoke(object, Boolean.parseBoolean(value.toString()));
+        } else if (type == Integer.class || type == int.class) {
+          foundMethod.invoke(object, Integer.parseInt(value.toString()));
+        } else {
+          foundMethod.invoke(object, value);
+        }
+      } catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
+        e.printStackTrace();
+      }
+    } else {
+      e("Couldn't find setter method for property" + name + " on " + object.getClass());
+    }
+  }
+
+//  private Method findSetter(Object object, String name) {
+//    String methodName = toCamelCaseSetter(name);
+//    Method foundMethod = null;
+//    for (Method m : object.getClass().getDeclaredMethods()) {
+//      if (m.getName().equals(methodName)) {
+//        foundMethod = m;
+//        break;
+//      }
+//    }
+//    return foundMethod;
+//  }
+
+  private void p(String string) {
+    System.out.println("[XDom] " + string);
+  }
+
+  private void e(String string) {
+    System.out.println("[XDom] [Error] " + string);
+  }
+
+  public static void main(String[] args) throws SAXException, IOException,
+      ParserConfigurationException, XPathExpressionException,
+      InstantiationException, IllegalAccessException, SecurityException,
+      IllegalArgumentException, NoSuchMethodException,
+      InvocationTargetException {
+    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+    factory.setNamespaceAware(true); // never forget this!
+    DocumentBuilder builder = factory.newDocumentBuilder();
+    FileInputStream inputStream =
+        new FileInputStream(new File(System.getProperty("user.home")
+            + "/java/testng/src/test/resources/testng-all.xml"));
+    Document doc = builder.parse(inputStream);
+    XmlSuite result = (XmlSuite) new XDom(new TestNGTagFactory(), doc).parse();
+
+    test(result);
+    System.out.println(result.toXml());
+  }
+
+  private static void test(XmlSuite s) {
+    Assert.assertEquals("TestNG", s.getName());
+    Assert.assertEquals(s.getDataProviderThreadCount(), 3);
+    Assert.assertEquals(s.getThreadCount(), 2);
+
+    {
+      // method-selectors
+      List<XmlMethodSelector> selectors = s.getMethodSelectors();
+      Assert.assertEquals(selectors.size(), 2);
+      XmlMethodSelector s1 = selectors.get(0);
+      Assert.assertEquals(s1.getLanguage(), "javascript");
+      Assert.assertEquals(s1.getExpression(), "foo()");
+      XmlMethodSelector s2 = selectors.get(1);
+      Assert.assertEquals(s2.getClassName(), "SelectorClass");
+      Assert.assertEquals(s2.getPriority(), 3);
+    }
+
+    {
+      // child-suites
+      List<String> suiteFiles = s.getSuiteFiles();
+      Assert.assertEquals(suiteFiles, Arrays.asList("./junit-suite.xml"));
+    }
+
+    {
+      // parameters
+      Map<String, String> p = s.getParameters();
+      Assert.assertEquals(p.size(), 2);
+      Assert.assertEquals(p.get("suiteParameter"), "suiteParameterValue");
+      Assert.assertEquals(p.get("first-name"), "Cedric");
+    }
+
+    {
+      // run
+      Assert.assertEquals(s.getIncludedGroups(), Arrays.asList("includeThisGroup"));
+      Assert.assertEquals(s.getExcludedGroups(), Arrays.asList("excludeThisGroup"));
+      XmlGroups groups = s.getGroups();
+
+      // define
+      List<XmlDefine> defines = groups.getDefines();
+      Assert.assertEquals(defines.size(), 1);
+      XmlDefine define = defines.get(0);
+      Assert.assertEquals(define.getName(), "bigSuite");
+      Assert.assertEquals(define.getIncludes(), Arrays.asList("suite1", "suite2"));
+
+      // packages
+      Assert.assertEquals(s.getPackageNames(), Arrays.asList("com.example1", "com.example2"));
+
+      // listeners
+      Assert.assertEquals(s.getListeners(),
+          Arrays.asList("com.beust.Listener1", "com.beust.Listener2"));
+      // dependencies
+      // only defined on test for now
+    }
+
+    {
+      // tests
+      Assert.assertEquals(s.getTests().size(), 3);
+      for (int i = 0; i < s.getTests().size(); i++) {
+        if ("Nopackage".equals(s.getTests().get(i).getName())) {
+          testNoPackage(s.getTests().get(i));
+        }
+      }
+    }
+  }
+
+  private static void testNoPackage(XmlTest t) {
+    Assert.assertEquals(t.getThreadCount(), 42);
+    Assert.assertTrue(t.getAllowReturnValues());
+
+    // define
+    Map<String, List<String>> metaGroups = t.getMetaGroups();
+    Assert.assertEquals(metaGroups.get("evenodd"), Arrays.asList("even", "odd"));
+
+    // run
+    Assert.assertEquals(t.getIncludedGroups(), Arrays.asList("nopackage", "includeThisGroup"));
+    Assert.assertEquals(t.getExcludedGroups(), Arrays.asList("excludeThisGroup"));
+
+    // dependencies
+    Map<String, String> dg = t.getXmlDependencyGroups();
+    Assert.assertEquals(dg.size(), 2);
+    Assert.assertEquals(dg.get("e"), "f");
+    Assert.assertEquals(dg.get("g"), "h");
+  }
+}
diff --git a/src/main/java/testng-1.0.dtd.html b/src/main/java/testng-1.0.dtd.html
new file mode 100644
index 0000000..53873aa
--- /dev/null
+++ b/src/main/java/testng-1.0.dtd.html
@@ -0,0 +1,206 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html>
+    <head>
+    <title>DTD for TestNG 1.0</title>
+        <link rel="stylesheet" href="http://beust.com/beust.css" type="text/css"/>
+    </head>
+<body>
+
+<pre class="code"><span class="xml-comment">&lt;!--
+
+Here is a quick overview of the main parts of this DTD.  For more information,
+refer to the &lt;a <span class="attribute">href="http://testng.org"</span> &gt;main web site<span class="tag">&lt;/a&gt;.
+
+</span>                                                      
+A <span class="tag">&lt;b&gt;suite&lt;/b&gt;</span> is made of <span class="tag">&lt;b&gt;tests&lt;/b&gt;</span> and <span class="tag">&lt;b&gt;parameters&lt;/b&gt;.
+</span>                                                      
+
+A <span class="tag">&lt;b&gt;test&lt;/b&gt;</span> is made of three parts:                        
+
+<span class="tag">&lt;ul&gt;
+&lt;li&gt;</span> <span class="tag">&lt;b&gt;parameters&lt;/b&gt;,</span> which override the suite parameters     
+<span class="tag">&lt;li&gt;</span> <span class="tag">&lt;b&gt;groups&lt;/b&gt;,</span> made of two parts                           
+
+<span class="tag">&lt;li&gt;</span> <span class="tag">&lt;b&gt;classes&lt;/b&gt;,</span> defining which classes are going to be part
+  of this test run                                    
+<span class="tag">&lt;/ul&gt;
+</span>                                                      
+In turn, <span class="tag">&lt;b&gt;groups&lt;/b&gt;</span> are made of two parts:                
+
+<span class="tag">&lt;ul&gt;
+&lt;li&gt;</span> Definitions, which allow you to group groups into   
+  bigger groups                                       
+<span class="tag">&lt;li&gt;</span> Runs, which defines the groups that the methods     
+  must belong to in order to be run during this test  
+<span class="tag">&lt;/ul&gt;
+</span>                                                      
+Cedric Beust & Alexandru Popescu                      
+@title DTD for TestNG                                    
+@root suite
+
+--&gt;</span>
+
+
+<span class="xml-comment">&lt;!-- A suite is the top-level element of a testng.xml file                  --&gt;</span>
+
+&lt;!ELEMENT suite (listeners|packages|test|parameter|method-selectors|suite-files)* &gt;
+
+<span class="xml-comment">&lt;!-- Attributes: --&gt;</span>
+<span class="xml-comment">&lt;!--
+@attr  name        The name of this suite (as it will appear in the reports)
+@attr  junit       Whether to run in JUnit mode.
+@attr  verbose     How verbose the output on the console will be.  
+                This setting has no impact on the HTML reports.
+@attr  parallel   Whether TestNG should use different threads
+                to run your tests (might speed up the process)
+@attr  configfailurepolicy  Whether to continue attempting Before/After
+                Class/Methods after they've failed once or just skip remaining.
+@attr  thread-count An integer giving the size of the thread pool to use
+                if you set parallel.
+@attr  annotations  If "javadoc", TestNG will look for
+                JavaDoc annotations in your sources, otherwise it will
+                use JDK5 annotations.
+@attr  time-out     The time to wait in milliseconds before aborting the
+                method (if <span class="attribute">parallel="methods"</span> ) or the test (parallel="tests")
+@attr  skipfailedinvocationCounts Whether to skip failed invocations.
+@attr  data-provider-thread-count An integer giving the size of the thread pool to use
+       for parallel data providers.
+@attr  object-factory A class that implements IObjectFactory that will be used to
+       instantiate the test objects.
+--&gt;</span>
+&lt;!ATTLIST suite 
+    name CDATA #REQUIRED
+    junit (true | false) "false"
+    verbose CDATA #IMPLIED
+    parallel (false | methods | tests | classes) "false"
+    configfailurepolicy (skip | continue) "skip"
+    thread-count CDATA "5"
+    annotations CDATA #IMPLIED
+    time-out CDATA #IMPLIED
+    skipfailedinvocationCounts (true | false) "false"
+    data-provider-thread-count CDATA "10"
+    object-factory CDATA #IMPLIED
+&gt;
+
+<span class="xml-comment">&lt;!-- A list of XML files that contain more suite descriptions --&gt;</span>
+&lt;!ELEMENT suite-files (suite-file)* &gt;
+
+&lt;!ELEMENT suite-file ANY &gt;
+&lt;!ATTLIST suite-file
+    path CDATA #REQUIRED
+&gt;
+
+<span class="xml-comment">&lt;!--
+Parameters can be defined at the <span class="tag">&lt;suite&gt;</span> or at the <span class="tag">&lt;test&gt;</span> level.
+Parameters defined at the <span class="tag">&lt;test&gt;</span> level override parameters of the same name in <span class="tag">&lt;suite&gt;
+
+Parameters</span> are used to link Java method parameters to their actual value, defined here.
+--&gt;</span>
+&lt;!ELEMENT parameter ANY&gt;
+&lt;!ATTLIST parameter
+    name CDATA #REQUIRED
+    value CDATA #REQUIRED &gt;
+
+<span class="xml-comment">&lt;!--
+Method selectors define user classes used to select which methods to run.
+They need to implement <span class="tag">&lt;tt&gt;org.testng.IMethodSelector&lt;/tt&gt;</span> 
+--&gt;</span>
+&lt;!ELEMENT method-selectors (method-selector*) &gt;
+
+&lt;!ELEMENT method-selector ((selector-class)*|script) &gt;
+&lt;!ELEMENT selector-class ANY&gt;
+&lt;!ATTLIST selector-class
+    name CDATA #REQUIRED
+  priority CDATA #IMPLIED
+&gt;
+&lt;!ELEMENT script ANY&gt;
+&lt;!ATTLIST script
+    language CDATA #REQUIRED
+&gt;
+
+<span class="xml-comment">&lt;!--
+A test contains parameters and classes.  Additionally, you can define additional groups ("groups of groups")
+--&gt;</span>
+
+&lt;!ELEMENT test (method-selectors?,parameter*,groups?,packages?,classes?) &gt;
+
+<span class="xml-comment">&lt;!--
+@attr  name         The name of this test (as it will appear in the reports)
+@attr  junit        Whether to run in JUnit mode.
+@attr  verbose      How verbose the output on the console will be.
+                This setting has no impact on the HTML reports.
+                Default value: suite level verbose.
+@attr  parallel     Whether TestNG should use different threads
+                to run your tests (might speed up the process)
+@attr  thread-count An integer giving the size of the thread pool to be used if
+                parallel mode is used. Overrides the suite level value.
+@attr  annotations  If "javadoc", TestNG will look for
+                JavaDoc annotations in your sources, otherwise it will
+                use JDK5 annotations.
+@attr  time-out     the time to wait in milliseconds before aborting
+                the method (if <span class="attribute">parallel="methods"</span> ) or the test (if <span class="attribute">parallel="tests"</span> )
+@attr  enabled      flag to enable/disable current test. Default value: true 
+@attr  skipfailedinvocationCounts Whether to skip failed invocations.
+--&gt;</span>
+&lt;!ATTLIST test
+    name CDATA #REQUIRED 
+    junit (true | false) "false"
+    verbose  CDATA #IMPLIED
+    parallel  CDATA #IMPLIED
+    thread-count CDATA #IMPLIED
+    annotations  CDATA #IMPLIED
+    time-out CDATA #IMPLIED
+    enabled CDATA #IMPLIED
+    skipfailedinvocationCounts (true | false) "false"
+&gt;
+
+<span class="xml-comment">&lt;!--
+Defines additional groups ("groups of groups") and also which groups to include in this test run
+--&gt;</span>
+&lt;!ELEMENT groups (define*,run?) &gt;
+
+&lt;!ELEMENT define (include*)&gt;
+
+&lt;!ATTLIST define
+    name CDATA #REQUIRED&gt;
+
+<span class="xml-comment">&lt;!-- Defines which groups to include in the current group of groups         --&gt;</span>
+&lt;!ELEMENT include ANY&gt;
+&lt;!ATTLIST include
+    name CDATA #REQUIRED&gt;
+
+<span class="xml-comment">&lt;!-- Defines which groups to exclude from the current group of groups       --&gt;</span>
+&lt;!ELEMENT exclude ANY&gt;
+&lt;!ATTLIST exclude
+    name CDATA #REQUIRED&gt;
+
+<span class="xml-comment">&lt;!-- The subtag of groups used to define which groups should be run         --&gt;</span>
+&lt;!ELEMENT run (include?,exclude?)* &gt;
+
+<span class="xml-comment">&lt;!-- The list of classes to include in this test                            --&gt;</span>
+&lt;!ELEMENT classes (class*) &gt;
+&lt;!ELEMENT class (methods*) &gt;
+&lt;!ATTLIST class
+    name CDATA #REQUIRED &gt;
+
+<span class="xml-comment">&lt;!-- The list of packages to include in this test                           --&gt;</span>
+&lt;!ELEMENT packages (package*) &gt;
+
+<span class="xml-comment">&lt;!-- The package description. 
+     If the package name ends with .* then subpackages are included too.
+--&gt;</span>
+&lt;!ELEMENT package (include?,exclude?)*&gt;
+&lt;!ATTLIST package
+    name CDATA #REQUIRED &gt;
+
+<span class="xml-comment">&lt;!-- The list of methods to include/exclude from this test                 --&gt;</span>
+&lt;!ELEMENT methods (include?,exclude?)* &gt;
+
+<span class="xml-comment">&lt;!-- The list of listeners that will be passed to TestNG --&gt;</span>
+&lt;!ELEMENT listeners (listener*) &gt;
+
+&lt;!ELEMENT listener ANY&gt;
+&lt;!ATTLIST listener
+    class-name CDATA #REQUIRED &gt;
+</pre>
diff --git a/src/main/resources/META-INF/services/org.testng.xml.ISuiteParser b/src/main/resources/META-INF/services/org.testng.xml.ISuiteParser
new file mode 100644
index 0000000..cc0fca3
--- /dev/null
+++ b/src/main/resources/META-INF/services/org.testng.xml.ISuiteParser
@@ -0,0 +1 @@
+org.testng.internal.YamlParser
\ No newline at end of file
diff --git a/src/main/resources/bullet_point.png b/src/main/resources/bullet_point.png
new file mode 100644
index 0000000..176e6d5
--- /dev/null
+++ b/src/main/resources/bullet_point.png
Binary files differ
diff --git a/src/main/resources/collapseall.gif b/src/main/resources/collapseall.gif
new file mode 100644
index 0000000..a2d80a9
--- /dev/null
+++ b/src/main/resources/collapseall.gif
Binary files differ
diff --git a/src/main/resources/failed.png b/src/main/resources/failed.png
new file mode 100644
index 0000000..c117be5
--- /dev/null
+++ b/src/main/resources/failed.png
Binary files differ
diff --git a/src/main/resources/header b/src/main/resources/header
new file mode 100644
index 0000000..e399c52
--- /dev/null
+++ b/src/main/resources/header
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+
+<html>
+  <head>
+  <meta charset='utf-8'>
+  <title>TestNG reports</title>
+
+    <link type="text/css" href="testng-reports.css" rel="stylesheet" />  
+    <script type="text/javascript" src="jquery-1.7.1.min.js"></script>
+    <script type="text/javascript" src="testng-reports.js"></script>
+    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
+    <script type='text/javascript'>
+      google.load('visualization', '1', {packages:['table']});
+      google.setOnLoadCallback(drawTable);
+      var suiteTableInitFunctions = new Array();
+      var suiteTableData = new Array();
+    </script>
+    <!--
+      <script type="text/javascript" src="jquery-ui/js/jquery-ui-1.8.16.custom.min.js"></script>
+     -->
+  </head>
+
+  <body>
diff --git a/src/main/resources/jquery-1.7.1.min.js b/src/main/resources/jquery-1.7.1.min.js
new file mode 100644
index 0000000..198b3ff
--- /dev/null
+++ b/src/main/resources/jquery-1.7.1.min.js
@@ -0,0 +1,4 @@
+/*! jQuery v1.7.1 jquery.com | jquery.org/license */
+(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),cm.close();d=cm.createElement(a),cm.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cl)}ck[a]=e}return ck[a]}function cu(a,b){var c={};f.each(cq.concat.apply([],cq.slice(0,b)),function(){c[this]=a});return c}function ct(){cr=b}function cs(){setTimeout(ct,0);return cr=f.now()}function cj(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ci(){try{return new a.XMLHttpRequest}catch(b){}}function cc(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function cb(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function ca(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bE.test(a)?d(a,e):ca(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)ca(a+"["+e+"]",b[e],c,d);else d(a,b)}function b_(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function b$(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bT,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=b$(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=b$(a,c,d,e,"*",g));return l}function bZ(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bP),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bC(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?bx:by,g=0,h=e.length;if(d>0){if(c!=="border")for(;g<h;g++)c||(d-=parseFloat(f.css(a,"padding"+e[g]))||0),c==="margin"?d+=parseFloat(f.css(a,c+e[g]))||0:d-=parseFloat(f.css(a,"border"+e[g]+"Width"))||0;return d+"px"}d=bz(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0;if(c)for(;g<h;g++)d+=parseFloat(f.css(a,"padding"+e[g]))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+e[g]+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+e[g]))||0);return d+"px"}function bp(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bo(a){var b=c.createElement("div");bh.appendChild(b),b.innerHTML=a.outerHTML;return b.firstChild}function bn(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bm(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bm)}function bm(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bl(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bk(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bj(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c+(i[c][d].namespace?".":"")+i[c][d].namespace,i[c][d],i[c][d].data)}h.data&&(h.data=f.extend({},h.data))}}function bi(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function U(a){var b=V.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function T(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(O.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.1",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(H)return H.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?m(g):h==="function"&&(!a.unique||!o.has(g))&&c.push(g)},n=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,l=j||0,j=0,k=c.length;for(;c&&l<k;l++)if(c[l].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}i=!1,c&&(a.once?e===!0?o.disable():c=[]:d&&d.length&&(e=d.shift(),o.fireWith(e[0],e[1])))},o={add:function(){if(c){var a=c.length;m(arguments),i?k=c.length:e&&e!==!0&&(j=a,n(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){i&&f<=k&&(k--,f<=l&&l--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&o.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(i?a.once||d.push([b,c]):(!a.once||!e)&&n(b,c));return this},fire:function(){o.fireWith(this,arguments);return this},fired:function(){return!!e}};return o};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progress:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){i.done.apply(i,arguments).fail.apply(i,arguments);return this},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var b,d,e,g,h,i,j,k,l,m,n,o,p,q=c.createElement("div"),r=c.documentElement;q.setAttribute("className","t"),q.innerHTML="   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=q.getElementsByTagName("*"),e=q.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=q.getElementsByTagName("input")[0],b={leadingWhitespace:q.firstChild.nodeType===3,tbody:!q.getElementsByTagName("tbody").length,htmlSerialize:!!q.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:q.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete q.test}catch(s){b.deleteExpando=!1}!q.addEventListener&&q.attachEvent&&q.fireEvent&&(q.attachEvent("onclick",function(){b.noCloneEvent=!1}),q.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),q.appendChild(i),k=c.createDocumentFragment(),k.appendChild(q.lastChild),b.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,k.removeChild(i),k.appendChild(q),q.innerHTML="",a.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",q.style.width="2px",q.appendChild(j),b.reliableMarginRight=(parseInt((a.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(q.attachEvent)for(o in{submit:1,change:1,focusin:1})n="on"+o,p=n in q,p||(q.setAttribute(n,"return;"),p=typeof q[n]=="function"),b[o+"Bubbles"]=p;k.removeChild(q),k=g=h=j=q=i=null,f(function(){var a,d,e,g,h,i,j,k,m,n,o,r=c.getElementsByTagName("body")[0];!r||(j=1,k="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",m="visibility:hidden;border:0;",n="style='"+k+"border:5px solid #000;padding:0;'",o="<div "+n+"><div></div></div>"+"<table "+n+" cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>",a=c.createElement("div"),a.style.cssText=m+"width:0;height:0;position:static;top:0;margin-top:"+j+"px",r.insertBefore(a,r.firstChild),q=c.createElement("div"),a.appendChild(q),q.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",l=q.getElementsByTagName("td"),p=l[0].offsetHeight===0,l[0].style.display="",l[1].style.display="none",b.reliableHiddenOffsets=p&&l[0].offsetHeight===0,q.innerHTML="",q.style.width=q.style.paddingLeft="1px",f.boxModel=b.boxModel=q.offsetWidth===2,typeof q.style.zoom!="undefined"&&(q.style.display="inline",q.style.zoom=1,b.inlineBlockNeedsLayout=q.offsetWidth===2,q.style.display="",q.innerHTML="<div style='width:4px;'></div>",b.shrinkWrapBlocks=q.offsetWidth!==2),q.style.cssText=k+m,q.innerHTML=o,d=q.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,i={doesNotAddBorder:e.offsetTop!==5,doesAddBorderForTableAndCells:h.offsetTop===5},e.style.position="fixed",e.style.top="20px",i.fixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",i.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,i.doesNotIncludeMarginInBodyOffset=r.offsetTop!==j,r.removeChild(a),q=a=null,f.extend(b,i))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[h]:a.removeAttribute?a.removeAttribute(h):a[h]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h=null;if(typeof a=="undefined"){if(this.length){h=f.data(this[0]);if(this[0].nodeType===1&&!f._data(this[0],"parsedAttrs")){e=this[0].attributes;for(var i=0,j=e.length;i<j;i++)g=e[i].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),l(this[0],g,h[g]));f._data(this[0],"parsedAttrs",!0)}}return h}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split("."),d[1]=d[1]?"."+d[1]:"";if(c===b){h=this.triggerHandler("getData"+d[1]+"!",[d[0]]),h===b&&this.length&&(h=f.data(this[0],a),h=l(this[0],a,h));return h===b&&d[1]?this.data(d[0]):h}return this.each(function(){var b=f(this),e=[d[0],c];b.triggerHandler("setData"+d[1]+"!",e),f.data(this,a,c),b.triggerHandler("changeData"+d[1]+"!",e)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise()}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;h<g;h++)e=d[h],e&&(c=f.propFix[e]||e,f.attr(a,e,""),a.removeAttribute(v?e:c),u.test(e)&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!!a&&i!==3&&i!==8&&i!==2){h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]}},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/\bhover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};
+f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,guid:d.guid,selector:g,quick:G(g),namespace:n.join(".")},p),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d,e){var g=f.hasData(a)&&f._data(a),h,i,j,k,l,m,n,o,p,q,r,s;if(!!g&&!!(o=g.events)){b=f.trim(I(b||"")).split(" ");for(h=0;h<b.length;h++){i=A.exec(b[h])||[],j=k=i[1],l=i[2];if(!j){for(j in o)f.event.remove(a,j+b[h],c,d,!0);continue}p=f.event.special[j]||{},j=(d?p.delegateType:p.bindType)||j,r=o[j]||[],m=r.length,l=l?new RegExp("(^|\\.)"+l.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;for(n=0;n<r.length;n++)s=r[n],(e||k===s.origType)&&(!c||c.guid===s.guid)&&(!l||l.test(s.namespace))&&(!d||d===s.selector||d==="**"&&s.selector)&&(r.splice(n--,1),s.selector&&r.delegateCount--,p.remove&&p.remove.call(a,s));r.length===0&&m!==r.length&&((!p.teardown||p.teardown.call(a,l)===!1)&&f.removeEvent(a,j,g.handle),delete o[j])}f.isEmptyObject(o)&&(q=g.handle,q&&(q.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;if(E.test(h+f.event.triggered))return;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length&&!c.isPropagationStopped();l++)m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d)===!1&&c.preventDefault();c.type=h,!g&&!c.isDefaultPrevented()&&(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(arguments,0),h=!c.exclusive&&!c.namespace,i=[],j,k,l,m,n,o,p,q,r,s,t;g[0]=c,c.delegateTarget=this;if(e&&!c.target.disabled&&(!c.button||c.type!=="click")){m=f(this),m.context=this.ownerDocument||this;for(l=c.target;l!=this;l=l.parentNode||this){o={},q=[],m[0]=l;for(j=0;j<e;j++)r=d[j],s=r.selector,o[s]===b&&(o[s]=r.quick?H(l,r.quick):m.is(s)),o[s]&&q.push(r);q.length&&i.push({elem:l,matches:q})}}d.length>e&&i.push({elem:this,matches:d.slice(e)});for(j=0;j<i.length&&!c.isPropagationStopped();j++){p=i[j],c.currentTarget=p.elem;for(k=0;k<p.matches.length&&!c.isImmediatePropagationStopped();k++){r=p.matches[k];if(h||!c.namespace&&!r.namespace||c.namespace_re&&c.namespace_re.test(r.namespace))c.data=r.data,c.handleObj=r,n=((f.event.special[r.origType]||{}).handle||r.handler).apply(p.elem,g),n!==b&&(c.result=n,n===!1&&(c.preventDefault(),c.stopPropagation()))}}return c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?K:J):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=K;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=K;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=K,this.stopPropagation()},isDefaultPrevented:J,isPropagationStopped:J,isImmediatePropagationStopped:J},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c=this,d=a.relatedTarget,e=a.handleObj,g=e.selector,h;if(!d||d!==c&&!f.contains(c,d))a.type=e.origType,h=e.handler.apply(this,arguments),a.type=b;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){this.parentNode&&!a.isTrigger&&f.event.simulate("submit",this.parentNode,a,!0)}),d._submit_attached=!0)})},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(z.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;z.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return z.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!="string"&&(d=c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=J;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on.call(this,a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.type+"."+e.namespace:e.type,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=J);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"**",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1||d===9){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var L=/Until$/,M=/^(?:parents|prevUntil|prevAll)/,N=/,/,O=/^.[^:#\[\.,]*$/,P=Array.prototype.slice,Q=f.expr.match.POS,R={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(T(this,a,!1),"not",a)},filter:function(a){return this.pushStack(T(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?Q.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=Q.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/<tbody/i,_=/<|&#?\w+;/,ba=/<(?:script|style)/i,bb=/<(?:script|object|embed|option|style)/i,bc=new RegExp("<(?:"+V+")","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function()
+{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bi(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bp)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!bb.test(j)&&(f.support.checkClone||!bd.test(j))&&(f.support.html5Clone||!bc.test(j))&&(g=!0,h=f.fragments[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||!bc.test("<"+a.nodeName)?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1></$2>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");b===c?bh.appendChild(o):U(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bn(k[i]);else bn(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||be.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bq=/alpha\([^)]*\)/i,br=/opacity=([^)]*)/,bs=/([A-Z]|^ms)/g,bt=/^-?\d+(?:px)?$/i,bu=/^-?\d/,bv=/^([\-+])=([\-+.\de]+)/,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Left","Right"],by=["Top","Bottom"],bz,bA,bB;f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bz(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bv.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bz)return bz(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){if(a.offsetWidth!==0)return bC(a,b,d);f.swap(a,bw,function(){e=bC(a,b,d)});return e}},set:function(a,b){if(!bt.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return br.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bq,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bq.test(g)?g.replace(bq,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,b){var c,d,e;b=b.replace(bs,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b)));return c}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bt.test(f)&&bu.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bD=/%20/g,bE=/\[\]$/,bF=/\r?\n/g,bG=/#.*$/,bH=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bI=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bJ=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bK=/^(?:GET|HEAD)$/,bL=/^\/\//,bM=/\?/,bN=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bO=/^(?:select|textarea)/i,bP=/\s+/,bQ=/([?&])_=[^&]*/,bR=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bS=f.fn.load,bT={},bU={},bV,bW,bX=["*/"]+["*"];try{bV=e.href}catch(bY){bV=c.createElement("a"),bV.href="",bV=bV.href}bW=bR.exec(bV.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bS)return bS.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bN,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bO.test(this.nodeName)||bI.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bF,"\r\n")}}):{name:b.name,value:c.replace(bF,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b_(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b_(a,b);return a},ajaxSettings:{url:bV,isLocal:bJ.test(bW[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bX},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bZ(bT),ajaxTransport:bZ(bU),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cb(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cc(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bH.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bG,"").replace(bL,bW[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bP),d.crossDomain==null&&(r=bR.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bW[1]&&r[2]==bW[2]&&(r[3]||(r[1]==="http:"?80:443))==(bW[3]||(bW[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bT,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bK.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bM.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bQ,"$1_="+x);d.url=y+(y===d.url?(bM.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bX+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bU,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)ca(g,a[g],c,e);return d.join("&").replace(bD,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cd=f.now(),ce=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cd++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ce.test(b.url)||e&&ce.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ce,l),b.url===j&&(e&&(k=k.replace(ce,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cf=a.ActiveXObject?function(){for(var a in ch)ch[a](0,1)}:!1,cg=0,ch;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ci()||cj()}:ci,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cf&&delete ch[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cg,cf&&(ch||(ch={},f(a).unload(cf)),ch[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ck={},cl,cm,cn=/^(?:toggle|show|hide)$/,co=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cp,cq=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cr;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cv(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cu("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cu("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cv(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)j=new f.fx(this,b,i),h=a[i],cn.test(h)?(o=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),o?(f._data(this,"toggle"+i,o==="show"?"hide":"show"),j[o]()):j[h]()):(k=co.exec(h),l=j.cur(),k?(m=parseFloat(k[2]),n=k[3]||(f.cssNumber[i]?"":"px"),n!=="px"&&(f.style(this,i,(m||1)+n),l=(m||1)/j.cur()*l,f.style(this,i,l+n)),k[1]&&(m=(k[1]==="-="?-1:1)*m+l),j.custom(l,m,n)):j.custom(l,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b]&&g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:cu("show",1),slideUp:cu("hide",1),slideToggle:cu("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=cr||cs(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){e.options.hide&&f._data(e.elem,"fxshow"+e.prop)===b&&f._data(e.elem,"fxshow"+e.prop,e.start)},h()&&f.timers.push(h)&&!cp&&(cp=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!==b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=cr||cs(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(cp),cp=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(["width","height"],function(a,b){f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now)+a.unit)}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cy(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.support.fixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cw.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.support.fixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window);
\ No newline at end of file
diff --git a/src/main/resources/navigator-bullet.png b/src/main/resources/navigator-bullet.png
new file mode 100644
index 0000000..36d90d3
--- /dev/null
+++ b/src/main/resources/navigator-bullet.png
Binary files differ
diff --git a/src/main/resources/org/testng/internal/VersionTemplateJava b/src/main/resources/org/testng/internal/VersionTemplateJava
new file mode 100644
index 0000000..ad43b84
--- /dev/null
+++ b/src/main/resources/org/testng/internal/VersionTemplateJava
@@ -0,0 +1,9 @@
+package org.testng.internal;
+
+public class Version {
+  public static final String VERSION = "@version@";
+
+  public static void displayBanner() {
+    System.out.println("...\n... TestNG " + VERSION + " by Cédric Beust (cedric@beust.com)\n...\n");
+  }
+}
diff --git a/src/main/resources/passed.png b/src/main/resources/passed.png
new file mode 100644
index 0000000..45e85bb
--- /dev/null
+++ b/src/main/resources/passed.png
Binary files differ
diff --git a/src/main/resources/skipped.png b/src/main/resources/skipped.png
new file mode 100644
index 0000000..c36a324
--- /dev/null
+++ b/src/main/resources/skipped.png
Binary files differ
diff --git a/src/main/resources/testng-1.0.dtd b/src/main/resources/testng-1.0.dtd
new file mode 100755
index 0000000..88b5316
--- /dev/null
+++ b/src/main/resources/testng-1.0.dtd
@@ -0,0 +1,211 @@
+<!--
+
+Here is a quick overview of the main parts of this DTD.  For more information,
+refer to the <a href="http://testng.org">main web site</a>.
+                                                      
+A <b>suite</b> is made of <b>tests</b> and <b>parameters</b>.
+                                                      
+A <b>test</b> is made of three parts:                        
+
+<ul>
+<li> <b>parameters</b>, which override the suite parameters     
+<li> <b>groups</b>, made of two parts                           
+<li> <b>classes</b>, defining which classes are going to be part
+  of this test run                                    
+</ul>
+                                                      
+In turn, <b>groups</b> are made of two parts:                
+<ul>
+<li> Definitions, which allow you to group groups into   
+  bigger groups                                       
+<li> Runs, which defines the groups that the methods     
+  must belong to in order to be run during this test  
+</ul>
+                                                      
+Cedric Beust & Alexandru Popescu                      
+@title DTD for TestNG                                    
+@root suite
+
+-->
+
+
+<!-- A suite is the top-level element of a testng.xml file                  -->
+<!ELEMENT suite (groups?,(listeners|packages|test|parameter|method-selectors|suite-files)*) >
+
+<!-- Attributes: -->
+<!--
+@attr  name        The name of this suite (as it will appear in the reports)
+@attr  junit       Whether to run in JUnit mode.
+@attr  verbose     How verbose the output on the console will be.  
+                This setting has no impact on the HTML reports.
+@attr  parallel   Whether TestNG should use different threads
+                to run your tests (might speed up the process)
+@attr  parent-module A module used to create the parent injector of all guice injectors used
+       in tests of the suite
+@attr  guice-stage The stage with which the parent injector is created
+@attr  configfailurepolicy  Whether to continue attempting Before/After
+                Class/Methods after they've failed once or just skip remaining.
+@attr  thread-count An integer giving the size of the thread pool to use
+                if you set parallel.
+@attr  annotations  If "javadoc", TestNG will look for
+                JavaDoc annotations in your sources, otherwise it will
+                use JDK5 annotations.
+@attr  time-out     The time to wait in milliseconds before aborting the
+                method (if parallel="methods") or the test (parallel="tests")
+@attr  skipfailedinvocationcounts Whether to skip failed invocations.
+@attr  data-provider-thread-count An integer giving the size of the thread pool to use
+       for parallel data providers.
+@attr  object-factory A class that implements IObjectFactory that will be used to
+       instantiate the test objects.
+@attr allow-return-values If true, tests that return a value will be run as well
+-->
+<!ATTLIST suite 
+    name CDATA #REQUIRED
+    junit (true | false) "false"
+    verbose CDATA #IMPLIED
+    parallel (false | methods | tests | classes | instances) "false"
+    parent-module CDATA #IMPLIED
+    guice-stage (DEVELOPMENT | PRODUCTION | TOOL) "DEVELOPMENT"
+    configfailurepolicy (skip | continue) "skip"
+    thread-count CDATA "5"
+    annotations CDATA #IMPLIED
+    time-out CDATA #IMPLIED
+    skipfailedinvocationcounts (true | false) "false"
+    data-provider-thread-count CDATA "10"
+    object-factory CDATA #IMPLIED
+    group-by-instances (true | false) "false"
+    preserve-order (true | false) "true"
+    allow-return-values (true | false) "false"
+>
+
+<!-- A list of XML files that contain more suite descriptions -->
+<!ELEMENT suite-files (suite-file)* >
+
+<!ELEMENT suite-file ANY >
+<!ATTLIST suite-file
+    path CDATA #REQUIRED
+>
+
+<!--
+Parameters can be defined at the <suite> or at the <test> level.
+Parameters defined at the <test> level override parameters of the same name in <suite>
+Parameters are used to link Java method parameters to their actual value, defined here.
+-->
+<!ELEMENT parameter ANY>
+<!ATTLIST parameter
+    name CDATA #REQUIRED
+    value CDATA #REQUIRED >
+
+<!--
+Method selectors define user classes used to select which methods to run.
+They need to implement <tt>org.testng.IMethodSelector</tt> 
+-->
+<!ELEMENT method-selectors (method-selector*) >
+<!ELEMENT method-selector ((selector-class)*|script) >
+<!ELEMENT selector-class ANY>
+<!ATTLIST selector-class
+    name CDATA #REQUIRED
+  priority CDATA #IMPLIED
+>
+<!ELEMENT script ANY>
+<!ATTLIST script
+    language CDATA #REQUIRED
+>
+
+<!--
+A test contains parameters and classes.  Additionally, you can define additional groups ("groups of groups")
+-->
+
+<!ELEMENT test (method-selectors?,parameter*,groups?,packages?,classes?) >
+
+<!--
+@attr  name         The name of this test (as it will appear in the reports)
+@attr  junit        Whether to run in JUnit mode.
+@attr  verbose      How verbose the output on the console will be.
+                This setting has no impact on the HTML reports.
+                Default value: suite level verbose.
+@attr  parallel     Whether TestNG should use different threads
+                to run your tests (might speed up the process)
+@attr  thread-count An integer giving the size of the thread pool to be used if
+                parallel mode is used. Overrides the suite level value.
+@attr  annotations  If "javadoc", TestNG will look for
+                JavaDoc annotations in your sources, otherwise it will
+                use JDK5 annotations.
+@attr  time-out     the time to wait in milliseconds before aborting
+                the method (if parallel="methods") or the test (if parallel="tests")
+@attr  enabled      flag to enable/disable current test. Default value: true 
+@attr  skipfailedinvocationcounts Whether to skip failed invocations.
+@attr preserve-order If true, the classes in this tag will be run in the same order as
+found in the XML file.
+@attr allow-return-values If true, tests that return a value will be run as well
+-->
+<!ATTLIST test
+    name CDATA #REQUIRED 
+    junit (true | false) "false"
+    verbose  CDATA #IMPLIED
+    parallel  (false | methods | tests | classes | instances) #IMPLIED
+    thread-count CDATA #IMPLIED
+    annotations  CDATA #IMPLIED
+    time-out CDATA #IMPLIED
+    enabled (true | false) #IMPLIED
+    skipfailedinvocationcounts (true | false) "false"
+    preserve-order (true | false) "true"
+    group-by-instances (true | false) "false"
+    allow-return-values (true | false) "false"
+>
+
+<!--
+Defines additional groups ("groups of groups") and also which groups to include in this test run
+-->
+<!ELEMENT groups (define*,run?,dependencies?) >
+
+<!ELEMENT define (include*)>
+<!ATTLIST define
+    name CDATA #REQUIRED>
+
+<!-- Defines which groups to include in the current group of groups         -->
+<!ELEMENT include ANY>
+<!ATTLIST include
+    name CDATA #REQUIRED
+    description CDATA #IMPLIED
+    invocation-numbers CDATA #IMPLIED>
+
+<!-- Defines which groups to exclude from the current group of groups       -->
+<!ELEMENT exclude ANY>
+<!ATTLIST exclude
+    name CDATA #REQUIRED>
+
+<!-- The subtag of groups used to define which groups should be run         -->
+<!ELEMENT run (include?,exclude?)* >
+
+<!ELEMENT dependencies (group*)>
+
+<!ELEMENT group ANY>
+<!ATTLIST group
+    name CDATA #REQUIRED
+    depends-on CDATA #REQUIRED>
+
+<!-- The list of classes to include in this test                            -->
+<!ELEMENT classes (class*,parameter*) >
+<!ELEMENT class (methods|parameter)* >
+<!ATTLIST class
+    name CDATA #REQUIRED >
+
+<!-- The list of packages to include in this test                           -->
+<!ELEMENT packages (package*) >
+<!-- The package description. 
+     If the package name ends with .* then subpackages are included too.
+-->
+<!ELEMENT package (include?,exclude?)*>
+<!ATTLIST package
+    name CDATA #REQUIRED >
+
+<!-- The list of methods to include/exclude from this test                 -->
+<!ELEMENT methods (include?,exclude?,parameter?)* >
+
+<!-- The list of listeners that will be passed to TestNG -->
+<!ELEMENT listeners (listener*) >
+
+<!ELEMENT listener ANY>
+<!ATTLIST listener
+    class-name CDATA #REQUIRED >
diff --git a/src/main/resources/testng-reports.css b/src/main/resources/testng-reports.css
new file mode 100644
index 0000000..29588e5
--- /dev/null
+++ b/src/main/resources/testng-reports.css
@@ -0,0 +1,309 @@
+body {
+    margin: 0px 0px 5px 5px;
+}
+
+ul {
+    margin: 0px;
+}
+
+li {
+    list-style-type: none;
+}
+
+a {
+    text-decoration: none;
+}
+
+a:hover {
+    text-decoration: underline;
+}
+
+.navigator-selected {
+    background: #ffa500;
+}
+
+.wrapper {
+    position: absolute;
+    top: 60px;
+    bottom: 0;
+    left: 400px;
+    right: 0;
+    overflow: auto;
+}
+
+.navigator-root {
+    position: absolute;
+    top: 60px;
+    bottom: 0;
+    left: 0;
+    width: 400px;
+    overflow-y: auto;
+}
+
+.suite {
+    margin: 0px 10px 10px 0px;
+    background-color: #fff8dc;
+}
+
+.suite-name {
+    padding-left: 10px;
+    font-size: 25px;
+    font-family: Times;
+}
+
+.main-panel-header {
+    padding: 5px;
+    background-color: #9FB4D9; //afeeee;
+    font-family: monospace;
+    font-size: 18px;
+}
+
+.main-panel-content {
+    padding: 5px;
+    margin-bottom: 10px;
+    background-color: #DEE8FC; //d0ffff;
+}
+
+.rounded-window {
+    border-radius: 10px;
+    border-style: solid;
+    border-width: 1px;
+}
+
+.rounded-window-top {
+    border-top-right-radius: 10px 10px;
+    border-top-left-radius: 10px 10px;
+    border-style: solid;
+    border-width: 1px;
+    overflow: auto;
+}
+
+.light-rounded-window-top {
+    border-top-right-radius: 10px 10px;
+    border-top-left-radius: 10px 10px;
+}
+
+.rounded-window-bottom {
+    border-style: solid;
+    border-width: 0px 1px 1px 1px;
+    border-bottom-right-radius: 10px 10px;
+    border-bottom-left-radius: 10px 10px;
+    overflow: auto;
+}
+
+.method-name {
+    font-size: 12px;
+    font-family: monospace;
+}
+
+.method-content {
+    border-style: solid;
+    border-width: 0px 0px 1px 0px;
+    margin-bottom: 10;
+    padding-bottom: 5px;
+    width: 80%;
+}
+
+.parameters {
+    font-size: 14px;
+    font-family: monospace;
+}
+
+.stack-trace {
+    white-space: pre;
+    font-family: monospace;
+    font-size: 12px;
+    font-weight: bold;
+    margin-top: 0px;
+    margin-left: 20px;
+}
+
+.testng-xml {
+    font-family: monospace;
+}
+
+.method-list-content {
+    margin-left: 10px;
+}
+
+.navigator-suite-content {
+    margin-left: 10px;
+    font: 12px 'Lucida Grande';
+}
+
+.suite-section-title {
+    margin-top: 10px;
+    width: 80%;
+    border-style: solid;
+    border-width: 1px 0px 0px 0px;
+    font-family: Times;
+    font-size: 18px;
+    font-weight: bold;
+}
+
+.suite-section-content {
+    list-style-image: url(bullet_point.png);
+}
+
+.top-banner-root {
+    position: absolute;
+    top: 0;
+    height: 45px;
+    left: 0;
+    right: 0;
+    padding: 5px;
+    margin: 0px 0px 5px 0px;
+    background-color: #0066ff;
+    font-family: Times;
+    color: #fff;
+    text-align: center;
+}
+
+.top-banner-title-font {
+    font-size: 25px;
+}
+
+.test-name {
+    font-family: 'Lucida Grande';
+    font-size: 16px;
+}
+
+.suite-icon {
+    padding: 5px;
+    float: right;
+    height: 20;
+}
+
+.test-group {
+    font: 20px 'Lucida Grande';
+    margin: 5px 5px 10px 5px;
+    border-width: 0px 0px 1px 0px;
+    border-style: solid;
+    padding: 5px;
+}
+
+.test-group-name {
+    font-weight: bold;
+}
+
+.method-in-group {
+    font-size: 16px;
+    margin-left: 80px;
+}
+
+table.google-visualization-table-table {
+    width: 100%;
+}
+
+.reporter-method-name {
+    font-size: 14px;
+    font-family: monospace;
+}
+
+.reporter-method-output-div {
+    padding: 5px;
+    margin: 0px 0px 5px 20px;
+    font-size: 12px;
+    font-family: monospace;
+    border-width: 0px 0px 0px 1px;
+    border-style: solid;
+}
+
+.ignored-class-div {
+    font-size: 14px;
+    font-family: monospace;
+}
+
+.ignored-methods-div {
+    padding: 5px;
+    margin: 0px 0px 5px 20px;
+    font-size: 12px;
+    font-family: monospace;
+    border-width: 0px 0px 0px 1px;
+    border-style: solid;
+}
+
+.border-failed {
+    border-top-left-radius: 10px 10px;
+    border-bottom-left-radius: 10px 10px;
+    border-style: solid;
+    border-width: 0px 0px 0px 10px;
+    border-color: #f00;
+}
+
+.border-skipped {
+    border-top-left-radius: 10px 10px;
+    border-bottom-left-radius: 10px 10px;
+    border-style: solid;
+    border-width: 0px 0px 0px 10px;
+    border-color: #edc600;
+}
+
+.border-passed {
+    border-top-left-radius: 10px 10px;
+    border-bottom-left-radius: 10px 10px;
+    border-style: solid;
+    border-width: 0px 0px 0px 10px;
+    border-color: #19f52d;
+}
+
+.times-div {
+    text-align: center;
+    padding: 5px;
+}
+
+.suite-total-time {
+    font: 16px 'Lucida Grande';
+}
+
+.configuration-suite {
+    margin-left: 20px;
+}
+
+.configuration-test {
+    margin-left: 40px;
+}
+
+.configuration-class {
+    margin-left: 60px;
+}
+
+.configuration-method {
+    margin-left: 80px;
+}
+
+.test-method {
+    margin-left: 100px;
+}
+
+.chronological-class {
+    background-color: #0ccff;
+    border-style: solid;
+    border-width: 0px 0px 1px 1px;
+}
+
+.method-start {
+    float: right;
+}
+
+.chronological-class-name {
+    padding: 0px 0px 0px 5px;
+    color: #008;
+}
+
+.after, .before, .test-method {
+    font-family: monospace;
+    font-size: 14px;
+}
+
+.navigator-suite-header {
+    font-size: 22px;
+    margin: 0px 10px 5px 0px;
+    background-color: #deb887;
+    text-align: center;
+}
+
+.collapse-all-icon {
+    padding: 5px;
+    float: right;
+}
diff --git a/src/main/resources/testng-reports.js b/src/main/resources/testng-reports.js
new file mode 100644
index 0000000..5159f81
--- /dev/null
+++ b/src/main/resources/testng-reports.js
@@ -0,0 +1,122 @@
+$(document).ready(function() {
+    $('a.navigator-link').click(function() {
+        // Extract the panel for this link
+        var panel = getPanelName($(this));
+
+        // Mark this link as currently selected
+        $('.navigator-link').parent().removeClass('navigator-selected');
+        $(this).parent().addClass('navigator-selected');
+
+        showPanel(panel);
+    });
+
+    installMethodHandlers('failed');
+    installMethodHandlers('skipped');
+    installMethodHandlers('passed', true); // hide passed methods by default
+
+    $('a.method').click(function() {
+        showMethod($(this));
+        return false;
+    });
+
+    // Hide all the panels and display the first one (do this last
+    // to make sure the click() will invoke the listeners)
+    $('.panel').hide();
+    $('.navigator-link').first().click();
+
+    // Collapse/expand the suites
+    $('a.collapse-all-link').click(function() {
+        var contents = $('.navigator-suite-content');
+        if (contents.css('display') == 'none') {
+            contents.show();
+        } else {
+            contents.hide();
+        }
+    });
+});
+
+// The handlers that take care of showing/hiding the methods
+function installMethodHandlers(name, hide) {
+    function getContent(t) {
+    return $('.method-list-content.' + name + "." + t.attr('panel-name'));
+    }
+
+    function getHideLink(t, name) {
+        var s = 'a.hide-methods.' + name + "." + t.attr('panel-name');
+        return $(s);
+    }
+
+    function getShowLink(t, name) {
+        return $('a.show-methods.' + name + "." + t.attr('panel-name'));
+    }
+
+    function getMethodPanelClassSel(element, name) {
+        var panelName = getPanelName(element);
+    var sel = '.' + panelName + "-class-" + name;
+        return $(sel);
+    }
+
+    $('a.hide-methods.' + name).click(function() {
+        var w = getContent($(this));
+        w.hide();
+        getHideLink($(this), name).hide();
+        getShowLink($(this), name).show();
+    getMethodPanelClassSel($(this), name).hide();
+    });
+
+    $('a.show-methods.' + name).click(function() {
+        var w = getContent($(this));
+        w.show();
+        getHideLink($(this), name).show();
+        getShowLink($(this), name).hide();
+    showPanel(getPanelName($(this)));
+    getMethodPanelClassSel($(this), name).show();
+    });
+
+    if (hide) {
+        $('a.hide-methods.' + name).click();
+    } else {
+        $('a.show-methods.' + name).click();
+    }
+}
+
+function getHashForMethod(element) {
+    return element.attr('hash-for-method');
+}
+
+function getPanelName(element) {
+    return element.attr('panel-name');
+}
+
+function showPanel(panelName) {
+    $('.panel').hide();
+    var panel = $('.panel[panel-name="' + panelName + '"]');
+    panel.show();
+}
+
+function showMethod(element) {
+    var hashTag = getHashForMethod(element);
+    var panelName = getPanelName(element);
+    showPanel(panelName);
+    var current = document.location.href;
+    var base = current.substring(0, current.indexOf('#'))
+    document.location.href = base + '#' + hashTag;
+    var newPosition = $(document).scrollTop() - 65;
+    $(document).scrollTop(newPosition);
+}
+
+function drawTable() {
+    for (var i = 0; i < suiteTableInitFunctions.length; i++) {
+        window[suiteTableInitFunctions[i]]();
+    }
+
+    for (var k in window.suiteTableData) {
+        var v = window.suiteTableData[k];
+        var div = v.tableDiv;
+        var data = v.tableData
+        var table = new google.visualization.Table(document.getElementById(div));
+        table.draw(data, {
+            showRowNumber : false
+        });
+    }
+}
diff --git a/src/main/resources/testng.css b/src/main/resources/testng.css
new file mode 100644
index 0000000..3904800
--- /dev/null
+++ b/src/main/resources/testng.css
@@ -0,0 +1,9 @@
+.invocation-failed,  .test-failed  { background-color: #DD0000; }

+.invocation-percent, .test-percent { background-color: #006600; }

+.invocation-passed,  .test-passed  { background-color: #00AA00; }

+.invocation-skipped, .test-skipped { background-color: #CCCC00; }

+

+.main-page {

+  font-size: x-large;

+}

+

diff --git a/src/main/resources/testngtasks b/src/main/resources/testngtasks
new file mode 100644
index 0000000..eeea86b
--- /dev/null
+++ b/src/main/resources/testngtasks
@@ -0,0 +1 @@
+testng=org.testng.TestNGAntTask
diff --git a/src/test/java/ConverterSample2.java b/src/test/java/ConverterSample2.java
new file mode 100644
index 0000000..e6c65f9
--- /dev/null
+++ b/src/test/java/ConverterSample2.java
@@ -0,0 +1,28 @@
+// Not that this file has no package (that's what we are testing) and therefore,
+// it is at the wrong location, but it's easier to leave it here.
+// Also, do not change the line numbers since the test will make sure
+// that the tags are generated in hardcoded line numbers
+import junit.framework.TestCase;
+public class ConverterSample2 extends TestCase {
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public final void testClassJunit() {
+    }
+
+
+    public final void testSetClassId() {
+    }
+
+    public final void testSetClassName() {
+    }
+
+}
diff --git a/src/test/java/ConverterSample4.java b/src/test/java/ConverterSample4.java
new file mode 100644
index 0000000..c33c2a5
--- /dev/null
+++ b/src/test/java/ConverterSample4.java
@@ -0,0 +1,82 @@
+

+/*

+ * Created on 12-Sep-2006 by micheb10

+ * it is at the wrong location, but it's easier to leave it here.

+ * Also, do not change the line numbers since the test will make sure

+ * that the tags are generated in hardcoded line numbers

+ */

+

+/**

+ * Sample file for the Javadocv annotations to Java 5 annotations converter for the default package

+ * @author micheb10 12-Sep-2006

+ *

+ */

+public class ConverterSample4 {

+	/**

+	 * This comment line should be preserved

+	 * @testng.before-suite alwaysRun = "true"

+	 */

+	public void beforeSuiteAlwaysRun() {

+		// We are just checking appropriate annotations are added so we don't care about body

+	}

+

+	/**

+	 * @testng.test

+	 */

+	public void plainTest() {

+		// Empty body

+	}

+

+	/**

+	 * @testng.test

+	 * @testng.expected-exceptions

+	 * value = "java.lang.NullPointerException java.lang.NumberFormatException"

+	 */

+	public void expectedExceptions() {

+		// Empty body

+	}

+

+	/**

+	 * @testng.test groups = "groupA groupB"

+	 */

+	public void testGroups() {

+		// Empty body

+	}

+

+	/**

+	 * @testng.after-method

+	 */

+	public void afterMethod() {

+		// Empty body

+	}

+

+	/**

+	 * This key should be preserved

+	 * @author The author is a standard tag and should not be touched

+	 * @testng.test groups = "groupA"

+	 * 		dependsOnMethods = "expectedExceptions" timeOut="3000" unrecognised="futureProof"

+	 * @version another standard tag should not be changed

+	 * @testng.expected-exceptions

+	 * value = "java.lang.NullPointerException java.lang.NumberFormatException"

+	 *

+	 */

+	public void testEverything() {

+

+	}

+

+	/**

+	 * @testng.data-provider name="test1"

+	 */

+	public Object[][] dataProvider() {

+		return null;

+	}

+

+	/**

+	 * @testng.factory

+	 */

+	@SuppressWarnings({"unchecked", "deprecation"})

+	public Object[] factory() {

+		return null;

+	}

+

+}

diff --git a/src/test/java/NoPackageTest.java b/src/test/java/NoPackageTest.java
new file mode 100644
index 0000000..394a7ea
--- /dev/null
+++ b/src/test/java/NoPackageTest.java
@@ -0,0 +1,20 @@
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
+/**
+ * @author Filippo Diotalevi
+ */
+public class NoPackageTest {
+	private boolean m_run = false;
+
+	@Test(groups = {"nopackage"})
+	public void test() {
+	   m_run = true;
+	}
+
+   @AfterMethod(groups = {"nopackage"})
+   public void after() {
+      assert m_run : "test method was not run";
+   }
+}
diff --git a/src/test/java/org/testng/AssertTest.java b/src/test/java/org/testng/AssertTest.java
new file mode 100644
index 0000000..deefb61
--- /dev/null
+++ b/src/test/java/org/testng/AssertTest.java
@@ -0,0 +1,186 @@
+package org.testng;

+

+import org.testng.annotations.Test;

+import org.testng.collections.Maps;

+import org.testng.collections.Sets;

+

+import java.util.Collection;

+import java.util.HashMap;

+import java.util.Map;

+import java.util.Set;

+

+

+/**

+ * This class/interface

+ */

+public class AssertTest {

+  @Test

+  public void nullObjectArrayAssertEquals() {

+    Object[] expected= null;

+    Object[] actual= null;

+    Assert.assertEquals(actual, expected);

+  }

+

+  @Test

+  public void nullObjectArrayAssertNoOrder() {

+    Object[] expected= null;

+    Object[] actual= null;

+    Assert.assertEqualsNoOrder(actual, expected);

+  }

+

+  @Test

+  public void nullCollectionAssertEquals() {

+    Collection expected = null;

+    Collection actual = null;

+    Assert.assertEquals(actual, expected);

+  }

+

+  @Test

+  public void nullSetAssertEquals() {

+    Set expected = null;

+    Set actual = null;

+    Assert.assertEquals(actual, expected);

+  }

+

+  @Test

+  public void nullMapAssertEquals() {

+    Map expected = null;

+    Map actual = null;

+    Assert.assertEquals(actual, expected);

+  }

+

+  @Test

+  public void setAssertEquals() {

+    Set expected = Sets.newHashSet();

+    Set actual = Sets.newHashSet();

+

+    expected.add(1);

+    expected.add("a");

+    actual.add("a");

+    actual.add(1);

+

+    Assert.assertEquals(actual, expected);

+  }

+

+  @Test

+  public void mapAssertEquals() {

+    Map expected = Maps.newHashMap();

+    Map actual = Maps.newHashMap();

+

+    expected.put(null, "a");

+    expected.put("a", "a");

+    expected.put("b", "c");

+    actual.put("b", "c");

+    actual.put(null, "a");

+    actual.put("a", "a");

+

+    Assert.assertEquals(actual, expected);

+  }

+

+  @Test

+  public void oneNullMapAssertEquals() {

+    Map expected = Maps.newHashMap();

+    Map actual = null;

+    try {

+      Assert.assertEquals(actual, expected);

+      Assert.fail("AssertEquals didn't fail");

+    }

+    catch (AssertionError error) {

+      //do nothing

+    }

+  }

+

+  @Test

+  public void oneNullSetAssertEquals() {

+    Set expected = null;

+    Set actual = Sets.newHashSet();

+    try {

+      Assert.assertEquals(actual, expected);

+      Assert.fail("AssertEquals didn't fail");

+    }

+    catch (AssertionError error) {

+      //do nothing

+    }

+  }

+

+  @Test(expectedExceptions = AssertionError.class)

+  public void assertEqualsMapShouldFail() {

+    Map<String, String> mapActual = new HashMap<String, String>() {{

+      put("a","1");

+    }};

+    Map<String, String> mapExpected = new HashMap<String, String>() {{

+      put("a","1");

+      put("b","2");

+    }};

+

+    Assert.assertEquals(mapActual, mapExpected);

+  }

+

+  @Test(expectedExceptions = AssertionError.class)

+  public void assertEqualsSymmetricScalar() {

+    Assert.assertEquals(new Asymmetric(42, 'd'), new Contrived(42));

+  }

+

+  @Test(expectedExceptions = AssertionError.class)

+  public void assertEqualsSymmetricArrays() {

+    Object[] actual = {1, new Asymmetric(42, 'd'), "inDay"};

+    Object[] expected = {1, new Contrived(42), "inDay"};

+    Assert.assertEquals(actual, expected);

+  }

+

+  class Contrived {

+

+    int integer;

+

+    Contrived(int integer){

+      this.integer = integer;

+    }

+

+    @Override

+    public boolean equals(Object o) {

+      if (this == o) return true;

+      if (!(o instanceof Contrived)) return false;

+

+      Contrived contrived = (Contrived) o;

+

+      if (integer != contrived.integer) return false;

+

+      return true;

+    }

+

+    @Override

+    public int hashCode() {

+      return integer;

+    }

+  }

+

+  class Asymmetric extends Contrived {

+

+      char character;

+

+      Asymmetric(int integer, char character) {

+          super(integer);

+          this.character = character;

+      }

+

+      @Override

+      public boolean equals(Object o) {

+          if (this == o) return true;

+          if (!(o instanceof Asymmetric)) return false;

+          if (!super.equals(o)) return false;

+

+          Asymmetric that = (Asymmetric) o;

+

+          if (character != that.character) return false;

+

+          return true;

+      }

+

+      @Override

+      public int hashCode() {

+          int result = super.hashCode();

+          result = 31 * result + (int) character;

+          return result;

+      }

+  }

+}

diff --git a/src/test/java/org/testng/internal/MethodInstanceTest.java b/src/test/java/org/testng/internal/MethodInstanceTest.java
new file mode 100644
index 0000000..f71bc6f
--- /dev/null
+++ b/src/test/java/org/testng/internal/MethodInstanceTest.java
@@ -0,0 +1,566 @@
+package org.testng.internal;

+

+import java.lang.reflect.Method;

+import java.util.ArrayList;

+import java.util.Collections;

+import java.util.List;

+import java.util.Map;

+

+import org.testng.Assert;

+import org.testng.IClass;

+import org.testng.IRetryAnalyzer;

+import org.testng.ITestClass;

+import org.testng.ITestNGMethod;

+import org.testng.annotations.Test;

+import org.testng.xml.XmlClass;

+import org.testng.xml.XmlInclude;

+import org.testng.xml.XmlTest;

+

+/**

+ * Unit tests for {@link MethodInstance}.

+ *

+ * @author Andreas Kluth

+ */

+public class MethodInstanceTest {

+

+  public static void main(String[] args) {

+    new MethodInstanceTest().sortByIndexSatisfiesContract();

+  }

+

+  @Test

+  public void sortByIndexSatisfiesContract() {

+

+    // Build a list of entries imposing the same behavior as the live suite, reduced to the

+    // minimum to create the same condition.

+    List<MethodInstance> methods = new ArrayList<>();

+    methods.add(buildMethodInstance("unittests", "StateTest", 1, "aCategorization"));

+    methods.add(buildMethodInstance("unittests", "StateTest", 1, "bCategorization"));

+    methods.add(buildMethodInstance("unittests", "StateTest", 1, "cCategorization"));

+    methods.add(buildMethodInstance("unittests", "StateTest", 1, "dCategorization"));

+    methods.add(buildMethodInstance("unittests", "StateTest", 1, "eCategorization"));

+    methods.add(buildMethodInstance("unittests", "StateTest", 1, "fCategorization"));

+    methods.add(buildMethodInstance("unittests", "StatusTest", 2, "aStatus"));

+    methods.add(buildTestNgFactoryMethodInstance("unittests"));

+    methods.add(buildTestNgFactoryMethodInstance("unittests"));

+    methods.add(buildTestNgFactoryMethodInstance("unittests"));

+    methods.add(buildTestNgFactoryMethodInstance("unittests"));

+    methods.add(buildTestNgFactoryMethodInstance("unittests"));

+    methods.add(buildTestNgFactoryMethodInstance("unittests"));

+    methods.add(buildTestNgFactoryMethodInstance("unittests"));

+    methods.add(buildTestNgFactoryMethodInstance("unittests"));

+    methods.add(buildTestNgFactoryMethodInstance("unittests"));

+    methods.add(buildTestNgFactoryMethodInstance("unittests"));

+    methods.add(buildMethodInstance("unittests", "ChangeTest", 3, "aChangeTest"));

+    methods.add(buildMethodInstance("unittests", "ChangeTest", 3, "bChangeTest"));

+    methods.add(buildMethodInstance("unittests", "ChangeTest", 3, "cChangeTest"));

+    methods.add(buildMethodInstance("unittests", "ChangeTest", 3, "dChangeTest"));

+    methods.add(buildMethodInstance("unittests", "ChangeTest", 3, "eChangeTest"));

+    methods.add(buildMethodInstance("unittests", "ChangeTest", 3, "fChangeTest"));

+    methods.add(buildMethodInstance("unittests", "ChangeTest", 3, "gChangeTest"));

+    methods.add(buildMethodInstance("unittests", "ChangeTest", 3, "eChangeTest"));

+    methods.add(buildMethodInstance("unittests", "ChangeTest", 3, "hChangeTest"));

+    methods.add(buildMethodInstance("unittests", "ChangeTest", 3, "iChangeTest"));

+    methods.add(buildMethodInstance("unittests", "IdentifierClassTest", 4, "aIdentifier"));

+    methods.add(buildMethodInstance("unittests", "IdentifierClassTest", 4, "bIdentifier"));

+    methods.add(buildMethodInstance("unittests", "StatisticsTest", 0, "aStatistics"));

+    methods.add(buildMethodInstance("unittests", "StatisticsTest", 0, "bStatistics"));

+    methods.add(buildMethodInstance("unittests", "StatisticsTest", 0, "cStatistics"));

+

+    try {

+      Collections.sort(methods, MethodInstance.SORT_BY_INDEX);

+    }

+    catch (IllegalArgumentException ex) {

+      Assert.fail("Comparison method violates its general contract!");

+    }

+  }

+

+  private MethodInstance buildTestNgFactoryMethodInstance(String xmlTestName) {

+    TestClassStub testClass = new TestClassStub(new XmlTestStub(xmlTestName), null);

+    return new MethodInstance(new TestNGMethodStub(null, testClass));

+  }

+

+  private MethodInstance buildMethodInstance(String xmlTestName, String xmlClassName, int xmlClassIndex, String methodName) {

+    TestClassStub testClass = new TestClassStub(new XmlTestStub(xmlTestName), new XmlClassStub(xmlClassName, xmlClassIndex));

+    return new MethodInstance(new TestNGMethodStub(methodName, testClass));

+  }

+

+  public static class XmlClassStub extends XmlClass {

+    private static final long serialVersionUID = 1L;

+    private int index;

+    private String name;

+

+    public XmlClassStub(String name, int index) {

+      this.name = name;

+      this.index = index;

+    }

+

+    @Override

+    public String getName() {

+      return name;

+    }

+

+    @Override

+    public int getIndex() {

+      return index;

+    }

+

+    @Override

+    public List<XmlInclude> getIncludedMethods() {

+      return Collections.emptyList();

+    }

+  }

+

+  public static class XmlTestStub extends XmlTest {

+    private static final long serialVersionUID = 1L;

+    private String name;

+

+    public XmlTestStub(String name) {

+      this.name = name;

+    }

+

+    @Override

+    public String getName() {

+      return name;

+    }

+  }

+

+  public static class TestClassStub implements ITestClass {

+    private static final long serialVersionUID = 1L;

+

+    private XmlTest xmlTest;

+    private XmlClass xmlClass;

+

+    public TestClassStub(XmlTest xmlTest, XmlClass xmlClass) {

+      this.xmlTest = xmlTest;

+      this.xmlClass = xmlClass;

+    }

+

+    @Override

+    public String getName() {

+      return null;

+    }

+

+    @Override

+    public XmlTest getXmlTest() {

+      return xmlTest;

+    }

+

+    @Override

+    public XmlClass getXmlClass() {

+      return xmlClass;

+    }

+

+    @Override

+    public String getTestName() {

+      return null;

+    }

+

+    @SuppressWarnings("rawtypes")

+    @Override

+    public Class getRealClass() {

+      return null;

+    }

+

+    @Override

+    public void addInstance(Object instance) {

+    }

+

+    @Override

+    public Object[] getInstances(boolean reuse) {

+      return null;

+    }

+

+    @Override

+    public long[] getInstanceHashCodes() {

+      return null;

+    }

+

+    @Override

+    public int getInstanceCount() {

+      return 0;

+    }

+

+    @Override

+    public ITestNGMethod[] getTestMethods() {

+      return null;

+    }

+

+    @Override

+    public ITestNGMethod[] getBeforeTestMethods() {

+      return null;

+    }

+

+    @Override

+    public ITestNGMethod[] getAfterTestMethods() {

+      return null;

+    }

+

+    @Override

+    public ITestNGMethod[] getBeforeClassMethods() {

+      return null;

+    }

+

+    @Override

+    public ITestNGMethod[] getAfterClassMethods() {

+      return null;

+    }

+

+    @Override

+    public ITestNGMethod[] getBeforeSuiteMethods() {

+      return null;

+    }

+

+    @Override

+    public ITestNGMethod[] getAfterSuiteMethods() {

+      return null;

+    }

+

+    @Override

+    public ITestNGMethod[] getBeforeTestConfigurationMethods() {

+      return null;

+    }

+

+    @Override

+    public ITestNGMethod[] getAfterTestConfigurationMethods() {

+      return null;

+    }

+

+    @Override

+    public ITestNGMethod[] getBeforeGroupsMethods() {

+      return null;

+    }

+

+    @Override

+    public ITestNGMethod[] getAfterGroupsMethods() {

+      return null;

+    }

+

+  }

+

+  public static class TestNGMethodStub implements ITestNGMethod {

+    private static final long serialVersionUID = 1L;

+    private TestClassStub testClassStub;

+    private String methodName;

+

+    public TestNGMethodStub(String methodName, TestClassStub testClassStub) {

+      this.methodName = methodName;

+      this.testClassStub = testClassStub;

+

+    }

+

+    @Override

+    public ITestNGMethod clone() {

+      return (TestNGMethodStub) this;

+    };

+

+    @Override

+    public int compareTo(Object o) {

+      return 0;

+    }

+

+    @SuppressWarnings("rawtypes")

+    @Override

+    public Class getRealClass() {

+      return null;

+    }

+

+    @Override

+    public ITestClass getTestClass() {

+      return testClassStub;

+    }

+

+    @Override

+    public void setTestClass(ITestClass cls) {

+    }

+

+    @Override

+    public Method getMethod() {

+      return null;

+    }

+

+    @Override

+    public String getMethodName() {

+      return methodName;

+    }

+

+    @Override

+    public Object[] getInstances() {

+      return null;

+    }

+

+    @Override

+    public Object getInstance() {

+      return null;

+    }

+

+    @Override

+    public long[] getInstanceHashCodes() {

+      return null;

+    }

+

+    @Override

+    public String[] getGroups() {

+      return null;

+    }

+

+    @Override

+    public String[] getGroupsDependedUpon() {

+      return null;

+    }

+

+    @Override

+    public String getMissingGroup() {

+      return null;

+    }

+

+    @Override

+    public void setMissingGroup(String group) {

+    }

+

+    @Override

+    public String[] getBeforeGroups() {

+      return null;

+    }

+

+    @Override

+    public String[] getAfterGroups() {

+      return null;

+    }

+

+    @Override

+    public String[] getMethodsDependedUpon() {

+      return null;

+    }

+

+    @Override

+    public void addMethodDependedUpon(String methodName) {

+    }

+

+    @Override

+    public boolean isTest() {

+      return false;

+    }

+

+    @Override

+    public boolean isBeforeMethodConfiguration() {

+      return false;

+    }

+

+    @Override

+    public boolean isAfterMethodConfiguration() {

+      return false;

+    }

+

+    @Override

+    public boolean isBeforeClassConfiguration() {

+      return false;

+    }

+

+    @Override

+    public boolean isAfterClassConfiguration() {

+      return false;

+    }

+

+    @Override

+    public boolean isBeforeSuiteConfiguration() {

+      return false;

+    }

+

+    @Override

+    public boolean isAfterSuiteConfiguration() {

+      return false;

+    }

+

+    @Override

+    public boolean isBeforeTestConfiguration() {

+      return false;

+    }

+

+    @Override

+    public boolean isAfterTestConfiguration() {

+      return false;

+    }

+

+    @Override

+    public boolean isBeforeGroupsConfiguration() {

+      return false;

+    }

+

+    @Override

+    public boolean isAfterGroupsConfiguration() {

+      return false;

+    }

+

+    @Override

+    public long getTimeOut() {

+      return 0;

+    }

+

+    @Override

+    public void setTimeOut(long timeOut) {

+    }

+

+    @Override

+    public int getInvocationCount() {

+      return 0;

+    }

+

+    @Override

+    public int getTotalInvocationCount() {

+      return 0;

+    }

+

+    @Override

+    public void setInvocationCount(int count) {

+    }

+

+    @Override

+    public int getSuccessPercentage() {

+      return 0;

+    }

+

+    @Override

+    public String getId() {

+      return null;

+    }

+

+    @Override

+    public void setId(String id) {

+    }

+

+    @Override

+    public long getDate() {

+      return 0;

+    }

+

+    @Override

+    public void setDate(long date) {

+    }

+

+    @Override

+    public boolean canRunFromClass(IClass testClass) {

+      return false;

+    }

+

+    @Override

+    public boolean isAlwaysRun() {

+      return false;

+    }

+

+    @Override

+    public int getThreadPoolSize() {

+      return 0;

+    }

+

+    @Override

+    public void setThreadPoolSize(int threadPoolSize) {

+    }

+

+    @Override

+    public boolean getEnabled() {

+      return false;

+    }

+

+    @Override

+    public String getDescription() {

+      return null;

+    }

+

+    @Override

+    public void setDescription(String description) {

+    }

+

+    @Override

+    public void incrementCurrentInvocationCount() {

+    }

+

+    @Override

+    public int getCurrentInvocationCount() {

+      return 0;

+    }

+

+    @Override

+    public void setParameterInvocationCount(int n) {

+    }

+

+    @Override

+    public int getParameterInvocationCount() {

+      return 0;

+    }

+

+    @Override

+    public IRetryAnalyzer getRetryAnalyzer() {

+      return null;

+    }

+

+    @Override

+    public void setRetryAnalyzer(IRetryAnalyzer retryAnalyzer) {

+    }

+

+    @Override

+    public boolean skipFailedInvocations() {

+      return false;

+    }

+

+    @Override

+    public void setSkipFailedInvocations(boolean skip) {

+    }

+

+    @Override

+    public long getInvocationTimeOut() {

+      return 0;

+    }

+

+    @Override

+    public boolean ignoreMissingDependencies() {

+      return false;

+    }

+

+    @Override

+    public void setIgnoreMissingDependencies(boolean ignore) {

+    }

+

+    @Override

+    public List<Integer> getInvocationNumbers() {

+      return null;

+    }

+

+    @Override

+    public void setInvocationNumbers(List<Integer> numbers) {

+    }

+

+    @Override

+    public void addFailedInvocationNumber(int number) {

+    }

+

+    @Override

+    public List<Integer> getFailedInvocationNumbers() {

+      return null;

+    }

+

+    @Override

+    public int getPriority() {

+      return 0;

+    }

+

+    @Override

+    public void setPriority(int priority) {

+    }

+

+    @Override

+    public XmlTest getXmlTest() {

+      return null;

+    }

+

+    @Override

+    public ConstructorOrMethod getConstructorOrMethod() {

+      return null;

+    }

+

+    @Override

+    public Map<String, String> findMethodParameters(XmlTest test) {

+      return null;

+    }

+  }

+

+}

diff --git a/src/test/java/org/testng/internal/UtilsTest.java b/src/test/java/org/testng/internal/UtilsTest.java
new file mode 100644
index 0000000..47d12ed
--- /dev/null
+++ b/src/test/java/org/testng/internal/UtilsTest.java
@@ -0,0 +1,46 @@
+package org.testng.internal;
+
+import java.util.List;
+
+import org.testng.annotations.Test;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static org.testng.Assert.assertEquals;
+import static org.testng.internal.Utils.join;
+
+/**
+ * Unit tests for {@link Utils}.
+ *
+ * @author Tomas Pollak
+ */
+public class UtilsTest {
+	private static final char INVALID_CHAR = 0xFFFE;
+	private static final char REPLACEMENT_CHAR = 0xFFFD;
+
+	@Test
+	public void escapeUnicode() {
+		assertEquals(Utils.escapeUnicode("test"), "test");
+		assertEquals(Utils.escapeUnicode(String.valueOf(INVALID_CHAR)),
+				String.valueOf(REPLACEMENT_CHAR));
+
+	}
+
+	@Test
+	public void createEmptyStringWhenJoiningEmptyListWithJoin() throws Exception {
+		List<String> emptyList = emptyList();
+		assertEquals("", join(emptyList, ","));
+	}
+
+	@Test
+	public void joinTwoStringsWithJoinStrings() throws Exception {
+		List<String> twoStrings = asList("one", "two");
+		assertEquals("one,two", Utils.join(twoStrings, ","));
+	}
+
+	@Test
+	public void createEmptyStringWhenJoiningEmptyListWithJoinStrings() throws Exception {
+		List<String> emptyList = emptyList();
+		assertEquals("", Utils.join(emptyList, ","));
+	}
+}
diff --git a/src/test/java/org/testng/internal/conflistener/FailingAfterClass.java b/src/test/java/org/testng/internal/conflistener/FailingAfterClass.java
new file mode 100644
index 0000000..ad3029d
--- /dev/null
+++ b/src/test/java/org/testng/internal/conflistener/FailingAfterClass.java
@@ -0,0 +1,17 @@
+package org.testng.internal.conflistener;

+

+import org.testng.annotations.AfterClass;

+import org.testng.annotations.Test;

+

+/**

+ * This class/interface does XXX

+ */

+public class FailingAfterClass {

+  @AfterClass

+  public void failingAfterClass() {

+    throw new RuntimeException("expected @AfterClass failure");

+  }

+

+  @Test

+  public void testBeforeFailingAfterClass() {}

+}

diff --git a/src/test/java/org/testng/internal/conflistener/FailingAfterSuite.java b/src/test/java/org/testng/internal/conflistener/FailingAfterSuite.java
new file mode 100644
index 0000000..d96aa47
--- /dev/null
+++ b/src/test/java/org/testng/internal/conflistener/FailingAfterSuite.java
@@ -0,0 +1,19 @@
+package org.testng.internal.conflistener;

+

+import org.testng.annotations.AfterSuite;

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class FailingAfterSuite {

+  @AfterSuite(alwaysRun=true)

+  public void afterSuite() {

+    throw new RuntimeException("Test exception");

+  }

+

+  @Test

+  public void dummytest() {

+  }

+}

diff --git a/src/test/java/org/testng/internal/conflistener/FailingAfterTest.java b/src/test/java/org/testng/internal/conflistener/FailingAfterTest.java
new file mode 100644
index 0000000..60e591d
--- /dev/null
+++ b/src/test/java/org/testng/internal/conflistener/FailingAfterTest.java
@@ -0,0 +1,23 @@
+package org.testng.internal.conflistener;

+

+import org.testng.annotations.AfterTest;

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class FailingAfterTest {

+  @AfterTest(alwaysRun=true)

+  public void afterTest() {

+    throw new RuntimeException("Test exception");

+  }

+

+  @AfterTest

+  public void skippedAfterTest() {

+  }

+

+  @Test

+  public void dummytest() {

+  }

+}

diff --git a/src/test/java/org/testng/internal/conflistener/FailingBeforeClass.java b/src/test/java/org/testng/internal/conflistener/FailingBeforeClass.java
new file mode 100644
index 0000000..e6d2929
--- /dev/null
+++ b/src/test/java/org/testng/internal/conflistener/FailingBeforeClass.java
@@ -0,0 +1,17 @@
+package org.testng.internal.conflistener;

+

+import org.testng.annotations.BeforeClass;

+import org.testng.annotations.Test;

+

+/**

+ * This class/interface does XXX

+ */

+public class FailingBeforeClass {

+  @BeforeClass

+  public void failingBeforeClass() {

+    throw new RuntimeException("expected @BeforeClass failure");

+  }

+

+  @Test

+  public void testAfterFailingBeforeClass() {}

+}

diff --git a/src/test/java/org/testng/internal/conflistener/FailingBeforeSuite.java b/src/test/java/org/testng/internal/conflistener/FailingBeforeSuite.java
new file mode 100644
index 0000000..b629022
--- /dev/null
+++ b/src/test/java/org/testng/internal/conflistener/FailingBeforeSuite.java
@@ -0,0 +1,19 @@
+package org.testng.internal.conflistener;

+

+import org.testng.annotations.BeforeSuite;

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class FailingBeforeSuite {

+  @BeforeSuite

+  public void beforeSuite() {

+    throw new RuntimeException("Test exception");

+  }

+

+  @Test

+  public void dummytest() {

+  }

+}

diff --git a/src/test/java/org/testng/internal/conflistener/FailingBeforeTest.java b/src/test/java/org/testng/internal/conflistener/FailingBeforeTest.java
new file mode 100644
index 0000000..8fcb0d4
--- /dev/null
+++ b/src/test/java/org/testng/internal/conflistener/FailingBeforeTest.java
@@ -0,0 +1,24 @@
+package org.testng.internal.conflistener;

+

+import org.testng.annotations.BeforeSuite;

+import org.testng.annotations.BeforeTest;

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class FailingBeforeTest {

+  @BeforeSuite

+  public void passingBeforeSuite() {

+  }

+

+  @BeforeTest

+  public void beforeTest() {

+    throw new RuntimeException("Test exception");

+  }

+

+  @Test

+  public void dummytest() {

+  }

+}

diff --git a/src/test/java/org/testng/internal/conflistener/testng.xml b/src/test/java/org/testng/internal/conflistener/testng.xml
new file mode 100644
index 0000000..faf2b37
--- /dev/null
+++ b/src/test/java/org/testng/internal/conflistener/testng.xml
@@ -0,0 +1,30 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

+<suite name="Configuration failures" verbose="2">

+	<!--

+  <test name="Suite Configuration">

+		<classes>

+			<class name="org.testng.internal.conflistener.FailingBeforeSuite" />

+			<class name="org.testng.internal.conflistener.FailingAfterSuite" />

+		</classes>

+  </test>

+	-->

+	<test name="Test Configuration">

+		<classes>

+			<class name="org.testng.internal.conflistener.FailingBeforeTest" />

+			<class name="org.testng.internal.conflistener.FailingAfterTest" />

+		</classes>

+  </test>

+	<test name="Class Configuration">

+		<classes>

+			<class name="org.testng.internal.conflistener.FailingBeforeClass" />

+      <class name="org.testng.internal.conflistener.FailingAfterClass" />

+    </classes>

+  </test>

+  <!--

+	<test name="X Configuration">

+		<classes>

+			<class name="org.testng.internal.conflistener." />

+		</classes>

+  </test>

+	-->

+</suite>
\ No newline at end of file
diff --git a/src/test/java/org/testng/internal/invokers/InvokedMethodListenerSubtypeTest.java b/src/test/java/org/testng/internal/invokers/InvokedMethodListenerSubtypeTest.java
new file mode 100644
index 0000000..f154930
--- /dev/null
+++ b/src/test/java/org/testng/internal/invokers/InvokedMethodListenerSubtypeTest.java
@@ -0,0 +1,59 @@
+package org.testng.internal.invokers;
+
+import org.testng.Assert;
+import org.testng.IInvokedMethod;
+import org.testng.IInvokedMethodListener;
+import org.testng.IInvokedMethodListener2;
+import org.testng.ITestContext;
+import org.testng.ITestResult;
+import org.testng.annotations.Test;
+
+@Test
+public class InvokedMethodListenerSubtypeTest {
+
+  @Test
+  public void testFromListenerUsingSimpleListenerInstance() {
+    final IInvokedMethodListener simpleListenerInstance = new SimpleInvokedMethodListenerDummy();
+
+    InvokedMethodListenerSubtype listenerSubtype =
+        InvokedMethodListenerSubtype.fromListener(simpleListenerInstance);
+
+    Assert.assertEquals(listenerSubtype, InvokedMethodListenerSubtype.SIMPLE_LISTENER);
+  }
+
+  @Test
+  public void testFromListenerUsingExtendedListenerInstance() {
+    IInvokedMethodListener2 extendedListenerInstance = new ExtendedInvokedMethodListenerDummy();
+
+    InvokedMethodListenerSubtype listenerSubtype =
+        InvokedMethodListenerSubtype.fromListener(extendedListenerInstance);
+
+    Assert.assertEquals(listenerSubtype, InvokedMethodListenerSubtype.EXTENDED_LISTENER);
+  }
+
+  static class SimpleInvokedMethodListenerDummy implements IInvokedMethodListener {
+
+    public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
+    }
+
+    public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
+    }
+  }
+
+  static class ExtendedInvokedMethodListenerDummy implements IInvokedMethodListener2 {
+
+    public void beforeInvocation(IInvokedMethod method, ITestResult testResult,
+                                 ITestContext context) {
+    }
+
+    public void afterInvocation(IInvokedMethod method, ITestResult testResult,
+                                ITestContext context) {
+    }
+
+    public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
+    }
+
+    public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
+    }
+  }
+}
diff --git a/src/test/java/org/testng/remote/strprotocol/TestResultMessageTest.java b/src/test/java/org/testng/remote/strprotocol/TestResultMessageTest.java
new file mode 100644
index 0000000..67da5cf
--- /dev/null
+++ b/src/test/java/org/testng/remote/strprotocol/TestResultMessageTest.java
@@ -0,0 +1,54 @@
+package org.testng.remote.strprotocol;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class TestResultMessageTest {
+
+	private TestResultMessage testResultMessage = new TestResultMessage(0,
+			"suiteName", "testName", "className", "methodName",
+			"testDescriptor", "testinstanceName", new String[] {}, 0, 0,
+			"stackTrace", 1, 0);
+
+	@DataProvider(name = "testGen")
+	private Object[][] getP() {
+		return new Object[][] {
+				{ null, new Class[] { null }, Arrays.asList() },
+				{ new Object[] { new byte[] { 1 } },
+						new Class[] { byte[].class }, Arrays.asList("[1]") },
+				{ new Object[] { new short[] { 1 } },
+						new Class[] { short[].class }, Arrays.asList("[1]") },
+				{ new Object[] { new long[] { 1 } },
+						new Class[] { long[].class }, Arrays.asList("[1]") },
+				{ new Object[] { new int[] { 1, 2, 3 } },
+						new Class[] { int[].class }, Arrays.asList("[1,2,3]") },
+				{ new Object[] { new boolean[] { true, false } },
+						new Class[] { boolean[].class },
+						Arrays.asList("[true,false]") },
+				{ new Object[] { new char[] { 'a', 'b', 'c' } },
+						new Class[] { int[].class }, Arrays.asList("[a,b,c]") },
+				{ new Object[] { new float[] { 1.1f, 2.2f, 3.3f } },
+						new Class[] { float[].class },
+						Arrays.asList("[1.1,2.2,3.3]") },
+				{ new Object[] { new double[] { 1.1, 2.2, 3.3 } },
+						new Class[] { double[].class },
+						Arrays.asList("[1.1,2.2,3.3]") },
+				{ new Object[] { new Object[] { "this is a string", false } },
+						new Class[] { String.class, Boolean.class },
+						Arrays.asList("[this is a string,false]") },
+				{ new Object[] { new Object[] { null, "" } },
+						new Class[] { String.class, Boolean.class },
+						Arrays.asList("[null,\"\"]") }, };
+	}
+
+	@Test(dataProvider = "testGen")
+	public void toStringTest(Object[] objects, Class<?>[] objectClasses,
+			List<String> expectedResults) throws Exception {
+		String[] results = testResultMessage.toString(objects, objectClasses);
+		Assert.assertEquals(Arrays.asList(results), expectedResults);
+	}
+}
diff --git a/src/test/java/org/testng/xml/SuiteXmlParserTest.java b/src/test/java/org/testng/xml/SuiteXmlParserTest.java
new file mode 100644
index 0000000..9faa9c5
--- /dev/null
+++ b/src/test/java/org/testng/xml/SuiteXmlParserTest.java
@@ -0,0 +1,41 @@
+package org.testng.xml;
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.FileInputStream;
+
+import static test.SimpleBaseTest.getPathToResource;
+
+public class SuiteXmlParserTest {
+
+    private static final File PARENT = new File(getPathToResource("xml"));
+
+    @DataProvider
+    private static Object[][] dp() {
+        return new Object[][] {
+                { "goodWithDoctype.xml", true },
+                { "goodWithoutDoctype.xml", true },
+                { "badWithDoctype.xml", false }, // TestNGException -> SAXParseException
+                { "badWithoutDoctype.xml", false } // NullPointerException
+        };
+    }
+
+    @Test(dataProvider = "dp")
+    public void testParse(String fileName, boolean shouldWork) {
+        SuiteXmlParser parser = new SuiteXmlParser();
+
+        try (FileInputStream stream = new FileInputStream(new File(PARENT, fileName))) {
+            parser.parse(fileName, stream, false);
+            if (!shouldWork) {
+                Assert.fail("Parsing of " + fileName + " is supposed to fail");
+            }
+        } catch (Exception e) {
+            if (shouldWork) {
+                Assert.fail("Parsing of " + fileName + " is supposed to work");
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/test/BaseDistributedTest.java b/src/test/java/test/BaseDistributedTest.java
new file mode 100644
index 0000000..2dcfa66
--- /dev/null
+++ b/src/test/java/test/BaseDistributedTest.java
@@ -0,0 +1,49 @@
+package test;
+
+import org.testng.Assert;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+public class BaseDistributedTest {
+  private boolean m_verbose = false;
+
+  protected void verifyTests(String title, String[] exp, Map found) {
+    Map expected = new HashMap();
+    for (String element : exp) {
+      expected.put(element, element);
+    }
+
+    Assert.assertEquals(found.size(), expected.size(),
+        "Verification for " + title + " tests failed:");
+
+    for (Object o : expected.values()) {
+      String name = (String) o;
+      if (null == found.get(name)) {
+        dumpMap("Expected", expected);
+        dumpMap("Found", found);
+      }
+
+      Assert.assertNotNull(found.get(name),
+              "Expected to find method " + name + " in " + title
+                      + " but didn't find it.");
+    }
+  }
+
+  protected void dumpMap(String title, Map<?, ?> m) {
+    if (m_verbose) {
+      System.out.println("==== " + title);
+      for (Map.Entry<?, ?> entry : m.entrySet()) {
+        ppp(entry.getKey() + "  => " + entry.getValue());
+      }
+    }
+  }
+  private void ppp(String s) {
+    if (m_verbose) {
+      System.out.println("[BaseDistributedTest] " + s);
+    }
+  }
+
+
+}
diff --git a/src/test/java/test/BaseLogTest.java b/src/test/java/test/BaseLogTest.java
new file mode 100644
index 0000000..3dad8df
--- /dev/null
+++ b/src/test/java/test/BaseLogTest.java
@@ -0,0 +1,28 @@
+package test;
+
+import org.testng.annotations.BeforeSuite;
+import org.testng.collections.Lists;
+
+import java.util.List;
+
+/**
+ * Base class for tests that need to log methods as they get called.
+ *
+ * @author cbeust
+ */
+public class BaseLogTest {
+  private static List<String> m_log;
+
+  @BeforeSuite
+  public void bc() {
+    m_log = Lists.newArrayList();
+  }
+
+  public static void log(String s) {
+    m_log.add(s);
+  }
+
+  public static List<String> getLog() {
+    return m_log;
+  }
+}
diff --git a/src/test/java/test/BaseTest.java b/src/test/java/test/BaseTest.java
new file mode 100644
index 0000000..81dc6f5
--- /dev/null
+++ b/src/test/java/test/BaseTest.java
@@ -0,0 +1,539 @@
+package test;

+

+

+import java.io.BufferedReader;

+import java.io.File;

+import java.io.FileReader;

+import java.io.IOException;

+import java.util.ArrayList;

+import java.util.Arrays;

+import java.util.Collection;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import java.util.Set;

+import java.util.regex.Pattern;

+

+import org.testng.Assert;

+import org.testng.IClassListener;

+import org.testng.IInvokedMethodListener;

+import org.testng.ISuite;

+import org.testng.ITestResult;

+import org.testng.ITestRunnerFactory;

+import org.testng.SuiteRunner;

+import org.testng.TestListenerAdapter;

+import org.testng.TestRunner;

+import org.testng.annotations.BeforeMethod;

+import org.testng.collections.Lists;

+import org.testng.internal.Configuration;

+import org.testng.internal.IConfiguration;

+import org.testng.reporters.JUnitXMLReporter;

+import org.testng.reporters.TestHTMLReporter;

+import org.testng.xml.XmlClass;

+import org.testng.xml.XmlInclude;

+import org.testng.xml.XmlMethodSelector;

+import org.testng.xml.XmlPackage;

+import org.testng.xml.XmlSuite;

+import org.testng.xml.XmlTest;

+

+/**

+ * Base class for tests

+ *

+ * @author Cedric Beust, May 5, 2004

+ *

+ */

+public class BaseTest extends BaseDistributedTest {

+  private static final String m_outputDirectory= "test-output-tests";

+

+  private XmlSuite m_suite= null;

+  private ITestRunnerFactory m_testRunnerFactory;

+  private IConfiguration m_configuration;

+

+  private Integer m_verbose = null;

+

+  public BaseTest() {

+    m_testRunnerFactory= new InternalTestRunnerFactory(this);

+    m_configuration = new Configuration();

+  }

+

+  private IConfiguration getConfiguration() {

+    return m_configuration;

+  }

+

+  protected void setDebug() {

+    getTest().setVerbose(9);

+  }

+

+  protected void setParallel(XmlSuite.ParallelMode parallel) {

+    getTest().setParallel(parallel);

+  }

+

+  protected void setVerbose(int n) {

+    m_verbose = n;

+  }

+

+  protected void setTestTimeOut(long n) {

+      getTest().setTimeOut(n);

+  }

+

+  protected void setSuiteTimeOut(long n) {

+      m_suite.setTimeOut(Long.toString(n));

+  }

+

+  protected void setJUnit(boolean f) {

+    getTest().setJUnit(f);

+  }

+

+  protected void setThreadCount(int count) {

+    getTest().getSuite().setThreadCount(count);

+  }

+

+  private Map<Long, XmlTest> m_tests= new HashMap<>();

+  private Map<Long, Map> m_passedTests= new HashMap<>();

+  private Map<Long, Map> m_failedTests= new HashMap<>();

+  private Map<Long, Map> m_skippedTests= new HashMap<>();

+  private Map<Long, XmlTest> m_testConfigs= new HashMap<>();

+  private Map<Long, Map> m_passedConfigs= new HashMap<>();

+  private Map<Long, Map> m_failedConfigs= new HashMap<>();

+  private Map<Long, Map> m_skippedConfigs= new HashMap<>();

+  private Map<Long, Map> m_failedButWithinSuccessPercentageTests= new HashMap<>();

+

+  protected Map<String, List<ITestResult>> getTests(Map<Long, Map> map) {

+    Map<String, List<ITestResult>> result= map.get(getId());

+    if(null == result) {

+      result= new HashMap<>();

+      map.put(getId(), result);

+    }

+

+    return result;

+  }

+

+  protected XmlTest getTest() {

+    return m_tests.get(getId());

+  }

+

+  protected void setTests(Map<Long, Map> map, Map m) {

+    map.put(getId(), m);

+  }

+

+  public Map<String, List<ITestResult>> getFailedTests() {

+    return getTests(m_failedTests);

+  }

+

+  public Map<String, List<ITestResult>> getFailedButWithinSuccessPercentageTests() {

+    return getTests(m_failedButWithinSuccessPercentageTests);

+  }

+

+  public Map<String, List<ITestResult>> getPassedTests() {

+    return getTests(m_passedTests);

+  }

+

+  public Map<String, List<ITestResult>> getSkippedTests() {

+    return getTests(m_skippedTests);

+  }

+

+  public Map<String, List<ITestResult>> getFailedConfigs() {

+    return getTests(m_failedConfigs);

+  }

+

+  public Map<String, List<ITestResult>> getPassedConfigs() {

+    return getTests(m_passedConfigs);

+  }

+

+  public Map<String, List<ITestResult>> getSkippedConfigs() {

+    return getTests(m_skippedConfigs);

+  }

+

+  public void setSkippedTests(Map m) {

+    setTests(m_skippedTests, m);

+  }

+

+  public void setPassedTests(Map m) {

+    setTests(m_passedTests, m);

+  }

+

+  public void setFailedTests(Map m) {

+    setTests(m_failedTests, m);

+  }

+

+  public void setFailedButWithinSuccessPercentageTests(Map m) {

+    setTests(m_failedButWithinSuccessPercentageTests, m);

+  }

+

+  public void setSkippedConfigs(Map m) {

+    setTests(m_skippedConfigs, m);

+  }

+

+  public void setPassedConfigs(Map m) {

+    setTests(m_passedConfigs, m);

+  }

+

+  public void setFailedConfigs(Map m) {

+    setTests(m_failedConfigs, m);

+  }

+

+

+  protected void run() {

+    assert null != getTest() : "Test wasn't set, maybe @Configuration methodSetUp() was never called";

+    setPassedTests(new HashMap());

+    setFailedTests(new HashMap());

+    setSkippedTests(new HashMap());

+    setPassedConfigs(new HashMap());

+    setFailedConfigs(new HashMap());

+    setSkippedConfigs(new HashMap());

+    setFailedButWithinSuccessPercentageTests(new HashMap());

+

+    m_suite.setVerbose(m_verbose != null ? m_verbose : 0);

+    SuiteRunner suite = new SuiteRunner(m_configuration,

+        m_suite, m_outputDirectory, m_testRunnerFactory);

+

+    suite.run();

+  }

+

+  protected void addMethodSelector(String className, int priority) {

+    XmlMethodSelector methodSelector= new XmlMethodSelector();

+    methodSelector.setName(className);

+    methodSelector.setPriority(priority);

+    getTest().getMethodSelectors().add(methodSelector);

+  }

+

+  protected XmlClass addClass(Class<?> cls) {

+    return addClass(cls.getName());

+  }

+

+  protected XmlClass addClass(String className) {

+    XmlClass result= new XmlClass(className);

+    getTest().getXmlClasses().add(result);

+

+    return result;

+  }

+

+  protected void setBeanShellExpression(String expression) {

+    getTest().setBeanShellExpression(expression);

+  }

+

+  protected void addPackage(String pkgName, String[] included, String[] excluded) {

+    XmlPackage pkg= new XmlPackage();

+    pkg.setName(pkgName);

+    pkg.getInclude().addAll(Arrays.asList(included));

+    pkg.getExclude().addAll(Arrays.asList(excluded));

+    getTest().getSuite().getXmlPackages().add(pkg);

+  }

+

+  private XmlClass findClass(String className) {

+    for(XmlClass cl : getTest().getXmlClasses()) {

+      if(cl.getName().equals(className)) {

+        return cl;

+      }

+    }

+

+    XmlClass result= addClass(className);

+

+    return result;

+  }

+

+  public void addIncludedMethod(String className, String m) {

+    XmlClass xmlClass= findClass(className);

+    xmlClass.getIncludedMethods().add(new XmlInclude(m));

+    getTest().getXmlClasses().add(xmlClass);

+  }

+

+  public void addExcludedMethod(String className, String m) {

+    XmlClass xmlClass= findClass(className);

+    xmlClass.getExcludedMethods().add(m);

+    getTest().getXmlClasses().add(xmlClass);

+  }

+

+  public void addIncludedGroup(String g) {

+    getTest().addIncludedGroup(g);

+  }

+

+  public void addExcludedGroup(String g) {

+    getTest().addExcludedGroup(g);

+  }

+

+  public void addMetaGroup(String mg, List<String> l) {

+    getTest().getMetaGroups().put(mg, l);

+  }

+

+  public void addMetaGroup(String mg, String n) {

+    List<String> l= new ArrayList<>();

+    l.add(n);

+    addMetaGroup(mg, l);

+  }

+

+  public void setParameter(String key, String value) {

+    getTest().addParameter(key, value);

+  }

+

+//  @Configuration(beforeTestMethod = true, groups = { "init", "initTest"})

+  @BeforeMethod(groups= { "init", "initTest" })

+  public void methodSetUp() {

+    m_suite= new XmlSuite();

+    m_suite.setName("Internal_suite");

+    XmlTest xmlTest= new XmlTest(m_suite);

+    xmlTest.setName("Internal_test_failures_are_expected");

+    m_tests.put(getId(), xmlTest);

+  }

+

+  private void addTest(Map<String, List<ITestResult>> tests, ITestResult t) {

+    List<ITestResult> l= tests.get(t.getMethod().getMethodName());

+    if(null == l) {

+      l= new ArrayList<>();

+      tests.put(t.getMethod().getMethodName(), l);

+    }

+    l.add(t);

+  }

+

+  public void addPassedTest(ITestResult t) {

+    addTest(getPassedTests(), t);

+  }

+

+  public void addFailedTest(ITestResult t) {

+    addTest(getFailedTests(), t);

+  }

+

+  public void addFailedButWithinSuccessPercentageTest(ITestResult t) {

+    addTest(getFailedButWithinSuccessPercentageTests(), t);

+  }

+

+  public void addSkippedTest(ITestResult t) {

+    addTest(getSkippedTests(), t);

+  }

+

+  public void addPassedConfig(ITestResult t) {

+    addTest(getPassedConfigs(), t);

+  }

+

+  public void addFailedConfig(ITestResult t) {

+    addTest(getFailedConfigs(), t);

+  }

+

+  public void addSkippedConfig(ITestResult t) {

+    addTest(getSkippedConfigs(), t);

+  }

+

+  private void ppp(String s) {

+    System.out.println("[BaseTest " + getId() + "] " + s);

+  }

+

+  protected Long getId() {

+    return 42L;

+//    long result = Thread.currentThread().getId();

+////    System.out.println("RETURNING ID " + result);

+//    return result;

+  }

+

+  public XmlSuite getSuite() {

+    return m_suite;

+  }

+

+  public void setSuite(XmlSuite suite) {

+    m_suite = suite;

+  }

+

+  /**

+   * Used for instanceCount testing, when we need to look inside the

+   * TestResult to count the various SUCCESS/FAIL/FAIL_BUT_OK

+   */

+  protected void verifyResults(Map<String, List<ITestResult>> tests,

+                               int expected,

+                               String message) {

+    if(tests.size() > 0) {

+      Set keys= tests.keySet();

+      Object firstKey= keys.iterator().next();

+      List<ITestResult> passedResult= tests.get(firstKey);

+      int n= passedResult.size();

+      assert n == expected : "Expected " + expected + " " + message + " but found " + n;

+    }

+    else {

+      assert expected == 0 : "Expected " + expected + " " + message + " but found "

+        + tests.size();

+    }

+  }

+

+  protected void dumpResults(String name, Map<String, List<ITestResult>> tests) {

+    ppp("============= " + name);

+    for(Map.Entry<String, List<ITestResult>> entry : tests.entrySet()) {

+      ppp("TEST:" + entry.getKey());

+      List<ITestResult> l= entry.getValue();

+      for(ITestResult tr : l) {

+        ppp("   " + tr);

+      }

+    }

+  }

+

+  protected static void verifyInstanceNames(String title, Map<String, List<ITestResult>> actual,

+      String[] expected)

+  {

+    List<String> actualNames = Lists.newArrayList();

+    for (Map.Entry<String, List<ITestResult>> es : actual.entrySet()) {

+      for (ITestResult tr : es.getValue()) {

+        Object instance = tr.getInstance();

+        actualNames.add(es.getKey() + "#" + (instance != null ? instance.toString() : ""));

+      }

+    }

+    Assert.assertEqualsNoOrder(actualNames.toArray(), expected);

+  }

+

+  protected void verifyPassedTests(String... expectedPassed) {

+    verifyTests("Passed", expectedPassed, getPassedTests());

+  }

+

+  protected void verifyFailedTests(String... expectedFailed) {

+    verifyTests("Failed", expectedFailed, getFailedTests());

+  }

+

+  /**

+     *

+     * @param fileName The filename to parse

+     * @param regexp The regular expression

+     * @param resultLines An out parameter that will contain all the lines

+     * that matched the regexp

+     * @return A List<Integer> containing the lines of all the matches

+     *

+     * Note that the size() of the returned valuewill always be equal to

+     * result.size() at the end of this function.

+     */

+    public static List<Integer> grep(File fileName, String regexp, List<String> resultLines) {

+      List<Integer> resultLineNumbers = new ArrayList<>();

+      BufferedReader fr = null;

+      try {

+        fr = new BufferedReader(new FileReader(fileName));

+        String line = fr.readLine();

+        int currentLine = 0;

+        Pattern p = Pattern.compile(".*" + regexp + ".*");

+

+        while(null != line) {

+  //        ppp("COMPARING " + p + " TO @@@" + line + "@@@");

+          if(p.matcher(line).matches()) {

+            resultLines.add(line);

+            resultLineNumbers.add(currentLine);

+          }

+

+          line = fr.readLine();

+          currentLine++;

+        }

+      } catch(IOException e) {

+        e.printStackTrace();

+      }

+      finally {

+        if(null != fr) {

+          try {

+            fr.close();

+          }

+          catch(IOException ex) {

+            ex.printStackTrace();

+          }

+        }

+      }

+

+      return resultLineNumbers;

+

+    }

+

+  private static class InternalTestRunnerFactory implements ITestRunnerFactory {

+    private final BaseTest m_baseTest;

+

+    public InternalTestRunnerFactory(final BaseTest baseTest) {

+      m_baseTest= baseTest;

+    }

+

+    /**

+     * @see org.testng.ITestRunnerFactory#newTestRunner(org.testng.ISuite, org.testng.xml.XmlTest)

+     */

+    @Override

+    public TestRunner newTestRunner(ISuite suite, XmlTest test,

+        Collection<IInvokedMethodListener> listeners, List<IClassListener> classListeners) {

+      TestRunner testRunner= new TestRunner(m_baseTest.getConfiguration(), suite, test, false,

+          listeners, classListeners);

+

+      testRunner.addTestListener(new TestHTMLReporter());

+      testRunner.addTestListener(new JUnitXMLReporter());

+      testRunner.addListener(new TestListener(m_baseTest));

+      if (listeners != null) {

+        for (IInvokedMethodListener l : listeners) {

+          testRunner.addListener(l);

+        }

+      }

+

+      return testRunner;

+    }

+  }

+

+  /**

+   *  Deletes all files and subdirectories under dir.

+

+   *  @return true if all deletions were successful.

+   *  If a deletion fails, the method stops attempting to delete and returns false.

+   */

+  public static boolean deleteDir(File dir) {

+    if (dir.isDirectory()) {

+      String[] children = dir.list();

+      for (String element : children) {

+        boolean success = deleteDir(new File(dir, element));

+        if (!success) {

+          return false;

+        }

+      }

+    }

+

+    // The directory is now empty so delete it

+    return dir.delete();

+  }

+

+  protected void runTest(String cls, String[] passed, String[] failed, String[] skipped) {

+    addClass(cls);

+    run();

+    verifyTests("Passed", passed, getPassedTests());

+    verifyTests("Failed", failed, getFailedTests());

+    verifyTests("Skipped", skipped, getSkippedTests());

+  }

+

+} // BaseTest

+

+////////////////////////////

+

+class TestListener extends TestListenerAdapter {

+  private static BaseTest m_test= null;

+

+  public TestListener(BaseTest t1) {

+    m_test= t1;

+  }

+

+  @Override

+  public void onTestSuccess(ITestResult tr) {

+    m_test.addPassedTest(tr);

+  }

+

+  @Override

+  public void onTestFailure(ITestResult tr) {

+    m_test.addFailedTest(tr);

+  }

+

+  @Override

+  public void onTestFailedButWithinSuccessPercentage(ITestResult result) {

+    m_test.addFailedButWithinSuccessPercentageTest(result);

+  }

+

+  @Override

+  public void onTestSkipped(ITestResult tr) {

+    m_test.addSkippedTest(tr);

+  }

+

+  @Override

+  public void onConfigurationSuccess(ITestResult tr) {

+    m_test.addPassedConfig(tr);

+  }

+

+  @Override

+  public void onConfigurationFailure(ITestResult tr) {

+    m_test.addFailedConfig(tr);

+  }

+

+  @Override

+  public void onConfigurationSkip(ITestResult tr) {

+    m_test.addSkippedConfig(tr);

+  }

+

+} // TestListener

diff --git a/src/test/java/test/CheckSuitesInitializationTest.java b/src/test/java/test/CheckSuitesInitializationTest.java
new file mode 100644
index 0000000..beddee8
--- /dev/null
+++ b/src/test/java/test/CheckSuitesInitializationTest.java
@@ -0,0 +1,42 @@
+package test;
+
+import java.util.Arrays;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+/**
+ * Check for a bug in how relative paths in suite files were being handled.
+ *
+ * All paths were being resolved using the initial suite's location and not
+ * that of the current suite being parsed/processed.
+ *
+ * This test checks that TestNG can handle cases where we have the following set of
+ * files (all linked using relative paths):
+ *
+ * - parent-suite -> [child-suite-1, children/child-suite-3]
+ * - children/child-suite-3 -> [../child-suite-2, child-suite-4, morechildren/child-suite-5]
+ *
+ * Check the <code>checksuitesinitialization</code> folder under test resources
+ *
+ * @author Nalin Makar
+ */
+public class CheckSuitesInitializationTest extends SimpleBaseTest {
+
+  /**
+   * Child suites and tests within different suites have same names
+   */
+  @Test
+  public void check() {
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG tng = create();
+    String testngXmlPath = getPathToResource("checksuitesinitialization/parent-suite.xml");
+    tng.setTestSuites(Arrays.asList(testngXmlPath));
+    tng.addListener(tla);
+    tng.run();
+    Assert.assertEquals(tla.getPassedTests().size(), 4);
+  }
+
+}
diff --git a/src/test/java/test/ClassConfigurations.java b/src/test/java/test/ClassConfigurations.java
new file mode 100644
index 0000000..336bbc4
--- /dev/null
+++ b/src/test/java/test/ClassConfigurations.java
@@ -0,0 +1,50 @@
+package test;
+
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class ClassConfigurations {
+
+  static int beforeCount = 0;
+  static int afterCount = 0;
+
+  @BeforeClass
+  public void beforeTestClass() {
+    ++beforeCount;
+//    System.out.println("@@@@@@ beforeTestClass has been called " + beforeCount + " time(s)");
+  }
+
+  @AfterTest
+  public void afterTest() {
+    beforeCount = 0;
+    afterCount = 0;
+  }
+
+  @AfterTest
+  public void afterTestClass() {
+    ++afterCount;
+//    System.out.println("@@@@@@@ afterTestClass has been called " + afterCount + " time(s)");
+  }
+
+  @Test
+  public void testOne() {
+//    System.out.println("testOne");
+    assert beforeCount == 1;
+    assert afterCount == 0;
+  }
+
+  @Test
+  public void testTwo() {
+//    System.out.println("testTwo");
+    assert beforeCount == 1;
+    assert afterCount == 0;
+  }
+
+  @Test
+  public void testThree() {
+//    System.out.println("testThree");
+    assert beforeCount == 1;
+    assert afterCount == 0;
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/CommandLineTest.java b/src/test/java/test/CommandLineTest.java
new file mode 100644
index 0000000..4fe4e0e
--- /dev/null
+++ b/src/test/java/test/CommandLineTest.java
@@ -0,0 +1,136 @@
+package test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import org.testng.Assert;
+import org.testng.ITestContext;
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.sample.JUnitSample1;
+import testhelper.OutputDirectoryPatch;
+
+import java.util.List;
+
+public class CommandLineTest {
+
+  /**
+   * Test -junit
+   */
+  @Test(groups = { "current" } )
+  public void junitParsing() {
+    String[] argv = {
+      "-log", "0",
+      "-d", OutputDirectoryPatch.getOutputDirectory(),
+      "-junit",
+      "-testclass", "test.sample.JUnitSample1"
+    };
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG.privateMain(argv, tla);
+
+    List<ITestResult> passed = tla.getPassedTests();
+    assertEquals(passed.size(), 2);
+    String test1 = passed.get(0).getMethod().getMethodName();
+    String test2 = passed.get(1).getMethod().getMethodName();
+
+    assertTrue(JUnitSample1.EXPECTED1.equals(test1) && JUnitSample1.EXPECTED2.equals(test2) ||
+        JUnitSample1.EXPECTED1.equals(test2) && JUnitSample1.EXPECTED2.equals(test1));
+    }
+
+  /**
+   * Test the absence of -junit
+   */
+  @Test(groups = { "current" } )
+  public void junitParsing2() {
+    String[] argv = {
+      "-log", "0",
+      "-d", OutputDirectoryPatch.getOutputDirectory(),
+      "-testclass", "test.sample.JUnitSample1"
+    };
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG.privateMain(argv, tla);
+
+    List<ITestResult> passed = tla.getPassedTests();
+    assertEquals(passed.size(), 0);
+    }
+
+  /**
+   * Test the ability to override the default command line Suite name
+   */
+  @Test(groups = { "current" } )
+  public void suiteNameOverride() {
+    String suiteName="MySuiteName";
+    String[] argv = {
+      "-log", "0",
+      "-d", OutputDirectoryPatch.getOutputDirectory(),
+      "-junit",
+      "-testclass", "test.sample.JUnitSample1",
+      "-suitename", "\""+suiteName+"\""
+    };
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG.privateMain(argv, tla);
+
+    List<ITestContext> contexts = tla.getTestContexts();
+    assertTrue(contexts.size()>0);
+    for (ITestContext context:contexts) {
+    	assertEquals(context.getSuite().getName(),suiteName);
+    }
+  }
+
+  /**
+   * Test the ability to override the default command line test name
+   */
+  @Test(groups = { "current" } )
+  public void testNameOverride() {
+    String testName="My Test Name";
+    String[] argv = {
+      "-log", "0",
+      "-d", OutputDirectoryPatch.getOutputDirectory(),
+      "-junit",
+      "-testclass", "test.sample.JUnitSample1",
+      "-testname", "\""+testName+"\""
+    };
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG.privateMain(argv, tla);
+
+    List<ITestContext> contexts = tla.getTestContexts();
+    assertTrue(contexts.size()>0);
+    for (ITestContext context:contexts) {
+    	assertEquals(context.getName(),testName);
+    }
+  }
+
+  @Test
+  public void testUseDefaultListenersArgument() {
+    TestNG.privateMain(new String[] {
+        "-log", "0", "-usedefaultlisteners", "false", "-testclass", "test.sample.JUnitSample1"
+    }, null);
+  }
+
+  @Test
+  public void testMethodParameter() {
+    String[] argv = {
+      "-log", "0",
+      "-d", OutputDirectoryPatch.getOutputDirectory(),
+      "-methods", "test.sample.Sample2.method1,test.sample.Sample2.method3",
+    };
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG.privateMain(argv, tla);
+
+    List<ITestResult> passed = tla.getPassedTests();
+    Assert.assertEquals(passed.size(), 2);
+    Assert.assertTrue((passed.get(0).getName().equals("method1") &&
+        passed.get(1).getName().equals("method3"))
+        ||
+        (passed.get(1).getName().equals("method1") &&
+        passed.get(0).getName().equals("method3")));
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[CommandLineTest] " + s);
+  }
+
+}
diff --git a/src/test/java/test/CountSampleTest.java b/src/test/java/test/CountSampleTest.java
new file mode 100644
index 0000000..29c440a
--- /dev/null
+++ b/src/test/java/test/CountSampleTest.java
@@ -0,0 +1,34 @@
+package test;
+
+import org.testng.Assert;
+import org.testng.SkipException;
+import org.testng.annotations.Test;
+
+/**
+ * Use this test to show run/failed/skip result
+ * differences between testng-5.12 and testng-5-14
+ *
+ * @author CA Technologies
+ */
+
+
+public class CountSampleTest {
+
+    @Test(groups = {"functional"})
+    public void testInvokedAndSkipped() throws SkipException {
+//        System.out.println("Skipping this test after it is invoked.");
+        throw new SkipException("This test is skipped after invocation");
+    }
+
+    @Test(groups = {"functional"})
+    public static void testInvokedAndFailed() {
+//        System.out.println("Failing this test after it is invoked.");
+        Assert.fail("Failing this test on purpose");
+    }
+
+    @Test(groups = {"functional"}, dependsOnMethods = {"testInvokedAndFailed"})
+    public static void testWillNotBeInvokedOnlySkipped() {
+//        System.out.println("This test will be skipped, " +
+//            "but not invoked because its dependsOnMethod fails.");
+    }
+}
diff --git a/src/test/java/test/CountTest.java b/src/test/java/test/CountTest.java
new file mode 100644
index 0000000..10e757c
--- /dev/null
+++ b/src/test/java/test/CountTest.java
@@ -0,0 +1,36 @@
+package test;
+
+import org.testng.Assert;
+import org.testng.IReporter;
+import org.testng.ISuite;
+import org.testng.ISuiteResult;
+import org.testng.ITestContext;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+
+import java.util.List;
+
+public class CountTest extends SimpleBaseTest {
+
+  @Test(description = "Make sure that skipped methods are accurately counted")
+  public void skippedMethodsShouldBeCounted() {
+    TestNG tng = create(CountSampleTest.class);
+
+    IReporter r = new IReporter() {
+      @Override
+      public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites,
+          String outputDirectory) {
+        for (ISuite s : suites) {
+          for (ISuiteResult sr : s.getResults().values()) {
+            ITestContext ctx = sr.getTestContext();
+            Assert.assertEquals(2, ctx.getSkippedTests().size());
+          }
+        }
+      }
+    };
+
+    tng.addListener(r);
+    tng.run();
+  }
+}
diff --git a/src/test/java/test/CtorCalledOnce.java b/src/test/java/test/CtorCalledOnce.java
new file mode 100644
index 0000000..55752ef
--- /dev/null
+++ b/src/test/java/test/CtorCalledOnce.java
@@ -0,0 +1,37 @@
+package test;
+
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.Test;
+
+/**
+ * this test verifys that the test class is instantiated exactly once
+ * regardless of how many test methods we have, showing that TestNG
+ * semantics is quite different from JUnit
+ */
+public class CtorCalledOnce {
+  public static int instantiated = 0;
+  public CtorCalledOnce() {
+    instantiated++;
+  }
+
+  @Test
+  public void testMethod1(){
+    assert instantiated == 1 : "Expected 1, was invoked " + instantiated + " times";
+  }
+
+  @Test
+  public void testMethod2(){
+    assert instantiated == 1 : "Expected 1, was invoked " + instantiated + " times";
+  }
+
+  @Test
+  public void testMethod3(){
+    assert instantiated == 1 : "Expected 1, was invoked " + instantiated + " times";
+  }
+
+  @AfterTest
+  public void afterTest() {
+    instantiated = 0;
+  }
+
+}
\ No newline at end of file
diff --git a/src/test/java/test/DynamicGraphTest.java b/src/test/java/test/DynamicGraphTest.java
new file mode 100644
index 0000000..b30e98a
--- /dev/null
+++ b/src/test/java/test/DynamicGraphTest.java
@@ -0,0 +1,75 @@
+package test;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.testng.internal.DynamicGraph;
+import org.testng.internal.DynamicGraph.Status;
+
+import java.util.List;
+import java.util.Set;
+
+public class DynamicGraphTest {
+
+  private static <T> void assertFreeNodesEquals(DynamicGraph<T> graph, T[] expected) {
+    Assert.assertEqualsNoOrder(graph.getFreeNodes().toArray(), expected);
+  }
+
+  @Test
+  public void test8() {
+    DynamicGraph<String> dg = new DynamicGraph<>();
+    dg.addNode("a1");
+    dg.addNode("a2");
+    dg.addNode("b1");
+    dg.addNode("b2");
+    dg.addNode("c1");
+    dg.addEdge("b1", "a1");
+    dg.addEdge("b1", "a2");
+    dg.addEdge("b2", "a1");
+    dg.addEdge("b2", "a2");
+    dg.addEdge("c1", "b1");
+    dg.addEdge("c1", "b2");
+    dg.addNode("x");
+    dg.addNode("y");
+    List<String> freeNodes = dg.getFreeNodes();
+    assertFreeNodesEquals(dg, new String[] {"a1", "a2", "y", "x"});
+
+    dg.setStatus(freeNodes, Status.RUNNING);
+    dg.setStatus("a1", Status.FINISHED);
+    assertFreeNodesEquals(dg, new String[] {});
+
+    dg.setStatus("a2", Status.FINISHED);
+    assertFreeNodesEquals(dg, new String[] { "b1", "b2"});
+
+    dg.setStatus("b2", Status.RUNNING);
+    dg.setStatus("b1", Status.FINISHED);
+    assertFreeNodesEquals(dg, new String[] {});
+
+    dg.setStatus("b2", Status.FINISHED);
+    assertFreeNodesEquals(dg, new String[] { "c1" });
+  }
+
+  @Test
+  public void test2() {
+    DynamicGraph<String> dg = new DynamicGraph<>();
+    dg.addNode("a1");
+    dg.addNode("a2");
+    dg.addNode("b1");
+    dg.addEdge("b1", "a1");
+    dg.addEdge("b1", "a2");
+    dg.addNode("x");
+    List<String> freeNodes = dg.getFreeNodes();
+    assertFreeNodesEquals(dg, new String[] { "a1", "a2", "x" });
+
+    dg.setStatus(freeNodes, Status.RUNNING);
+    dg.setStatus("a1", Status.FINISHED);
+    assertFreeNodesEquals(dg, new String[] {});
+
+    dg.setStatus("a2", Status.FINISHED);
+    assertFreeNodesEquals(dg, new String[] { "b1" });
+
+    dg.setStatus("b2", Status.RUNNING);
+    dg.setStatus("b1", Status.FINISHED);
+    assertFreeNodesEquals(dg, new String[] {});
+  }
+
+}
diff --git a/src/test/java/test/EclipseTest.java b/src/test/java/test/EclipseTest.java
new file mode 100644
index 0000000..6260dad
--- /dev/null
+++ b/src/test/java/test/EclipseTest.java
@@ -0,0 +1,20 @@
+package test;
+
+import org.testng.Assert;
+import org.testng.ITestContext;
+import org.testng.annotations.Test;
+
+/**
+ * Make sure that these test pass when run by the Eclipse plug-in.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ *
+ */
+public class EclipseTest {
+
+  @Test
+  public void xmlFileShouldBeRunAtItsPlaceAndNotCopied(ITestContext ctx) {
+    String fileName = ctx.getSuite().getXmlSuite().getFileName().replace("\\", "/");
+    Assert.assertTrue(fileName.contains("src/test/resources"));
+  }
+}
diff --git a/src/test/java/test/Exclude.java b/src/test/java/test/Exclude.java
new file mode 100644
index 0000000..e5a6d17
--- /dev/null
+++ b/src/test/java/test/Exclude.java
@@ -0,0 +1,51 @@
+package test;
+
+import org.testng.annotations.Test;
+
+public class Exclude {
+  private boolean m_included1 = false;
+  private boolean m_included2 = false;
+  private boolean m_excluded1 = true;
+  private boolean m_excluded2 = true;
+
+  @Test(groups = { "group1"} )
+  public void included1() {
+    ppp("INCLUDED1");
+    m_included1 = true;
+  }
+
+  @Test(groups = { "group1"} )
+  public void included2() {
+    ppp("INCLUDED2");
+    m_included2 = true;
+  }
+
+  @Test(groups = { "group1"} )
+  public void excluded1() {
+    ppp("EXCLUDED1");
+    m_excluded1 = false;
+  }
+
+  @Test(groups = { "group1"} )
+  public void excluded2() {
+    ppp("EXCLUDED1");
+    m_excluded2 = false;
+  }
+
+  @Test(dependsOnGroups = { "group1" }, groups =  {"group2"} )
+  public void verify() {
+    ppp("VERIFY");
+    assert m_included1 && m_included2 && m_excluded1 && m_excluded2:
+      "Should all be true: " + m_included1 + " "
+      + m_included2 + " "
+      + m_excluded1 + " "
+      + m_excluded2;
+  }
+
+  static private void ppp(String s) {
+    if (false) {
+      System.out.println("[Exclude] " + s);
+    }
+  }
+
+}
diff --git a/src/test/java/test/FileStringBufferTest.java b/src/test/java/test/FileStringBufferTest.java
new file mode 100644
index 0000000..379a1bb
--- /dev/null
+++ b/src/test/java/test/FileStringBufferTest.java
@@ -0,0 +1,30 @@
+package test;
+
+import org.testng.annotations.Test;
+import org.testng.reporters.FileStringBuffer;
+
+public class FileStringBufferTest {
+
+  @Test
+  public void basic() {
+    {
+      FileStringBuffer fsb = new FileStringBuffer(5);
+      String s = "0123456789";
+      String s3 = s + s + s;
+  
+      fsb.append(s3);
+//      Assert.assertEquals(s3, fsb.toString());
+    }
+
+    {
+      FileStringBuffer fsb = new FileStringBuffer(5);
+      String s = "0123456789";
+      String s3 = s + s + s;
+  
+      fsb.append(s);
+      fsb.append(s);
+      fsb.append(s);
+//      Assert.assertEquals(s3, fsb.toString());
+    }
+  }
+}
diff --git a/src/test/java/test/GraphTest.java b/src/test/java/test/GraphTest.java
new file mode 100644
index 0000000..871f959
--- /dev/null
+++ b/src/test/java/test/GraphTest.java
@@ -0,0 +1,197 @@
+package test;
+
+import org.testng.Assert;
+import org.testng.TestNGException;
+import org.testng.annotations.Test;
+import org.testng.internal.Graph;
+import org.testng.internal.Tarjan;
+
+import java.util.List;
+
+
+/**
+ * This class
+ *
+ * @author cbeust
+ */
+public class GraphTest {
+
+  @Test
+  public void sort() {
+    Graph<String> g = new Graph<>();
+    g.addNode("3");
+    g.addNode("1");
+    g.addNode("2.2");
+    g.addNode("independent");
+    g.addNode("2.1");
+    g.addNode("2");
+
+    g.addPredecessor("3", "2");
+    g.addPredecessor("3", "2.1");
+    g.addPredecessor("3", "2.2");
+    g.addPredecessor("2", "1");
+    g.addPredecessor("2.1", "1");
+    g.addPredecessor("2.2", "1");
+
+    g.topologicalSort();
+    List<String> l = g.getStrictlySortedNodes();
+    int i = 0;
+    Assert.assertTrue("1".equals(l.get(i)));
+    i++;
+    Assert.assertTrue("2".equals(l.get(i)) || "2.1".equals(l.get(i)) || "2.2".equals(l.get(i)));
+    i++;
+    Assert.assertTrue("2".equals(l.get(i)) || "2.1".equals(l.get(i)) || "2.2".equals(l.get(i)));
+    i++;
+    Assert.assertTrue("2".equals(l.get(i)) || "2.1".equals(l.get(i)) || "2.2".equals(l.get(i)));
+    i++;
+    Assert.assertTrue("3".equals(l.get(i)));
+
+    Assert.assertTrue(1 == g.getIndependentNodes().size());
+  }
+
+  @Test(expectedExceptions = org.testng.TestNGException.class)
+  public void cycleShouldFail() {
+    Graph<String> g = createCyclicGraph();
+    g.topologicalSort();
+  }
+
+  @Test
+  public void cycleShouldBeCorrect() {
+    Graph<String> g = null;
+    try {
+      g = createCyclicGraph();
+      g.topologicalSort();
+    }
+    catch(TestNGException ex) {
+      Tarjan<String> t = new Tarjan<>(g, "1");
+      Assert.assertEquals(t.getCycle().size(), 3);
+    }
+
+  }
+
+  private Graph<String> createCyclicGraph() {
+    Graph<String> g = new Graph<>();
+    g.addNode("3");
+    g.addNode("2");
+    g.addNode("1");
+
+    g.addPredecessor("3", "2");
+    g.addPredecessor("2", "1");
+    g.addPredecessor("1", "3");
+
+    return g;
+  }
+
+  @Test
+  public void findPredecessors() {
+    Graph<String> g = new Graph<>();
+    g.addNode("3");
+    g.addNode("1");
+    g.addNode("2.2");
+    g.addNode("independent");
+    g.addNode("2.1");
+    g.addNode("2");
+
+    // 1 ->  2.1, 2.2, 2.3 --> 3
+    g.addPredecessor("3", "2");
+    g.addPredecessor("3", "2.1");
+    g.addPredecessor("3", "2.2");
+    g.addPredecessor("2", "1");
+    g.addPredecessor("2.1", "1");
+    g.addPredecessor("2.2", "1");
+
+    // Invoke sort to make sure there is no side effect
+    g.topologicalSort();
+
+    //
+    // Test findPredecessors
+    //
+    {
+      List<String> predecessors = g.findPredecessors("2");
+      Assert.assertTrue(predecessors.size() == 1);
+      Assert.assertTrue(predecessors.get(0).equals("1"));
+    }
+
+    {
+      List<String> predecessors = g.findPredecessors("3");
+
+      Assert.assertTrue(predecessors.size() == 4);
+      Assert.assertTrue(predecessors.get(0).equals("1"));
+      Assert.assertTrue(predecessors.get(1).equals("2.1")
+        || predecessors.get(1).equals("2.2")
+        || predecessors.get(1).equals("2"));
+      Assert.assertTrue(predecessors.get(2).equals("2.1")
+        || predecessors.get(2).equals("2.2")
+        || predecessors.get(2).equals("2"));
+      Assert.assertTrue(predecessors.get(3).equals("2.1")
+        || predecessors.get(3).equals("2.2")
+        || predecessors.get(3).equals("2"));
+    }
+  }
+
+  // Using an earlier implementation of Graph.findPrecessors, finding
+  // predecessors in certain kinds of graphs where many nodes have the
+  // same predecessors could be very slow, since the old implementation
+  // would explore the same nodes repeatedly.  This situation could
+  // happen when test methods are organized in several groups, with
+  // dependsOnGroups annotations so that each method in one group depends
+  // on all of the methods in another group.  If there were several
+  // such groups depending on each other in a chain, the combinatorics
+  // of the old method became excessive.  This test builds a 72-node graph that
+  // emulates this situation, then call Graph.findPredecessors.  The old
+  // implementation run this in 70+ seconds on my computer, the new implementation
+  // takes a few milliseconds.  In practice, with larger graphs, the former
+  // slowness could get very extreme, taking hours or more to complete
+  // in some real user situations.
+  //
+  @Test(timeOut = 5000) // If this takes more than 5 seconds we've definitely regressed.
+  public void findPredecessorsTiming() {
+    Graph<String> g = new Graph<>();
+
+    final String rootNode = "myroot";
+    final String independentNode = "independent";
+    g.addNode(rootNode);
+    g.addNode(independentNode);
+
+    final int maxDepth = 7;
+    final int nodesPerDepth = 10; // must be < 100
+    //
+    // Add maxDepth groups of new nodes, where each group contains nodesPerDepth
+    // nodes, and each node in a group a depth N has each node in the group
+    // at depth (N-1) as a predecessor.
+    //
+    for (int depth = 1; depth <= maxDepth; depth++) {
+      for (int i = 0; i < nodesPerDepth; i++) {
+        String newNode = String.valueOf(i + (100 * depth));
+        g.addNode(newNode);
+        if (depth == 1) {
+          continue;
+        }
+        for (int j = 0; j < nodesPerDepth; j++) {
+          String prevNode = String.valueOf(j + (100 * (depth - 1)));
+          g.addPredecessor(newNode, prevNode);
+        }
+      }
+    }
+
+    // Finally, make all of the nodes in the group at depth maxDepth
+    // be predecessors of rootNode.
+    //
+    for (int i = 0; i < nodesPerDepth; i++) {
+      String node = String.valueOf(i + (100 * maxDepth));
+      g.addPredecessor(rootNode, node);
+    }
+
+    // Now we're done building the graph, which has (maxDepth * nodesPerDepth) + 2
+    // nodes.  rootNode has all of the other nodes except independentNode
+    // as predecessors.
+
+    //
+    // Test findPredecessors
+    //
+    {
+      List<String> predecessors = g.findPredecessors(rootNode);
+      Assert.assertTrue(predecessors.size() == (maxDepth * nodesPerDepth));
+    }
+  }
+}
diff --git a/src/test/java/test/IndividualMethodsTest.java b/src/test/java/test/IndividualMethodsTest.java
new file mode 100644
index 0000000..33dc7ad
--- /dev/null
+++ b/src/test/java/test/IndividualMethodsTest.java
@@ -0,0 +1,30 @@
+package test;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ * Test that if an individual method is specified on testng.xml, the @Configuration
+ * method still runs.
+ *
+ * Created on Aug 1, 2005
+ * @author cbeust
+ */
+public class IndividualMethodsTest
+{
+    private boolean m_setUpCalled = false;
+
+    @BeforeMethod
+    public void setUp()
+    {
+        m_setUpCalled = true;
+    }
+
+    @Test
+    public void testMethod()
+    {
+        // this line causes the test to fail, showing that setUp() hadn't been run
+        Assert.assertTrue(m_setUpCalled);
+    }
+}
diff --git a/src/test/java/test/InvocationAndSuccessPercentageTest.java b/src/test/java/test/InvocationAndSuccessPercentageTest.java
new file mode 100644
index 0000000..56f4434
--- /dev/null
+++ b/src/test/java/test/InvocationAndSuccessPercentageTest.java
@@ -0,0 +1,101 @@
+package test;
+
+import org.testng.annotations.Test;
+
+/**
+ * This class tests invocationCount and successPercentage
+ *
+ * @author cbeust
+ */
+public class InvocationAndSuccessPercentageTest extends BaseTest {
+
+   @Test
+    public void invocationCount() {
+      addClass("test.sample.InvocationCountTest");
+      addIncludedGroup("invocationOnly");
+      run();
+      String[] passed = {
+          "tenTimesShouldSucceed",
+      };
+      String[] failed = {
+      };
+      verifyResults(getPassedTests(), 10, "passed tests");
+//      Map passedTests = getPassedTests();
+//      Set keys = passedTests.keySet();
+//      Object firstKey = keys.iterator().next();
+//      ITestResult passedResult = (ITestResult) passedTests.get(firstKey);
+//      int n = passedResult.getPassedMethods().size();
+//      assert n == 10 :
+//        "Expected 10 tests to have passed, but only found " + n;
+      verifyTests("Passed", passed, getPassedTests());
+      verifyTests("Failed", failed, getFailedTests());
+    }
+
+   /**
+    * Result expected:
+    * 8 passed
+    * 2 failed but within success percentage
+    */
+   @Test
+   public void successPercentageThatSucceeds() {
+     addClass("test.sample.InvocationCountTest");
+     addIncludedGroup("successPercentageThatSucceedsOnly");
+     run();
+     String[] passed = {
+         "successPercentageShouldSucceed",
+     };
+     String[] failed = {
+     };
+     String[] failedButWithinSuccessPercentage = {
+         "successPercentageShouldSucceed",
+     };
+     verifyTests("FailedButWithinSuccessPercentage",
+         failedButWithinSuccessPercentage, getFailedButWithinSuccessPercentageTests());
+     verifyTests("Passed", passed, getPassedTests());
+     verifyTests("Failed", failed, getFailedTests());
+
+     // Should have 8 passed, 2 failed but within success percentage
+     verifyResults(getPassedTests(), 8, "passed tests");
+     verifyResults(
+       getFailedButWithinSuccessPercentageTests(), 2,
+       "failed_but_within_success_percentage_tests");
+   }
+
+   /**
+    * Result expected:
+    * 8 passed
+    * 1 failed but within success percentage
+    * 1 failed
+    */
+   @Test
+   public void successPercentageThatFails() {
+     addClass(test.sample.InvocationCountTest.class);
+     addIncludedGroup("successPercentageThatFailsOnly");
+     run();
+     String[] passed = {
+         "successPercentageShouldFail",
+     };
+     String[] failed = {
+         "successPercentageShouldFail",
+     };
+     String[] failedButWithinSuccessPercentage = {
+         "successPercentageShouldFail",
+     };
+     verifyTests("FailedButWithinSuccessPercentage",
+         failedButWithinSuccessPercentage, getFailedButWithinSuccessPercentageTests());
+     verifyTests("Passed", passed, getPassedTests());
+     verifyTests("Failed", failed, getFailedTests());
+
+     // Should have 8 passed, 2 failed but within success percentage
+     verifyResults(getPassedTests(), 8, "passed tests");
+     verifyResults(getFailedTests(), 1, "failed tests");
+     verifyResults(
+       getFailedButWithinSuccessPercentageTests(), 1,
+       "failed_but_within_success_percentage_tests");
+   }
+
+   public static void ppp(String s) {
+    System.out.println("[Invocation] " + s);
+  }
+
+}
diff --git a/src/test/java/test/InvokedMethodNameListener.java b/src/test/java/test/InvokedMethodNameListener.java
new file mode 100644
index 0000000..25f56f7
--- /dev/null
+++ b/src/test/java/test/InvokedMethodNameListener.java
@@ -0,0 +1,54 @@
+package test;
+
+import org.testng.IInvokedMethod;
+import org.testng.IInvokedMethodListener;
+import org.testng.ITestResult;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+// TODO replace other test IInvokedMethodListener by this one
+public class InvokedMethodNameListener implements IInvokedMethodListener {
+
+  private final List<String> invokedMethodNames = new ArrayList<>();
+  private final List<String> failedMethodNames = new ArrayList<>();
+  private final List<String> skippedMethodNames = new ArrayList<>();
+  private final List<String> succeedMethodNames = new ArrayList<>();
+
+  @Override
+  public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
+    invokedMethodNames.add(method.getTestMethod().getConstructorOrMethod().getName());
+  }
+
+  @Override
+  public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
+    switch (testResult.getStatus()) {
+      case ITestResult.FAILURE:
+        failedMethodNames.add(method.getTestMethod().getConstructorOrMethod().getName());
+        break;
+      case ITestResult.SKIP:
+        skippedMethodNames.add(method.getTestMethod().getConstructorOrMethod().getName());
+        break;
+      case ITestResult.SUCCESS:
+        succeedMethodNames.add(method.getTestMethod().getConstructorOrMethod().getName());
+        break;
+    }
+  }
+
+  public List<String> getInvokedMethodNames() {
+    return Collections.unmodifiableList(invokedMethodNames);
+  }
+
+  public List<String> getFailedMethodNames() {
+    return failedMethodNames;
+  }
+
+  public List<String> getSkippedMethodNames() {
+    return skippedMethodNames;
+  }
+
+  public List<String> getSucceedMethodNames() {
+    return succeedMethodNames;
+  }
+}
diff --git a/src/test/java/test/JUnit4Test.java b/src/test/java/test/JUnit4Test.java
new file mode 100644
index 0000000..d9d9f27
--- /dev/null
+++ b/src/test/java/test/JUnit4Test.java
@@ -0,0 +1,96 @@
+package test;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import test.junit4.JUnit4Child;
+import test.junit4.JUnit4ParameterizedTest;
+import test.junit4.JUnit4Sample2;
+import test.junit4.JUnit4SampleSuite;
+
+/**
+ *
+ * @author lukas
+ */
+public class JUnit4Test extends BaseTest {
+
+    @BeforeMethod(dependsOnGroups = {"initTest"})
+    public void initJUnitFlag() {
+        getTest().setJUnit(true);
+    }
+
+    @Test
+    public void testTests() {
+        addClass("test.junit4.JUnit4Sample2");
+        assert getTest().isJUnit();
+
+        run();
+        String[] passed = JUnit4Sample2.EXPECTED;
+        String[] failed = JUnit4Sample2.FAILED;
+        String[] skipped = JUnit4Sample2.SKIPPED;
+
+        verifyTests("Passed", passed, getPassedTests());
+        verifyTests("Failed", failed, getFailedTests());
+        verifyTests("Skipped", skipped, getSkippedTests());
+    }
+
+    @Test
+    public void testSuite() {
+        addClass("test.junit4.JUnit4SampleSuite");
+        assert getTest().isJUnit();
+
+        run();
+        String[] passed = JUnit4SampleSuite.EXPECTED;
+        String[] failed = JUnit4SampleSuite.FAILED;
+        String[] skipped = JUnit4SampleSuite.SKIPPED;
+
+        verifyTests("Passed", passed, getPassedTests());
+        verifyTests("Failed", failed, getFailedTests());
+        verifyTests("Skipped", skipped, getSkippedTests());
+    }
+
+    @Test
+    public void testSuiteInheritance() {
+        addClass("test.junit4.JUnit4Child");
+        assert getTest().isJUnit();
+
+        run();
+        String[] passed = JUnit4Child.EXPECTED;
+        String[] failed = {};
+        String[] skipped = {};
+
+        verifyTests("Passed", passed, getPassedTests());
+        verifyTests("Failed", failed, getFailedTests());
+        verifyTests("Skipped", skipped, getSkippedTests());
+    }
+
+    @Test
+    public void testTestInheritance() {
+        addClass("test.junit4.InheritedTest");
+        addClass("test.junit4.JUnit4Sample1");
+        assert getTest().isJUnit();
+
+        run();
+        String[] passed = {"t1", "t1"};
+        String[] failed = {};
+        String[] skipped = {};
+
+        verifyTests("Passed", passed, getPassedTests());
+        verifyTests("Failed", failed, getFailedTests());
+        verifyTests("Skipped", skipped, getSkippedTests());
+    }
+
+    @Test
+    public void testTestParameterized() {
+        addClass("test.junit4.JUnit4ParameterizedTest");
+        assert getTest().isJUnit();
+
+        run();
+        String[] passed = JUnit4ParameterizedTest.EXPECTED;
+        String[] failed = JUnit4ParameterizedTest.FAILED;
+        String[] skipped = JUnit4ParameterizedTest.SKIPPED;
+
+        verifyTests("Passed", passed, getPassedTests());
+        verifyTests("Failed", failed, getFailedTests());
+        verifyTests("Skipped", skipped, getSkippedTests());
+    }
+}
diff --git a/src/test/java/test/JUnitTest1.java b/src/test/java/test/JUnitTest1.java
new file mode 100644
index 0000000..ecc72e3
--- /dev/null
+++ b/src/test/java/test/JUnitTest1.java
@@ -0,0 +1,137 @@
+package test;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import test.junit.SetNameTest;
+import test.sample.JUnitSample1;
+import test.sample.JUnitSample2;
+
+
+/**
+ * This class
+ *
+ * @author Cedric Beust, May 5, 2004
+ *
+ */
+public class JUnitTest1 extends BaseTest {
+  @BeforeMethod(dependsOnGroups = { "initTest"} )
+  public void initJUnitFlag() {
+    getTest().setJUnit(true);
+  }
+
+  @Test
+  public void methodsThatStartWithTest() {
+    addClass("test.sample.JUnitSample1");
+    assert getTest().isJUnit();
+
+    run();
+    String[] passed = {
+        JUnitSample1.EXPECTED1, JUnitSample1.EXPECTED2
+    };
+    String[] failed = {
+    };
+
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void methodsWithSetup() {
+    addClass("test.sample.JUnitSample2");
+    run();
+    String[] passed = {
+      "testSample2ThatSetUpWasRun",
+    };
+    String[] failed = {
+    };
+
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void testSuite() {
+    addClass("test.sample.AllJUnitTests");
+    run();
+    String[] passed = {
+        JUnitSample1.EXPECTED1, /*JUnitSample1.EXPECTED2,*/
+        JUnitSample2.EXPECTED,
+    };
+    String[] failed = {
+    };
+
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void testNewInstance() {
+    addClass("test.sample.JUnitSample3");
+    run();
+    String[] passed = {
+      "test1", "test2"
+    };
+    String[] failed = {
+    };
+
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void setUpFailingShouldCauseMethodsToBeSkipped() {
+    addClass("test.junit.SetUpExceptionSampleTest");
+    run();
+    String[] passed = {
+    };
+    String[] failed = {
+      "testM1"/*, "testM1", "tearDown"*/
+    };
+    String[] skipped = {
+      /*"testM1", "tearDown"*/
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Skipped", skipped, getSkippedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void setNameShouldBeInvoked() {
+    addClass("test.junit.SetNameTest");
+    SetNameTest.m_ctorCount = 0;
+    run();
+    String[] passed = {
+      "testFoo", "testBar",
+    };
+    String[] failed = {
+    };
+    String[] skipped = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Skipped", skipped, getSkippedTests());
+    verifyTests("Failed", failed, getFailedTests());
+
+    Assert.assertEquals(SetNameTest.m_ctorCount, 2,
+        "Expected 2 instances to be created, found " + SetNameTest.m_ctorCount);
+  }
+
+  public static void ppp(String s) {
+    System.out.println("[JUnitTest1] " + s);
+  }
+
+  @Test
+  public void testAbstract() {
+    addClass("test.sample.JUnitSample4");
+    run();
+    String[] passed = {
+      "testXY", "testXY", "testXY"
+    };
+    String[] failed = {
+    };
+
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+}
diff --git a/src/test/java/test/MethodTest.java b/src/test/java/test/MethodTest.java
new file mode 100644
index 0000000..651a96f
--- /dev/null
+++ b/src/test/java/test/MethodTest.java
@@ -0,0 +1,54 @@
+package test;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import test.sample.Sample2;
+
+public class MethodTest extends BaseTest {
+  private static final String CLASS_NAME = Sample2.class.getName();
+
+  @Test(groups = { "current" })
+  public void includeMethodsOnly() {
+    addClass(CLASS_NAME);
+    Assert.assertEquals(getTest().getXmlClasses().size(), 1);
+    addIncludedMethod(CLASS_NAME, ".*method2");
+    run();
+    String[] passed = {
+      "method2",
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test(groups = { "current" })
+  public void excludeMethodsOnly() {
+    addClass(CLASS_NAME);
+    Assert.assertEquals(getTest().getXmlClasses().size(), 1);
+    addExcludedMethod(CLASS_NAME, ".*method2");
+    run();
+    String[] passed = {
+      "method1", "method3"
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void excludePackage() {
+    addClass(CLASS_NAME);
+    assert 1 == getTest().getXmlClasses().size();
+    addExcludedMethod(CLASS_NAME, ".*");
+    run();
+    String[] passed = {
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/Misc.java b/src/test/java/test/Misc.java
new file mode 100644
index 0000000..2a68c70
--- /dev/null
+++ b/src/test/java/test/Misc.java
@@ -0,0 +1,31 @@
+package test;
+
+import org.testng.annotations.Test;
+
+/**
+ * This class
+ *
+ * @author cbeust
+ */
+public class Misc extends BaseTest {
+
+  @Test
+  public void makeSureSetUpWithParameterWithNoParametersFails() {
+    addClass("test.sample.SetUpWithParameterTest");
+    setVerbose(0);
+//    setParallel(XmlSuite.PARALLEL_METHODS);
+    run();
+    String[] passed = {
+      };
+      // @Configuration failures are not reported in the ITestListener
+      String[] failed = {
+      };
+      String[] skipped = {
+          "test",
+      };
+      verifyTests("Passed", passed, getPassedTests());
+      verifyTests("Failed", failed, getFailedTests());
+      verifyTests("Failed", skipped, getSkippedTests());
+  }
+
+}
diff --git a/src/test/java/test/NestedStaticSampleTest.java b/src/test/java/test/NestedStaticSampleTest.java
new file mode 100644
index 0000000..3ab85cd
--- /dev/null
+++ b/src/test/java/test/NestedStaticSampleTest.java
@@ -0,0 +1,16 @@
+package test;
+
+import org.testng.annotations.Test;
+
+public class NestedStaticSampleTest {
+
+  @Test
+  public void f() {
+  }
+
+  public class Nested {
+    @Test
+    public void nested() {
+    }
+  }
+}
diff --git a/src/test/java/test/NestedStaticTest.java b/src/test/java/test/NestedStaticTest.java
new file mode 100644
index 0000000..29c1880
--- /dev/null
+++ b/src/test/java/test/NestedStaticTest.java
@@ -0,0 +1,35 @@
+package test;
+
+import org.testng.Assert;
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.collections.Sets;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class NestedStaticTest extends SimpleBaseTest {
+
+  @Test
+  public void nestedClassShouldBeIncluded() {
+    TestNG tng = create(NestedStaticSampleTest.class);
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+    Set<String> expected = new HashSet<String>() {{
+      add("nested");
+      add("f");
+    }};
+    Set<String> actual = Sets.newHashSet();
+    List<ITestResult> passedTests = tla.getPassedTests();
+    for (ITestResult t : passedTests) {
+      actual.add(t.getMethod().getMethodName());
+    }
+
+    Assert.assertEquals(actual, expected);
+  }
+}
diff --git a/src/test/java/test/ParameterConstructorTest.java b/src/test/java/test/ParameterConstructorTest.java
new file mode 100644
index 0000000..56091db
--- /dev/null
+++ b/src/test/java/test/ParameterConstructorTest.java
@@ -0,0 +1,54 @@
+package test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+/**
+ * Test parameters passed to constructors
+ *
+ * @author cbeust
+ */
+public class ParameterConstructorTest {
+  private String m_string = null;
+  private int m_int = -1;
+  private boolean m_boolean = false;
+  private byte m_byte = -1;
+  private char m_char = 0;
+  private double m_double = 0.0;
+  private float m_float = 0.0f;
+  private long m_long = 0;
+  private short m_short = 0;
+
+  @Parameters({ "string", "int", "boolean", "byte", "char", "double",
+      "float", "long", "short"  })
+  public ParameterConstructorTest(String s, int i, boolean bo, byte b, char c,
+      double d, float f, long l, short sh)
+  {
+    m_string = s;
+    m_int = i;
+    m_boolean = bo;
+    m_byte = b;
+    m_char = c;
+    m_double = d;
+    m_float = f;
+    m_long = l;
+    m_short = sh;
+  }
+
+  @Test
+  public void verify() {
+    assertEquals("Cedric", m_string);
+    assertEquals(42, m_int);
+    assertTrue(m_boolean);
+    assertEquals(43, m_byte);
+    assertEquals('c', m_char);
+    assertEquals(44.0, m_double, 0.1);
+    assertEquals(45.0f, m_float, 0.1);
+    assertEquals(46, m_long);
+    assertEquals(47, m_short);
+  }
+
+}
diff --git a/src/test/java/test/ReporterApiTest.java b/src/test/java/test/ReporterApiTest.java
new file mode 100644
index 0000000..0126806
--- /dev/null
+++ b/src/test/java/test/ReporterApiTest.java
@@ -0,0 +1,20 @@
+package test;
+
+import org.testng.Reporter;
+import org.testng.annotations.Test;
+
+/**
+ * Make sure we don't remove public API's from Reporter.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public class ReporterApiTest {
+
+  @Test
+  public void testApi() {
+    Reporter.log("foo");
+    Reporter.log("foo", false);
+    Reporter.log("foo", 2);
+    Reporter.log("foo", 2, false);
+  }
+}
diff --git a/src/test/java/test/ReturnValueSampleTest.java b/src/test/java/test/ReturnValueSampleTest.java
new file mode 100644
index 0000000..dc15468
--- /dev/null
+++ b/src/test/java/test/ReturnValueSampleTest.java
@@ -0,0 +1,11 @@
+package test;
+
+import org.testng.annotations.Test;
+
+public class ReturnValueSampleTest {
+
+  @Test
+  public int shouldRun() {
+    return 0;
+  }
+}
diff --git a/src/test/java/test/ReturnValueTest.java b/src/test/java/test/ReturnValueTest.java
new file mode 100644
index 0000000..51e3aab
--- /dev/null
+++ b/src/test/java/test/ReturnValueTest.java
@@ -0,0 +1,59 @@
+package test;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import java.util.Arrays;
+
+public class ReturnValueTest extends SimpleBaseTest {
+
+  private XmlSuite m_suite;
+  private XmlTest m_test;
+
+  @BeforeMethod
+  public void before() {
+    m_suite = createXmlSuite("suite");
+    m_test = createXmlTest(m_suite, "test", ReturnValueSampleTest.class.getName());
+  }
+
+  @Test
+  public void suiteReturnValueTestShouldBeRun() {
+    m_suite.setAllowReturnValues(true);
+    runTest(true);
+  }
+  
+  @Test
+  public void suiteReturnValueTestShouldNotBeRun() {
+    runTest(false);
+  }
+
+  @Test
+  public void testReturnValueTestShouldBeRun() {
+    m_test.setAllowReturnValues(true);
+    runTest(true);
+  }
+  
+  private void runTest(boolean allowed) {
+    TestNG tng = create();
+    tng.setXmlSuites(Arrays.asList(m_suite));
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+    if (allowed) {
+      Assert.assertEquals(tla.getFailedTests().size(), 0);
+      Assert.assertEquals(tla.getSkippedTests().size(), 0);
+      assertTestResultsEqual(tla.getPassedTests(), Arrays.asList("shouldRun"));
+    } else {
+      Assert.assertEquals(tla.getFailedTests().size(), 0);
+      Assert.assertEquals(tla.getPassedTests().size(), 0);
+      Assert.assertEquals(tla.getSkippedTests().size(), 0);
+    }
+  }
+  
+}
diff --git a/src/test/java/test/SampleInheritance.java b/src/test/java/test/SampleInheritance.java
new file mode 100644
index 0000000..d5cd6ad
--- /dev/null
+++ b/src/test/java/test/SampleInheritance.java
@@ -0,0 +1,45 @@
+package test;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import test.sample.BaseSampleInheritance;
+
+public class SampleInheritance extends BaseSampleInheritance {
+
+  // Test dependency of configuration methods
+  @BeforeClass(groups= { "configuration0"})
+  public void configuration0() {
+    addConfiguration("configuration0");
+//    System.out.println("CONFIGURATION 0");
+  }
+
+  @BeforeClass(dependsOnGroups = { "configuration1"})
+  public void configuration2() {
+    assert m_configurations.size() == 2
+	: "Expected size 2 found " + m_configurations.size();
+    assert "configuration0".equals(m_configurations.get(0))
+	: "Expected configuration0 to be run";
+    assert "configuration1".equals(m_configurations.get(1))
+	: "Expected configuration1 to be run";
+    addConfiguration("configuration2");
+  }
+
+  @Test(dependsOnGroups = { "inheritedTestMethod"} )
+  public void inheritedMethodsWereCalledInOrder() {
+    assert m_invokedBaseMethod : "Didn't invoke test method in base class";
+    assert m_invokedBaseConfiguration : "Didn't invoke configuration method in base class";
+
+  }
+
+  @Test
+  public void configurationsWereCalledInOrder() {
+    assert m_configurations.size() == 3;
+    assert "configuration0".equals(m_configurations.get(0))
+	: "Expected configuration0 to be run";
+    assert "configuration1".equals(m_configurations.get(1))
+	: "Expected configuration1 to be run";
+    assert "configuration2".equals(m_configurations.get(2))
+	: "Expected configuration1 to be run";
+  }
+}
diff --git a/src/test/java/test/SerializationTest.java b/src/test/java/test/SerializationTest.java
new file mode 100644
index 0000000..dba8f7a
--- /dev/null
+++ b/src/test/java/test/SerializationTest.java
@@ -0,0 +1,27 @@
+package test;
+
+import org.testng.ITestContext;
+import org.testng.annotations.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
+public class SerializationTest {
+
+  @Test(groups = "broken")
+  public void iSuiteShouldBeSerializable(ITestContext context) throws IOException {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    ObjectOutputStream oos = new ObjectOutputStream(out);
+    oos.writeObject(context.getSuite());
+    oos.close();
+  }
+
+  @Test(groups = { "broken", "maven-broken" })
+  public void testngMethodShouldBeSerializable(ITestContext context) throws IOException {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    ObjectOutputStream oos = new ObjectOutputStream(out);
+    oos.writeObject(context.getAllTestMethods()[0]);
+    oos.close();
+  }
+}
diff --git a/src/test/java/test/SimpleBaseTest.java b/src/test/java/test/SimpleBaseTest.java
new file mode 100644
index 0000000..92c7d89
--- /dev/null
+++ b/src/test/java/test/SimpleBaseTest.java
@@ -0,0 +1,121 @@
+package test;
+
+import org.testng.Assert;
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.collections.Lists;
+import org.testng.internal.Utils;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlInclude;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+public class SimpleBaseTest {
+  // System property specifying where the resources (e.g. xml files) can be found
+  private static final String TEST_RESOURCES_DIR = "test.resources.dir";
+
+  public static TestNG create() {
+    TestNG result = new TestNG();
+    result.setUseDefaultListeners(false);
+    result.setVerbose(0);
+    return result;
+  }
+
+  public static TestNG create(Class<?>... testClasses) {
+    TestNG result = create();
+    result.setTestClasses(testClasses);
+    return result;
+  }
+
+  protected TestNG create(XmlSuite... suites) {
+    TestNG result = create();
+    result.setXmlSuites(Arrays.asList(suites));
+    return result;
+  }
+
+  protected TestNG createTests(String suiteName, Class<?>... testClasses) {
+    XmlSuite suite = createXmlSuite(suiteName);
+    int i=0;
+    for (Class<?> testClass : testClasses) {
+      createXmlTest(suite, testClass.getName() + i, testClass);
+      i++;
+    }
+    return create(suite);
+  }
+
+  protected XmlSuite createXmlSuite(String name) {
+    XmlSuite result = new XmlSuite();
+    result.setName(name);
+    return result;
+  }
+
+  protected XmlTest createXmlTest(XmlSuite suite, String name, Class clazz, Class... classes) {
+    XmlTest result = new XmlTest(suite);
+    int index = 0;
+    result.setName(name);
+    XmlClass xc = new XmlClass(clazz.getName(), index++, true /* load classes */);
+    result.getXmlClasses().add(xc);
+    for (Class c : classes) {
+      xc = new XmlClass(c.getName(), index++, true /* load classes */);
+      result.getXmlClasses().add(xc);
+    }
+
+    return result;
+  }
+
+  protected XmlTest createXmlTest(XmlSuite suite, String name, String... classes) {
+    XmlTest result = new XmlTest(suite);
+    int index = 0;
+    result.setName(name);
+    for (String c : classes) {
+      XmlClass xc = new XmlClass(c, index++, true /* load classes */);
+      result.getXmlClasses().add(xc);
+    }
+
+    return result;
+  }
+
+  protected void addMethods(XmlClass cls, String... methods) {
+    int index = 0;
+    for (String m : methods) {
+      XmlInclude include = new XmlInclude(m, index++);
+      cls.getIncludedMethods().add(include);
+    }
+  }
+
+  public static String getPathToResource(String fileName) {
+    String result = System.getProperty(TEST_RESOURCES_DIR);
+    if (result == null) {
+      throw new IllegalArgumentException("System property " + TEST_RESOURCES_DIR + " was not defined.");
+    }
+    return result + File.separatorChar + fileName;
+  }
+
+  protected void verifyPassedTests(TestListenerAdapter tla, String... methodNames) {
+    Iterator<ITestResult> it = tla.getPassedTests().iterator();
+    Assert.assertEquals(tla.getPassedTests().size(), methodNames.length);
+
+    int i = 0;
+    while (it.hasNext()) {
+      Assert.assertEquals(it.next().getName(), methodNames[i++]);
+    }
+  }
+
+  /**
+   * Compare a list of ITestResult with a list of String method names,
+   */
+  public static void assertTestResultsEqual(List<ITestResult> results, List<String> methods) {
+    List<String> resultMethods = Lists.newArrayList();
+    for (ITestResult r : results) {
+      resultMethods.add(r.getMethod().getMethodName());
+    }
+    Assert.assertEquals(resultMethods, methods);
+  }
+
+}
diff --git a/src/test/java/test/StaticTest.java b/src/test/java/test/StaticTest.java
new file mode 100644
index 0000000..898b20d
--- /dev/null
+++ b/src/test/java/test/StaticTest.java
@@ -0,0 +1,16 @@
+package test;
+
+import org.testng.annotations.Test;
+
+/**
+ * This used to create a StackOverflowError.
+ */
+public class StaticTest {
+  @Test
+  public void test() {
+  }
+
+  @Test
+  public static class InnerStaticClass extends StaticTest {
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/Test1.java b/src/test/java/test/Test1.java
new file mode 100644
index 0000000..ecf825a
--- /dev/null
+++ b/src/test/java/test/Test1.java
@@ -0,0 +1,143 @@
+package test;
+
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class Test1 extends BaseTest {
+
+  /**
+   * This constructor is package protected on purpose, to test that
+   * TestNG can still instantiate the class.
+   */
+  Test1() {}
+
+  @Test(groups = { "current" })
+  public void includedGroups() {
+    addClass("test.sample.Sample1");
+    assert 1 == getTest().getXmlClasses().size();
+    addIncludedGroup("odd");
+    run();
+    String[] passed = {
+      "method1", "method3",
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void groupsOfGroupsSimple() {
+    addClass("test.sample.Sample1");
+    assert 1 == getTest().getXmlClasses().size();
+    // should match all methods belonging to group "odd" and "even"
+    addIncludedGroup("evenodd");
+    List l = new ArrayList<>();
+    l.add("even");
+    l.add("odd");
+    addMetaGroup("evenodd", l);
+    run();
+   String passed[] = {
+    "method1", "method2", "method3",
+   };
+  String[] failed = {
+  };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void groupsOfGroupsWithIndirections() {
+    addClass("test.sample.Sample1");
+    addIncludedGroup("all");
+    List l = new ArrayList<>();
+    l.add("methods");
+    l.add("broken");
+    addMetaGroup("all", l);
+    l = new ArrayList<>();
+    l.add("odd");
+    l.add("even");
+    addMetaGroup("methods", l);
+    addMetaGroup("broken", "broken");
+    run();
+    String[] passed = {
+      "method1", "method2", "method3", "broken"
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void groupsOfGroupsWithCycle() {
+    addClass("test.sample.Sample1");
+    addIncludedGroup("all");
+    addMetaGroup("all", "all2");
+    addMetaGroup("all2", "methods");
+    addMetaGroup("methods", "all");
+    run();
+    String[] passed = {
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test // (groups = { "one" })
+  public void excludedGroups() {
+    addClass("test.sample.Sample1");
+    addExcludedGroup("odd");
+    run();
+   String passed[] = {
+    "method2",
+    "broken", "throwExpectedException1ShouldPass",
+    "throwExpectedException2ShouldPass"
+   };
+  String[] failed = {
+      "throwExceptionShouldFail", "verifyLastNameShouldFail"
+  };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void regexp() {
+    addClass("test.sample.Sample1");
+    // should matches all methods belonging to group "odd"
+    addIncludedGroup("o.*");
+    run();
+   String passed[] = {
+      "method1", "method3"
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test(groups = { "currentold" })
+  public void logger() {
+    Logger logger = Logger.getLogger("");
+//    System.out.println("# HANDLERS:" + logger.getHandlers().length);
+    for (Handler handler : logger.getHandlers())
+    {
+      handler.setLevel(Level.WARNING);
+      handler.setFormatter(new org.testng.log.TextFormatter());
+    }
+    logger.setLevel(Level.SEVERE);
+  }
+
+  static public void ppp(String s) {
+    System.out.println("[Test1] " + s);
+  }
+
+} // Test1
+
+
diff --git a/src/test/java/test/Test2.java b/src/test/java/test/Test2.java
new file mode 100644
index 0000000..94acc91
--- /dev/null
+++ b/src/test/java/test/Test2.java
@@ -0,0 +1,87 @@
+package test;
+
+import org.testng.Assert;
+import org.testng.annotations.Configuration;
+import org.testng.annotations.Test;
+
+
+/**
+ *
+ * @author Cedric Beust, May 5, 2004
+ *
+ */
+public class Test2 extends BaseTest {
+  private boolean m_initializedCorrectly = false;
+
+  @Configuration(beforeTestMethod = true)
+//  @BeforeMethod
+  public void correctSetup() {
+    m_initializedCorrectly = true;
+  }
+
+  // Shouldn't be called
+  @Configuration(beforeTestMethod = true, groups = "excludeThisGroup")
+//  @BeforeMethod(groups = { "excludeThisGroup"} )
+  public void incorrectSetup() {
+    throw new RuntimeException("Should never be run");
+  }
+
+  @Test
+  public void noGroups() {
+    addClass("test.sample.Sample1");
+    run();
+    String[] passed = {
+      "method1",
+      "method2", "method3",
+      "broken", "throwExpectedException1ShouldPass",
+      "throwExpectedException2ShouldPass"
+    };
+    String[] failed = {
+        "throwExceptionShouldFail", "verifyLastNameShouldFail"
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void setUpWithGroups() {
+    run();
+    Assert.assertTrue(m_initializedCorrectly, "Should have run the correctSetup method");
+  }
+
+  @Test
+  public void partialGroupsClass() {
+    addClass("test.sample.PartialGroupTest");
+    addIncludedGroup("classGroup");
+    run();
+    String[] passed = {
+        "testMethodGroup", "testClassGroup"
+      };
+      String[] failed = {
+          "testMethodGroupShouldFail", "testClassGroupShouldFail"
+      };
+      verifyTests("Passed", passed, getPassedTests());
+      verifyTests("Failed", failed, getFailedTests());
+
+//      ppp("@@@@@@@@@@@@@@@@@@ PASSED TESTS");
+//      for (Object o : getPassedTests().values()) {
+//        ppp("@@@@@@@@@@ PASSED:" + o);
+//      }
+  }
+
+  @Test
+  public void partialGroupsMethod() {
+    addClass("test.sample.PartialGroupTest");
+    addIncludedGroup("methodGroup");
+    run();
+    String[] passed = {
+        "testMethodGroup",
+      };
+      String[] failed = {
+         "testMethodGroupShouldFail"
+      };
+      verifyTests("Passed", passed, getPassedTests());
+      verifyTests("Failed", failed, getFailedTests());
+  }
+
+}
diff --git a/src/test/java/test/TestHelper.java b/src/test/java/test/TestHelper.java
new file mode 100644
index 0000000..3e2ebf8
--- /dev/null
+++ b/src/test/java/test/TestHelper.java
@@ -0,0 +1,80 @@
+package test;
+
+import org.testng.TestNG;
+import org.testng.collections.Lists;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class TestHelper {
+
+  public static XmlSuite createSuite(String cls, String suiteName) {
+    return createSuite(cls, suiteName, "TmpTest");
+  }
+
+  public static XmlSuite createSuite(Class<?> cls, String suiteName, String testName) {
+    return createSuite(cls.getName(), suiteName, testName);
+  }
+
+  private static XmlSuite createSuite(String cls, String suiteName, String testName) {
+    XmlSuite result = new XmlSuite();
+    result.setName(suiteName);
+
+    XmlTest test = new XmlTest(result);
+    test.setName(testName);
+    List<XmlClass> classes = new ArrayList<>();
+    classes.add(new XmlClass(cls));
+    test.setXmlClasses(classes);
+
+    return result;
+  }
+
+  public static TestNG createTestNG(String outputDir) {
+    return createTestNG(null, outputDir);
+  }
+
+  public static TestNG createTestNG() {
+    return createTestNG(null, null);
+  }
+
+  public static TestNG createTestNG(XmlSuite suite) {
+    return createTestNG(suite, System.getProperty("java.io.tmpdir"));
+  }
+
+  public static TestNG createTestNG(XmlSuite suite, String outputDir) {
+    TestNG result = new TestNG();
+    if (suite != null) {
+      List<XmlSuite> suites = Lists.newArrayList();
+      suites.add(suite);
+      result.setXmlSuites(suites);
+    }
+    if (outputDir == null) {
+      outputDir = createRandomDirectory().getAbsolutePath();
+    }
+    result.setOutputDirectory(outputDir);
+    result.setVerbose(-1);
+
+    return result;
+  }
+
+  public static File createRandomDirectory() {
+    String dir = System.getProperty("java.io.tmpdir");
+    Random r = new Random(System.currentTimeMillis());
+    String name = "testng-tmp-" + r.nextInt();
+    File result = new File(dir + File.separatorChar + name);
+    result.deleteOnExit();
+    result.mkdirs();
+
+    return result;
+  }
+
+  private static void ppp(String string) {
+    System.out.println("[TestHelper] " + string);
+  }
+
+}
diff --git a/src/test/java/test/abstractconfmethod/B.java b/src/test/java/test/abstractconfmethod/B.java
new file mode 100644
index 0000000..1db108c
--- /dev/null
+++ b/src/test/java/test/abstractconfmethod/B.java
@@ -0,0 +1,12 @@
+package test.abstractconfmethod;
+
+import org.testng.annotations.BeforeMethod;
+
+import test.abstractconfmethod.foo.A;
+
+public abstract class B extends A
+{
+   @BeforeMethod(dependsOnMethods = {"testSetup"})
+   public void doSomethingInMiddle() {}
+
+}
diff --git a/src/test/java/test/abstractconfmethod/C.java b/src/test/java/test/abstractconfmethod/C.java
new file mode 100644
index 0000000..e9b47e0
--- /dev/null
+++ b/src/test/java/test/abstractconfmethod/C.java
@@ -0,0 +1,14 @@
+package test.abstractconfmethod;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class C extends B
+{
+   @Override
+  @BeforeMethod
+   public void testSetup() {}
+
+   @Test(description="Test depends on a config method that has implements an abstract methods")
+   public void test1() {}
+}
diff --git a/src/test/java/test/abstractconfmethod/foo/A.java b/src/test/java/test/abstractconfmethod/foo/A.java
new file mode 100644
index 0000000..f29c3d7
--- /dev/null
+++ b/src/test/java/test/abstractconfmethod/foo/A.java
@@ -0,0 +1,6 @@
+package test.abstractconfmethod.foo;
+
+public abstract class A
+{
+   public abstract void testSetup();
+}
diff --git a/src/test/java/test/abstractmethods/AbstractTest.java b/src/test/java/test/abstractmethods/AbstractTest.java
new file mode 100644
index 0000000..f0268c4
--- /dev/null
+++ b/src/test/java/test/abstractmethods/AbstractTest.java
@@ -0,0 +1,20 @@
+package test.abstractmethods;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import test.SimpleBaseTest;
+
+public class AbstractTest extends SimpleBaseTest {
+
+  @Test(description = "Abstract methods defined in a superclass should be run")
+  public void abstractShouldRun() {
+    TestNG tng = create(CRUDTest2.class);
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+    Assert.assertEquals(tla.getPassedTests().size(), 2);
+  }
+}
diff --git a/src/test/java/test/abstractmethods/CRUDTest.java b/src/test/java/test/abstractmethods/CRUDTest.java
new file mode 100644
index 0000000..f81f3d8
--- /dev/null
+++ b/src/test/java/test/abstractmethods/CRUDTest.java
@@ -0,0 +1,12 @@
+package test.abstractmethods;
+
+import org.testng.annotations.Test;
+
+abstract public class CRUDTest {
+
+  @Test
+  public abstract void create();
+
+  @Test(dependsOnMethods = "create")
+  public abstract void read();
+}
diff --git a/src/test/java/test/abstractmethods/CRUDTest2.java b/src/test/java/test/abstractmethods/CRUDTest2.java
new file mode 100644
index 0000000..713f6be
--- /dev/null
+++ b/src/test/java/test/abstractmethods/CRUDTest2.java
@@ -0,0 +1,13 @@
+package test.abstractmethods;
+
+public class CRUDTest2 extends CRUDTest {
+
+  @Override
+  public void create() {
+  }
+
+  @Override
+  public void read() {
+  }
+
+}
diff --git a/src/test/java/test/access/BasePrivateAccessConfigurationMethods.java b/src/test/java/test/access/BasePrivateAccessConfigurationMethods.java
new file mode 100644
index 0000000..e9e2d0f
--- /dev/null
+++ b/src/test/java/test/access/BasePrivateAccessConfigurationMethods.java
@@ -0,0 +1,24 @@
+package test.access;
+
+import org.testng.annotations.BeforeMethod;
+
+public class BasePrivateAccessConfigurationMethods {
+  protected boolean m_baseProtected = false;
+  protected boolean m_baseDefault = false;
+  protected boolean m_basePrivate = true;
+
+  @BeforeMethod
+  void baseDefaultConfBeforeMethod() {
+    m_baseDefault = true;
+  }
+
+  @BeforeMethod
+  protected void baseProtectedConfBeforeMethod() {
+    m_baseProtected = true;
+  }
+
+  @BeforeMethod
+  private void basePrivateConfBeforeMethod() {
+    m_basePrivate = false;
+  }
+}
diff --git a/src/test/java/test/access/PrivateAccessConfigurationMethods.java b/src/test/java/test/access/PrivateAccessConfigurationMethods.java
new file mode 100644
index 0000000..d15477e
--- /dev/null
+++ b/src/test/java/test/access/PrivateAccessConfigurationMethods.java
@@ -0,0 +1,52 @@
+package test.access;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ * Test that private and protected @Configuration methods are run
+ *
+ * @author cbeust
+ */
+public class PrivateAccessConfigurationMethods
+  extends BasePrivateAccessConfigurationMethods
+{
+  private boolean m_private = false;
+  private boolean m_default = false;
+  private boolean m_protected = false;
+  private boolean m_public = false;
+
+  @BeforeMethod
+  private void privateConfBeforeMethod() {
+    m_private = true;
+  }
+
+  @BeforeMethod
+  void defaultConfBeforeMethod() {
+    m_default = true;
+  }
+
+  @BeforeMethod
+  protected void protectedConfBeforeMethod() {
+    m_protected = true;
+  }
+
+  @BeforeMethod
+  public void publicConfBeforeMethod() {
+    m_public = true;
+  }
+
+  @Test
+  public void allAccessModifiersConfiguration() {
+    Assert.assertTrue(m_private, "private @Configuration should have been run");
+    Assert.assertTrue(m_default, "default @Configuration should have been run");
+    Assert.assertTrue(m_protected, "protected @Configuration should have been run");
+    Assert.assertTrue(m_public, "public @Configuration should have been run");
+
+    Assert.assertTrue(m_baseProtected, "protected base @Configuration should have been run");
+    Assert.assertTrue(m_baseDefault, "default base @Configuration should have been run");
+    Assert.assertTrue(m_basePrivate, "private base @Configuration should not have been run");
+
+  }
+}
diff --git a/src/test/java/test/alwaysrun/AlwaysRunAfter1.java b/src/test/java/test/alwaysrun/AlwaysRunAfter1.java
new file mode 100644
index 0000000..dcb0e20
--- /dev/null
+++ b/src/test/java/test/alwaysrun/AlwaysRunAfter1.java
@@ -0,0 +1,29 @@
+package test.alwaysrun;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class AlwaysRunAfter1 {
+  private static boolean m_success = false;
+
+  @BeforeClass
+  public void setUpShouldFail() {
+    throw new RuntimeException("Failing in setUp");
+  }
+
+  @AfterClass(alwaysRun = true)
+  public void tearDown() {
+    m_success = true;
+  }
+
+  // Adding this method or @Configuration will never be invoked
+  @Test
+  public void dummy() {
+
+  }
+
+  static public boolean success() {
+    return m_success;
+  }
+}
diff --git a/src/test/java/test/alwaysrun/AlwaysRunAfter2.java b/src/test/java/test/alwaysrun/AlwaysRunAfter2.java
new file mode 100644
index 0000000..75a2363
--- /dev/null
+++ b/src/test/java/test/alwaysrun/AlwaysRunAfter2.java
@@ -0,0 +1,24 @@
+package test.alwaysrun;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+
+public class AlwaysRunAfter2 {
+
+  private static boolean m_success = true;
+
+  @BeforeClass
+  public void setUpShouldFail() {
+    throw new RuntimeException("Failing in setUp");
+  }
+
+  // Should not be run
+  @AfterClass
+  public void tearDown() {
+    m_success = false;
+  }
+
+  static public boolean success() {
+    return m_success;
+  }
+}
diff --git a/src/test/java/test/alwaysrun/AlwaysRunBefore1.java b/src/test/java/test/alwaysrun/AlwaysRunBefore1.java
new file mode 100644
index 0000000..395d532
--- /dev/null
+++ b/src/test/java/test/alwaysrun/AlwaysRunBefore1.java
@@ -0,0 +1,56 @@
+package test.alwaysrun;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+/**
+ * Tests alwaysRun on a before Configuration method.  Invoke this test
+ * by running group "A"
+ *
+ * @author cbeust
+ * @date Mar 11, 2006
+ */
+public class AlwaysRunBefore1 {
+  private static boolean m_beforeSuiteSuccess = false;
+  private static boolean m_beforeTestSuccess = false;
+  private static boolean m_beforeTestClassSuccess = false;
+  private static boolean m_beforeTestMethodSuccess = false;
+
+  @BeforeSuite(alwaysRun = true)
+  public void initSuite() {
+    m_beforeSuiteSuccess = true;
+  }
+
+  @BeforeTest(alwaysRun = true)
+  public void initTest() {
+    m_beforeTestSuccess = true;
+  }
+
+  @BeforeClass(alwaysRun = true)
+  public void initTestClass() {
+    m_beforeTestClassSuccess = true;
+  }
+
+  @BeforeMethod(alwaysRun = true)
+  public void initTestMethod() {
+    m_beforeTestMethodSuccess = true;
+  }
+
+  @Test(groups = "A")
+  public void foo() {
+    Assert.assertTrue(m_beforeSuiteSuccess);
+    Assert.assertTrue(m_beforeTestSuccess);
+    Assert.assertTrue(m_beforeTestClassSuccess);
+    Assert.assertTrue(m_beforeTestMethodSuccess);
+  }
+
+  public static boolean success() {
+    return m_beforeSuiteSuccess && m_beforeTestSuccess &&
+      m_beforeTestClassSuccess && m_beforeTestMethodSuccess;
+  }
+
+}
diff --git a/src/test/java/test/alwaysrun/AlwaysRunTest.java b/src/test/java/test/alwaysrun/AlwaysRunTest.java
new file mode 100644
index 0000000..0fbd14a
--- /dev/null
+++ b/src/test/java/test/alwaysrun/AlwaysRunTest.java
@@ -0,0 +1,51 @@
+package test.alwaysrun;
+
+import static org.testng.Assert.assertTrue;
+
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+import testhelper.OutputDirectoryPatch;
+
+public class AlwaysRunTest extends SimpleBaseTest {
+
+  @Test
+  public void withAlwaysRunAfter() {
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG testng = create();
+    testng.setOutputDirectory(OutputDirectoryPatch.getOutputDirectory());
+    testng.setTestClasses(new Class[] { AlwaysRunAfter1.class });
+    testng.addListener(tla);
+    testng.run();
+    assertTrue(AlwaysRunAfter1.success(), "afterTestMethod should have run");
+  }
+
+  @Test
+  public void withoutAlwaysRunAfter() {
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG testng = create();
+    testng.setOutputDirectory(OutputDirectoryPatch.getOutputDirectory());
+    testng.setTestClasses(new Class[] { AlwaysRunAfter2.class });
+    testng.addListener(tla);
+    testng.run();
+    assertTrue(AlwaysRunAfter2.success(), "afterTestMethod should not have run");
+  }
+
+  @Test
+  public void withoutAlwaysRunBefore() {
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG testng = create();
+    testng.setOutputDirectory(OutputDirectoryPatch.getOutputDirectory());
+    testng.setTestClasses(new Class[] { AlwaysRunBefore1.class });
+    testng.setGroups("A");
+    testng.addListener(tla);
+    testng.run();
+    assertTrue(AlwaysRunBefore1.success(), "before alwaysRun methods should have been run");
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[AlwaysRunTest] " + s);
+  }
+}
diff --git a/src/test/java/test/annotationtransformer/AnnotationTransformerClassInvocationSampleTest.java b/src/test/java/test/annotationtransformer/AnnotationTransformerClassInvocationSampleTest.java
new file mode 100644
index 0000000..7673ae6
--- /dev/null
+++ b/src/test/java/test/annotationtransformer/AnnotationTransformerClassInvocationSampleTest.java
@@ -0,0 +1,11 @@
+package test.annotationtransformer;
+
+import org.testng.annotations.Test;
+
+@Test(invocationCount = 3)
+public class AnnotationTransformerClassInvocationSampleTest {
+
+  public void f1() {}
+
+  public void f2() {}
+}
diff --git a/src/test/java/test/annotationtransformer/AnnotationTransformerClassSampleTest.java b/src/test/java/test/annotationtransformer/AnnotationTransformerClassSampleTest.java
new file mode 100644
index 0000000..94b5f6d
--- /dev/null
+++ b/src/test/java/test/annotationtransformer/AnnotationTransformerClassSampleTest.java
@@ -0,0 +1,33 @@
+package test.annotationtransformer;
+
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+/**
+ * This test will fail unless a time out transformer
+ * is applied to it.
+ *
+ * @author cbeust
+ *
+ */
+@Test(timeOut = 1000)
+@Listeners(MySuiteListener.class)
+public class AnnotationTransformerClassSampleTest {
+
+  public void one() {
+    try {
+      Thread.sleep(2000);
+//      ppp("FINISHED SLEEPING");
+    }
+    catch (InterruptedException handled) {
+      Thread.currentThread().interrupt();
+//      ppp("WAS INTERRUPTED");
+      // ignore
+    }
+  }
+
+  private void ppp(String string) {
+    System.out.println("[Transformer] " + string);
+  }
+
+}
diff --git a/src/test/java/test/annotationtransformer/AnnotationTransformerDataProviderSampleTest.java b/src/test/java/test/annotationtransformer/AnnotationTransformerDataProviderSampleTest.java
new file mode 100644
index 0000000..d976126
--- /dev/null
+++ b/src/test/java/test/annotationtransformer/AnnotationTransformerDataProviderSampleTest.java
@@ -0,0 +1,20 @@
+package test.annotationtransformer;
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class AnnotationTransformerDataProviderSampleTest {
+
+  @DataProvider
+  public Object[][] dp() {
+    return new Integer[][] {
+        new Integer[] { 42 },
+    };
+  }
+
+  @Test(dataProvider = "dataProvider")
+  public void f(Integer n) {
+    Assert.assertEquals(n, Integer.valueOf(42));
+  }
+}
diff --git a/src/test/java/test/annotationtransformer/AnnotationTransformerFactorySampleTest.java b/src/test/java/test/annotationtransformer/AnnotationTransformerFactorySampleTest.java
new file mode 100644
index 0000000..50956ad
--- /dev/null
+++ b/src/test/java/test/annotationtransformer/AnnotationTransformerFactorySampleTest.java
@@ -0,0 +1,20 @@
+package test.annotationtransformer;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Factory;
+
+public class AnnotationTransformerFactorySampleTest {
+  @DataProvider
+  public Object[][] dataProvider() {
+    return new Integer[][] {
+        new Integer[] { 42 },
+    };
+  }
+
+  @Factory(dataProvider = "dp")
+  public Object[] init(int n) {
+    return new Object[] {
+        new SimpleTest(n)
+    };
+  }
+}
diff --git a/src/test/java/test/annotationtransformer/AnnotationTransformerInTestngXml.java b/src/test/java/test/annotationtransformer/AnnotationTransformerInTestngXml.java
new file mode 100644
index 0000000..1fda328
--- /dev/null
+++ b/src/test/java/test/annotationtransformer/AnnotationTransformerInTestngXml.java
@@ -0,0 +1,20 @@
+package test.annotationtransformer;
+
+import org.testng.IAnnotationTransformer;
+import org.testng.annotations.ITestAnnotation;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+public class AnnotationTransformerInTestngXml implements IAnnotationTransformer {
+
+  @Test(enabled = false)
+  public void shouldRunAfterTransformation() {}
+
+  @Override
+  public void transform(ITestAnnotation annotation, Class testClass,
+      Constructor testConstructor, Method testMethod) {
+    annotation.setEnabled(true);
+  }
+}
diff --git a/src/test/java/test/annotationtransformer/AnnotationTransformerInvocationCountTest.java b/src/test/java/test/annotationtransformer/AnnotationTransformerInvocationCountTest.java
new file mode 100644
index 0000000..9557408
--- /dev/null
+++ b/src/test/java/test/annotationtransformer/AnnotationTransformerInvocationCountTest.java
@@ -0,0 +1,32 @@
+package test.annotationtransformer;
+
+import org.testng.IAnnotationTransformer;
+import org.testng.annotations.ITestAnnotation;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+public class AnnotationTransformerInvocationCountTest {
+
+  public static class InvocationCountTransformer implements IAnnotationTransformer {
+
+    private final int invocationCount;
+
+    public InvocationCountTransformer(int invocationCount) {
+      this.invocationCount = invocationCount;
+    }
+
+    @Override
+    public void transform(ITestAnnotation annotation, Class testClass,
+                          Constructor testConstructor, Method testMethod) {
+      if ("concurrencyTest".equals(testMethod.getName())) {
+        annotation.setInvocationCount(invocationCount);
+      }
+    }
+  }
+
+  @Test(invocationCount = 3)
+  public void concurrencyTest() {
+  }
+}
diff --git a/src/test/java/test/annotationtransformer/AnnotationTransformerSampleTest.java b/src/test/java/test/annotationtransformer/AnnotationTransformerSampleTest.java
new file mode 100644
index 0000000..e930be7
--- /dev/null
+++ b/src/test/java/test/annotationtransformer/AnnotationTransformerSampleTest.java
@@ -0,0 +1,41 @@
+package test.annotationtransformer;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+public class AnnotationTransformerSampleTest {
+
+  private int m_two = 0;
+  private int m_five = 0;
+  private int m_three = 0;
+  private int m_four = 0;
+
+  @Test(invocationCount = 2)
+  public void two() {
+    m_two++;
+  }
+
+  @Test(invocationCount = 5)
+  public void four() {
+    m_four++;
+  }
+
+  @Test(invocationCount = 5)
+  public void three() {
+    m_three++;
+  }
+
+  @Test
+  public void five() {
+    m_five++;
+  }
+
+  @Test(dependsOnMethods = {"two", "three", "four", "five"})
+  public void verify() {
+    Assert.assertEquals(m_two, 2);
+    Assert.assertEquals(m_three, 3);
+    Assert.assertEquals(m_four, 4);
+    Assert.assertEquals(m_five, 5);
+  }
+}
diff --git a/src/test/java/test/annotationtransformer/AnnotationTransformerTest.java b/src/test/java/test/annotationtransformer/AnnotationTransformerTest.java
new file mode 100644
index 0000000..5500a6d
--- /dev/null
+++ b/src/test/java/test/annotationtransformer/AnnotationTransformerTest.java
@@ -0,0 +1,252 @@
+package test.annotationtransformer;
+
+import org.assertj.core.api.iterable.Extractor;
+import org.testng.Assert;
+import org.testng.IAnnotationTransformer;
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.Parser;
+import org.testng.xml.XmlSuite;
+
+import test.SimpleBaseTest;
+
+import java.io.ByteArrayInputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AnnotationTransformerTest extends SimpleBaseTest {
+
+  private static final Extractor NAME_EXTRACTOR = new Extractor<ITestResult, String>() {
+    @Override
+    public String extract(ITestResult input) {
+      return input.getName();
+    }
+  };
+
+  /**
+   * Make sure that without a transformer in place, a class-level
+   * annotation invocationCount is correctly used.
+   */
+  @Test
+  public void verifyAnnotationWithoutTransformer() {
+    TestNG tng = create(AnnotationTransformerSampleTest.class);
+    tng.setPreserveOrder(true);
+
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+
+    tng.run();
+
+    assertThat(tla.getPassedTests()).extracting(NAME_EXTRACTOR)
+        .containsExactly(
+            "five",
+            "four", "four", "four", "four", "four",
+            "three", "three", "three", "three", "three",
+            "two", "two"
+        );
+    assertThat(tla.getFailedTests()).extracting(NAME_EXTRACTOR)
+        .containsExactly("verify");
+  }
+
+  /**
+   * Test a transformer on a method-level @Test
+   */
+  @Test
+  public void verifyAnnotationTransformerMethod() {
+    TestNG tng = create(AnnotationTransformerSampleTest.class);
+    tng.setPreserveOrder(true);
+
+    MyTransformer transformer = new MyTransformer();
+    tng.setAnnotationTransformer(transformer);
+
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+
+    tng.run();
+
+    assertThat(transformer.getMethodNames()).contains("two", "three", "four", "five", "verify");
+
+    assertThat(tla.getPassedTests()).extracting(NAME_EXTRACTOR)
+        .containsExactly(
+            "five", "five", "five", "five", "five",
+            "four", "four", "four", "four",
+            "three", "three", "three",
+            "two", "two",
+            "verify"
+        );
+    assertThat(tla.getFailedTests()).isEmpty();
+  }
+
+  @Test
+  public void verifyAnnotationTransformerHasOnlyOneNonNullArgument() {
+    TestNG tng = create(AnnotationTransformerSampleTest.class);
+
+    MyParamTransformer transformer = new MyParamTransformer();
+    tng.setAnnotationTransformer(transformer);
+
+    tng.run();
+
+    assertThat(transformer.isSuccess()).isTrue();
+  }
+
+  @Test
+  public void verifyMyParamTransformerOnlyOneNonNull() {
+    assertThat(MyParamTransformer.onlyOneNonNull(null, null, null)).isFalse();
+    assertThat(MyParamTransformer.onlyOneNonNull(
+        MyParamTransformer.class, MyParamTransformer.class.getConstructors()[0], null)).isFalse();
+    assertThat(MyParamTransformer.onlyOneNonNull(MyParamTransformer.class, null, null)).isTrue();
+  }
+
+  /**
+   * Without an annotation transformer, we should have zero
+   * passed tests and one failed test called "one".
+   */
+  @Test
+  public void verifyAnnotationTransformerClass2() {
+    runTest(null, null, "one");
+  }
+
+  /**
+   * With an annotation transformer, we should have one passed
+   * test called "one" and zero failed tests.
+   */
+  @Test
+  public void verifyAnnotationTransformerClass() {
+    runTest(new MyTimeOutTransformer(), "one", null);
+  }
+
+  private void runTest(IAnnotationTransformer transformer,
+      String passedName, String failedName)
+  {
+    MySuiteListener.triggered = false;
+    MySuiteListener2.triggered = false;
+    TestNG tng = new TestNG();
+    tng.setVerbose(0);
+    if (transformer != null) {
+      tng.setAnnotationTransformer(transformer);
+    }
+    tng.setTestClasses(new Class[] { AnnotationTransformerClassSampleTest.class});
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+
+    tng.run();
+
+    List<ITestResult> results =
+      passedName != null ? tla.getPassedTests() : tla.getFailedTests();
+    String name = passedName != null ? passedName : failedName;
+
+    Assert.assertEquals(results.size(), 1);
+    Assert.assertEquals(name, results.get(0).getMethod().getMethodName());
+    Assert.assertTrue(MySuiteListener.triggered);
+    Assert.assertFalse(MySuiteListener2.triggered);
+  }
+
+  @Test
+  public void verifyListenerAnnotationTransformerClass() {
+    MySuiteListener.triggered = false;
+    MySuiteListener2.triggered = false;
+    TestNG tng = new TestNG();
+    tng.setVerbose(0);
+    tng.setAnnotationTransformer(new MyListenerTransformer());
+    tng.setTestClasses(new Class[]{AnnotationTransformerClassSampleTest.class});
+
+    tng.run();
+    Assert.assertFalse(MySuiteListener.triggered);
+    Assert.assertTrue(MySuiteListener2.triggered);
+  }
+
+  @Test
+  public void verifyConfigurationTransformer() {
+    TestNG tng = new TestNG();
+    tng.setAnnotationTransformer(new ConfigurationTransformer());
+    tng.setVerbose(0);
+    tng.setTestClasses(new Class[] { ConfigurationSampleTest.class});
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+
+    tng.run();
+
+    Assert.assertEquals(ConfigurationSampleTest.getBefore(), "correct");
+  }
+
+  @Test
+  public void verifyDataProviderTransformer() {
+    TestNG tng = create();
+    tng.setAnnotationTransformer(new DataProviderTransformer());
+    tng.setTestClasses(new Class[] { AnnotationTransformerDataProviderSampleTest.class});
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+
+    tng.run();
+
+    Assert.assertEquals(tla.getPassedTests().size(), 1);
+  }
+
+  @Test
+  public void verifyFactoryTransformer() {
+    TestNG tng = create();
+    tng.setAnnotationTransformer(new FactoryTransformer());
+    tng.setTestClasses(new Class[] { AnnotationTransformerFactorySampleTest.class});
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+
+    tng.run();
+
+    Assert.assertEquals(tla.getPassedTests().size(), 1);
+  }
+
+  @Test(description = "Test for issue #605")
+  public void verifyInvocationCountTransformer() {
+    TestNG tng = create();
+    tng.setTestClasses(new Class[] { AnnotationTransformerInvocationCountTest.class });
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+
+    tng.run();
+
+    Assert.assertEquals(tla.getPassedTests().size(), 3);
+
+    tng = create();
+    tng.setAnnotationTransformer(new AnnotationTransformerInvocationCountTest.InvocationCountTransformer(5));
+    tng.setTestClasses(new Class[]{AnnotationTransformerInvocationCountTest.class});
+    tla = new TestListenerAdapter();
+    tng.addListener(tla);
+
+    tng.run();
+
+    Assert.assertEquals(tla.getPassedTests().size(), 5);
+  }
+
+  @Test
+  public void annotationTransformerInXmlShouldBeRun() throws Exception {
+    String xml = "<suite name=\"SingleSuite\" >" +
+        "  <listeners>" +
+        "    <listener class-name=\"test.annotationtransformer.AnnotationTransformerInTestngXml\" />" +
+        "  </listeners>" +
+        "  <test enabled=\"true\" name=\"SingleTest\">" +
+        "    <classes>" +
+        "      <class name=\"test.annotationtransformer.AnnotationTransformerInTestngXml\" />" +
+        "    </classes>" +
+        "  </test>" +
+        "</suite>"
+        ;
+
+    ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes());
+    Collection<XmlSuite> suites = new Parser(is).parse();
+
+    TestNG tng = create();
+    tng.setXmlSuites(Arrays.asList(suites.toArray(new XmlSuite[0])));
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+
+    tng.run();
+
+    Assert.assertEquals(tla.getPassedTests().size(), 1);
+
+  }
+}
diff --git a/src/test/java/test/annotationtransformer/ConfigurationSampleTest.java b/src/test/java/test/annotationtransformer/ConfigurationSampleTest.java
new file mode 100644
index 0000000..7b0c871
--- /dev/null
+++ b/src/test/java/test/annotationtransformer/ConfigurationSampleTest.java
@@ -0,0 +1,32 @@
+package test.annotationtransformer;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class ConfigurationSampleTest {
+  private static String m_before = "uninitialized";
+
+  @BeforeClass
+  public void beforeClass() {
+    m_before = "correct";
+  }
+
+  @BeforeMethod(enabled = false)
+  public void testingEnabledOnConfiguration() {
+    m_before = "this method is not enabled, we should not be here";
+  }
+
+  // will be disabled by the configuration transformer
+  @BeforeMethod
+  public void beforeMethod() {
+    m_before = "incorrect";
+  }
+
+  @Test
+  public void f() {}
+
+  public static String getBefore() {
+    return m_before;
+  }
+}
diff --git a/src/test/java/test/annotationtransformer/ConfigurationTransformer.java b/src/test/java/test/annotationtransformer/ConfigurationTransformer.java
new file mode 100644
index 0000000..345244c
--- /dev/null
+++ b/src/test/java/test/annotationtransformer/ConfigurationTransformer.java
@@ -0,0 +1,37 @@
+package test.annotationtransformer;
+
+import org.testng.IAnnotationTransformer2;
+import org.testng.annotations.IConfigurationAnnotation;
+import org.testng.annotations.IDataProviderAnnotation;
+import org.testng.annotations.IFactoryAnnotation;
+import org.testng.annotations.ITestAnnotation;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+public class ConfigurationTransformer implements IAnnotationTransformer2 {
+
+  @Override
+  public void transform(ITestAnnotation annotation, Class testClass,
+      Constructor testConstructor, Method testMethod)
+  {
+  }
+
+  @Override
+  public void transform(IConfigurationAnnotation annotation, Class testClass,
+      Constructor testConstructor, Method testMethod)
+  {
+    if (annotation.getBeforeTestMethod()) {
+      annotation.setEnabled(false);
+    }
+  }
+
+  @Override
+  public void transform(IDataProviderAnnotation annotation, Method testMethod) {
+  }
+
+  @Override
+  public void transform(IFactoryAnnotation annotation, Method testMethod) {
+  }
+
+}
diff --git a/src/test/java/test/annotationtransformer/DataProviderTransformer.java b/src/test/java/test/annotationtransformer/DataProviderTransformer.java
new file mode 100644
index 0000000..de4b3f9
--- /dev/null
+++ b/src/test/java/test/annotationtransformer/DataProviderTransformer.java
@@ -0,0 +1,34 @@
+package test.annotationtransformer;
+
+import org.testng.IAnnotationTransformer2;
+import org.testng.annotations.IConfigurationAnnotation;
+import org.testng.annotations.IDataProviderAnnotation;
+import org.testng.annotations.IFactoryAnnotation;
+import org.testng.annotations.ITestAnnotation;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+public class DataProviderTransformer implements IAnnotationTransformer2 {
+
+  @Override
+  public void transform(IConfigurationAnnotation annotation, Class testClass,
+      Constructor testConstructor, Method testMethod)
+  {
+  }
+
+  @Override
+  public void transform(IDataProviderAnnotation annotation, Method testMethod) {
+    annotation.setName("dataProvider");
+  }
+
+  @Override
+  public void transform(ITestAnnotation annotation, Class testClass,
+      Constructor testConstructor, Method testMethod)
+  {
+  }
+
+  @Override
+  public void transform(IFactoryAnnotation annotation, Method testMethod) {
+  }
+}
diff --git a/src/test/java/test/annotationtransformer/FactoryTransformer.java b/src/test/java/test/annotationtransformer/FactoryTransformer.java
new file mode 100644
index 0000000..fb2ae17
--- /dev/null
+++ b/src/test/java/test/annotationtransformer/FactoryTransformer.java
@@ -0,0 +1,34 @@
+package test.annotationtransformer;
+
+import org.testng.IAnnotationTransformer2;
+import org.testng.annotations.IConfigurationAnnotation;
+import org.testng.annotations.IDataProviderAnnotation;
+import org.testng.annotations.IFactoryAnnotation;
+import org.testng.annotations.ITestAnnotation;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+public class FactoryTransformer implements IAnnotationTransformer2 {
+
+  @Override
+  public void transform(IConfigurationAnnotation annotation, Class testClass,
+      Constructor testConstructor, Method testMethod)
+  {
+  }
+
+  @Override
+  public void transform(IDataProviderAnnotation annotation, Method testMethod) {
+  }
+
+  @Override
+  public void transform(ITestAnnotation annotation, Class testClass,
+      Constructor testConstructor, Method testMethod)
+  {
+  }
+
+  @Override
+  public void transform(IFactoryAnnotation annotation, Method testMethod) {
+    annotation.setDataProvider("dataProvider");
+  }
+}
diff --git a/src/test/java/test/annotationtransformer/MyListenerTransformer.java b/src/test/java/test/annotationtransformer/MyListenerTransformer.java
new file mode 100644
index 0000000..ad5cad6
--- /dev/null
+++ b/src/test/java/test/annotationtransformer/MyListenerTransformer.java
@@ -0,0 +1,33 @@
+package test.annotationtransformer;
+
+import org.testng.IAnnotationTransformer3;
+import org.testng.annotations.IConfigurationAnnotation;
+import org.testng.annotations.IDataProviderAnnotation;
+import org.testng.annotations.IFactoryAnnotation;
+import org.testng.annotations.IListenersAnnotation;
+import org.testng.annotations.ITestAnnotation;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+public class MyListenerTransformer implements IAnnotationTransformer3 {
+
+  @Override
+  public void transform(IListenersAnnotation annotation, Class testClass) {
+    annotation.setValue(new Class[]{MySuiteListener2.class});
+  }
+
+  @Override
+  public void transform(IConfigurationAnnotation annotation, Class testClass,
+                        Constructor testConstructor, Method testMethod) {}
+
+  @Override
+  public void transform(IDataProviderAnnotation annotation, Method method) {}
+
+  @Override
+  public void transform(IFactoryAnnotation annotation, Method method) {}
+
+  @Override
+  public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor,
+                        Method testMethod) {}
+}
diff --git a/src/test/java/test/annotationtransformer/MyParamTransformer.java b/src/test/java/test/annotationtransformer/MyParamTransformer.java
new file mode 100644
index 0000000..33aad84
--- /dev/null
+++ b/src/test/java/test/annotationtransformer/MyParamTransformer.java
@@ -0,0 +1,30 @@
+package test.annotationtransformer;
+
+import org.testng.IAnnotationTransformer;
+import org.testng.annotations.ITestAnnotation;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+public class MyParamTransformer implements IAnnotationTransformer {
+
+  private boolean success = true;
+
+  @Override
+  public void transform(ITestAnnotation annotation, Class testClass,
+      Constructor testConstructor, Method testMethod) {
+    if (!onlyOneNonNull(testClass, testConstructor, testMethod)) {
+      success = false;
+    }
+  }
+
+  public static boolean onlyOneNonNull(Class testClass, Constructor testConstructor, Method testMethod) {
+    return ((testClass != null && testConstructor == null && testMethod == null) ||
+            (testClass == null && testConstructor != null && testMethod == null) ||
+            (testClass == null && testConstructor == null && testMethod != null) );
+  }
+
+  public boolean isSuccess() {
+    return success;
+  }
+}
diff --git a/src/test/java/test/annotationtransformer/MySuiteListener.java b/src/test/java/test/annotationtransformer/MySuiteListener.java
new file mode 100644
index 0000000..4698df9
--- /dev/null
+++ b/src/test/java/test/annotationtransformer/MySuiteListener.java
@@ -0,0 +1,17 @@
+package test.annotationtransformer;
+
+import org.testng.ISuite;
+import org.testng.ISuiteListener;
+
+public class MySuiteListener implements ISuiteListener {
+
+  public static boolean triggered = false;
+
+  @Override
+  public void onStart(ISuite suite) {
+    triggered = true;
+  }
+
+  @Override
+  public void onFinish(ISuite suite) {}
+}
diff --git a/src/test/java/test/annotationtransformer/MySuiteListener2.java b/src/test/java/test/annotationtransformer/MySuiteListener2.java
new file mode 100644
index 0000000..243099a
--- /dev/null
+++ b/src/test/java/test/annotationtransformer/MySuiteListener2.java
@@ -0,0 +1,17 @@
+package test.annotationtransformer;
+
+import org.testng.ISuite;
+import org.testng.ISuiteListener;
+
+public class MySuiteListener2 implements ISuiteListener {
+
+  public static boolean triggered = false;
+
+  @Override
+  public void onStart(ISuite suite) {
+    triggered = true;
+  }
+
+  @Override
+  public void onFinish(ISuite suite) {}
+}
diff --git a/src/test/java/test/annotationtransformer/MyTimeOutTransformer.java b/src/test/java/test/annotationtransformer/MyTimeOutTransformer.java
new file mode 100644
index 0000000..4c74e64
--- /dev/null
+++ b/src/test/java/test/annotationtransformer/MyTimeOutTransformer.java
@@ -0,0 +1,19 @@
+package test.annotationtransformer;
+
+import org.testng.IAnnotationTransformer;
+import org.testng.annotations.ITestAnnotation;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+public class MyTimeOutTransformer implements IAnnotationTransformer {
+
+  @Override
+  public void transform(ITestAnnotation annotation, Class testClass,
+      Constructor testConstructor, Method testMethod)
+  {
+    annotation.setTimeOut(5000); // 5 seconds
+  }
+
+
+}
diff --git a/src/test/java/test/annotationtransformer/MyTransformer.java b/src/test/java/test/annotationtransformer/MyTransformer.java
new file mode 100644
index 0000000..b3eeeb4
--- /dev/null
+++ b/src/test/java/test/annotationtransformer/MyTransformer.java
@@ -0,0 +1,38 @@
+package test.annotationtransformer;
+
+import org.testng.IAnnotationTransformer;
+import org.testng.annotations.ITestAnnotation;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MyTransformer implements IAnnotationTransformer {
+
+  private final List<String> methodNames = new ArrayList<>();
+
+  @Override
+  public void transform(ITestAnnotation annotation, Class testClass,
+      Constructor testConstructor, Method testMethod) {
+    annotation.setTimeOut(10000);
+    if (testMethod != null) {
+      switch (testMethod.getName()) {
+        case "three":
+          annotation.setInvocationCount(3);
+          break;
+        case "four":
+          annotation.setInvocationCount(4);
+          break;
+        case "five":
+          annotation.setInvocationCount(5);
+          break;
+      }
+      methodNames.add(testMethod.getName());
+    }
+  }
+
+  public List<String> getMethodNames() {
+    return methodNames;
+  }
+}
diff --git a/src/test/java/test/annotationtransformer/SimpleTest.java b/src/test/java/test/annotationtransformer/SimpleTest.java
new file mode 100644
index 0000000..54532c1
--- /dev/null
+++ b/src/test/java/test/annotationtransformer/SimpleTest.java
@@ -0,0 +1,17 @@
+package test.annotationtransformer;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class SimpleTest {
+  private int m_n;
+
+  public SimpleTest(int n) {
+    m_n = n;
+  }
+
+  @Test
+  public void run() {
+    Assert.assertEquals(m_n, 42);
+  }
+}
diff --git a/src/test/java/test/ant/AntSystemPropertySet.java b/src/test/java/test/ant/AntSystemPropertySet.java
new file mode 100644
index 0000000..fe48ceb
--- /dev/null
+++ b/src/test/java/test/ant/AntSystemPropertySet.java
@@ -0,0 +1,28 @@
+package test.ant;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import org.testng.annotations.Test;
+
+/**
+ * Test whether nested propertysets are passed passed from the ant task. Executed by the "run:antprop" target
+ * in test/build.xml.
+ *
+ * @author <a href="mailto:ttopwells@gmail.com">Todd Wells</a>
+ */
+public class AntSystemPropertySet {
+
+  @Test
+  public void outputTestProperties()
+  {
+    assertNotNull(System.getProperty("syspropset1"), "syspropset1 not found");
+    assertEquals(System.getProperty("syspropset1"), "value 1", "Wrong value for syspropset1");
+
+    assertNotNull(System.getProperty("syspropset2"), "syspropset2 not found");
+    assertEquals(System.getProperty("syspropset2"), "value 2", "Wrong value for syspropset2");
+
+    assertNotNull(System.getProperty("sysprop1"), "sysprop1 not found");
+    assertEquals(System.getProperty("sysprop1"), "value 3", "Wrong value for sysprop1");
+  }
+}
diff --git a/src/test/java/test/ant/DontOverrideSuiteNameTest.java b/src/test/java/test/ant/DontOverrideSuiteNameTest.java
new file mode 100644
index 0000000..65fe3e9
--- /dev/null
+++ b/src/test/java/test/ant/DontOverrideSuiteNameTest.java
@@ -0,0 +1,13 @@
+package test.ant;
+
+import org.testng.annotations.Test;
+
+@Test
+public class DontOverrideSuiteNameTest {
+	private boolean m_run = false;
+
+	@Test(groups = {"nopackage"})
+	public void test() {
+	   m_run = true;
+	}
+}
diff --git a/src/test/java/test/ant/MultipleThreadTest.java b/src/test/java/test/ant/MultipleThreadTest.java
new file mode 100644
index 0000000..53f11db
--- /dev/null
+++ b/src/test/java/test/ant/MultipleThreadTest.java
@@ -0,0 +1,128 @@
+package test.ant;

+

+import org.testng.Assert;

+import org.testng.annotations.AfterClass;

+import org.testng.annotations.BeforeClass;

+import org.testng.annotations.Test;

+

+import java.util.Collections;

+import java.util.HashSet;

+import java.util.Set;

+/**

+ * Tests that more than one thread is used for running tests

+ * @author micheb10 2 Oct 2006

+ *

+ */

+public class MultipleThreadTest {

+	public static Set<Thread> _threads;

+

+	@BeforeClass

+	public void prepareHashSet() {

+		_threads=Collections.synchronizedSet(new HashSet<Thread>());

+	}

+

+	@Test

+	public void recordThread00() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@Test

+	public void recordThread01() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@Test

+	public void recordThread02() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@Test

+	public void recordThread03() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@Test

+	public void recordThread04() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@Test

+	public void recordThread05() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@Test

+	public void recordThread06() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@Test

+	public void recordThread07() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@Test

+	public void recordThread08() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@Test

+	public void recordThread09() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@Test

+	public void recordThread10() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@Test

+	public void recordThread11() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@Test

+	public void recordThread12() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@Test

+	public void recordThread13() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@Test

+	public void recordThread14() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@Test

+	public void recordThread15() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@Test

+	public void recordThread16() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@Test

+	public void recordThread17() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@Test

+	public void recordThread18() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@Test

+	public void recordThread19() {

+		_threads.add(Thread.currentThread());

+	}

+

+	@AfterClass

+	public void confirmMultipleThreads() {

+		Assert.assertTrue(_threads.size()>1,"More than one thread should have been used for running the tests - "+_threads.size()+" was used");

+	}

+}

diff --git a/src/test/java/test/ant/NoPackageTest.java b/src/test/java/test/ant/NoPackageTest.java
new file mode 100644
index 0000000..11db76f
--- /dev/null
+++ b/src/test/java/test/ant/NoPackageTest.java
@@ -0,0 +1,21 @@
+package test.ant;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
+/**
+ * @author Filippo Diotalevi
+ */
+public class NoPackageTest {
+	private boolean m_run = false;
+
+	@Test(groups = {"nopackage"})
+	public void test() {
+	   m_run = true;
+	}
+
+   @AfterMethod(groups = {"nopackage"})
+   public void after() {
+      assert m_run : "test method was not run";
+   }
+}
diff --git a/src/test/java/test/ant/TestCommandLineArgs.java b/src/test/java/test/ant/TestCommandLineArgs.java
new file mode 100644
index 0000000..641f5bd
--- /dev/null
+++ b/src/test/java/test/ant/TestCommandLineArgs.java
@@ -0,0 +1,51 @@
+package test.ant;
+
+import static org.testng.AssertJUnit.assertEquals;
+
+import org.testng.annotations.Test;
+
+import java.io.File;
+
+
+/**
+ * Tests some of the functionality in {@link TestNGCommandLineArgs}.
+ *
+ * @author jkuhnert
+ */
+public class TestCommandLineArgs
+{
+
+  @Test
+  public void testUnixPathResolution()
+  {
+    String path = "/wee/wom/flibble.txt";
+
+    String[] segments = path.split("[/\\\\]", -1);
+
+    assertEquals(4, segments.length);
+    assertEquals("wee", segments[1]);
+  }
+
+  @Test
+  public void testDOSPathResolution()
+  {
+    String path = "c:\\\\com\\pants\\wibble.txt";
+
+    String[] segments = path.split("[/\\\\]", -1);
+
+    assertEquals(5, segments.length);
+    assertEquals("com", segments[2]); // because c: is actually \\ which will be split twice
+  }
+
+  @Test
+  public void testPathResolution()
+  {
+    File file = new File("pom.xml");
+
+    assert file.exists();
+
+    String path = file.getAbsolutePath();
+
+    assert path.split("[/\\\\]", -1).length > 1;
+  }
+}
diff --git a/src/test/java/test/assertion/AssertionTest.java b/src/test/java/test/assertion/AssertionTest.java
new file mode 100644
index 0000000..bf6f3b8
--- /dev/null
+++ b/src/test/java/test/assertion/AssertionTest.java
@@ -0,0 +1,48 @@
+package test.assertion;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.testng.asserts.LoggingAssert;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AssertionTest {
+  private LoggingAssert m_assert;
+  private MyRawAssertion rawAssertion;
+
+  @BeforeMethod
+  public void bm() {
+    m_assert = new LoggingAssert();
+    rawAssertion = new MyRawAssertion();
+  }
+
+  @Test(expectedExceptions = AssertionError.class)
+  public void test1() {
+    m_assert.assertTrue(false, "new TestNG Assertion Failed");
+  }
+
+  @Test
+  public void test2() {
+    rawAssertion.assertTrue(true);
+    rawAssertion.myAssert("test", true, "Raw test");
+
+    assertThat(rawAssertion.getMethods())
+        .containsExactly("onBeforeAssert", "onAssertSuccess", "onAfterAssert",
+                         "onBeforeAssert", "onAssertSuccess", "onAfterAssert");
+  }
+
+  @Test(expectedExceptions = AssertionError.class, expectedExceptionsMessageRegExp = "Raw test .*")
+  public void test2_fails() {
+    try {
+      rawAssertion.assertTrue(true);
+      rawAssertion.myAssert("test", false, "Raw test");
+    } catch (AssertionError error) {
+
+      assertThat(rawAssertion.getMethods())
+          .containsExactly("onBeforeAssert", "onAssertSuccess", "onAfterAssert",
+                           "onBeforeAssert", "onAssertFailure", "deprecated_onAssertFailure", "onAfterAssert");
+
+      throw error;
+    }
+  }
+}
diff --git a/src/test/java/test/assertion/MyRawAssertion.java b/src/test/java/test/assertion/MyRawAssertion.java
new file mode 100644
index 0000000..0f35db2
--- /dev/null
+++ b/src/test/java/test/assertion/MyRawAssertion.java
@@ -0,0 +1,72 @@
+package test.assertion;
+
+import org.testng.Assert;
+import org.testng.asserts.Assertion;
+import org.testng.asserts.IAssert;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MyRawAssertion extends Assertion {
+
+  private final List<String> methods = new ArrayList<>();
+
+  @Override
+  public void onAssertSuccess(IAssert assertCommand) {
+    methods.add("onAssertSuccess");
+    super.onAssertSuccess(assertCommand);
+  }
+
+  @Override
+  public void onAssertFailure(IAssert assertCommand) {
+    methods.add("deprecated_onAssertFailure");
+    super.onAssertFailure(assertCommand);
+  }
+
+  @Override
+  public void onAssertFailure(IAssert assertCommand, AssertionError ex) {
+    methods.add("onAssertFailure");
+    super.onAssertFailure(assertCommand, ex);
+  }
+
+  @Override
+  public void onBeforeAssert(IAssert assertCommand) {
+    methods.add("onBeforeAssert");
+    super.onBeforeAssert(assertCommand);
+  }
+
+  @Override
+  public void onAfterAssert(IAssert assertCommand) {
+    methods.add("onAfterAssert");
+    super.onAfterAssert(assertCommand);
+  }
+
+  public List<String> getMethods() {
+    return methods;
+  }
+
+  public void myAssert(final String actual, final boolean expected, final String message) {
+    doAssert(new IAssert() {
+      @Override
+      public String getMessage() {
+        return message;
+      }
+
+      @Override
+      public void doAssert() {
+        Assert.assertNotNull(actual, message);
+        Assert.assertTrue(expected, message);
+      }
+
+      @Override
+      public Object getActual() {
+        return actual;
+      }
+
+      @Override
+      public Object getExpected() {
+        return expected;
+      }
+    });
+  }
+}
diff --git a/src/test/java/test/assertion/SoftAssertTest.java b/src/test/java/test/assertion/SoftAssertTest.java
new file mode 100644
index 0000000..2aebaed
--- /dev/null
+++ b/src/test/java/test/assertion/SoftAssertTest.java
@@ -0,0 +1,50 @@
+package test.assertion;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.testng.asserts.IAssert;
+import org.testng.asserts.SoftAssert;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class SoftAssertTest {
+
+  @Test
+  public void testOnSucceedAndFailureCalled() throws Exception {
+    final Collection<IAssert> succeed = new ArrayList<>();
+    final Collection<IAssert> failures = new ArrayList<>();
+    final SoftAssert sa = new SoftAssert() {
+      @Override
+      public void onAssertSuccess(IAssert assertCommand) {
+        succeed.add(assertCommand);
+      }
+
+      @Override
+      public void onAssertFailure(IAssert assertCommand, AssertionError ex) {
+        failures.add(assertCommand);
+      }
+    };
+    sa.assertTrue(true);
+    sa.assertTrue(false);
+    Assert.assertEquals(succeed.size(), 1, succeed.toString());
+    Assert.assertEquals(failures.size(), 1, failures.toString());
+  }
+
+  @Test
+  public void testAssertAllCount() throws Exception {
+    String message = "My message";
+    SoftAssert sa = new SoftAssert();
+    sa.assertTrue(true);
+    sa.assertTrue(false, message);
+    try {
+      sa.assertAll();
+      Assert.fail("Exception expected");
+    } catch (AssertionError e) {
+      String[] lines = e.getMessage().split("\r?\n");
+      Assert.assertEquals(lines.length, 2);
+      lines[1] = lines[1].replaceFirst(message, "");
+      Assert.assertFalse(lines[1].contains(message));
+    }
+  }
+}
diff --git a/src/test/java/test/asserttests/ArrayEqualityAssertTest.java b/src/test/java/test/asserttests/ArrayEqualityAssertTest.java
new file mode 100644
index 0000000..b98cff3
--- /dev/null
+++ b/src/test/java/test/asserttests/ArrayEqualityAssertTest.java
@@ -0,0 +1,252 @@
+package test.asserttests;

+

+import org.testng.annotations.Test;

+

+import java.util.ArrayList;

+import java.util.Arrays;

+import java.util.Collections;

+import java.util.HashMap;

+import java.util.HashSet;

+import java.util.List;

+import java.util.Map;

+import java.util.Set;

+

+import static org.testng.Assert.assertEquals;

+import static org.testng.Assert.assertNotEquals;

+

+/**

+ * Tests different equality cases for nested collections

+ * and arrays.

+ */

+public class ArrayEqualityAssertTest {

+

+    @Test

+    public void arrayAssertEquals() {

+        assertEquals(new int[]{ 42 }, new int[] { 42 },

+                "arrays of primitives are compared by value in assertEquals");

+    }

+

+    @Test(expectedExceptions = AssertionError.class)

+    public void arrayAssertNotEquals() {

+        assertNotEquals(new int[]{ 42 }, new int[] { 42 },

+                "arrays of primitives are compared by value in assertNotEquals");

+    }

+

+    @Test

+    public void boxedArrayAssertEquals() {

+        assertEquals(new Integer[]{ 42 }, new Integer[] { 42 },

+                "arrays of wrapped values are compared by value in assertEquals");

+    }

+

+    @Test(expectedExceptions = AssertionError.class)

+    public void boxedArrayAssertNotEquals() {

+        assertNotEquals(new Integer[]{ 42 }, new Integer[] { 42 },

+                "arrays of wrapped values are compared by value in assertNotEquals");

+    }

+

+    @Test

+    public void mixedArraysAssertEquals() {

+        assertEquals(new int[]{ 42 }, new Integer[] { 42 },

+                "arrays of wrapped values are compared by value in assertEquals");

+        assertEquals(new Integer[]{ 42 }, new int[] { 42 },

+                "arrays of wrapped values are compared by value in assertEquals");

+    }

+

+    @Test(expectedExceptions = AssertionError.class)

+    public void mixedArraysAssertNotEquals() {

+        assertNotEquals(new int[]{ 42 }, new Integer[] { 42 },

+                "arrays of wrapped values are compared by value in assertNotEquals");

+        assertNotEquals(new Integer[]{ 42 }, new int[] { 42 },

+                "arrays of wrapped values are compared by value in assertNotEquals");

+    }

+

+    @Test(expectedExceptions = AssertionError.class)

+    public void arrayInsideListAssertEquals() {

+        List<int[]> list = Arrays.asList(

+            new int[]{ 42 }

+        );

+        List<int[]> listCopy = Arrays.asList(

+            new int[]{ 42 }

+        );

+        assertEquals(list, listCopy,

+                "arrays inside lists are compared by reference in assertEquals");

+    }

+

+    @Test

+    public void arrayInsideListAssertNotEquals() {

+        List<int[]> list = Arrays.asList(

+            new int[]{ 42 }

+        );

+        List<int[]> listCopy = Arrays.asList(

+            new int[]{ 42 }

+        );

+        assertNotEquals(list, listCopy,

+                "arrays inside lists are compared by reference in assertNotEquals");

+    }

+

+    @Test(expectedExceptions = AssertionError.class)

+    public void arrayInsideMapAssertEquals() {

+        Map<String, int[]> map = new HashMap<>();

+        map.put("array", new int[]{ 42 });

+        Map<String, int[]> mapCopy = new HashMap<>();

+        mapCopy.put("array", new int[]{ 42 });

+

+        // arrays inside maps are compared by reference in assertEquals(Map,Map)

+        assertEquals(map, mapCopy);

+    }

+

+    @Test(expectedExceptions = AssertionError.class)

+    public void arrayInsideMapAssertEqualsWithMessage() {

+        Map<String, int[]> map = new HashMap<>();

+        map.put("array", new int[]{ 42 });

+        Map<String, int[]> mapCopy = new HashMap<>();

+        mapCopy.put("array", new int[]{ 42 });

+

+        assertEquals(map, mapCopy,

+                "arrays inside maps are compared by reference in assertEquals(Map,Map,String)");

+    }

+

+    @Test

+    public void arrayInsideMapAssertNotEquals() {

+        Map<String, int[]> map = new HashMap<>();

+        map.put("array", new int[]{ 42 });

+        Map<String, int[]> mapCopy = new HashMap<>();

+        mapCopy.put("array", new int[]{ 42 });

+

+        assertNotEquals(map, mapCopy,

+                "arrays inside maps are compared by reference in assertNotEquals");

+    }

+

+    @Test(expectedExceptions = AssertionError.class)

+    public void arrayInsideSetAssertEquals() {

+        Set<int[]> set = new HashSet<>();

+        set.add(new int[]{ 42 });

+        Set<int[]> setCopy = new HashSet<>();

+        setCopy.add(new int[]{ 42 });

+

+        assertEquals(set, setCopy,

+                "arrays inside sets are compared by reference in assertNotEquals");

+    }

+

+    @Test

+    public void arrayInsideSetAssertNotEquals() {

+        Set<int[]> set = new HashSet<>();

+        set.add(new int[]{ 42 });

+        Set<int[]> setCopy = new HashSet<>();

+        setCopy.add(new int[]{ 42 });

+

+        assertNotEquals(set, setCopy,

+                "arrays inside sets are compared by reference in assertNotEquals");

+    }

+

+    @Test(expectedExceptions = AssertionError.class)

+    public void arrayDeepInListsAssertEquals() {

+        List<List<int[]>> list = Collections.singletonList(Arrays.asList(new int[]{ 42 }));

+        List<List<int[]>> listCopy = Collections.singletonList(Arrays.asList(new int[]{ 42 }));

+

+        assertEquals(list, listCopy,

+                "arrays inside lists which are inside lists themselves are compared by reference in assertEquals");

+    }

+

+    @Test(expectedExceptions = AssertionError.class)

+    public void arrayDeepInMapsAssertEquals() {

+        Map<String, Map<String, int[]>> map = new HashMap<>();

+        Map<String, int[]> innerMap = new HashMap<>();

+        innerMap.put("array", new int[]{ 42 });

+        map.put("map", innerMap);

+        Map<String, Map<String, int[]>> mapCopy = new HashMap<>();

+        Map<String, int[]> innerMapCopy = new HashMap<>();

+        innerMapCopy.put("array", new int[]{ 42 });

+        mapCopy.put("map", innerMapCopy);

+

+        assertEquals(map, mapCopy,

+                "arrays inside maps which are inside maps themselves are compared by reference in assertEquals");

+    }

+

+    @Test(expectedExceptions = AssertionError.class)

+    public void arrayDeepInListAndMapAssertEquals() {

+        List<Map<String, int[]>> list = new ArrayList<>();

+        Map<String, int[]> innerMap = new HashMap<>();

+        innerMap.put("array", new int[]{ 42 });

+        list.add(innerMap);

+        List<Map<String, int[]>> listCopy = new ArrayList<>();

+        Map<String, int[]> innerMapCopy = new HashMap<>();

+        innerMapCopy.put("array", new int[]{ 42 });

+        list.add(innerMapCopy);

+

+        assertEquals(list, listCopy,

+                "arrays inside maps which are inside lists themselves are compared by reference in assertEquals");

+    }

+

+    @Test(expectedExceptions = AssertionError.class)

+    public void arrayDeepInMapAndListAssertEquals() {

+        Map<String, List<int[]>> map = new HashMap<>();

+        map.put("list", Arrays.asList(new int[]{ 42 }));

+        Map<String, List<int[]>> mapCopy = new HashMap<>();

+        mapCopy.put("list", Arrays.asList(new int[]{ 42 }));

+

+        assertEquals(map, mapCopy,

+                "arrays inside maps which are inside lists themselves are compared by reference in assertEquals");

+    }

+

+    @Test(expectedExceptions = AssertionError.class)

+    public void arrayInsideIterableAssertEquals() {

+        Iterable<int[]> iterable = Arrays.asList(

+            new int[]{ 42 }

+        );

+        Iterable<int[]> iterableCopy = Arrays.asList(

+            new int[]{ 42 }

+        );

+        assertEquals(iterable, iterableCopy,

+                "arrays inside Iterables are compared by reference in assertEquals");

+    }

+

+    @Test(expectedExceptions = AssertionError.class)

+    public void arrayDeepInIterablesAssertEquals() {

+        List<List<int[]>> iterable = Collections.singletonList(Arrays.asList(new int[]{ 42 }));

+        List<List<int[]>> iterableCopy = Collections.singletonList(Arrays.asList(new int[]{ 42 }));

+

+        assertEquals(iterable, iterableCopy,

+                "arrays inside Iterables which are inside Iterables themselves are compared by reference in assertEquals");

+    }

+

+    @Test(expectedExceptions = AssertionError.class)

+    public void arrayInsideArrayAssertEquals() {

+        int[][] array = new int[][] {

+            new int[]{ 42 }

+        };

+        int[][] arrayCopy = new int[][] {

+            new int[]{ 42 }

+        };

+        assertEquals(array, arrayCopy,

+                "arrays inside arrays are compared by reference in assertEquals");

+    }

+

+    @Test(expectedExceptions = AssertionError.class)

+    public void arrayDeepInArraysAssertEquals() {

+        int[][][] array = new int[][][] {

+            new int[][] { new int[]{ 42 } }

+        };

+        int[][][] arrayCopy = new int[][][] {

+            new int[][] { new int[]{ 42 } }

+        };

+

+        assertEquals(array, arrayCopy,

+                "arrays inside arrays which are inside arrays themselves are compared by reference in assertEquals");

+    }

+

+    @SuppressWarnings("unchecked")

+    @Test(expectedExceptions = AssertionError.class)

+    public void arrayDeepInArrayAndListAssertEquals() {

+        List<int[]>[] array = new List[] {

+            Arrays.asList(new int[]{ 42 })

+        };

+        List<int[]>[] arrayCopy = new List[] {

+            Arrays.asList(new int[]{ 42 })

+        };

+

+        assertEquals(array, arrayCopy,

+                "arrays inside arrays which are inside arrays themselves are compared by reference in assertEquals");

+    }

+

+}

diff --git a/src/test/java/test/asserttests/AssertTest.java b/src/test/java/test/asserttests/AssertTest.java
new file mode 100644
index 0000000..1191d6c
--- /dev/null
+++ b/src/test/java/test/asserttests/AssertTest.java
@@ -0,0 +1,156 @@
+package test.asserttests;
+
+import org.testng.Assert;
+import org.testng.Assert.ThrowingRunnable;
+import org.testng.annotations.Test;
+import org.testng.collections.Sets;
+
+import java.io.IOException;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+import static org.testng.Assert.expectThrows;
+
+public class AssertTest {
+
+  @Test
+  public void noOrderSuccess() {
+    String[] rto1 = { "boolean", "BigInteger", "List",};
+    String[] rto2 = {  "List", "BigInteger", "boolean",};
+    Assert.assertEqualsNoOrder(rto1, rto2);
+  }
+
+  @Test(expectedExceptions = AssertionError.class)
+  public void noOrderFailure() {
+    String[] rto1 = { "a", "a", "b",};
+    String[] rto2 = {  "a", "b", "b",};
+    Assert.assertEqualsNoOrder(rto1, rto2);
+  }
+
+  @Test
+  public void intArray_Issue4() {
+    int[] intArr00 = {1};
+    int[] intArr01 = {1};
+    Assert.assertEquals(intArr00, intArr01);
+  }
+
+  @Test(expectedExceptions = AssertionError.class)
+  public void arraysFailures_1() {
+    int[] intArr = {1, 2};
+    long[] longArr = {1, 2};
+    Assert.assertEquals(intArr, longArr);
+  }
+
+  @Test(expectedExceptions = AssertionError.class)
+  public void arraysFailures_2() {
+    int[] intArr = {1, 2};
+    Assert.assertEquals(intArr, (long) 1);
+  }
+
+  @Test(expectedExceptions = AssertionError.class)
+  public void arraysFailures_3() {
+    long[] longArr = {1};
+    Assert.assertEquals((long) 1, longArr);
+  }
+
+  @Test
+  public void setsSuccess() {
+    Set<Integer> set1 = Sets.newHashSet();
+    Set<Integer> set2 = Sets.newHashSet();
+
+    set1.add(1);
+    set2.add(1);
+
+    set1.add(3);
+    set2.add(3);
+
+    set1.add(2);
+    set2.add(2);
+
+    Assert.assertEquals(set1, set2);
+    Assert.assertEquals(set2, set1);
+  }
+
+  @Test(expectedExceptions = AssertionError.class)
+  public void expectThrowsRequiresAnExceptionToBeThrown() {
+    expectThrows(Throwable.class, nonThrowingRunnable());
+  }
+
+  @Test
+  public void expectThrowsIncludesAnInformativeDefaultMessage() {
+    try {
+      expectThrows(Throwable.class, nonThrowingRunnable());
+    } catch (AssertionError ex) {
+      assertEquals("Expected Throwable to be thrown, but nothing was thrown", ex.getMessage());
+      return;
+    }
+    fail();
+  }
+
+  @Test
+  public void expectThrowsReturnsTheSameObjectThrown() {
+    NullPointerException npe = new NullPointerException();
+
+    Throwable throwable = expectThrows(Throwable.class, throwingRunnable(npe));
+
+    assertSame(npe, throwable);
+  }
+
+  @Test(expectedExceptions = AssertionError.class)
+  public void expectThrowsDetectsTypeMismatchesViaExplicitTypeHint() {
+    NullPointerException npe = new NullPointerException();
+
+    expectThrows(IOException.class, throwingRunnable(npe));
+  }
+
+  @Test
+  public void expectThrowsWrapsAndPropagatesUnexpectedExceptions() {
+    NullPointerException npe = new NullPointerException("inner-message");
+
+    try {
+      expectThrows(IOException.class, throwingRunnable(npe));
+    } catch (AssertionError ex) {
+      assertSame(npe, ex.getCause());
+      assertEquals("inner-message", ex.getCause().getMessage());
+      return;
+    }
+    fail();
+  }
+
+  @Test
+  public void expectThrowsSuppliesACoherentErrorMessageUponTypeMismatch() {
+    NullPointerException npe = new NullPointerException();
+
+    try {
+      expectThrows(IOException.class, throwingRunnable(npe));
+    } catch (AssertionError error) {
+      assertEquals("Expected IOException to be thrown, but NullPointerException was thrown",
+              error.getMessage());
+      assertSame(npe, error.getCause());
+      return;
+    }
+    fail();
+  }
+
+  private static ThrowingRunnable nonThrowingRunnable() {
+    return new ThrowingRunnable() {
+      public void run() throws Throwable {
+      }
+    };
+  }
+
+  private static ThrowingRunnable throwingRunnable(final Throwable t) {
+    return new ThrowingRunnable() {
+      public void run() throws Throwable {
+        throw t;
+      }
+    };
+  }
+
+  @Test
+  public void doubleNaNAssertion() {
+    Assert.assertEquals(Double.NaN, Double.NaN, 0.0);
+  }
+}
diff --git a/src/test/java/test/attributes/AttributeTest.java b/src/test/java/test/attributes/AttributeTest.java
new file mode 100644
index 0000000..079dd4fe
--- /dev/null
+++ b/src/test/java/test/attributes/AttributeTest.java
@@ -0,0 +1,37 @@
+package test.attributes;
+
+import org.testng.ITestContext;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.Set;
+
+import junit.framework.Assert;
+
+public class AttributeTest {
+
+  @BeforeClass
+  public void bc(ITestContext ctx) {
+    ctx.setAttribute("test", "1");
+  }
+
+  @Test
+  public void f1(ITestContext ctx) {
+    Set<String> names = ctx.getAttributeNames();
+    Assert.assertEquals(1, names.size());
+    Assert.assertTrue(names.contains("test"));
+    Assert.assertEquals(ctx.getAttribute("test"), "1");
+    Object v = ctx.removeAttribute("test");
+    Assert.assertNotNull(v);
+    ctx.setAttribute("test2", "2");
+  }
+
+  @Test(dependsOnMethods = "f1")
+  public void f2(ITestContext ctx) {
+    Set<String> names = ctx.getAttributeNames();
+    Assert.assertEquals(1, names.size());
+    Assert.assertTrue(names.contains("test2"));
+    Assert.assertTrue(ctx.getAttribute("test2").equals("2"));
+  }
+
+}
diff --git a/src/test/java/test/bug90/Bug90Test.java b/src/test/java/test/bug90/Bug90Test.java
new file mode 100644
index 0000000..b554bc9
--- /dev/null
+++ b/src/test/java/test/bug90/Bug90Test.java
@@ -0,0 +1,30 @@
+package test.bug90;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlInclude;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.SimpleBaseTest;
+
+import java.util.Arrays;
+
+public class Bug90Test extends SimpleBaseTest {
+
+  @Test(description = "Fix for https://github.com/cbeust/testng/issues/90")
+  public void afterClassShouldRun() {
+    XmlSuite s = createXmlSuite("Bug90");
+    XmlTest t = createXmlTest(s, "Bug90 test", Sample.class.getName());
+    XmlClass c = t.getClasses().get(0);
+    c.setIncludedMethods(Arrays.asList(new XmlInclude("test1")));
+    TestNG tng = create();
+    tng.setXmlSuites(Arrays.asList(s));
+    Sample.m_afterClassWasRun = false;
+    tng.run();
+
+    Assert.assertTrue(Sample.m_afterClassWasRun);
+  }
+}
diff --git a/src/test/java/test/bug90/Sample.java b/src/test/java/test/bug90/Sample.java
new file mode 100644
index 0000000..069b7b0
--- /dev/null
+++ b/src/test/java/test/bug90/Sample.java
@@ -0,0 +1,22 @@
+package test.bug90;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.Test;
+
+public class Sample {
+  static boolean m_afterClassWasRun = false;
+
+  @Test
+  public void test1() {
+  }
+
+
+  @Test
+  public void test2() {
+  }
+  
+  @AfterClass
+  public void afterClass() {
+    m_afterClassWasRun = true;
+  }
+}
diff --git a/src/test/java/test/bug92/Bug92Test.java b/src/test/java/test/bug92/Bug92Test.java
new file mode 100644
index 0000000..f4b73f0
--- /dev/null
+++ b/src/test/java/test/bug92/Bug92Test.java
@@ -0,0 +1,29 @@
+package test.bug92;
+
+import java.util.Arrays;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.SimpleBaseTest;
+
+public class Bug92Test extends SimpleBaseTest {
+
+	@Test(description = "Fix for https://github.com/cbeust/testng/issues/92")
+	public void BeforeTestShouldRunOnce() {
+		XmlSuite s = createXmlSuite("Bug92");
+		XmlTest t = createXmlTest(s, "Bug92 test", TestAlpha.class.getName(),
+				TestBravo.class.getName());
+		s.setTests(Arrays.asList(t));
+		TestNG tng = create();
+		tng.setXmlSuites(Arrays.asList(s));
+		TestBase.beforeTestCount = 0;
+		TestBase.beforeTestAlwaysCount = 0;
+		tng.run();
+		Assert.assertEquals(TestBase.beforeTestCount, 1);
+		Assert.assertEquals(TestBase.beforeTestAlwaysCount, 1);
+	}
+}
diff --git a/src/test/java/test/bug92/TestAlpha.java b/src/test/java/test/bug92/TestAlpha.java
new file mode 100644
index 0000000..598bfc3
--- /dev/null
+++ b/src/test/java/test/bug92/TestAlpha.java
@@ -0,0 +1,14 @@
+package test.bug92;
+
+import org.testng.annotations.Test;
+
+public class TestAlpha extends TestBase {
+
+	@Test
+	public void test1() {
+	}
+
+	@Test
+	public void test2() {
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/test/bug92/TestBase.java b/src/test/java/test/bug92/TestBase.java
new file mode 100644
index 0000000..c71b279
--- /dev/null
+++ b/src/test/java/test/bug92/TestBase.java
@@ -0,0 +1,20 @@
+package test.bug92;
+
+import org.testng.annotations.BeforeTest;
+
+public class TestBase {
+
+	static int beforeTestCount = 0;
+	static int beforeTestAlwaysCount = 0;
+
+	@BeforeTest
+	public void baseTestBeforeTest() {
+		beforeTestCount++;
+	}
+
+	@BeforeTest(alwaysRun = true)
+	public void baseTestBeforeTestAlways() {
+		beforeTestAlwaysCount++;
+	}
+
+}
\ No newline at end of file
diff --git a/src/test/java/test/bug92/TestBravo.java b/src/test/java/test/bug92/TestBravo.java
new file mode 100644
index 0000000..1561dab
--- /dev/null
+++ b/src/test/java/test/bug92/TestBravo.java
@@ -0,0 +1,14 @@
+package test.bug92;
+
+import org.testng.annotations.Test;
+
+public class TestBravo extends TestBase {
+
+	@Test
+	public void test1() {
+	}
+
+	@Test
+	public void test2() {
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/test/classgroup/First.java b/src/test/java/test/classgroup/First.java
new file mode 100644
index 0000000..92273ef
--- /dev/null
+++ b/src/test/java/test/classgroup/First.java
@@ -0,0 +1,23 @@
+package test.classgroup;
+
+import org.testng.annotations.Test;
+
+@Test(groups = { "first" })
+public class First {
+  private static boolean m_first1 = false;
+  private static boolean m_first2 = false;
+
+  @Test
+  public void first1() {
+    m_first1 = true;
+  }
+
+  @Test
+  public void first2() {
+    m_first2 = true;
+  }
+
+   static boolean allRun() {
+    return m_first1 && m_first2;
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/classgroup/Second.java b/src/test/java/test/classgroup/Second.java
new file mode 100644
index 0000000..149c1c7
--- /dev/null
+++ b/src/test/java/test/classgroup/Second.java
@@ -0,0 +1,13 @@
+package test.classgroup;
+
+import org.testng.annotations.Test;
+
+@Test(dependsOnGroups = { "first" })
+public class Second {
+
+  @Test
+  public void verify() {
+    assert First.allRun() : "Methods for class First should have been invoked first.";
+  }
+
+}
\ No newline at end of file
diff --git a/src/test/java/test/commandline/CommandLineOverridesXml.java b/src/test/java/test/commandline/CommandLineOverridesXml.java
new file mode 100644
index 0000000..3102ca2
--- /dev/null
+++ b/src/test/java/test/commandline/CommandLineOverridesXml.java
@@ -0,0 +1,44 @@
+package test.commandline;
+
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.SimpleBaseTest;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class CommandLineOverridesXml extends SimpleBaseTest {
+
+  @Test(description = "Specifying -groups on the command line should override testng.xml")
+  public void commandLineGroupsShouldOverrideXml() {
+    runTest("go", null, Arrays.asList(new String[] { "f2" }));
+  }
+
+  @Test(description = "Specifying -excludegroups on the command line should override testng.xml")
+  public void commandLineExcludedGroupsShouldOverrideXml() {
+    runTest(null, "go", Arrays.asList(new String[] { "f1" }));
+  }
+
+  @Test
+  public void shouldRunBothMethods() {
+    runTest(null, null, Arrays.asList(new String[] { "f1", "f2" }));
+  }
+
+  private void runTest(String group, String excludedGroups, List<String> methods) {
+    XmlSuite s = createXmlSuite(getClass().getName());
+    XmlTest t = createXmlTest(s, "Test", OverrideSampleTest.class.getName());
+    TestNG tng = create();
+    if (group != null) tng.setGroups(group);
+    if (excludedGroups != null) tng.setExcludedGroups(excludedGroups);
+    tng.setXmlSuites(Arrays.asList(new XmlSuite[] { s }));
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+    assertTestResultsEqual(tla.getPassedTests(), methods);
+  }
+}
diff --git a/src/test/java/test/commandline/OverrideSampleTest.java b/src/test/java/test/commandline/OverrideSampleTest.java
new file mode 100644
index 0000000..5aea3d0
--- /dev/null
+++ b/src/test/java/test/commandline/OverrideSampleTest.java
@@ -0,0 +1,12 @@
+package test.commandline;
+
+import org.testng.annotations.Test;
+
+public class OverrideSampleTest {
+
+  @Test
+  public void f1() {}
+
+  @Test(groups = "go")
+  public void f2() {}
+}
diff --git a/src/test/java/test/conffailure/ClassWithFailedBeforeSuite.java b/src/test/java/test/conffailure/ClassWithFailedBeforeSuite.java
new file mode 100644
index 0000000..29e9eb8
--- /dev/null
+++ b/src/test/java/test/conffailure/ClassWithFailedBeforeSuite.java
@@ -0,0 +1,12 @@
+package test.conffailure;
+
+import org.testng.annotations.BeforeSuite;
+
+public class ClassWithFailedBeforeSuite {
+
+  @BeforeSuite
+  public void setUpShouldFail() {
+    throw new RuntimeException("Failing in setUp");
+  }
+
+}
diff --git a/src/test/java/test/conffailure/ClassWithFailedBeforeSuiteVerification.java b/src/test/java/test/conffailure/ClassWithFailedBeforeSuiteVerification.java
new file mode 100644
index 0000000..e42a950
--- /dev/null
+++ b/src/test/java/test/conffailure/ClassWithFailedBeforeSuiteVerification.java
@@ -0,0 +1,26 @@
+package test.conffailure;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+
+public class ClassWithFailedBeforeSuiteVerification {
+
+  private static boolean m_success1 = true;
+  private static boolean m_success2 = true;
+
+  // Should not be run because beforeSuite failed on the other class
+  @BeforeClass
+  public void setUp() {
+    m_success1 = false;
+  }
+
+  // Should not be run because beforeSuite failed on the other class
+  @AfterClass
+  public void tearDown() {
+    m_success2 = false;
+  }
+
+  static public boolean success() {
+    return m_success1 && m_success2;
+  }
+}
diff --git a/src/test/java/test/conffailure/ClassWithFailedBeforeTestClass.java b/src/test/java/test/conffailure/ClassWithFailedBeforeTestClass.java
new file mode 100644
index 0000000..9b4a1ea
--- /dev/null
+++ b/src/test/java/test/conffailure/ClassWithFailedBeforeTestClass.java
@@ -0,0 +1,18 @@
+package test.conffailure;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class ClassWithFailedBeforeTestClass {
+  @BeforeClass
+  public void setUpShouldFail() {
+    throw new RuntimeException("Failing in setUp");
+  }
+
+  // Adding this method or @Configuration will never be invoked
+  @Test
+  public void dummy() {
+
+  }
+
+}
diff --git a/src/test/java/test/conffailure/ClassWithFailedBeforeTestClassVerification.java b/src/test/java/test/conffailure/ClassWithFailedBeforeTestClassVerification.java
new file mode 100644
index 0000000..d8feb16
--- /dev/null
+++ b/src/test/java/test/conffailure/ClassWithFailedBeforeTestClassVerification.java
@@ -0,0 +1,33 @@
+package test.conffailure;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class ClassWithFailedBeforeTestClassVerification {
+
+  private static boolean m_success1 = false;
+  private static boolean m_success2 = false;
+
+  // Should be run even though ClassWithFailedBeforeTestClass failed in its configuration
+  @BeforeClass
+  public void setUpShouldPass() {
+    m_success1 = true;
+  }
+
+  // Should be run even though ClassWithFailedBeforeTestClass  failed in its configuration
+  @AfterClass
+  public void tearDown() {
+    m_success2 = true;
+  }
+
+  // Adding this method or @Configuration will never be invoked
+  @Test
+  public void dummy() {
+
+  }
+
+  static public boolean success() {
+    return m_success1 && m_success2;
+  }
+}
diff --git a/src/test/java/test/conffailure/ConfigurationFailure.java b/src/test/java/test/conffailure/ConfigurationFailure.java
new file mode 100644
index 0000000..249a801
--- /dev/null
+++ b/src/test/java/test/conffailure/ConfigurationFailure.java
@@ -0,0 +1,51 @@
+package test.conffailure;
+
+import static org.testng.Assert.assertTrue;
+
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import testhelper.OutputDirectoryPatch;
+
+/**
+ * Test various cases where the @Configuration methods fail
+ *
+ * Created on Jul 20, 2005
+ * @author cbeust
+ */
+public class ConfigurationFailure {
+
+  @Test
+  public void beforeTestClassFails() {
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG testng = new TestNG();
+    testng.setOutputDirectory(OutputDirectoryPatch.getOutputDirectory());
+    testng.setTestClasses(new Class[] {
+        ClassWithFailedBeforeTestClass.class,
+        ClassWithFailedBeforeTestClassVerification.class
+    });
+    testng.addListener(tla);
+    testng.setVerbose(0);
+    testng.run();
+    assertTrue(ClassWithFailedBeforeTestClassVerification.success(),
+        "Not all the @Configuration methods of Run2 were run");
+  }
+
+  @Test
+  public void beforeTestSuiteFails() {
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG testng = new TestNG();
+    testng.setOutputDirectory(OutputDirectoryPatch.getOutputDirectory());
+    testng.setTestClasses(new Class[] { ClassWithFailedBeforeSuite.class, ClassWithFailedBeforeSuiteVerification.class });
+    testng.addListener(tla);
+    testng.setVerbose(0);
+    testng.run();
+    assertTrue(ClassWithFailedBeforeSuiteVerification.success(),
+        "No @Configuration methods should have run");
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[AlwaysRunTest] " + s);
+  }
+}
diff --git a/src/test/java/test/configuration/Base.java b/src/test/java/test/configuration/Base.java
new file mode 100644
index 0000000..0749316
--- /dev/null
+++ b/src/test/java/test/configuration/Base.java
@@ -0,0 +1,24 @@
+package test.configuration;

+

+import org.testng.annotations.BeforeGroups;

+import org.testng.annotations.BeforeTest;

+import org.testng.annotations.Test;

+

+@Test(groups = {"base"})

+public class Base {

+  static int m_count;

+

+  @BeforeTest

+  public void init() {

+    m_count = 0;

+  }

+

+  @BeforeGroups(groups = "foo")

+  public void beforeGroups() {

+    m_count++;

+  }

+

+  private static void ppp(String s) {

+    System.out.println("[Base] " + s);

+  }

+}
\ No newline at end of file
diff --git a/src/test/java/test/configuration/Base3.java b/src/test/java/test/configuration/Base3.java
new file mode 100644
index 0000000..0991c5d
--- /dev/null
+++ b/src/test/java/test/configuration/Base3.java
@@ -0,0 +1,35 @@
+package test.configuration;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeGroups;
+
+public class Base3 {
+
+  static private boolean m_before = false;
+
+  /**
+   * @return the m_before
+   */
+  public static boolean getBefore() {
+    return m_before;
+  }
+
+  @BeforeGroups("cg34-1")
+  public void anotherBefore1() {
+    log("anotherBefore1");
+    Assert.assertFalse(m_before);
+    Assert.assertFalse(ConfigurationGroups3SampleTest.getF1());
+    m_before = true;
+  }
+
+  private void log(String string) {
+    ppp(string);
+  }
+
+  private void ppp(String s) {
+    if (false) {
+      System.out.println("[Base3] " + s);
+    }
+  }
+
+}
diff --git a/src/test/java/test/configuration/BaseBeforeTestOrdering.java b/src/test/java/test/configuration/BaseBeforeTestOrdering.java
new file mode 100644
index 0000000..7a6daa3
--- /dev/null
+++ b/src/test/java/test/configuration/BaseBeforeTestOrdering.java
@@ -0,0 +1,8 @@
+package test.configuration;
+
+public class BaseBeforeTestOrdering {
+
+  public void log(String s) {
+    BeforeTestOrderingTest.addTest(s);
+  }
+}
diff --git a/src/test/java/test/configuration/BaseGroupsASampleTest.java b/src/test/java/test/configuration/BaseGroupsASampleTest.java
new file mode 100644
index 0000000..d1fcbb1
--- /dev/null
+++ b/src/test/java/test/configuration/BaseGroupsASampleTest.java
@@ -0,0 +1,10 @@
+package test.configuration;

+

+import org.testng.annotations.Test;

+

+public class BaseGroupsASampleTest extends Base {

+   @Test(groups = "foo")

+   public void a() {

+//       System.out.println( "a" );

+   }

+}
\ No newline at end of file
diff --git a/src/test/java/test/configuration/BaseGroupsBSampleTest.java b/src/test/java/test/configuration/BaseGroupsBSampleTest.java
new file mode 100644
index 0000000..e345dc9
--- /dev/null
+++ b/src/test/java/test/configuration/BaseGroupsBSampleTest.java
@@ -0,0 +1,10 @@
+package test.configuration;

+

+import org.testng.annotations.Test;

+

+public class BaseGroupsBSampleTest extends Base {

+   @Test(groups = "foo")

+   public void b() {

+//       System.out.println( "b" );

+   }

+}
\ No newline at end of file
diff --git a/src/test/java/test/configuration/BaseGroupsTest.java b/src/test/java/test/configuration/BaseGroupsTest.java
new file mode 100644
index 0000000..de19db0
--- /dev/null
+++ b/src/test/java/test/configuration/BaseGroupsTest.java
@@ -0,0 +1,36 @@
+package test.configuration;

+

+import org.testng.Assert;

+import org.testng.TestListenerAdapter;

+import org.testng.TestNG;

+import org.testng.annotations.Test;

+

+/**

+ * Verify that a base class with a BeforeGroups method only gets invoked

+ * once, no matter how many subclasses it has

+ *

+ * Created on Jan 23, 2007

+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>

+ */

+public class BaseGroupsTest {

+

+    @Test

+    public void verifySingleInvocation() {

+      TestNG tng = new TestNG();

+      tng.setVerbose(0);

+      tng.setTestClasses(new Class[] {

+          BaseGroupsASampleTest.class,

+          BaseGroupsBSampleTest.class,

+      });

+      TestListenerAdapter tla = new TestListenerAdapter();

+      tng.addListener(tla);

+

+      tng.run();

+

+      Assert.assertEquals(Base.m_count, 1);

+    }

+

+    private static void ppp(String s) {

+      System.out.println("[BaseGroupsTest] " + s);

+    }

+}

diff --git a/src/test/java/test/configuration/BaseSuiteTest.java b/src/test/java/test/configuration/BaseSuiteTest.java
new file mode 100644
index 0000000..2a80722
--- /dev/null
+++ b/src/test/java/test/configuration/BaseSuiteTest.java
@@ -0,0 +1,22 @@
+package test.configuration;

+

+import org.testng.annotations.BeforeSuite;

+

+import java.util.ArrayList;

+import java.util.List;

+

+public class BaseSuiteTest {

+  public static List<Integer> m_order;

+

+  @BeforeSuite

+  public void beforeSuiteParent(){

+    m_order = new ArrayList<>();

+    m_order.add(1);

+  }

+

+//  @AfterSuite

+//  public void afterSuiteParent(){

+//    m_order.add(5);

+//    System.out.println("AFTER SUITE PARENT");

+//  }

+}

diff --git a/src/test/java/test/configuration/BeforeClassThreadA.java b/src/test/java/test/configuration/BeforeClassThreadA.java
new file mode 100644
index 0000000..c940f43
--- /dev/null
+++ b/src/test/java/test/configuration/BeforeClassThreadA.java
@@ -0,0 +1,18 @@
+package test.configuration;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class BeforeClassThreadA {
+    public static long WHEN;
+
+    @BeforeClass(alwaysRun = true)
+    public void setup() throws InterruptedException {
+        WHEN = System.currentTimeMillis();
+        Thread.sleep(2000);
+    }
+
+    @Test
+    public void execute() {
+    }
+}
diff --git a/src/test/java/test/configuration/BeforeClassThreadB.java b/src/test/java/test/configuration/BeforeClassThreadB.java
new file mode 100644
index 0000000..fd641df
--- /dev/null
+++ b/src/test/java/test/configuration/BeforeClassThreadB.java
@@ -0,0 +1,18 @@
+package test.configuration;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class BeforeClassThreadB {
+    public static long WHEN;
+
+    @BeforeClass(alwaysRun = true)
+    public void setup() throws InterruptedException {
+        WHEN = System.currentTimeMillis();
+        Thread.sleep(2000);
+    }
+
+    @Test
+    public void execute() {
+    }
+}
diff --git a/src/test/java/test/configuration/BeforeClassThreadTest.java b/src/test/java/test/configuration/BeforeClassThreadTest.java
new file mode 100644
index 0000000..a1f5deb
--- /dev/null
+++ b/src/test/java/test/configuration/BeforeClassThreadTest.java
@@ -0,0 +1,20 @@
+package test.configuration;
+
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+
+import test.SimpleBaseTest;
+import junit.framework.Assert;
+
+public class BeforeClassThreadTest extends SimpleBaseTest{
+
+    @Test
+    public void beforeClassMethodsShouldRunInParallel() {
+        TestNG tng = create(new Class[] { BeforeClassThreadA.class, BeforeClassThreadB.class });
+        tng.setParallel(XmlSuite.ParallelMode.METHODS);
+        tng.run();
+
+        Assert.assertTrue(Math.abs(BeforeClassThreadA.WHEN - BeforeClassThreadB.WHEN) < 1000);
+    }
+}
diff --git a/src/test/java/test/configuration/BeforeClassWithDisabledTest.java b/src/test/java/test/configuration/BeforeClassWithDisabledTest.java
new file mode 100644
index 0000000..ad7d525
--- /dev/null
+++ b/src/test/java/test/configuration/BeforeClassWithDisabledTest.java
@@ -0,0 +1,20 @@
+package test.configuration;
+
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+import junit.framework.Assert;
+
+public class BeforeClassWithDisabledTest extends SimpleBaseTest {
+
+    @Test
+    public void afterClassShouldRunEvenWithDisabledMethods() {
+        TestNG tng = create(new Class[] { ConfigurationDisabledSampleTest.class });
+        Assert.assertFalse(ConfigurationDisabledSampleTest.m_afterWasRun);
+        tng.run();
+        Assert.assertTrue(ConfigurationDisabledSampleTest.m_afterWasRun);
+    }
+}
+
+
diff --git a/src/test/java/test/configuration/BeforeMethodTest.java b/src/test/java/test/configuration/BeforeMethodTest.java
new file mode 100644
index 0000000..d0f1305
--- /dev/null
+++ b/src/test/java/test/configuration/BeforeMethodTest.java
@@ -0,0 +1,29 @@
+package test.configuration;
+
+import org.testng.Assert;
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Method;
+
+public class BeforeMethodTest {
+  private Method m_method;
+  private ITestContext m_context;
+
+  @BeforeMethod
+  public void before(Method m, ITestContext ctx) {
+    m_method = m;
+    m_context = ctx;
+  }
+
+  @Test
+  public void mainTest() {
+    Assert.assertEquals(m_method.getName(), "mainTest");
+    ITestNGMethod[] methods = m_context.getAllTestMethods();
+    Assert.assertEquals(1, methods.length);
+    Assert.assertEquals(methods[0].getMethod().getName(), "mainTest");
+
+  }
+}
diff --git a/src/test/java/test/configuration/BeforeTestOrdering1Test.java b/src/test/java/test/configuration/BeforeTestOrdering1Test.java
new file mode 100644
index 0000000..a945821
--- /dev/null
+++ b/src/test/java/test/configuration/BeforeTestOrdering1Test.java
@@ -0,0 +1,28 @@
+package test.configuration;
+
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+public class BeforeTestOrdering1Test extends BaseBeforeTestOrdering {
+
+  @BeforeTest
+  public void bt1() {
+    log("bt1");
+  }
+
+  @AfterTest
+  public void at1() {
+    log("at1");
+  }
+
+  @Test
+  public void f1() {
+    log("f1");
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[BeforeTestOrdering1Test] " + s);
+  }
+
+}
diff --git a/src/test/java/test/configuration/BeforeTestOrdering2Test.java b/src/test/java/test/configuration/BeforeTestOrdering2Test.java
new file mode 100644
index 0000000..1831fbb
--- /dev/null
+++ b/src/test/java/test/configuration/BeforeTestOrdering2Test.java
@@ -0,0 +1,28 @@
+package test.configuration;
+
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+public class BeforeTestOrdering2Test extends BaseBeforeTestOrdering{
+
+  @BeforeTest
+  public void bt2() {
+    log("bt2");
+  }
+
+  @AfterTest
+  public void at2() {
+    log("at2");
+  }
+
+  @Test
+  public void f2() {
+    log("f2");
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[BeforeTestOrdering2Test] " + s);
+  }
+
+}
diff --git a/src/test/java/test/configuration/BeforeTestOrderingTest.java b/src/test/java/test/configuration/BeforeTestOrderingTest.java
new file mode 100644
index 0000000..428765c
--- /dev/null
+++ b/src/test/java/test/configuration/BeforeTestOrderingTest.java
@@ -0,0 +1,65 @@
+package test.configuration;
+
+import org.testng.Assert;
+import org.testng.Reporter;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.TestHelper;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class BeforeTestOrderingTest {
+  private static List<String> m_testNames;
+
+  @BeforeSuite
+  public void init() {
+    m_testNames = new ArrayList<>();
+  }
+
+  static void addTest(String testName) {
+    m_testNames.add(testName);
+  }
+
+  @Test
+  public void verifyBeforeTestOrdering() {
+
+    XmlSuite s = new XmlSuite();
+
+    Reporter.log("BEFORE");
+
+    XmlTest t1 = new XmlTest(s);
+    XmlClass c1 = new XmlClass("test.configuration.BeforeTestOrdering1Test");
+    t1.getXmlClasses().add(c1);
+
+    XmlTest t2 = new XmlTest(s);
+    XmlClass c2 = new XmlClass("test.configuration.BeforeTestOrdering2Test");
+    t2.getXmlClasses().add(c2);
+
+    TestNG tng = TestHelper.createTestNG();
+    TestListenerAdapter tl = new TestListenerAdapter();
+    tng.addListener(tl);
+    tng.setXmlSuites(Arrays.asList(new XmlSuite[] { s }));
+    tng.run();
+
+    List<String> expected = Arrays.asList(new String[] {
+      "bt1", "f1", "at1", "bt2", "f2", "at2",
+    });
+
+    Assert.assertEquals(expected, m_testNames);
+  }
+
+
+
+  private static void ppp(String s) {
+    System.out.println("[BeforeTestOrderingTest] " + s);
+  }
+
+}
diff --git a/src/test/java/test/configuration/ConfigurationDisabledSampleTest.java b/src/test/java/test/configuration/ConfigurationDisabledSampleTest.java
new file mode 100644
index 0000000..debda3a
--- /dev/null
+++ b/src/test/java/test/configuration/ConfigurationDisabledSampleTest.java
@@ -0,0 +1,26 @@
+package test.configuration;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class ConfigurationDisabledSampleTest {
+
+  public static boolean m_afterWasRun;
+
+  @BeforeClass(alwaysRun = true)
+  public void before() {
+    m_afterWasRun = false;
+  }
+
+  @AfterClass(alwaysRun = true)
+  public void after() {
+    m_afterWasRun = true;
+  }
+
+  @Test
+  public void f1() {}
+
+  @Test(enabled = false)
+  public void f2() {}
+}
\ No newline at end of file
diff --git a/src/test/java/test/configuration/ConfigurationGroupBothSampleTest.java b/src/test/java/test/configuration/ConfigurationGroupBothSampleTest.java
new file mode 100644
index 0000000..0e1c972
--- /dev/null
+++ b/src/test/java/test/configuration/ConfigurationGroupBothSampleTest.java
@@ -0,0 +1,49 @@
+package test.configuration;

+

+import org.testng.annotations.AfterGroups;

+import org.testng.annotations.BeforeGroups;

+import org.testng.annotations.DataProvider;

+import org.testng.annotations.Test;

+

+import java.util.ArrayList;

+import java.util.Collections;

+import java.util.List;

+

+public class ConfigurationGroupBothSampleTest {

+  static List<Integer> m_list = Collections.synchronizedList(new ArrayList<Integer>());

+

+  private synchronized static void addToList(Integer n) {

+    m_list.add(n);

+  }

+

+  @BeforeGroups(groups={"twice"}, value={"twice"})

+  public void a(){

+    ppp("BEFORE()");

+    addToList(1);

+  }

+

+  @Test(groups={"twice"}, dataProvider="MyData", invocationCount = 2, threadPoolSize=2)

+  public void b(int a, int b) {

+    addToList(2);

+    ppp("B()"  + a + "," + b);

+  }

+

+  @AfterGroups(groups={"twice"}, value={"twice"})

+  public void c(){

+    addToList(3);

+    ppp("AFTER()");

+  }

+

+  @DataProvider(name="MyData")

+  public Object[][] input(){

+    return new Object[][]{ {1,1}, {2,2}, {3,3}};

+  }

+

+  private void ppp(String string) {

+    if (false) {

+      System.out.println("[A] " + string + " on Thread:" + Thread.currentThread());

+    }

+  }

+

+

+}

diff --git a/src/test/java/test/configuration/ConfigurationGroupDataProviderSampleTest.java b/src/test/java/test/configuration/ConfigurationGroupDataProviderSampleTest.java
new file mode 100644
index 0000000..5a686fd
--- /dev/null
+++ b/src/test/java/test/configuration/ConfigurationGroupDataProviderSampleTest.java
@@ -0,0 +1,44 @@
+package test.configuration;
+
+import org.testng.annotations.AfterGroups;
+import org.testng.annotations.BeforeGroups;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ConfigurationGroupDataProviderSampleTest {
+  static List<Integer> m_list = new ArrayList<>();
+
+  @BeforeGroups(groups={"twice"}, value={"twice"})
+  public void a(){
+    ppp("BEFORE()");
+    m_list.add(1);
+  }
+
+  @Test(groups={"twice"}, dataProvider="MyData")
+  public void b(int a, int b) {
+    m_list.add(2);
+    ppp("B()"  + a + "," + b);
+  }
+
+  @AfterGroups(groups={"twice"}, value={"twice"})
+  public void c(){
+    m_list.add(3);
+    ppp("AFTER()");
+  }
+
+  @DataProvider(name="MyData")
+  public Object[][] input(){
+    return new Object[][]{ {1,1}, {2,2}, {3,3}};
+  }
+
+  private void ppp(String string) {
+    if (false) {
+      System.out.println("[A] " + string);
+    }
+  }
+
+
+}
diff --git a/src/test/java/test/configuration/ConfigurationGroupInvocationCountSampleTest.java b/src/test/java/test/configuration/ConfigurationGroupInvocationCountSampleTest.java
new file mode 100644
index 0000000..ea41b34
--- /dev/null
+++ b/src/test/java/test/configuration/ConfigurationGroupInvocationCountSampleTest.java
@@ -0,0 +1,38 @@
+package test.configuration;
+
+import org.testng.annotations.AfterGroups;
+import org.testng.annotations.BeforeGroups;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ConfigurationGroupInvocationCountSampleTest {
+  static List<Integer> m_list = new ArrayList<>();
+
+  @BeforeGroups(groups={"twice"}, value={"twice"})
+  public void a(){
+    ppp("BEFORE()");
+    m_list.add(1);
+  }
+
+  @Test(groups = {"twice"}, invocationCount = 3)
+  public void b() {
+    m_list.add(2);
+    ppp("B()");
+  }
+
+  @AfterGroups(groups={"twice"}, value={"twice"})
+  public void c(){
+    m_list.add(3);
+    ppp("AFTER()");
+  }
+
+  private void ppp(String string) {
+    if (false) {
+      System.out.println("[A] " + string);
+    }
+  }
+
+
+}
diff --git a/src/test/java/test/configuration/ConfigurationGroups1SampleTest.java b/src/test/java/test/configuration/ConfigurationGroups1SampleTest.java
new file mode 100644
index 0000000..ebf2639
--- /dev/null
+++ b/src/test/java/test/configuration/ConfigurationGroups1SampleTest.java
@@ -0,0 +1,59 @@
+package test.configuration;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeGroups;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Simple beforeGroups test:  1 before method and 2 test method
+ *
+ * @author cbeust
+ * @date Mar 3, 2006
+ */
+public class ConfigurationGroups1SampleTest {
+  private boolean m_before = false;
+  private boolean m_f1 = false;
+
+  @BeforeGroups("cg1-1")
+  public void before1() {
+    Assert.assertFalse(m_before);
+    Assert.assertFalse(m_f1);
+    m_before = true;
+    log("before1");
+  }
+
+  @Test(groups = "cg1-a")
+  public void fa() {
+    log("fa");
+  }
+
+  @Test(groups = "cg1-1")
+  public void f1() {
+    Assert.assertTrue(m_before);
+    m_f1 = true;
+    log("f1");
+  }
+
+  private List<String> m_list = new ArrayList<>();
+
+  @Test(dependsOnGroups = {"cg1-a", "cg1-1"})
+  public void verify() {
+    Assert.assertTrue(m_before);
+    Assert.assertTrue(m_f1);
+  }
+
+  private void log(String s) {
+    m_list.add(s);
+    ppp(s);
+  }
+
+  private void ppp(String s) {
+    if (false) {
+      System.out.println("[ConfigurationGroups1SampleTest] " + s);
+    }
+  }
+
+}
diff --git a/src/test/java/test/configuration/ConfigurationGroups2SampleTest.java b/src/test/java/test/configuration/ConfigurationGroups2SampleTest.java
new file mode 100644
index 0000000..442f5f2
--- /dev/null
+++ b/src/test/java/test/configuration/ConfigurationGroups2SampleTest.java
@@ -0,0 +1,70 @@
+package test.configuration;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeGroups;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * beforeGroups test:  make sure that the beforeGroups method is invoked
+ * only once even if two test methods belong to the group
+ *
+ * @author cbeust
+ * @date Mar 3, 2006
+ */
+public class ConfigurationGroups2SampleTest {
+  private boolean m_before = false;
+  private boolean m_f1 = false;
+  private boolean m_g1 = false;
+
+  @BeforeGroups("cg2-1")
+  public void before1() {
+    Assert.assertFalse(m_before);
+    Assert.assertFalse(m_f1);
+    Assert.assertFalse(m_g1);
+    m_before = true;
+    log("before1");
+  }
+
+  @Test(groups = "cg2-a")
+  public void fa() {
+    log("fa");
+  }
+
+  @Test(groups = "cg2-1")
+  public void f1() {
+    Assert.assertTrue(m_before);
+    m_f1 = true;
+    log("f1");
+  }
+
+  @Test(groups = "cg2-1")
+  public void g1() {
+    Assert.assertTrue(m_before);
+    m_g1 = true;
+    log("g1");
+  }
+
+  private List<String> m_list = new ArrayList<>();
+
+  @Test(dependsOnGroups = {"cg2-a", "cg2-1"})
+  public void verify() {
+    Assert.assertTrue(m_before);
+    Assert.assertTrue(m_f1);
+    Assert.assertTrue(m_g1);
+  }
+
+  private void log(String s) {
+    m_list.add(s);
+    ppp(s);
+  }
+
+  private void ppp(String s) {
+    if (false) {
+      System.out.println("[ConfigurationGroups2SampleTest] " + s);
+    }
+  }
+
+}
diff --git a/src/test/java/test/configuration/ConfigurationGroups3SampleTest.java b/src/test/java/test/configuration/ConfigurationGroups3SampleTest.java
new file mode 100644
index 0000000..ab30607
--- /dev/null
+++ b/src/test/java/test/configuration/ConfigurationGroups3SampleTest.java
@@ -0,0 +1,66 @@
+package test.configuration;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeGroups;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * beforeGroups test:  make sure that if before methods are scattered on
+ * more than one class, they are still taken into account
+ *
+ * @author cbeust
+ * @date Mar 3, 2006
+ */
+public class ConfigurationGroups3SampleTest extends Base3 {
+  private boolean m_before = false;
+  static private boolean m_f1 = false;
+
+  @BeforeGroups("cg34-1")
+  public void before1() {
+    Assert.assertFalse(m_before);
+    Assert.assertFalse(m_f1);
+    m_before = true;
+    log("before1");
+  }
+
+  @Test(groups = "cg34-a")
+  public void fa() {
+    log("fa");
+  }
+
+  @Test(groups = "cg34-1")
+  public void f1() {
+    Assert.assertTrue(m_before);
+    Assert.assertTrue(Base3.getBefore());
+    m_f1 = true;
+    log("f1");
+  }
+
+  private List<String> m_list = new ArrayList<>();
+
+  @Test(dependsOnGroups = {"cg34-a", "cg34-1"})
+  public void verify() {
+    Assert.assertTrue(m_before);
+    Assert.assertTrue(Base3.getBefore());
+    Assert.assertTrue(m_f1);
+  }
+
+  private void log(String s) {
+    m_list.add(s);
+    ppp(s);
+  }
+
+  private void ppp(String s) {
+    if (false) {
+      System.out.println("[ConfigurationGroups3SampleTest] " + s);
+    }
+  }
+
+  public static boolean getF1() {
+    return m_f1;
+  }
+
+}
diff --git a/src/test/java/test/configuration/ConfigurationGroups4SampleTest.java b/src/test/java/test/configuration/ConfigurationGroups4SampleTest.java
new file mode 100644
index 0000000..7881397
--- /dev/null
+++ b/src/test/java/test/configuration/ConfigurationGroups4SampleTest.java
@@ -0,0 +1,55 @@
+package test.configuration;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterGroups;
+import org.testng.annotations.Test;
+
+/**
+ * Simple afterGroups test
+ *
+ * @author cbeust
+ * @date Mar 7, 2006
+ */
+public class ConfigurationGroups4SampleTest {
+
+  private boolean m_after = false;
+  private boolean m_run = false;
+
+  @Test
+  public void f() {
+    log("f");
+  }
+
+  @Test(groups = "cg4-1")
+  public void run() {
+    log("run");
+    Assert.assertFalse(m_after);
+    m_run = true;
+  }
+
+  @AfterGroups("cg4-1")
+  public void after1() {
+    log("after1");
+    Assert.assertTrue(m_run);
+    Assert.assertFalse(m_after);
+    m_after = true;
+  }
+
+  @Test(dependsOnGroups = "cg4-1")
+  public void verify() {
+    log("verify");
+    Assert.assertTrue(m_run, "run() wasn't run");
+    Assert.assertTrue(m_after, "after1() wasn't run");
+  }
+
+  private void log(String string) {
+    ppp(string);
+  }
+
+  private void ppp(String s) {
+    if (false) {
+      System.out.println("[ConfigurationGroups4SampleTest] " + s);
+    }
+  }
+
+}
diff --git a/src/test/java/test/configuration/ConfigurationGroups5SampleTest.java b/src/test/java/test/configuration/ConfigurationGroups5SampleTest.java
new file mode 100644
index 0000000..3cf728e
--- /dev/null
+++ b/src/test/java/test/configuration/ConfigurationGroups5SampleTest.java
@@ -0,0 +1,73 @@
+package test.configuration;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterGroups;
+import org.testng.annotations.Test;
+
+/**
+ * afterGroups test with more than one group
+ *
+ * @author cbeust
+ * @date Mar 7, 2006
+ */
+public class ConfigurationGroups5SampleTest {
+
+  private boolean m_after = false;
+  private boolean m_run1 = false;
+  private boolean m_run2 = false;
+  private int m_afterCount = 0;
+
+  @Test
+  public void f() {
+    log("f");
+  }
+
+  @Test(groups = "cg5-1")
+  public void run1() {
+    log("run1");
+    if (m_afterCount == 0) {
+      Assert.assertFalse(m_after);
+    }
+    m_run1 = true;
+  }
+
+  @Test(groups = "cg5-2")
+  public void run2() {
+    log("run2");
+    if (m_afterCount == 0) {
+      Assert.assertFalse(m_after);
+    }
+    m_run2 = true;
+  }
+
+  @AfterGroups({ "cg5-1", "cg5-2"})
+  public void after() {
+    log("after");
+    m_afterCount++;
+    Assert.assertTrue(m_run1 || m_run2);
+    if (m_afterCount == 0) {
+      Assert.assertFalse(m_after);
+    }
+    m_after = true;
+  }
+
+  @Test(dependsOnGroups = { "cg5-1", "cg5-2" })
+  public void verify() {
+    log("verify");
+    Assert.assertTrue(m_run1, "run1() wasn't run");
+    Assert.assertTrue(m_run2, "run2() wasn't run");
+    Assert.assertTrue(m_after, "after1() wasn't run");
+    Assert.assertEquals(2, m_afterCount);
+  }
+
+  private void log(String string) {
+    ppp(string);
+  }
+
+  private void ppp(String s) {
+    if (false) {
+      System.out.println("[ConfigurationGroups5SampleTest] " + s);
+    }
+  }
+
+}
diff --git a/src/test/java/test/configuration/ConfigurationGroups6SampleTest.java b/src/test/java/test/configuration/ConfigurationGroups6SampleTest.java
new file mode 100644
index 0000000..f61affc
--- /dev/null
+++ b/src/test/java/test/configuration/ConfigurationGroups6SampleTest.java
@@ -0,0 +1,65 @@
+package test.configuration;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterGroups;
+import org.testng.annotations.Test;
+
+/**
+ * afterGroups test when the group contains more than one method
+ *
+ * @author cbeust
+ * @date Mar 7, 2006
+ */
+public class ConfigurationGroups6SampleTest {
+
+  private boolean m_after = false;
+  private boolean m_run1 = false;
+  private boolean m_run2 = false;
+
+  @Test
+  public void f() {
+    log("f");
+  }
+
+  @Test(groups = "cg6-1")
+  public void run1() {
+    log("run1");
+    Assert.assertFalse(m_after);
+    m_run1 = true;
+  }
+
+  @Test(groups = "cg6-1")
+  public void run2() {
+    log("run2");
+    Assert.assertFalse(m_after);
+    m_run2 = true;
+  }
+
+  @AfterGroups("cg6-1")
+  public void after() {
+    log("after");
+    Assert.assertTrue(m_run1);
+    Assert.assertTrue(m_run2);
+    Assert.assertFalse(m_after);
+    m_after = true;
+  }
+
+  @Test(dependsOnGroups = { "cg6-1" })
+  public void verify() {
+    log("verify");
+    Assert.assertTrue(m_run1, "run1() wasn't run");
+    Assert.assertTrue(m_run2, "run2() wasn't run");
+    Assert.assertTrue(m_after, "after1() wasn't run");
+  }
+
+  private void log(String string) {
+    ppp(string);
+  }
+
+  private void ppp(String s) {
+    if (false) {
+      System.out.println("[ConfigurationGroups4SampleTest] " + s);
+    }
+  }
+
+}
diff --git a/src/test/java/test/configuration/ConfigurationGroups7SampleTest.java b/src/test/java/test/configuration/ConfigurationGroups7SampleTest.java
new file mode 100644
index 0000000..448e032
--- /dev/null
+++ b/src/test/java/test/configuration/ConfigurationGroups7SampleTest.java
@@ -0,0 +1,40 @@
+package test.configuration;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterGroups;
+import org.testng.annotations.BeforeGroups;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class ConfigurationGroups7SampleTest {
+  private List<String> m_log = new ArrayList<>();
+
+   @BeforeGroups({"A"})
+   private void initA() {
+     m_log.add("1");
+   }
+
+   @Test(groups = {"A"})
+   public void testSomething() {
+     m_log.add("2");
+   }
+
+   @Test(groups = {"A"})
+   public void testSomethingMore() {
+     m_log.add("2");
+   }
+
+   @AfterGroups({"A"})
+   private void cleanUpA() {
+     m_log.add("3");
+   }
+
+   @Test(dependsOnGroups = "A")
+   public void verify() {
+     Assert.assertEquals(Arrays.asList(new String[] { "1", "2", "2", "3"}), m_log);
+   }
+
+}
diff --git a/src/test/java/test/configuration/ConfigurationGroups8SampleTest.java b/src/test/java/test/configuration/ConfigurationGroups8SampleTest.java
new file mode 100644
index 0000000..ee049c1
--- /dev/null
+++ b/src/test/java/test/configuration/ConfigurationGroups8SampleTest.java
@@ -0,0 +1,53 @@
+package test.configuration;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Run with group "A" and "B"
+ * Make sure that only methods and configurations belonging to that group
+ * get invoked.
+ *
+ * @author cbeust
+ * @date Mar 9, 2006
+ */
+public class ConfigurationGroups8SampleTest {
+  private List<String> m_log = new ArrayList<>();
+
+  @Test
+  public void dummy() {
+    m_log.add("should not be invoked");
+  }
+
+  @Test(groups = { "A" })
+  public void testSomething() {
+    m_log.add("1");
+  }
+
+  @Test(groups = { "A" })
+  public void testSomethingMore() {
+    m_log.add("1");
+  }
+
+  @AfterMethod
+  private void cleanUpDummy() {
+    m_log.add("should not be invoked");
+  }
+
+  @AfterMethod(groups = "A")
+  private void cleanUpA() {
+    m_log.add("a");
+  }
+
+  @Test(dependsOnGroups = "A", groups = "B")
+  public void verify() {
+    Assert.assertEquals(Arrays.asList(new String[] { "1", "a", "1", "a" }),
+        m_log);
+  }
+
+}
diff --git a/src/test/java/test/configuration/ConfigurationInheritGroupsSampleTest.java b/src/test/java/test/configuration/ConfigurationInheritGroupsSampleTest.java
new file mode 100644
index 0000000..5b3d873
--- /dev/null
+++ b/src/test/java/test/configuration/ConfigurationInheritGroupsSampleTest.java
@@ -0,0 +1,23 @@
+package test.configuration;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+@Test(groups = { "group1" })
+public class ConfigurationInheritGroupsSampleTest {
+  private boolean m_ok = false;
+
+  @BeforeMethod
+  public void setUp() {
+    m_ok = true;
+  }
+
+  public void test1() {
+    Assert.assertTrue(m_ok);
+  }
+
+  private void ppp(String s) {
+    System.out.println("[ConfigurationInheritGroupsSampleTest] " + s);
+  }
+}
diff --git a/src/test/java/test/configuration/ConfigurationListenerSampleTest.java b/src/test/java/test/configuration/ConfigurationListenerSampleTest.java
new file mode 100644
index 0000000..0cbc3f9
--- /dev/null
+++ b/src/test/java/test/configuration/ConfigurationListenerSampleTest.java
@@ -0,0 +1,31 @@
+package test.configuration;
+
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+import test.configuration.ConfigurationListenerSampleTest.MyTLA;
+
+@Listeners(MyTLA.class)
+public class ConfigurationListenerSampleTest {
+  static boolean m_passed = false;
+
+  public static class MyTLA extends TestListenerAdapter {
+    @Override
+    public void onConfigurationFailure(ITestResult itr) {
+      m_passed = true;
+    }
+  }
+
+  @BeforeClass
+  public void bm() {
+    m_passed = false;
+    throw new RuntimeException();
+  }
+
+  @Test
+  public void f1() {
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/configuration/ConfigurationListenerTest.java b/src/test/java/test/configuration/ConfigurationListenerTest.java
new file mode 100644
index 0000000..5fbf5d6
--- /dev/null
+++ b/src/test/java/test/configuration/ConfigurationListenerTest.java
@@ -0,0 +1,18 @@
+package test.configuration;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+public class ConfigurationListenerTest extends SimpleBaseTest {
+
+  @Test
+  public void listenerShouldBeCalled() {
+    TestNG tng = create(ConfigurationListenerSampleTest.class);
+    Assert.assertFalse(ConfigurationListenerSampleTest.m_passed);
+    tng.run();
+    Assert.assertTrue(ConfigurationListenerSampleTest.m_passed);
+  }
+}
diff --git a/src/test/java/test/configuration/ConfigurationTest.java b/src/test/java/test/configuration/ConfigurationTest.java
new file mode 100644
index 0000000..1cd36a7
--- /dev/null
+++ b/src/test/java/test/configuration/ConfigurationTest.java
@@ -0,0 +1,114 @@
+package test.configuration;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+/**
+ * Test @Configuration
+ *
+ * @author cbeust
+ */
+public class ConfigurationTest {
+  private boolean m_beforeSuite = false;
+  private boolean m_afterSuite = false;
+  private boolean m_beforeClass = false;
+  private boolean m_afterClass = false;
+  private boolean m_beforeMethod  = false;
+  private boolean m_afterMethod = false;
+
+  @BeforeSuite
+  public void beforeSuite() {
+    ppp("@@@@ BEFORE_SUITE");
+    assert ! m_afterSuite : "afterSuite shouldn't have run";
+    assert ! m_beforeClass : "beforeClass shouldn't have run";
+    assert ! m_afterClass : "afterClass shouldn't have run";
+    assert ! m_beforeMethod: "beforeMethod shouldn't have run";
+    assert ! m_afterMethod: "afterMethod shouldn't have run";
+    m_beforeSuite = true;
+  }
+
+  @BeforeClass
+  public void beforeClass() {
+    ppp("@@@@ BEFORE_CLASS");
+    assert m_beforeSuite : "beforeSuite should have run";
+    assert ! m_afterSuite : "afterSuite shouldn't have run";
+    assert ! m_beforeClass : "beforeClass shouldn't have run";
+    assert ! m_afterClass : "afterClass shouldn't have run";
+    assert ! m_beforeMethod: "beforeMethod shouldn't have run";
+    assert ! m_afterMethod: "afterMethod shouldn't have run";
+    m_beforeClass = true;
+  }
+
+  @BeforeMethod
+  public void beforeMethod() {
+    ppp("@@@@ BEFORE_METHOD");
+    assert m_beforeSuite : "beforeSuite should have run";
+    assert m_beforeClass : "beforeClass have run";
+    assert ! m_afterSuite : "afterSuite shouldn't have run";
+    assert ! m_afterClass : "afterClass shouldn't have run";
+    assert ! m_beforeMethod: "beforeMethod shouldn't have run";
+    assert ! m_afterMethod: "afterMethod shouldn't have run";
+    m_beforeMethod = true;
+  }
+
+  @AfterMethod
+  public void afterMethod() {
+    ppp("@@@@ AFTER_METHOD");
+    assert m_beforeSuite : "beforeSuite should have run";
+    assert m_beforeClass : "beforeClass have run";
+    assert m_beforeMethod: "beforeMethod should have run";
+    assert ! m_afterSuite : "afterSuite shouldn't have run";
+    assert ! m_afterClass : "afterClass shouldn't have run";
+    assert ! m_afterMethod: "afterMethod shouldn't have run";
+    m_afterMethod = true;
+  }
+
+  @AfterClass
+  public void afterClass() {
+    ppp("@@@@ AFTER_CLASS");
+    assert m_beforeSuite : "beforeSuite should have run";
+    assert m_beforeClass : "beforeClass have run";
+    assert m_beforeMethod: "beforeMethod should have run";
+    assert m_afterMethod: "afterMethod should have run";
+    assert ! m_afterClass : "afterClass shouldn't have run";
+    assert ! m_afterSuite : "afterSuite shouldn't have run";
+    m_afterClass = true;
+  }
+
+  @AfterSuite
+  public void afterSuite() {
+    ppp("@@@@ AFTER_SUITE");
+    ppp(m_beforeSuite + " " + m_beforeClass + " " + m_beforeMethod
+        + " " + m_afterMethod + " " + m_afterClass + " " + m_afterSuite);
+    assert m_beforeSuite : "beforeSuite should have run";
+    assert m_beforeClass : "beforeClass have run";
+    assert m_beforeMethod: "beforeMethod should have run";
+    assert m_afterMethod: "afterMethod should have run";
+    assert m_afterClass : "afterClass should have run";
+    assert ! m_afterSuite : "afterSuite shouldn't have run";
+    m_afterSuite = true;
+  }
+
+  @Test
+  public void verify() {
+    ppp("@@@@ VERIFY");
+    assert m_beforeSuite : "beforeSuite should have run";
+    assert m_beforeClass : "beforeClass have run";
+    assert m_beforeMethod: "beforeMethod should have run";
+    assert ! m_afterSuite : "afterSuite shouldn't have run";
+    assert ! m_afterClass : "afterClass shouldn't have run";
+    assert ! m_afterMethod: "afterMethod shouldn't have run";
+  }
+
+  private static void ppp(String s) {
+    if (false) {
+      System.out.println("[ConfigurationTest] " + s);
+    }
+  }
+
+}
diff --git a/src/test/java/test/configuration/ConfigurationWithParameters.java b/src/test/java/test/configuration/ConfigurationWithParameters.java
new file mode 100644
index 0000000..91e3c72
--- /dev/null
+++ b/src/test/java/test/configuration/ConfigurationWithParameters.java
@@ -0,0 +1,23 @@
+package test.configuration;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+public class ConfigurationWithParameters {
+  private String m_param;
+
+  @Parameters({ "param" })
+  @BeforeTest
+  public void testInit(String param) {
+    m_param = param;
+  }
+
+  @Parameters({ "param" })
+  @Test
+  public void testMethod(String param) {
+    Assert.assertEquals(m_param, param);
+  }
+
+}
diff --git a/src/test/java/test/configuration/ExternalConfigurationClass.java b/src/test/java/test/configuration/ExternalConfigurationClass.java
new file mode 100644
index 0000000..a32ec9e
--- /dev/null
+++ b/src/test/java/test/configuration/ExternalConfigurationClass.java
@@ -0,0 +1,53 @@
+package test.configuration;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.BeforeTest;
+
+
+public class ExternalConfigurationClass {
+  public static boolean s_afterMethod;
+  public static boolean s_afterClass;
+  public static boolean s_afterTest;
+
+  @BeforeSuite
+  public void beforeSuite() {
+    MethodCallOrderTest.s_beforeSuite = true;
+  }
+
+  @AfterSuite
+  public void cleanUp() {
+    s_afterMethod = false;
+    s_afterClass = false;
+    s_afterTest = false;
+  }
+
+  @BeforeTest
+  public void beforeTest() {
+    assertTrue(MethodCallOrderTest.s_beforeSuite);
+    assertFalse(MethodCallOrderTest.s_beforeTest);
+    assertFalse(MethodCallOrderTest.s_beforeClass);
+    assertFalse(MethodCallOrderTest.s_beforeMethod);
+
+    MethodCallOrderTest.s_beforeTest = true;
+  }
+
+  @AfterTest
+  public void afterTest() {
+    assertTrue(s_afterMethod, "afterTestMethod should have been run");
+    assertTrue(s_afterClass, "afterTestClass should have been run");
+    assertFalse(s_afterTest, "afterTest should haven't been run");
+    s_afterTest = true;
+  }
+
+  @AfterSuite
+  public void afterSuite() {
+    assertTrue(s_afterMethod, "afterTestMethod should have been run");
+    assertTrue(s_afterClass, "afterTestClass should have been run");
+    assertTrue(s_afterTest, "afterTest should have been run");
+  }
+}
diff --git a/src/test/java/test/configuration/GroupsTest.java b/src/test/java/test/configuration/GroupsTest.java
new file mode 100644
index 0000000..d096160
--- /dev/null
+++ b/src/test/java/test/configuration/GroupsTest.java
@@ -0,0 +1,55 @@
+package test.configuration;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class GroupsTest {
+
+  private TestNG m_testNg;
+
+  @BeforeMethod
+  public void setUp() {
+    m_testNg = new TestNG();
+    m_testNg.setVerbose(0);
+  }
+
+  @Test
+  public void verifyDataProviderAfterGroups() {
+    runTest(ConfigurationGroupDataProviderSampleTest.class,
+        ConfigurationGroupDataProviderSampleTest.m_list,
+        Arrays.asList(new Integer[] {
+            1, 2, 2, 2, 3
+      }));
+  }
+
+  @Test
+  public void verifyParametersAfterGroups() {
+    runTest(ConfigurationGroupInvocationCountSampleTest.class,
+        ConfigurationGroupInvocationCountSampleTest.m_list,
+        Arrays.asList(new Integer[] {
+            1, 2, 2, 2, 3
+      }));
+  }
+
+  @Test
+  public void verifyBothAfterGroups() {
+    runTest(ConfigurationGroupBothSampleTest.class,
+        ConfigurationGroupBothSampleTest.m_list,
+        Arrays.asList(new Integer[] {
+          1, 2, 2, 2, 2, 2, 2, 3
+      }));
+  }
+
+  private void runTest(Class cls, List<Integer> list, List<Integer> expected) {
+      m_testNg.setTestClasses(new Class[] {
+          cls
+      });
+      m_testNg.run();
+      Assert.assertEquals(list, expected);
+  }
+}
diff --git a/src/test/java/test/configuration/MethodCallOrderTest.java b/src/test/java/test/configuration/MethodCallOrderTest.java
new file mode 100644
index 0000000..843a96c
--- /dev/null
+++ b/src/test/java/test/configuration/MethodCallOrderTest.java
@@ -0,0 +1,71 @@
+package test.configuration;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class MethodCallOrderTest {
+  public static boolean s_beforeSuite;
+  public static boolean s_beforeTest;
+  public static boolean s_beforeClass;
+  public static boolean s_beforeMethod;
+
+  @BeforeClass
+  public void beforeClass() {
+    assertTrue(s_beforeSuite);
+    assertTrue(s_beforeTest);
+    assertFalse(s_beforeClass);
+    assertFalse(s_beforeMethod);
+
+    s_beforeClass = true;
+  }
+
+  @AfterSuite
+  public void cleanUp() {
+    s_beforeSuite = false;
+    s_beforeTest = false;
+    s_beforeClass = false;
+    s_beforeMethod = false;
+  }
+
+
+  @BeforeMethod
+  public void beforeMethod() {
+    assertTrue(s_beforeSuite);
+    assertTrue(s_beforeTest);
+    assertTrue(s_beforeClass);
+    assertFalse(s_beforeMethod);
+    s_beforeMethod = true;
+  }
+
+  @Test
+  public void realTest() {
+    assertTrue(s_beforeSuite);
+    assertTrue(s_beforeTest);
+    assertTrue(s_beforeClass);
+    assertTrue(s_beforeMethod);
+  }
+
+  @AfterMethod
+  public void afterMethod() {
+    assertFalse(ExternalConfigurationClass.s_afterMethod, "afterTestMethod shouldn't have been run");
+    assertFalse(ExternalConfigurationClass.s_afterClass, "afterTestClass shouldn't have been run");
+    assertFalse(ExternalConfigurationClass.s_afterTest, "afterTest should haven't been run");
+
+    ExternalConfigurationClass.s_afterMethod = true;
+  }
+
+  @AfterClass
+  public void afterClass() {
+    assertTrue(ExternalConfigurationClass.s_afterMethod, "afterTestMethod should have been run");
+    assertFalse(ExternalConfigurationClass.s_afterClass, "afterTestClass shouldn't have been run");
+    assertFalse(ExternalConfigurationClass.s_afterTest, "afterTest should haven't been run");
+    ExternalConfigurationClass.s_afterClass = true;
+  }
+}
diff --git a/src/test/java/test/configuration/MultipleBeforeGroupTest.java b/src/test/java/test/configuration/MultipleBeforeGroupTest.java
new file mode 100644
index 0000000..0191a10
--- /dev/null
+++ b/src/test/java/test/configuration/MultipleBeforeGroupTest.java
@@ -0,0 +1,25 @@
+package test.configuration;

+

+import org.testng.Assert;

+import org.testng.annotations.BeforeGroups;

+import org.testng.annotations.Test;

+

+@Test( groups = "foo" )

+public class MultipleBeforeGroupTest {

+  private int m_count = 0;

+

+  @BeforeGroups( "foo" )

+  public void beforeGroups() {

+    m_count++;

+  }

+

+  @Test()

+  public void test() {

+  }

+

+  @Test(dependsOnMethods = "test")

+  public void verify() {

+    Assert.assertEquals(1, m_count);

+  }

+

+}

diff --git a/src/test/java/test/configuration/ReflectMethodParametrizedConfigurationMethodTest.java b/src/test/java/test/configuration/ReflectMethodParametrizedConfigurationMethodTest.java
new file mode 100644
index 0000000..beba936
--- /dev/null
+++ b/src/test/java/test/configuration/ReflectMethodParametrizedConfigurationMethodTest.java
@@ -0,0 +1,44 @@
+package test.configuration;

+

+import org.testng.Assert;

+import org.testng.annotations.AfterClass;

+import org.testng.annotations.AfterMethod;

+import org.testng.annotations.BeforeMethod;

+import org.testng.annotations.Test;

+

+import java.lang.reflect.Method;

+import java.util.HashMap;

+import java.util.Map;

+

+

+/**

+ * This class/interface

+ */

+public class ReflectMethodParametrizedConfigurationMethodTest {

+  private Map<String, String> m_before= new HashMap<>();

+  private Map<String, String> m_after= new HashMap<>();

+

+  @BeforeMethod

+  public void beforeMethod(Method tobeInvokedTestMethod) {

+    m_before.put(tobeInvokedTestMethod.getName(), tobeInvokedTestMethod.getName());

+  }

+

+  @Test

+  public void test1() {}

+

+  @Test

+  public void test2() {}

+

+  @AfterMethod

+  public void afterMethod(Method invokedTestMethod) {

+    m_after.put(invokedTestMethod.getName(), invokedTestMethod.getName());

+  }

+

+  @AfterClass

+  public void assertBeforeAfterMethodsInvocations() {

+    Assert.assertTrue(m_before.containsKey("test1"), "@Test method should have been passed to @BeforeMethod");

+    Assert.assertTrue(m_before.containsKey("test2"), "@Test method should have been passed to @BeforeMethod");

+    Assert.assertTrue(m_after.containsKey("test1"), "@Test method should have been passed to @AfterMethod");

+    Assert.assertTrue(m_before.containsKey("test2"), "@Test method should have been passed to @AfterMethod");

+  }

+}

diff --git a/src/test/java/test/configuration/SingleConfigurationTest.java b/src/test/java/test/configuration/SingleConfigurationTest.java
new file mode 100644
index 0000000..ae6b71d
--- /dev/null
+++ b/src/test/java/test/configuration/SingleConfigurationTest.java
@@ -0,0 +1,40 @@
+package test.configuration;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+
+/**
+ * Make sure that @BeforeTest is only called once if a factory is used
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public class SingleConfigurationTest {
+
+  private static int m_before;
+
+  @Factory(dataProvider = "dp")
+  public SingleConfigurationTest(int n) {
+  }
+
+  @DataProvider
+  public static Object[][] dp() {
+    return new Object[][] {
+        new Object[] { 42 },
+        new Object[] { 43 },
+    };
+  }
+
+  @BeforeTest
+  public void bt() {
+    m_before++;
+  }
+
+  @Test
+  public void verify() {
+    Assert.assertEquals(m_before, 1);
+  }
+}
diff --git a/src/test/java/test/configuration/SuiteFactoryOnceSample1Test.java b/src/test/java/test/configuration/SuiteFactoryOnceSample1Test.java
new file mode 100644
index 0000000..401e593
--- /dev/null
+++ b/src/test/java/test/configuration/SuiteFactoryOnceSample1Test.java
@@ -0,0 +1,25 @@
+package test.configuration;
+
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+public class SuiteFactoryOnceSample1Test {
+  public static int m_before = 0;
+  public static int m_after = 0;
+
+  @BeforeSuite
+  public void bs() {
+    m_before++;
+  }
+
+  @AfterSuite
+  public void as() {
+    m_after++;
+  }
+
+  @Test
+  public void g1() {
+  }
+
+}
\ No newline at end of file
diff --git a/src/test/java/test/configuration/SuiteFactoryOnceSample2Test.java b/src/test/java/test/configuration/SuiteFactoryOnceSample2Test.java
new file mode 100644
index 0000000..d2bf77c
--- /dev/null
+++ b/src/test/java/test/configuration/SuiteFactoryOnceSample2Test.java
@@ -0,0 +1,11 @@
+package test.configuration;
+
+import org.testng.annotations.Factory;
+
+public class SuiteFactoryOnceSample2Test {
+
+  @Factory
+  public Object[] factory() {
+    return new Object[] { new SuiteFactoryOnceSample1Test(), new SuiteFactoryOnceSample1Test() };
+  }
+}
diff --git a/src/test/java/test/configuration/SuiteFactoryOnceTest.java b/src/test/java/test/configuration/SuiteFactoryOnceTest.java
new file mode 100644
index 0000000..dadf035
--- /dev/null
+++ b/src/test/java/test/configuration/SuiteFactoryOnceTest.java
@@ -0,0 +1,22 @@
+package test.configuration;
+
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+import junit.framework.Assert;
+
+public class SuiteFactoryOnceTest extends SimpleBaseTest {
+
+  @Test
+  public void suiteMethodsShouldOnlyRunOnce() {
+    TestNG tng = create(SuiteFactoryOnceSample2Test.class);
+    SuiteFactoryOnceSample1Test.m_before = 0;
+    SuiteFactoryOnceSample1Test.m_after = 0;
+    tng.run();
+
+    Assert.assertEquals(1, SuiteFactoryOnceSample1Test.m_before);
+    Assert.assertEquals(1, SuiteFactoryOnceSample1Test.m_after);
+  }
+
+}
diff --git a/src/test/java/test/configuration/SuiteTest.java b/src/test/java/test/configuration/SuiteTest.java
new file mode 100644
index 0000000..dd70b71
--- /dev/null
+++ b/src/test/java/test/configuration/SuiteTest.java
@@ -0,0 +1,22 @@
+package test.configuration;

+

+import org.testng.annotations.BeforeSuite;

+import org.testng.annotations.Test;

+

+public class SuiteTest extends BaseSuiteTest {

+  @BeforeSuite(dependsOnMethods={"beforeSuiteParent"})

+  public void beforeSuiteChild(){

+    m_order.add(2);

+  }

+

+//  @AfterSuite(dependsOnMethods={"afterSuiteParent"})

+//  public void afterSuiteChild(){

+//    m_order.add(4);

+//    System.out.println("AFTER SUITE CHILD");

+//  }

+

+  @Test

+  public void test1(){

+    m_order.add(3);

+  }

+}

diff --git a/src/test/java/test/configuration/VerifySuiteTest.java b/src/test/java/test/configuration/VerifySuiteTest.java
new file mode 100644
index 0000000..fb873cc
--- /dev/null
+++ b/src/test/java/test/configuration/VerifySuiteTest.java
@@ -0,0 +1,14 @@
+package test.configuration;

+

+import org.testng.Assert;

+import org.testng.annotations.AfterSuite;

+

+import java.util.Arrays;

+

+public class VerifySuiteTest {

+

+  @AfterSuite

+  public void verify() {

+    Assert.assertEquals(Arrays.asList(1, 2, 3), SuiteTest.m_order);

+  }

+}

diff --git a/src/test/java/test/configurationfailurepolicy/ClassWithFailedBeforeClassMethod.java b/src/test/java/test/configurationfailurepolicy/ClassWithFailedBeforeClassMethod.java
new file mode 100644
index 0000000..45fa3f0
--- /dev/null
+++ b/src/test/java/test/configurationfailurepolicy/ClassWithFailedBeforeClassMethod.java
@@ -0,0 +1,23 @@
+package test.configurationfailurepolicy;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class ClassWithFailedBeforeClassMethod {
+
+  @BeforeClass
+  public void setupClassFails() {
+    throw new RuntimeException( "setup class fail" );
+  }
+
+  @BeforeMethod
+  public void setupMethod() {
+
+  }
+
+  @Test
+  public void test1() {
+
+  }
+}
diff --git a/src/test/java/test/configurationfailurepolicy/ClassWithFailedBeforeMethod.java b/src/test/java/test/configurationfailurepolicy/ClassWithFailedBeforeMethod.java
new file mode 100644
index 0000000..250dd84
--- /dev/null
+++ b/src/test/java/test/configurationfailurepolicy/ClassWithFailedBeforeMethod.java
@@ -0,0 +1,17 @@
+package test.configurationfailurepolicy;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class ClassWithFailedBeforeMethod {
+
+  @BeforeMethod
+  public void setupShouldFail() {
+    throw new RuntimeException("Failing in setUp");
+  }
+
+  @Test
+  public void test1() {
+
+  }
+}
diff --git a/src/test/java/test/configurationfailurepolicy/ClassWithFailedBeforeMethodAndMultipleInvocations.java b/src/test/java/test/configurationfailurepolicy/ClassWithFailedBeforeMethodAndMultipleInvocations.java
new file mode 100644
index 0000000..58e1ed3
--- /dev/null
+++ b/src/test/java/test/configurationfailurepolicy/ClassWithFailedBeforeMethodAndMultipleInvocations.java
@@ -0,0 +1,31 @@
+package test.configurationfailurepolicy;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class ClassWithFailedBeforeMethodAndMultipleInvocations {
+
+  @BeforeMethod
+  public void setupShouldFail() {
+    throw new RuntimeException("Failing in setUp");
+  }
+
+  @DataProvider( name = "data.provider" )
+  public Object[][] dataProvider() {
+    return new Object[][] {
+        new Object[] { "data1" },
+        new Object[] { "data2" }
+    };
+  }
+  
+  @Test( dataProvider = "data.provider" )
+  public void test1( String s ) {
+
+  }
+  
+  @Test( invocationCount = 2 )
+  public void test2() {
+    
+  }
+}
diff --git a/src/test/java/test/configurationfailurepolicy/ClassWithFailedBeforeMethodAndMultipleTests.java b/src/test/java/test/configurationfailurepolicy/ClassWithFailedBeforeMethodAndMultipleTests.java
new file mode 100644
index 0000000..8ae3f31
--- /dev/null
+++ b/src/test/java/test/configurationfailurepolicy/ClassWithFailedBeforeMethodAndMultipleTests.java
@@ -0,0 +1,22 @@
+package test.configurationfailurepolicy;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class ClassWithFailedBeforeMethodAndMultipleTests {
+
+  @BeforeMethod
+  public void setupShouldFail() {
+    throw new RuntimeException("Failing in setUp");
+  }
+
+  @Test
+  public void test1() {
+
+  }
+
+  @Test
+  public void test2() {
+
+  }
+}
diff --git a/src/test/java/test/configurationfailurepolicy/ClassWithSkippingBeforeMethod.java b/src/test/java/test/configurationfailurepolicy/ClassWithSkippingBeforeMethod.java
new file mode 100644
index 0000000..27eada2
--- /dev/null
+++ b/src/test/java/test/configurationfailurepolicy/ClassWithSkippingBeforeMethod.java
@@ -0,0 +1,24 @@
+package test.configurationfailurepolicy;
+
+import org.testng.SkipException;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class ClassWithSkippingBeforeMethod {
+
+  private int invocations = 0;
+
+  @BeforeMethod
+  public void beforeMethod() {
+      invocations++;
+      if (invocations ==2) {
+        throw new SkipException("skipping");
+      }
+  }
+
+  @Test
+  public void test1() {}
+
+  @Test
+  public void test2() {}
+}
diff --git a/src/test/java/test/configurationfailurepolicy/ExtendsClassWithFailedBeforeClassMethod.java b/src/test/java/test/configurationfailurepolicy/ExtendsClassWithFailedBeforeClassMethod.java
new file mode 100644
index 0000000..9942227
--- /dev/null
+++ b/src/test/java/test/configurationfailurepolicy/ExtendsClassWithFailedBeforeClassMethod.java
@@ -0,0 +1,11 @@
+package test.configurationfailurepolicy;
+
+import org.testng.annotations.Test;
+
+public class ExtendsClassWithFailedBeforeClassMethod extends ClassWithFailedBeforeClassMethod {
+
+  @Test
+  public void test2() {
+        // should be skipped, but BeforeClass method attempted again
+  }
+}
diff --git a/src/test/java/test/configurationfailurepolicy/ExtendsClassWithFailedBeforeMethod.java b/src/test/java/test/configurationfailurepolicy/ExtendsClassWithFailedBeforeMethod.java
new file mode 100644
index 0000000..875c657
--- /dev/null
+++ b/src/test/java/test/configurationfailurepolicy/ExtendsClassWithFailedBeforeMethod.java
@@ -0,0 +1,17 @@
+package test.configurationfailurepolicy;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class ExtendsClassWithFailedBeforeMethod extends ClassWithFailedBeforeMethod {
+
+  @BeforeMethod
+  public void setupExtension() {
+
+  }
+
+  @Test
+  public void test2() {
+
+  }
+}
diff --git a/src/test/java/test/configurationfailurepolicy/FactoryClassWithFailedBeforeClassMethod.java b/src/test/java/test/configurationfailurepolicy/FactoryClassWithFailedBeforeClassMethod.java
new file mode 100644
index 0000000..2cc53b9
--- /dev/null
+++ b/src/test/java/test/configurationfailurepolicy/FactoryClassWithFailedBeforeClassMethod.java
@@ -0,0 +1,13 @@
+package test.configurationfailurepolicy;
+
+import org.testng.annotations.Factory;
+
+public class FactoryClassWithFailedBeforeClassMethod extends ClassWithFailedBeforeClassMethod {
+  @Factory
+  public Object[] createTests() {
+    Object[] instances = new Object[2];
+    instances[0] = new FactoryClassWithFailedBeforeClassMethod();
+    instances[1] = new FactoryClassWithFailedBeforeClassMethod();
+    return instances;
+  }
+}
diff --git a/src/test/java/test/configurationfailurepolicy/FactoryClassWithFailedBeforeMethod.java b/src/test/java/test/configurationfailurepolicy/FactoryClassWithFailedBeforeMethod.java
new file mode 100644
index 0000000..3a4582f
--- /dev/null
+++ b/src/test/java/test/configurationfailurepolicy/FactoryClassWithFailedBeforeMethod.java
@@ -0,0 +1,13 @@
+package test.configurationfailurepolicy;
+
+import org.testng.annotations.Factory;
+
+public class FactoryClassWithFailedBeforeMethod extends ClassWithFailedBeforeMethod {
+  @Factory
+  public Object[] createTests() {
+    Object[] instances = new Object[2];
+    instances[0] = new FactoryClassWithFailedBeforeMethod();
+    instances[1] = new FactoryClassWithFailedBeforeMethod();
+    return instances;
+  }
+}
diff --git a/src/test/java/test/configurationfailurepolicy/FactoryClassWithFailedBeforeMethodAndMultipleInvocations.java b/src/test/java/test/configurationfailurepolicy/FactoryClassWithFailedBeforeMethodAndMultipleInvocations.java
new file mode 100644
index 0000000..a5af767
--- /dev/null
+++ b/src/test/java/test/configurationfailurepolicy/FactoryClassWithFailedBeforeMethodAndMultipleInvocations.java
@@ -0,0 +1,13 @@
+package test.configurationfailurepolicy;
+
+import org.testng.annotations.Factory;
+
+public class FactoryClassWithFailedBeforeMethodAndMultipleInvocations extends ClassWithFailedBeforeMethodAndMultipleInvocations {
+  @Factory
+  public Object[] createTests() {
+    Object[] instances = new Object[2];
+    instances[0] = new FactoryClassWithFailedBeforeMethodAndMultipleInvocations();
+    instances[1] = new FactoryClassWithFailedBeforeMethodAndMultipleInvocations();
+    return instances;
+  }
+}
diff --git a/src/test/java/test/configurationfailurepolicy/FailurePolicyTest.java b/src/test/java/test/configurationfailurepolicy/FailurePolicyTest.java
new file mode 100644
index 0000000..97ebdd2
--- /dev/null
+++ b/src/test/java/test/configurationfailurepolicy/FailurePolicyTest.java
@@ -0,0 +1,105 @@
+package test.configurationfailurepolicy;
+
+import static org.testng.Assert.assertEquals;
+
+import org.testng.ITestContext;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+
+import testhelper.OutputDirectoryPatch;
+
+public class FailurePolicyTest {
+
+  // only if this is run from an xml file that sets this on the suite
+  @BeforeClass(enabled=false)
+  public void setupClass( ITestContext testContext) {
+    assertEquals(testContext.getSuite().getXmlSuite().getConfigFailurePolicy(), XmlSuite.CONTINUE);
+  }
+
+  @DataProvider( name="dp" )
+  public Object[][] getData() {
+    Object[][] data = new Object[][] {
+      // params - confFail, confSkip, skipedTests
+      new Object[] { new Class[] { ClassWithFailedBeforeClassMethod.class }, 1, 1, 1 },
+      new Object[] { new Class[] { ClassWithFailedBeforeMethodAndMultipleTests.class }, 2, 0, 2 },
+      new Object[] { new Class[] { ClassWithFailedBeforeMethodAndMultipleInvocations.class }, 4, 0, 4 },
+      new Object[] { new Class[] { ExtendsClassWithFailedBeforeMethod.class }, 2, 2, 2 },
+      new Object[] { new Class[] { ClassWithFailedBeforeClassMethod.class }, 1, 1, 1 },
+      new Object[] { new Class[] { ExtendsClassWithFailedBeforeClassMethod.class }, 1, 2, 2 },
+      new Object[] { new Class[] { ClassWithFailedBeforeClassMethod.class, ExtendsClassWithFailedBeforeClassMethod.class }, 2, 3, 3 },
+      new Object[] { new Class[] { ClassWithSkippingBeforeMethod.class }, 0, 1, 1 },
+      new Object[] { new Class[] { FactoryClassWithFailedBeforeMethod.class }, 2, 0, 2 },
+      new Object[] { new Class[] { FactoryClassWithFailedBeforeMethodAndMultipleInvocations.class }, 8, 0, 8 },
+      new Object[] { new Class[] { FactoryClassWithFailedBeforeClassMethod.class }, 2, 2, 2 },
+    };
+    return data;
+  }
+
+  @Test( dataProvider = "dp" )
+  public void confFailureTest(Class[] classesUnderTest, int configurationFailures, int configurationSkips, int skippedTests) {
+
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG testng = new TestNG();
+    testng.setOutputDirectory(OutputDirectoryPatch.getOutputDirectory());
+    testng.setTestClasses(classesUnderTest);
+    testng.addListener(tla);
+    testng.setVerbose(0);
+    testng.setConfigFailurePolicy(XmlSuite.CONTINUE);
+    testng.run();
+
+    verify(tla, configurationFailures, configurationSkips, skippedTests);
+  }
+
+  @Test
+  public void commandLineTest_policyAsSkip() {
+    String[] argv = new String[] { "-log", "0", "-d", OutputDirectoryPatch.getOutputDirectory(),
+            "-configfailurepolicy", "skip",
+            "-testclass", "test.configurationfailurepolicy.ClassWithFailedBeforeMethodAndMultipleTests" };
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG.privateMain(argv, tla);
+
+    verify(tla, 1, 1, 2);
+  }
+
+  @Test
+  public void commandLineTest_policyAsContinue() {
+    String[] argv = new String[] { "-log", "0", "-d", OutputDirectoryPatch.getOutputDirectory(),
+            "-configfailurepolicy", "continue",
+            "-testclass", "test.configurationfailurepolicy.ClassWithFailedBeforeMethodAndMultipleTests" };
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG.privateMain(argv, tla);
+
+    verify(tla, 2, 0, 2);
+  }
+
+  @Test
+  public void commandLineTestWithXMLFile_policyAsSkip() {
+    String[] argv = new String[] { "-log", "0", "-d", OutputDirectoryPatch.getOutputDirectory(),
+            "-configfailurepolicy", "skip", "src/test/resources/testng-configfailure.xml" };
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG.privateMain(argv, tla);
+
+    verify(tla, 1, 1, 2);
+  }
+
+  @Test
+  public void commandLineTestWithXMLFile_policyAsContinue() {
+    String[] argv = new String[] { "-log", "0", "-d", OutputDirectoryPatch.getOutputDirectory(),
+            "-configfailurepolicy", "continue", "src/test/resources/testng-configfailure.xml" };
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG.privateMain(argv, tla);
+
+    verify(tla, 2, 0, 2);
+  }
+
+  private void verify( TestListenerAdapter tla, int configurationFailures, int configurationSkips, int skippedTests ) {
+      assertEquals(tla.getConfigurationFailures().size(), configurationFailures, "wrong number of configuration failures");
+      assertEquals(tla.getConfigurationSkips().size(), configurationSkips, "wrong number of configuration skips");
+      assertEquals(tla.getSkippedTests().size(), skippedTests, "wrong number of skipped tests");
+  }
+
+}
diff --git a/src/test/java/test/converter/ConverterSample1.java b/src/test/java/test/converter/ConverterSample1.java
new file mode 100644
index 0000000..0505977
--- /dev/null
+++ b/src/test/java/test/converter/ConverterSample1.java
@@ -0,0 +1,28 @@
+package test.converter;
+
+import junit.framework.TestCase;
+
+
+public class ConverterSample1 extends TestCase {
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public final void testClassJunit() {
+    }
+
+
+    public final void testSetClassId() {
+    }
+
+    public final void testSetClassName() {
+    }
+
+}
diff --git a/src/test/java/test/cyclic/AbstractGenericTests.java b/src/test/java/test/cyclic/AbstractGenericTests.java
new file mode 100644
index 0000000..62c78ce
--- /dev/null
+++ b/src/test/java/test/cyclic/AbstractGenericTests.java
@@ -0,0 +1,13 @@
+package test.cyclic;
+
+import org.testng.annotations.Test;
+
+public abstract class AbstractGenericTests extends BaseIntegrationTest {
+
+    @Test(groups="integration")
+    public final void testSomething() {
+        //...
+    }
+
+ }
+
diff --git a/src/test/java/test/cyclic/BaseIntegrationTest.java b/src/test/java/test/cyclic/BaseIntegrationTest.java
new file mode 100644
index 0000000..53a5bd6
--- /dev/null
+++ b/src/test/java/test/cyclic/BaseIntegrationTest.java
@@ -0,0 +1,18 @@
+package test.cyclic;
+
+import org.testng.annotations.BeforeClass;
+
+public abstract class BaseIntegrationTest {
+
+    @BeforeClass(groups="integration")
+    protected void initIntegrationTesting() {
+        //...
+    }
+
+    @BeforeClass(groups="integration")
+    void executeBeforeClassDbOperations() {
+        //...
+    }
+
+ }
+
diff --git a/src/test/java/test/cyclic/HibernateConcreteTests.java b/src/test/java/test/cyclic/HibernateConcreteTests.java
new file mode 100644
index 0000000..070016c
--- /dev/null
+++ b/src/test/java/test/cyclic/HibernateConcreteTests.java
@@ -0,0 +1,12 @@
+package test.cyclic;
+
+import org.testng.annotations.Test;
+
+public class HibernateConcreteTests extends AbstractGenericTests {
+
+    @Test(groups="integration")
+    public void testSomethingElse() {
+        //...
+    }
+
+ }
\ No newline at end of file
diff --git a/src/test/java/test/cyclic/SomeConcreteTests.java b/src/test/java/test/cyclic/SomeConcreteTests.java
new file mode 100644
index 0000000..2f27a85
--- /dev/null
+++ b/src/test/java/test/cyclic/SomeConcreteTests.java
@@ -0,0 +1,12 @@
+package test.cyclic;
+
+import org.testng.annotations.Test;
+
+public class SomeConcreteTests extends AbstractGenericTests {
+
+    @Test(groups="integration")
+    public void testSomethingElse() {
+        //...
+    }
+
+ }
\ No newline at end of file
diff --git a/src/test/java/test/dataprovider/BooleanTest.java b/src/test/java/test/dataprovider/BooleanTest.java
new file mode 100644
index 0000000..74292e7
--- /dev/null
+++ b/src/test/java/test/dataprovider/BooleanTest.java
@@ -0,0 +1,39 @@
+package test.dataprovider;
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class BooleanTest {
+  private boolean m_false = false;
+  private boolean m_true = false;
+
+  @Test(dataProvider = "allBooleans")
+  public void doStuff(boolean t) {
+    if (t) {
+      m_true = true;
+    }
+    if (! t) {
+      m_false = true;
+    }
+  }
+
+  @Test(dependsOnMethods = {"doStuff"} )
+  public void verify() {
+    Assert.assertTrue(m_true);
+    Assert.assertTrue(m_false);
+  }
+
+  private void ppp(String string) {
+    System.out.println("[BooleanTest] " + string);
+  }
+
+  @DataProvider(name = "allBooleans")
+  public Object[][] createData() {
+    return new Object[][] {
+      new Object[] { true },
+      new Object[] { false },
+    };
+  }
+
+}
diff --git a/src/test/java/test/dataprovider/ClassSampleTest.java b/src/test/java/test/dataprovider/ClassSampleTest.java
new file mode 100644
index 0000000..87f4388
--- /dev/null
+++ b/src/test/java/test/dataprovider/ClassSampleTest.java
@@ -0,0 +1,23 @@
+package test.dataprovider;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+@Test(dataProvider = "dp")
+public class ClassSampleTest {
+
+  @DataProvider
+  public Object[][] dp() {
+    return new Object[][] {
+        new Object[] { "a" },
+        new Object[] { "b" },
+    };
+  }
+
+  public void f(String a) {
+  }
+
+  public void g(String a) {
+  }
+
+}
diff --git a/src/test/java/test/dataprovider/ClassTest.java b/src/test/java/test/dataprovider/ClassTest.java
new file mode 100644
index 0000000..56df444
--- /dev/null
+++ b/src/test/java/test/dataprovider/ClassTest.java
@@ -0,0 +1,20 @@
+package test.dataprovider;
+
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+
+public class ClassTest extends BaseTest {
+  @Test(groups = { "current" })
+  public void includeMethodsOnly() {
+    addClass(ClassSampleTest.class.getName());
+    run();
+    String[] passed = {
+      "f", "g"
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+}
diff --git a/src/test/java/test/dataprovider/ConfigurationAndDataProvidersTest.java b/src/test/java/test/dataprovider/ConfigurationAndDataProvidersTest.java
new file mode 100644
index 0000000..fec38af
--- /dev/null
+++ b/src/test/java/test/dataprovider/ConfigurationAndDataProvidersTest.java
@@ -0,0 +1,69 @@
+package test.dataprovider;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * Make sure that all before methods except beforeTestMethod are invoked
+ * before DataProvider.
+ *
+ * Created on Jan 19, 2006
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+public class ConfigurationAndDataProvidersTest {
+  private boolean m_beforeSuite = false;
+  private boolean m_beforeTest = false;
+  private boolean m_beforeClass = false;
+  private boolean m_beforeTestMethod = false;
+
+  @DataProvider(name = "test1")
+  public Object[][] createData() {
+    Assert.assertTrue(m_beforeSuite, "beforeSuite should have been invoked");
+    Assert.assertTrue(m_beforeTest, "beforeTest should have been invoked");
+    Assert.assertTrue(m_beforeClass, "beforeClass should have been invoked");
+    Assert.assertFalse(m_beforeTestMethod, "beforeMethod should not have been invoked");
+    return new Object[][] { new Object[] { "Test" } };
+  }
+
+  @Test(dataProvider = "test1")
+  public void verifyNames(Object p) {
+    // do nothing
+  }
+
+
+  @BeforeSuite
+  public void setUpSuite () {
+    m_beforeSuite  = true;
+    ppp("BEFORE SUITE");
+  }
+
+  @BeforeTest
+  public void setUpTest() {
+    m_beforeTest = true;
+    ppp("BEFORE TEST");
+  }
+
+  @BeforeClass
+  public void setUpClass() {
+    m_beforeClass = true;
+    ppp("BEFORE TEST CLASS");
+  }
+
+  @BeforeMethod
+  public void setUp() {
+    m_beforeTestMethod = true;
+    ppp("BEFORE TEST METHOD");
+  }
+
+  private static void ppp(String s) {
+    if (false) {
+      System.out.println("[ConfigurationAndDataProvidersTest] " + s);
+    }
+  }
+
+}
diff --git a/src/test/java/test/dataprovider/ConstructorInjectionProvider.java b/src/test/java/test/dataprovider/ConstructorInjectionProvider.java
new file mode 100644
index 0000000..e6f2cbe
--- /dev/null
+++ b/src/test/java/test/dataprovider/ConstructorInjectionProvider.java
@@ -0,0 +1,23 @@
+package test.dataprovider;
+
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+
+import org.testng.annotations.DataProvider;
+
+public class ConstructorInjectionProvider {
+
+  private final String value;
+
+  @Inject
+  public ConstructorInjectionProvider(@Named("test") String value) {
+    this.value = value;
+  }
+
+  @DataProvider(name = "injection")
+  public Object[][] create() {
+    return new Object[][] {
+        new Object[] { value },
+    };
+  }
+}
diff --git a/src/test/java/test/dataprovider/CreateDataTest.java b/src/test/java/test/dataprovider/CreateDataTest.java
new file mode 100644
index 0000000..55ee4fb
--- /dev/null
+++ b/src/test/java/test/dataprovider/CreateDataTest.java
@@ -0,0 +1,29 @@
+package test.dataprovider;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class CreateDataTest {
+
+  /**
+   * @testng.data-provider name = "create-data"
+   */
+  @DataProvider(name = "create-data")
+  public Object[][] create() {
+         return new Object[][] {
+                 new Object[] { new MyObject() }
+         };
+  }
+
+  /**
+   * @testng.test data-provider = "create-data"
+   */
+  @Test(dataProvider = "create-data")
+  public void testMyTest(MyObject o) {
+   // do something with o
+  }
+
+
+}
+
+class MyObject {}
diff --git a/src/test/java/test/dataprovider/DataProviderAsTest.java b/src/test/java/test/dataprovider/DataProviderAsTest.java
new file mode 100644
index 0000000..30b7893
--- /dev/null
+++ b/src/test/java/test/dataprovider/DataProviderAsTest.java
@@ -0,0 +1,22 @@
+package test.dataprovider;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+
+/**
+ * Test that if a class @Test is used, the @DataProvider
+ * method won't be considered as a test.
+ */
+@Test
+public class DataProviderAsTest {
+
+  public void f() {
+  }
+
+  @DataProvider
+  public Object[][] dataProvider() {
+    throw new RuntimeException();
+  }
+
+}
diff --git a/src/test/java/test/dataprovider/DataProviderWithError.java b/src/test/java/test/dataprovider/DataProviderWithError.java
new file mode 100644
index 0000000..a7b8c7f
--- /dev/null
+++ b/src/test/java/test/dataprovider/DataProviderWithError.java
@@ -0,0 +1,25 @@
+package test.dataprovider;
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * @author Vladislav.Rassokhin
+ */
+public class DataProviderWithError {
+  @Test(dataProvider = "Data", invocationCount = 2)
+  public void testShouldSkip() throws Exception {
+    Assert.fail();
+  }
+
+  @Test(dataProvider = "Data", invocationCount = 2, successPercentage = 10)
+  public void testShouldSkipEvenIfSuccessPercentage() throws Exception {
+    Assert.fail();
+  }
+
+  @DataProvider(name = "Data")
+  public static Object[][] Data() {
+    throw new RuntimeException("Fail");
+  }
+}
diff --git a/src/test/java/test/dataprovider/DependentSampleTest.java b/src/test/java/test/dataprovider/DependentSampleTest.java
new file mode 100644
index 0000000..11b8a6e
--- /dev/null
+++ b/src/test/java/test/dataprovider/DependentSampleTest.java
@@ -0,0 +1,22 @@
+package test.dataprovider;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class DependentSampleTest {
+  @DataProvider(name = "data")
+  public Object[][] dp() {
+      return new Object[][] { { "ok" }, { "not ok" }, };
+  }
+
+  @Test(groups = { "a" }, dataProvider = "data")
+  public void method1(String s) {
+      if (!"ok".equals(s)) {
+          throw new RuntimeException("error " + s);
+      }
+  }
+
+  @Test(groups = { "b" }, dependsOnGroups = { "a" })
+  public void method2() throws InterruptedException {
+  }
+}
diff --git a/src/test/java/test/dataprovider/DuplicateDataProviderSampleTest.java b/src/test/java/test/dataprovider/DuplicateDataProviderSampleTest.java
new file mode 100644
index 0000000..3e56959
--- /dev/null
+++ b/src/test/java/test/dataprovider/DuplicateDataProviderSampleTest.java
@@ -0,0 +1,21 @@
+package test.dataprovider;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class DuplicateDataProviderSampleTest {
+
+  @Test(dataProvider = "duplicate")
+  public void f() {
+  }
+
+  @DataProvider(name = "duplicate")
+  public Object[] dp1() {
+    return new Object[0][];
+  }
+
+  @DataProvider(name = "duplicate")
+  public Object[] dp2() {
+    return new Object[0][];
+  }
+}
diff --git a/src/test/java/test/dataprovider/ExplicitDataProviderNameSample.java b/src/test/java/test/dataprovider/ExplicitDataProviderNameSample.java
new file mode 100644
index 0000000..089b13b
--- /dev/null
+++ b/src/test/java/test/dataprovider/ExplicitDataProviderNameSample.java
@@ -0,0 +1,16 @@
+package test.dataprovider;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class ExplicitDataProviderNameSample {
+	
+	@Test(dataProvider="dp_name")
+	public void should_find_exactly_one_data_provider(int i) {}
+	
+	@DataProvider(name="dp_name")
+	Object[][] whatever_implicit_name() {return new Object[][] {{1}};}
+	
+	@DataProvider(name="whatever_explicit_name")
+	Object[][] dp_name() {return null;}
+}
\ No newline at end of file
diff --git a/src/test/java/test/dataprovider/ExplicitDataProviderNameTest.java b/src/test/java/test/dataprovider/ExplicitDataProviderNameTest.java
new file mode 100644
index 0000000..fba7bd8
--- /dev/null
+++ b/src/test/java/test/dataprovider/ExplicitDataProviderNameTest.java
@@ -0,0 +1,22 @@
+package test.dataprovider;
+
+import static org.testng.Assert.assertEquals;
+
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+public class ExplicitDataProviderNameTest extends SimpleBaseTest {
+
+	@Test(description = "TESTNG-576: Prefer DataProvider explicit name")
+	public void should_prefer_dataProvider_explicit_name() {
+	    TestNG testng = create(ExplicitDataProviderNameSample.class);
+	    TestListenerAdapter tla = new TestListenerAdapter();
+	    testng.addListener(tla);
+	    testng.run();
+	    
+	    assertEquals(tla.getPassedTests().size(), 1, "All tests should success");
+	  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/dataprovider/FailedDataProviderSample.java b/src/test/java/test/dataprovider/FailedDataProviderSample.java
new file mode 100644
index 0000000..dc362dd
--- /dev/null
+++ b/src/test/java/test/dataprovider/FailedDataProviderSample.java
@@ -0,0 +1,24 @@
+package test.dataprovider;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class FailedDataProviderSample {
+  @DataProvider
+  public Object[][] dp() {
+    return new Integer[][] {
+        new Integer[] { 1 },
+        new Integer[] { 2 },
+        new Integer[] { 3 },
+    };
+  }
+
+  @Test(dataProvider = "dp")
+  public void f(int n) {
+    FailedDataProviderTest.m_total += n;
+    if (n == 2) {
+      throw new RuntimeException("Failed")  ;
+    }
+  }
+
+}
diff --git a/src/test/java/test/dataprovider/FailedDataProviderTest.java b/src/test/java/test/dataprovider/FailedDataProviderTest.java
new file mode 100644
index 0000000..560f118
--- /dev/null
+++ b/src/test/java/test/dataprovider/FailedDataProviderTest.java
@@ -0,0 +1,51 @@
+package test.dataprovider;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+
+public class FailedDataProviderTest extends BaseTest {
+  static int m_total = 0;
+
+  @BeforeMethod
+  public void init() {
+    m_total = 0;
+  }
+
+  /**
+   * Make sure that if a test method fails in the middle of a data provider, the rest
+   * of the data set is still run.
+   */
+  @Test
+  public void allMethodsShouldBeInvoked() {
+    TestNG tng = new TestNG();
+    tng.setTestClasses(new Class[] { FailedDataProviderSample.class });
+    tng.setVerbose(0);
+    tng.run();
+
+    Assert.assertEquals(m_total, 6);
+  }
+
+  @Test
+  public void failedDataProviderShouldCauseSkip() {
+    addClass("test.dataprovider.DependentSampleTest");
+
+    run();
+    String[] passed = {
+      "method1"
+    };
+    String[] failed = {
+      "method1"
+    };
+    String[] skipped = {
+      "method2"
+    };
+    verifyTests("Failed", failed, getFailedTests());
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Skipped", skipped, getSkippedTests());
+  }
+}
+
diff --git a/src/test/java/test/dataprovider/FailingDataProvider.java b/src/test/java/test/dataprovider/FailingDataProvider.java
new file mode 100644
index 0000000..77e2efe
--- /dev/null
+++ b/src/test/java/test/dataprovider/FailingDataProvider.java
@@ -0,0 +1,21 @@
+package test.dataprovider;

+

+import org.testng.Assert;

+import org.testng.annotations.DataProvider;

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class FailingDataProvider {

+  @DataProvider

+  public Object[][] throwsExpectedException() {

+    throw new RuntimeException("expected exception from @DP");

+  }

+

+  @Test(dataProvider="throwsExpectedException")

+  public void dpThrowingException() {

+    Assert.fail("Method should never get invoked");

+  }

+}

diff --git a/src/test/java/test/dataprovider/FailingDataProviderTest.java b/src/test/java/test/dataprovider/FailingDataProviderTest.java
new file mode 100644
index 0000000..ee31493
--- /dev/null
+++ b/src/test/java/test/dataprovider/FailingDataProviderTest.java
@@ -0,0 +1,34 @@
+package test.dataprovider;

+

+import org.testng.Assert;

+import org.testng.TestListenerAdapter;

+import org.testng.TestNG;

+import org.testng.annotations.Test;

+

+import test.SimpleBaseTest;

+

+public class FailingDataProviderTest extends SimpleBaseTest {

+  private void shouldSkip(Class cls, String message, int expected) {

+    TestNG testng = create(cls);

+    TestListenerAdapter tla = new TestListenerAdapter();

+    testng.addListener(tla);

+    testng.run();

+    Assert.assertEquals(tla.getSkippedTests().size(), expected, message);

+  }

+

+  @Test(description = "TESTNG-142: Exceptions in DataProvider are not reported as failed test")

+  public void failingDataProvider() {

+    shouldSkip(FailingDataProvider.class, "Test method should be marked as skipped", 1);

+  }

+

+  @Test(description = "TESTNG-447: Abort when two data providers have the same name")

+  public void duplicateDataProviders() {

+    shouldSkip(DuplicateDataProviderSampleTest.class, "", 1);

+  }

+

+  @Test

+  public void failingDataProviderAndInvocationCount() throws Exception {

+    shouldSkip(DataProviderWithError.class,

+        "Test should be skipped even if invocation counter and success percentage set", 4);

+  }

+}

diff --git a/src/test/java/test/dataprovider/FailingIterableDataProvider.java b/src/test/java/test/dataprovider/FailingIterableDataProvider.java
new file mode 100644
index 0000000..3fbc0c2
--- /dev/null
+++ b/src/test/java/test/dataprovider/FailingIterableDataProvider.java
@@ -0,0 +1,38 @@
+package test.dataprovider;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.util.Iterator;
+
+public class FailingIterableDataProvider {
+
+  @DataProvider(name = "dp")
+  public Iterator<Object[]> createData() {
+    return new Iterator<Object[]>() {
+    int count=0;
+
+    @Override
+    public boolean hasNext() {
+      return count<10;
+    }
+
+    @Override
+    public Object[] next() {
+      if (++count==6) {
+        throw new RuntimeException();
+      }
+      return new Object[] { count };
+    }
+
+    @Override
+    public void remove() {}
+
+    };
+  }
+
+  @Test(dataProvider="dp")
+  public void happyTest(int count) {
+    //pass
+  }
+}
diff --git a/src/test/java/test/dataprovider/FailingIterableDataProviderTest.java b/src/test/java/test/dataprovider/FailingIterableDataProviderTest.java
new file mode 100644
index 0000000..15cb275
--- /dev/null
+++ b/src/test/java/test/dataprovider/FailingIterableDataProviderTest.java
@@ -0,0 +1,31 @@
+package test.dataprovider;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+
+/**
+ * TESTNG-291:
+ * Exceptions thrown by Iterable DataProviders are not caught, no failed test reported
+ */
+public class FailingIterableDataProviderTest {
+  @Test
+  public void failingDataProvider() {
+    TestNG testng= new TestNG(false);
+    testng.setTestClasses(new Class[] {FailingIterableDataProvider.class});
+    TestListenerAdapter tla = new TestListenerAdapter();
+    testng.addListener(tla);
+    testng.setVerbose(0);
+    try {
+      testng.run();
+    } catch (RuntimeException e) {
+      Assert.fail("Exceptions thrown during tests should always be caught!", e);
+    }
+    Assert.assertEquals(tla.getFailedTests().size(), 1,
+      "Should have 1 failure from a bad data-provider iteration");
+    Assert.assertEquals(tla.getPassedTests().size(), 5,
+      "Should have 5 passed test from before the bad data-provider iteration");
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/test/dataprovider/FieldInjectionProvider.java b/src/test/java/test/dataprovider/FieldInjectionProvider.java
new file mode 100644
index 0000000..5c0c54d
--- /dev/null
+++ b/src/test/java/test/dataprovider/FieldInjectionProvider.java
@@ -0,0 +1,19 @@
+package test.dataprovider;
+
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+
+import org.testng.annotations.DataProvider;
+
+public class FieldInjectionProvider {
+
+  @Inject @Named("test")
+  private String value;
+
+  @DataProvider(name = "injection")
+  public Object[][] create() {
+    return new Object[][] {
+        new Object[] { value },
+    };
+  }
+}
diff --git a/src/test/java/test/dataprovider/IndicesTest.java b/src/test/java/test/dataprovider/IndicesTest.java
new file mode 100644
index 0000000..4267129
--- /dev/null
+++ b/src/test/java/test/dataprovider/IndicesTest.java
@@ -0,0 +1,21 @@
+package test.dataprovider;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class IndicesTest {
+
+  @DataProvider(indices = { 0, 2 })
+  public Object[][] dp1() {
+    return new Object[][] {
+      new Object[] { 1 },  
+      new Object[] { 2 },  
+      new Object[] { 3 },  
+    };
+  }
+
+  @Test(dataProvider = "dp1")
+  public void indicesShouldWork(int n) {
+    if (n == 2) throw new RuntimeException("This method should not have received a 1");
+  }
+}
diff --git a/src/test/java/test/dataprovider/InheritanceATest.java b/src/test/java/test/dataprovider/InheritanceATest.java
new file mode 100644
index 0000000..9d00060
--- /dev/null
+++ b/src/test/java/test/dataprovider/InheritanceATest.java
@@ -0,0 +1,15 @@
+package test.dataprovider;
+
+import org.testng.annotations.DataProvider;
+
+public class InheritanceATest {
+
+  @DataProvider
+  public Object[][] dp() {
+    return new Object[][] {
+        new Object[] { "a"}
+    };
+  }
+
+
+}
diff --git a/src/test/java/test/dataprovider/InheritanceBTest.java b/src/test/java/test/dataprovider/InheritanceBTest.java
new file mode 100644
index 0000000..17687b9
--- /dev/null
+++ b/src/test/java/test/dataprovider/InheritanceBTest.java
@@ -0,0 +1,10 @@
+package test.dataprovider;
+
+import org.testng.annotations.Test;
+
+public class InheritanceBTest extends InheritanceATest {
+
+  @Test(dataProvider = "dp")
+  public void f(String s) {
+  }
+}
diff --git a/src/test/java/test/dataprovider/InheritedDataProviderBaseSampleTest.java b/src/test/java/test/dataprovider/InheritedDataProviderBaseSampleTest.java
new file mode 100644
index 0000000..a137e96
--- /dev/null
+++ b/src/test/java/test/dataprovider/InheritedDataProviderBaseSampleTest.java
@@ -0,0 +1,9 @@
+package test.dataprovider;
+
+import org.testng.annotations.Test;
+
+//@Test(description = "parent")
+@Test (dataProviderClass=InheritedDataProviderSample1Test.class)
+public class InheritedDataProviderBaseSampleTest {
+
+}
diff --git a/src/test/java/test/dataprovider/InheritedDataProviderSample1Test.java b/src/test/java/test/dataprovider/InheritedDataProviderSample1Test.java
new file mode 100644
index 0000000..1fb8cbd
--- /dev/null
+++ b/src/test/java/test/dataprovider/InheritedDataProviderSample1Test.java
@@ -0,0 +1,13 @@
+package test.dataprovider;
+
+import org.testng.annotations.DataProvider;
+
+public class InheritedDataProviderSample1Test {
+
+  @DataProvider
+  static public Object[][] dp() {
+    return new Object[][] {
+      new Object[] { "a" }
+    };
+  }
+}
diff --git a/src/test/java/test/dataprovider/InheritedDataProviderTest.java b/src/test/java/test/dataprovider/InheritedDataProviderTest.java
new file mode 100644
index 0000000..d42c37e
--- /dev/null
+++ b/src/test/java/test/dataprovider/InheritedDataProviderTest.java
@@ -0,0 +1,11 @@
+package test.dataprovider;
+
+import org.testng.annotations.Test;
+
+//@Test(description = "class")
+public class InheritedDataProviderTest extends InheritedDataProviderBaseSampleTest {
+
+  @Test(dataProvider = "dp")
+  public void f(String a) {
+  }
+}
diff --git a/src/test/java/test/dataprovider/InnexistentDataProvider.java b/src/test/java/test/dataprovider/InnexistentDataProvider.java
new file mode 100644
index 0000000..3d33b58
--- /dev/null
+++ b/src/test/java/test/dataprovider/InnexistentDataProvider.java
@@ -0,0 +1,15 @@
+package test.dataprovider;

+

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class InnexistentDataProvider {

+  @Test(dataProvider="doesnotexist")

+  public void testMethod(String s)

+  {

+    // doesn't matter

+  }

+}

diff --git a/src/test/java/test/dataprovider/InstanceDataProviderSampleTest.java b/src/test/java/test/dataprovider/InstanceDataProviderSampleTest.java
new file mode 100644
index 0000000..1a3b724
--- /dev/null
+++ b/src/test/java/test/dataprovider/InstanceDataProviderSampleTest.java
@@ -0,0 +1,40 @@
+package test.dataprovider;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class InstanceDataProviderSampleTest {
+  @DataProvider
+  public Object[][] dp() {
+    p("DATA PROVIDER");
+    return new Object[][] {
+        new Object[] {hashCode()},
+    };
+  }
+
+  @BeforeClass
+  public void beforeTest() {
+    p("BEFORE");
+  }
+
+  @Test(dataProvider = "dp")
+  public void f(Integer n) {
+    p("  PARAM:" + n);
+    Assert.assertEquals(n, Integer.valueOf(hashCode()));
+  }
+
+  @AfterClass
+  public void afterTest() {
+    p("AFTER");
+  }
+
+  private void p(String s) {
+    if (false) {
+      System.out.println(hashCode() + " " + s);
+    }
+  }
+
+}
diff --git a/src/test/java/test/dataprovider/InstanceDataProviderTest.java b/src/test/java/test/dataprovider/InstanceDataProviderTest.java
new file mode 100644
index 0000000..73e7c46
--- /dev/null
+++ b/src/test/java/test/dataprovider/InstanceDataProviderTest.java
@@ -0,0 +1,15 @@
+package test.dataprovider;
+
+import org.testng.annotations.Factory;
+
+
+public class InstanceDataProviderTest {
+
+  @Factory
+  public Object[] create() {
+    return new Object[] {
+        new InstanceDataProviderSampleTest(),
+        new InstanceDataProviderSampleTest()
+    };
+  }
+}
diff --git a/src/test/java/test/dataprovider/IterableTest.java b/src/test/java/test/dataprovider/IterableTest.java
new file mode 100644
index 0000000..1cc63cd
--- /dev/null
+++ b/src/test/java/test/dataprovider/IterableTest.java
@@ -0,0 +1,54 @@
+package test.dataprovider;
+
+import org.testng.Assert;
+
+import java.util.Iterator;
+
+public class IterableTest {
+  private boolean m_ok1 = false;
+  private boolean m_ok2 = false;
+
+  public static final String FN2 = "Anne Marie";
+  public static final Integer LN2 = 37;
+  public static final String FN1 = "Cedric";
+  public static final Integer LN1 = 36;
+
+  public static final Object[][] DATA = new Object[][] {
+    new Object[] { FN1, LN1 },
+    new Object[] { FN2, LN2 },
+  };
+
+  /**
+   * @testng.data-provider name="test1"
+   */
+  public Iterator createData() {
+    return new MyIterator(DATA);
+  }
+
+  /**
+   * @testng.test dataProvider="test1"
+   */
+  public void verifyNames(String firstName, Integer age) {
+    if (firstName.equals(FN1) && age.equals(LN1)) {
+      m_ok1 = true;
+      Assert.assertEquals(MyIterator.getCount(), 1);
+    }
+    if (firstName.equals(FN2) && age.equals(LN2)) {
+      m_ok2 = true;
+      Assert.assertEquals(MyIterator.getCount(), 2);
+    }
+  }
+
+  /**
+   * @testng.test dependsOnMethods = "verifyNames"
+   */
+  public void verifyCount() {
+    Assert.assertTrue(m_ok1 && m_ok2);
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[IterableTest] " + s);
+  }
+}
+
+
diff --git a/src/test/java/test/dataprovider/MethodTest.java b/src/test/java/test/dataprovider/MethodTest.java
new file mode 100644
index 0000000..d30ce91
--- /dev/null
+++ b/src/test/java/test/dataprovider/MethodTest.java
@@ -0,0 +1,60 @@
+package test.dataprovider;
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Method;
+
+public class MethodTest {
+
+  @DataProvider(name = "dp1")
+  public Object[][] createData(Method m) {
+    Assert.assertEquals("test1", m.getName());
+    Assert.assertEquals("test.dataprovider.MethodTest", m.getDeclaringClass().getName());
+    return new Object[][] {
+      new Object[] { "Cedric" },
+      new Object[] { "Alois" },
+    };
+  }
+
+  @Test(dataProvider = "dp1")
+  public void test1(String s) {
+    Assert.assertTrue("Cedric".equals(s) || "Alois".equals(s));
+  }
+
+  private int m_test2 = 0;
+  private int m_test3 = 0;
+
+  @DataProvider(name = "dp2")
+  public Object[][] createData2(Method m) {
+    if ("test2".equals(m.getName())) {
+      m_test2++;
+    } else if ("test3".equals(m.getName())) {
+      m_test3++;
+    } else {
+      throw new RuntimeException("Received method " + m + ", expected test2 or test3");
+    }
+    Assert.assertEquals("test.dataprovider.MethodTest", m.getDeclaringClass().getName());
+    return new Object[][] {
+      new Object[] { "Cedric" },
+    };
+  }
+
+  @Test(dataProvider = "dp2")
+  public void test2(String s) {
+    Assert.assertTrue("Cedric".equals(s));
+  }
+
+  @Test(dataProvider = "dp2")
+  public void test3(String s) {
+    Assert.assertTrue("Cedric".equals(s));
+  }
+
+  @Test(dependsOnMethods = {"test2", "test3"})
+  public void multipleTestMethods() {
+    Assert.assertEquals(1, m_test2);
+    Assert.assertEquals(1, m_test3);
+  }
+
+}
diff --git a/src/test/java/test/dataprovider/MyIterator.java b/src/test/java/test/dataprovider/MyIterator.java
new file mode 100644
index 0000000..f987928
--- /dev/null
+++ b/src/test/java/test/dataprovider/MyIterator.java
@@ -0,0 +1,36 @@
+package test.dataprovider;
+
+import java.util.Iterator;
+
+public class MyIterator implements Iterator<Object[]> {
+  private static int m_count = 0;
+  private Object[][] m_data;
+
+  public MyIterator(Object[][] data) {
+    m_data = data;
+  }
+
+  @Override
+  public boolean hasNext() {
+    return m_count < m_data.length;
+  }
+
+  @Override
+  public Object[] next() {
+    ppp("RETURNING INDEX " + m_count);
+    return m_data[m_count++];
+  }
+
+  @Override
+  public void remove() {
+  }
+
+  public static int getCount() {
+    return m_count;
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[MyIterator] " + s);
+  }
+
+}
diff --git a/src/test/java/test/dataprovider/NonStaticProvider.java b/src/test/java/test/dataprovider/NonStaticProvider.java
new file mode 100644
index 0000000..c49b3d7
--- /dev/null
+++ b/src/test/java/test/dataprovider/NonStaticProvider.java
@@ -0,0 +1,13 @@
+package test.dataprovider;
+
+import org.testng.annotations.DataProvider;
+
+public class NonStaticProvider {
+
+  @DataProvider(name = "external")
+  public Object[][] create() {
+    return new Object[][] {
+        new Object[] { "Cedric" },
+    };
+  }
+}
diff --git a/src/test/java/test/dataprovider/ParallelDataProvider2Test.java b/src/test/java/test/dataprovider/ParallelDataProvider2Test.java
new file mode 100644
index 0000000..13c9a19
--- /dev/null
+++ b/src/test/java/test/dataprovider/ParallelDataProvider2Test.java
@@ -0,0 +1,32 @@
+package test.dataprovider;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * @author Jacek Pulut <jacek.pulut@gmail.com>
+ *
+ * Make sure this class can run without causing a ConcurrentModificationException.
+ */
+public class ParallelDataProvider2Test {
+  @DataProvider(parallel = true)
+  Iterator<Integer[]> provide()
+  {
+    final List<Integer[]> ret = Lists.newArrayList();
+    for (int i = 0; i < 1000; i++)
+    {
+      ret.add(new Integer[] { i });
+    }
+    return ret.iterator();
+  }
+
+  @Test(groups = "cme", dataProvider = "provide", invocationCount = 2, threadPoolSize = 2)
+  public void checkCME(final Integer i)
+  {
+//    Reporter.log("" + i, true);
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/dataprovider/ParallelDataProviderTest.java b/src/test/java/test/dataprovider/ParallelDataProviderTest.java
new file mode 100644
index 0000000..7ccdf73
--- /dev/null
+++ b/src/test/java/test/dataprovider/ParallelDataProviderTest.java
@@ -0,0 +1,41 @@
+package test.dataprovider;
+
+import org.testng.ITestContext;
+import org.testng.TestNG;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+/**
+ * Data providers were not working properly with parallel=true
+ * @author cbeust
+ */
+public class ParallelDataProviderTest extends SimpleBaseTest {
+//  protected static Logger logger = Logger
+//    .getLogger(SampleMessageLoaderTest2.class);
+//  // This method will provide data to any test method that declares that its
+  // Data Provider
+  // is named "test1"
+  @DataProvider(name = "test1", parallel = true)
+  public Object[][] createData1() {
+   return new Object[][] {
+     { "Cedric", 36},
+     { "Anne", 37},
+     { "A", 36},
+     { "B", 37}
+   };
+  }
+  // This test method declares that its data should be supplied by the Data
+  // Provider
+  // named "test1"
+  @Test(dataProvider = "test1", threadPoolSize = 5)
+  public void verifyData1(ITestContext testContext, String n1, Integer n2) {
+  }
+
+  @Test
+  public void shouldNotThrowConcurrentModificationException() {
+    TestNG tng = create(ParallelDataProvider2Test.class);
+    tng.run();
+  }
+}
diff --git a/src/test/java/test/dataprovider/Sample1Test.java b/src/test/java/test/dataprovider/Sample1Test.java
new file mode 100644
index 0000000..55dbf04
--- /dev/null
+++ b/src/test/java/test/dataprovider/Sample1Test.java
@@ -0,0 +1,38 @@
+package test.dataprovider;
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class Sample1Test {
+  private boolean m_ok1 = false;
+  private boolean m_ok2 = false;
+
+  private static final String FN2 = "Anne Marie";
+  private static final Integer LN2 = 37;
+  private static final String FN1 = "Cedric";
+  private static final Integer LN1 = 36;
+
+  @DataProvider(name = "test1")
+  public Object[][] createData() {
+    return new Object[][] {
+        new Object[] { FN1, LN1 },
+        new Object[] { FN2, LN2 },
+      };
+  }
+
+  @Test(dataProvider = "test1")
+  public void verifyNames(String firstName, Integer age) {
+    if (firstName.equals(FN1) && age.equals(LN1)) {
+      m_ok1 = true;
+    }
+    if (firstName.equals(FN2) && age.equals(LN2)) {
+      m_ok2 = true;
+    }
+  }
+
+  @Test(dependsOnMethods = {"verifyNames"})
+  public void verifyCount() {
+    Assert.assertTrue(m_ok1 && m_ok2);
+  }
+}
diff --git a/src/test/java/test/dataprovider/StaticDataProviderSampleTest.java b/src/test/java/test/dataprovider/StaticDataProviderSampleTest.java
new file mode 100644
index 0000000..3f78c21
--- /dev/null
+++ b/src/test/java/test/dataprovider/StaticDataProviderSampleTest.java
@@ -0,0 +1,40 @@
+package test.dataprovider;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.name.Names;
+
+import org.testng.Assert;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+@Guice(modules = StaticDataProviderSampleTest.InjectionProviderModule.class)
+public class StaticDataProviderSampleTest {
+
+  @Test(dataProvider = "static", dataProviderClass = StaticProvider.class)
+  public void verifyStatic(String s) {
+    Assert.assertEquals(s, "Cedric");
+  }
+
+  @Test(dataProvider = "external", dataProviderClass = NonStaticProvider.class)
+  public void verifyExternal(String s) {
+    Assert.assertEquals(s, "Cedric");
+  }
+
+  @Test(dataProvider = "injection", dataProviderClass = FieldInjectionProvider.class)
+  public void verifyFieldInjection(String s) {
+    Assert.assertEquals(s, "Cedric");
+  }
+
+  @Test(dataProvider = "injection", dataProviderClass = ConstructorInjectionProvider.class)
+  public void verifyConstructorInjection(String s) {
+    Assert.assertEquals(s, "Cedric");
+  }
+
+  public static class InjectionProviderModule extends AbstractModule {
+
+    @Override
+    protected void configure() {
+      bind(String.class).annotatedWith(Names.named("test")).toInstance("Cedric");
+    }
+  }
+}
diff --git a/src/test/java/test/dataprovider/StaticProvider.java b/src/test/java/test/dataprovider/StaticProvider.java
new file mode 100644
index 0000000..0608edf
--- /dev/null
+++ b/src/test/java/test/dataprovider/StaticProvider.java
@@ -0,0 +1,13 @@
+package test.dataprovider;
+
+import org.testng.annotations.DataProvider;
+
+public class StaticProvider {
+
+  @DataProvider(name = "static")
+  public static Object[][] create() {
+    return new Object[][] {
+        new Object[] { "Cedric" },
+    };
+  }
+}
diff --git a/src/test/java/test/dataprovider/TestContextSampleTest.java b/src/test/java/test/dataprovider/TestContextSampleTest.java
new file mode 100644
index 0000000..e597489
--- /dev/null
+++ b/src/test/java/test/dataprovider/TestContextSampleTest.java
@@ -0,0 +1,50 @@
+package test.dataprovider;

+

+import org.testng.Assert;

+import org.testng.ITestContext;

+import org.testng.annotations.DataProvider;

+import org.testng.annotations.Test;

+

+/**

+ * Tests that when a DataProvider is declared with an ITestContext,

+ * this parameter is correctly passed.

+ *

+ * Created on Dec 28, 2006

+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>

+ */

+public class TestContextSampleTest {

+

+  /**

+   * @return As many parameters as the name of the included group

+   */

+  @DataProvider(name = "testContext")

+  public Object[][] createContext(ITestContext ctx) {

+//    ppp("CONTEXT:" + ctx);

+    String[] groups = ctx.getIncludedGroups();

+

+    int n = groups.length > 0 ? new Integer(groups[0]): 0;

+    Object[] result = new Object[n];

+    for (int i = 0; i < n; i++) {

+      result[i] = "foo";

+    }

+

+    return new Object[][] {

+        new Object[] { result },

+    };

+  }

+

+  private static void ppp(String s) {

+    System.out.println("[TestContextSampleTest] " + s);

+  }

+

+  @Test(dataProvider = "testContext", groups="10")

+  public void verifyTen(Object[] objects) {

+    Assert.assertEquals(objects.length, 10);

+  }

+

+  @Test(dataProvider = "testContext", groups="5")

+  public void verifyFive(Object[] objects) {

+    Assert.assertEquals(objects.length, 5);

+  }

+

+}

diff --git a/src/test/java/test/dataprovider/TestContextTest.java b/src/test/java/test/dataprovider/TestContextTest.java
new file mode 100644
index 0000000..f94988d
--- /dev/null
+++ b/src/test/java/test/dataprovider/TestContextTest.java
@@ -0,0 +1,46 @@
+package test.dataprovider;

+

+import org.testng.Assert;

+import org.testng.TestListenerAdapter;

+import org.testng.TestNG;

+import org.testng.annotations.Test;

+

+public class TestContextTest {

+

+  @Test

+  public void verifyTen() {

+    verify("10", "verifyTen", 1, 0);

+  }

+

+  @Test

+  public void verifyFive() {

+    verify("5", "verifyFive", 1, 0);

+  }

+

+  @Test

+  public void verifySix() {

+    // Not including any group, so the two test methods should fail

+    verify(null, null, 0, 2);

+  }

+

+  private void verify(String groupName, String passed, int passedCount, int failedCount) {

+    TestNG tng = new TestNG();

+    tng.setVerbose(0);

+    tng.setTestClasses(new Class[] { TestContextSampleTest.class });

+    if (groupName != null) {

+      tng.setGroups(groupName);

+    }

+    TestListenerAdapter al = new TestListenerAdapter();

+    tng.addListener(al);

+    tng.run();

+

+    if (passedCount > 0) {

+      Assert.assertEquals(al.getPassedTests().size(), passedCount);

+      Assert.assertEquals(al.getPassedTests().get(0).getMethod().getMethodName(), passed);

+    }

+

+    if (failedCount > 0) {

+      Assert.assertEquals(al.getFailedTests().size(), failedCount);

+    }

+  }

+}

diff --git a/src/test/java/test/dataprovider/TestInstanceFactory.java b/src/test/java/test/dataprovider/TestInstanceFactory.java
new file mode 100644
index 0000000..755be9d
--- /dev/null
+++ b/src/test/java/test/dataprovider/TestInstanceFactory.java
@@ -0,0 +1,15 @@
+package test.dataprovider;
+
+import org.testng.annotations.Factory;
+
+public class TestInstanceFactory {
+  @Factory
+  public Object[] init() {
+    return new Object[] {
+        new TestInstanceTest(1),
+        new TestInstanceTest(2)
+    };
+  }
+
+
+}
diff --git a/src/test/java/test/dataprovider/TestInstanceTest.java b/src/test/java/test/dataprovider/TestInstanceTest.java
new file mode 100644
index 0000000..fa9272d
--- /dev/null
+++ b/src/test/java/test/dataprovider/TestInstanceTest.java
@@ -0,0 +1,44 @@
+package test.dataprovider;
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import org.testng.annotations.TestInstance;
+
+import java.lang.reflect.Method;
+
+public class TestInstanceTest {
+
+  private int m_n;
+  private static int m_instanceCount = 0;
+
+  public TestInstanceTest() {}
+
+  public TestInstanceTest(int n) {
+    this.m_n = n;
+  }
+
+  @DataProvider
+  public Object[][] dp(Method m, @TestInstance Object instance) {
+    TestInstanceTest o0 = (TestInstanceTest) instance;
+    Assert.assertTrue(o0.m_n == 1 || o0.m_n == 2);
+    m_instanceCount++;
+    return new Object[][] {
+        new Object[] {42},
+        new Object[] {43},
+    };
+  }
+
+  @Test(dataProvider = "dp")
+  public void f(int o) {
+  }
+
+  @Override
+  public String toString() {
+    return "[A n:" + m_n + "]";
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[A] " + s);
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/dataprovider/TestNG411SampleTest.java b/src/test/java/test/dataprovider/TestNG411SampleTest.java
new file mode 100644
index 0000000..e0d11ae
--- /dev/null
+++ b/src/test/java/test/dataprovider/TestNG411SampleTest.java
@@ -0,0 +1,52 @@
+package test.dataprovider;
+
+import org.testng.Assert;
+import org.testng.ITestContext;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * Used by {@link TestNG411Test}. Do not change number of methods or method names
+ * @author nullin
+ *
+ */
+public class TestNG411SampleTest
+{
+  private static final String CHECK_MAX_DATA = "checkMaxData";
+  private static final String CHECK_MIN_DATA = "checkMinData";
+
+  @DataProvider(name = CHECK_MAX_DATA)
+  public Object[][] dataProviderCheckMax() {
+    return new Object[][] {
+      { 1, 2, 3, 3 },
+    };
+  }
+
+  @Test(description = "Number of parameters to this test don't match the " +
+      "ones passed by data provider", dataProvider = CHECK_MAX_DATA)
+  public void checkMaxTest(int nr1, int nr2, int expected) {
+    Assert.fail("This code shouldnt be executed");
+  }
+
+  @DataProvider(name = CHECK_MIN_DATA)
+  public Object[][] dataProviderCheckMin() {
+    return new Object[][] {
+      { 1, 2, },
+    };
+  }
+
+  @Test(description = "Number of parameters to this test don't match the " +
+      "ones passed by data provider", dataProvider = CHECK_MIN_DATA)
+  public void checkMinTest(int nr1, int nr2, int expected) {
+    Assert.fail("This code shouldnt be executed");
+  }
+
+  @Test(description = "Number of parameters to this test don't match the " +
+        "ones passed by data provider. But an ojbect will be injected",
+        dataProvider = CHECK_MIN_DATA)
+  public void checkMinTest_injection(int nr1, int nr2, ITestContext ctx) {
+    int result = Math.min(nr1, nr2);
+    Assert.assertEquals(result, nr1);
+    Assert.assertNotNull(ctx);
+  }
+}
diff --git a/src/test/java/test/dataprovider/TestNG411Test.java b/src/test/java/test/dataprovider/TestNG411Test.java
new file mode 100644
index 0000000..ba05436
--- /dev/null
+++ b/src/test/java/test/dataprovider/TestNG411Test.java
@@ -0,0 +1,24 @@
+package test.dataprovider;

+

+import org.testng.Assert;

+import org.testng.TestListenerAdapter;

+import org.testng.TestNG;

+import org.testng.annotations.Test;

+

+public class TestNG411Test {

+

+  @Test

+  public void verify() {

+    TestNG tng = new TestNG();

+    tng.setVerbose(0);

+    tng.setTestClasses(new Class[] { TestNG411SampleTest.class });

+    TestListenerAdapter al = new TestListenerAdapter();

+    tng.addListener(al);

+    tng.run();

+

+    Assert.assertEquals(al.getPassedTests().size(), 1);

+    Assert.assertEquals(al.getPassedTests().get(0).getMethod().getMethodName(), "checkMinTest_injection");

+

+    Assert.assertEquals(al.getFailedTests().size(), 2);

+  }

+}

diff --git a/src/test/java/test/dataprovider/UnnamedDataProviderTest.java b/src/test/java/test/dataprovider/UnnamedDataProviderTest.java
new file mode 100644
index 0000000..8e1ffa5
--- /dev/null
+++ b/src/test/java/test/dataprovider/UnnamedDataProviderTest.java
@@ -0,0 +1,38 @@
+package test.dataprovider;

+

+import org.testng.Assert;

+import org.testng.annotations.DataProvider;

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class UnnamedDataProviderTest {

+  private boolean m_false = false;

+  private boolean m_true = false;

+

+  @Test(dataProvider = "unnamedDataProvider")

+  public void doStuff(boolean t) {

+    if (t) {

+      m_true = true;

+    }

+    if (! t) {

+      m_false = true;

+    }

+  }

+

+  @Test(dependsOnMethods = {"doStuff"} )

+  public void verify() {

+    Assert.assertTrue(m_true);

+    Assert.assertTrue(m_false);

+  }

+

+  @DataProvider

+  public Object[][] unnamedDataProvider() {

+    return new Object[][] {

+        {Boolean.TRUE},

+        {Boolean.FALSE}

+    };

+  }

+}

diff --git a/src/test/java/test/dataprovider/VarArgsDataProviderTest.java b/src/test/java/test/dataprovider/VarArgsDataProviderTest.java
new file mode 100644
index 0000000..3bab181
--- /dev/null
+++ b/src/test/java/test/dataprovider/VarArgsDataProviderTest.java
@@ -0,0 +1,19 @@
+package test.dataprovider;
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class VarArgsDataProviderTest {
+  private static final String[] S = new String[] { "a", "b", "c" };
+
+  @DataProvider
+  public Object[][] data() {
+    return new Object[][] { S };
+  }
+
+  @Test(dataProvider = "data")
+  public void testWithTwoEntriesInTestToolWindow(String... o) {
+    Assert.assertEquals(o, S);
+  }
+}
diff --git a/src/test/java/test/dependent/BaseOrderMethodTest.java b/src/test/java/test/dependent/BaseOrderMethodTest.java
new file mode 100644
index 0000000..3390779
--- /dev/null
+++ b/src/test/java/test/dependent/BaseOrderMethodTest.java
@@ -0,0 +1,47 @@
+package test.dependent;
+
+import org.testng.annotations.Test;
+
+/**
+ * This class
+ *
+ * @author Cedric Beust, Aug 20, 2004
+ *
+ */
+public class BaseOrderMethodTest {
+  protected boolean[] m_group1 = {
+      false, false
+  };
+  protected boolean[] m_group2 = {
+      false, false
+  };
+  protected boolean[] m_group3 = {
+      false
+  };
+
+  @Test(groups = { "2.0" }, dependsOnGroups = { "1.0", "1.1" })
+  public void a_second0() {
+    verifyGroup(2, m_group1);
+    m_group2[0] = true;
+  }
+
+  @Test(groups = { "3" }, dependsOnGroups = { "2.0" })
+  public void third0() {
+    verifyGroup(3, m_group2);
+    m_group3[0] = true;
+  }
+
+    public static void ppp(String s) {
+	    System.out.println("[BaseOrderMethodTest] " + s);
+    }
+
+
+    protected void verifyGroup(int groupNumber, boolean[] group) {
+      for (int i = 0; i < group.length; i++) {
+        assert group[i] : "Error while running group " + groupNumber + ": "
+        + " index " + i
+        + " of previous group should have been run before.";
+      }
+    }
+
+}
diff --git a/src/test/java/test/dependent/C1.java b/src/test/java/test/dependent/C1.java
new file mode 100644
index 0000000..4a7ed78
--- /dev/null
+++ b/src/test/java/test/dependent/C1.java
@@ -0,0 +1,14 @@
+package test.dependent;
+
+import static org.testng.Assert.fail;
+
+import org.testng.annotations.Test;
+
+@Test(groups="group1")
+public class C1 {
+
+   public void failingTest() {
+      fail("always fails");
+   }
+
+}
diff --git a/src/test/java/test/dependent/C2.java b/src/test/java/test/dependent/C2.java
new file mode 100644
index 0000000..52f224f
--- /dev/null
+++ b/src/test/java/test/dependent/C2.java
@@ -0,0 +1,14 @@
+package test.dependent;
+
+
+import org.testng.annotations.Test;
+
+@Test(groups={"group2"}, dependsOnGroups={"group1"})
+public class C2 {
+
+   public void shouldBeSkipped() {
+      // the expectation is that this test will be SKIPPED because
+      // a test in group1 failed and we have a dependsOnGroups={"group1"}
+   }
+
+}
diff --git a/src/test/java/test/dependent/ClassDependsOnGroups.java b/src/test/java/test/dependent/ClassDependsOnGroups.java
new file mode 100644
index 0000000..0c9ab90
--- /dev/null
+++ b/src/test/java/test/dependent/ClassDependsOnGroups.java
@@ -0,0 +1,40 @@
+package test.dependent;
+
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+
+
+public class ClassDependsOnGroups extends BaseTest {
+  @Test
+  public void verifyDependsOnGroups() {
+     addClass(test.dependent.DifferentClassDependsOnGroupsTest1.class.getName());
+     addClass(test.dependent.DifferentClassDependsOnGroupsTest2.class.getName());
+
+     run();
+     String[] failed = {
+        "test0"
+     };
+     String[] skipped = {
+         "test1", "test2"
+     };
+     verifyTests("Failed", failed, getFailedTests());
+     verifyTests("Skipped", skipped, getSkippedTests());
+  }
+
+  @Test
+  public void verifyGroupsAcrossClasses() {
+     addClass(test.dependent.C1.class.getName());
+     addClass(test.dependent.C2.class.getName());
+
+     run();
+     String[] failed = {
+        "failingTest"
+     };
+     String[] skipped = {
+         "shouldBeSkipped"
+     };
+     verifyTests("Failed", failed, getFailedTests());
+     verifyTests("Skipped", skipped, getSkippedTests());
+  }
+}
diff --git a/src/test/java/test/dependent/ClassWide1Test.java b/src/test/java/test/dependent/ClassWide1Test.java
new file mode 100644
index 0000000..ce290fa
--- /dev/null
+++ b/src/test/java/test/dependent/ClassWide1Test.java
@@ -0,0 +1,22 @@
+package test.dependent;
+
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+public class ClassWide1Test {
+  private static boolean m_ok = false;
+
+  @BeforeTest
+  public void init() {
+    m_ok = false;
+  }
+
+  @Test
+  public void m1() {
+    m_ok = true;
+  }
+
+  public static boolean m1WasRun() {
+    return m_ok;
+  }
+}
diff --git a/src/test/java/test/dependent/ClassWide2Test.java b/src/test/java/test/dependent/ClassWide2Test.java
new file mode 100644
index 0000000..15a74ac
--- /dev/null
+++ b/src/test/java/test/dependent/ClassWide2Test.java
@@ -0,0 +1,19 @@
+package test.dependent;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class ClassWide2Test {
+
+//  @Test(dependsOnMethods = {"m1" })
+  @Test(dependsOnMethods = {"test.dependent.ClassWide1Test.m1" })
+  public void m2() {
+    Assert.assertTrue(ClassWide1Test.m1WasRun());
+  }
+
+//  @Test
+//  public void m1() {
+//
+//  }
+
+}
diff --git a/src/test/java/test/dependent/DepBugSampleTest.java b/src/test/java/test/dependent/DepBugSampleTest.java
new file mode 100644
index 0000000..9d82b8f
--- /dev/null
+++ b/src/test/java/test/dependent/DepBugSampleTest.java
@@ -0,0 +1,46 @@
+package test.dependent;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Test
+public class DepBugSampleTest {
+  private static List<String> m_log = new ArrayList<>();
+
+  private static void log(String s) {
+//    ppp(s);
+    m_log.add(s);
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[DepBugSampleTest] " + s);
+  }
+
+  public static List<String> getLog() {
+    return m_log;
+  }
+
+  @BeforeClass
+  public void setup() throws Exception {
+    log("setup");
+  }
+
+  @AfterClass
+  public void destroy() throws Exception {
+    log("destroy");
+  }
+
+  @Test(dependsOnMethods = "send")
+  public void get() throws Exception {
+    log("get");
+  }
+
+  public void send() throws Exception {
+    log("send");
+  }
+
+}
diff --git a/src/test/java/test/dependent/DepBugVerifyTest.java b/src/test/java/test/dependent/DepBugVerifyTest.java
new file mode 100644
index 0000000..0c42a3d
--- /dev/null
+++ b/src/test/java/test/dependent/DepBugVerifyTest.java
@@ -0,0 +1,20 @@
+package test.dependent;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.util.List;
+
+public class DepBugVerifyTest {
+
+  @Test
+  public void verify() {
+    List<String> log = DepBugSampleTest.getLog();
+    String[] expected = new String[] {
+      "setup", "send", "get", "destroy"
+    };
+    for (int i = 0; i < log.size(); i++) {
+      Assert.assertEquals(expected[i], log.get(i));
+    }
+  }
+}
diff --git a/src/test/java/test/dependent/DependencyFixTest.java b/src/test/java/test/dependent/DependencyFixTest.java
new file mode 100644
index 0000000..8c2eedc
--- /dev/null
+++ b/src/test/java/test/dependent/DependencyFixTest.java
@@ -0,0 +1,24 @@
+package test.dependent;
+
+import static org.testng.Assert.assertTrue;
+
+import org.testng.annotations.Test;
+
+public class DependencyFixTest {
+	@Test(dependsOnMethods = "helloWorld", ignoreMissingDependencies = true)
+	public void dependentOnNonExistingMethod() {
+		assertTrue(true);
+	}
+
+	@Test(dependsOnMethods = "dependentOnNonExistingMethod")
+	public void dependentOnExistingMethod() {
+		assertTrue(true);
+	}
+
+	@Test(groups = "selfSufficient", dependsOnGroups = "nonExistingGroup", ignoreMissingDependencies = true)
+	public void dependentOnNonExistingGroup() {
+		assertTrue(true);
+
+	}
+
+}
diff --git a/src/test/java/test/dependent/DependentAlwaysRunTest.java b/src/test/java/test/dependent/DependentAlwaysRunTest.java
new file mode 100644
index 0000000..44e71a1
--- /dev/null
+++ b/src/test/java/test/dependent/DependentAlwaysRunTest.java
@@ -0,0 +1,62 @@
+package test.dependent;
+
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+
+public class DependentAlwaysRunTest extends BaseTest {
+  @Test
+  public void verifyDependsOnMethodsAlwaysRun() {
+    addClass("test.dependent.DependentOnMethod1AlwaysRunSampleTest");
+
+    run();
+    String[] passed = {
+        "b", "verify"
+     };
+    String[] failed = {
+       "a"
+    };
+    String[] skipped = {
+    };
+    verifyTests("Failed", failed, getFailedTests());
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Skipped", skipped, getSkippedTests());
+  }
+
+  @Test
+  public void verifyDependsOnGroups1AlwaysRun() {
+    addClass("test.dependent.DependentOnGroup1AlwaysRunSampleTest");
+
+    run();
+    String[] passed = {
+        "b", "verify"
+     };
+    String[] failed = {
+       "a"
+    };
+    String[] skipped = {
+    };
+    verifyTests("Failed", failed, getFailedTests());
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Skipped", skipped, getSkippedTests());
+  }
+
+  @Test
+  public void verifyDependsOnGroups2AlwaysRun() {
+    addClass("test.dependent.DependentOnGroup2AlwaysRunSampleTest");
+
+    run();
+    String[] passed = {
+        "a2", "b", "verify"
+     };
+    String[] failed = {
+       "a"
+    };
+    String[] skipped = {
+    };
+    verifyTests("Failed", failed, getFailedTests());
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Skipped", skipped, getSkippedTests());
+  }
+
+}
diff --git a/src/test/java/test/dependent/DependentOnGroup1AlwaysRunSampleTest.java b/src/test/java/test/dependent/DependentOnGroup1AlwaysRunSampleTest.java
new file mode 100644
index 0000000..8c75a48
--- /dev/null
+++ b/src/test/java/test/dependent/DependentOnGroup1AlwaysRunSampleTest.java
@@ -0,0 +1,30 @@
+package test.dependent;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+/**
+ * a will fail but b should run anyway because of alwaysRun=true
+ *
+ * Created on Nov 18, 2005
+ * @author cbeust
+ */
+public class DependentOnGroup1AlwaysRunSampleTest {
+
+  private boolean m_ok = false;
+
+  @Test(groups = { "group-a"})
+  public void a() {
+    throw new RuntimeException("Voluntary failure");
+  }
+
+  @Test(dependsOnGroups = {"group-a"}, alwaysRun = true)
+  public void b() {
+    m_ok = true;
+  }
+
+  @Test(dependsOnMethods = {"b"})
+  public void verify() {
+    Assert.assertTrue(m_ok, "method b() should have been invoked");
+  }
+}
diff --git a/src/test/java/test/dependent/DependentOnGroup2AlwaysRunSampleTest.java b/src/test/java/test/dependent/DependentOnGroup2AlwaysRunSampleTest.java
new file mode 100644
index 0000000..165d23e
--- /dev/null
+++ b/src/test/java/test/dependent/DependentOnGroup2AlwaysRunSampleTest.java
@@ -0,0 +1,34 @@
+package test.dependent;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+/**
+ * a will fail but b should run anyway because of alwaysRun=true
+ *
+ * Created on Nov 18, 2005
+ * @author cbeust
+ */
+public class DependentOnGroup2AlwaysRunSampleTest {
+
+  private boolean m_ok = false;
+
+  @Test(groups = { "group-a"})
+  public void a() {
+    throw new RuntimeException("Voluntary failure");
+  }
+
+  @Test(groups = { "group-a"})
+  public void a2() {
+  }
+
+  @Test(dependsOnGroups = {"group-a"}, alwaysRun = true)
+  public void b() {
+    m_ok = true;
+  }
+
+  @Test(dependsOnMethods = {"b"})
+  public void verify() {
+    Assert.assertTrue(m_ok, "method b() should have been invoked");
+  }
+}
diff --git a/src/test/java/test/dependent/DependentOnMethod1AlwaysRunSampleTest.java b/src/test/java/test/dependent/DependentOnMethod1AlwaysRunSampleTest.java
new file mode 100644
index 0000000..86da5e9
--- /dev/null
+++ b/src/test/java/test/dependent/DependentOnMethod1AlwaysRunSampleTest.java
@@ -0,0 +1,30 @@
+package test.dependent;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+/**
+ * a will fail but b should run anyway because of alwaysRun=true
+ *
+ * Created on Nov 18, 2005
+ * @author cbeust
+ */
+public class DependentOnMethod1AlwaysRunSampleTest {
+
+  private boolean m_ok = false;
+
+  @Test
+  public void a() {
+    throw new RuntimeException("Voluntary failure");
+  }
+
+  @Test(dependsOnMethods = {"a"}, alwaysRun = true)
+  public void b() {
+    m_ok = true;
+  }
+
+  @Test(dependsOnMethods = {"b"})
+  public void verify() {
+    Assert.assertTrue(m_ok, "method b() should have been invoked");
+  }
+}
diff --git a/src/test/java/test/dependent/DependentTest.java b/src/test/java/test/dependent/DependentTest.java
new file mode 100644
index 0000000..dbe9445
--- /dev/null
+++ b/src/test/java/test/dependent/DependentTest.java
@@ -0,0 +1,117 @@
+package test.dependent;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.ExpectedExceptions;
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+import test.SimpleBaseTest;
+
+import java.util.List;
+
+public class DependentTest extends BaseTest {
+
+  @Test
+  public void simpleSkip() {
+    addClass(SampleDependent1.class.getName());
+    run();
+    String[] passed = {};
+    String[] failed = { "fail" };
+    String[] skipped = { "shouldBeSkipped" };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+    verifyTests("Skipped", skipped, getSkippedTests());
+  }
+
+  @Test
+  public void dependentMethods() {
+    addClass(SampleDependentMethods.class.getName());
+    run();
+    String[] passed = { "oneA", "oneB", "secondA", "thirdA", "canBeRunAnytime" };
+    String[] failed = {};
+    String[] skipped = {};
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+    verifyTests("Skipped", skipped, getSkippedTests());
+  }
+
+  @Test
+  public void dependentMethodsWithSkip() {
+    addClass(SampleDependentMethods4.class.getName());
+    run();
+    String[] passed = { "step1", };
+    String[] failed = { "step2", };
+    String[] skipped = { "step3" };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+    verifyTests("Skipped", skipped, getSkippedTests());
+  }
+
+  @Test
+  @ExpectedExceptions({ org.testng.TestNGException.class })
+  public void dependentMethodsWithNonExistentMethod() {
+    addClass(SampleDependentMethods5.class.getName());
+    run();
+    String[] passed = { "step1", "step2" };
+    String[] failed = {};
+    String[] skipped = {};
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+    verifyTests("Skipped", skipped, getSkippedTests());
+  }
+
+  @Test(expectedExceptions = org.testng.TestNGException.class)
+  public void dependentMethodsWithCycle() {
+    addClass(SampleDependentMethods6.class.getName());
+    run();
+  }
+
+  @Test(expectedExceptions = org.testng.TestNGException.class)
+  public void dependentGroupsWithCycle() {
+    addClass("test.dependent.SampleDependentMethods7");
+    run();
+  }
+
+  @Test
+  public void multipleSkips() {
+    addClass(MultipleDependentSampleTest.class.getName());
+    run();
+    String[] passed = { "init", };
+    String[] failed = { "fail", };
+    String[] skipped = { "skip1", "skip2" };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+    verifyTests("Skipped", skipped, getSkippedTests());
+  }
+
+  @Test
+  public void instanceDependencies() {
+    addClass(InstanceSkipSampleTest.class.getName());
+    run();
+    verifyInstanceNames("Passed", getPassedTests(),
+        new String[] { "f#1", "f#3", "g#1", "g#3"});
+    verifyInstanceNames("Failed", getFailedTests(),
+        new String[] { "f#2" });
+    verifyInstanceNames("Skipped", getSkippedTests(),
+        new String[] { "g#" });
+  }
+
+  @Test
+  public void dependentWithDataProvider() {
+    TestNG tng = SimpleBaseTest.create(DependentWithDataProviderSampleTest.class);
+    tng.setGroupByInstances(true);
+    List<String> log = DependentWithDataProviderSampleTest.m_log;
+    log.clear();
+    tng.run();
+    for (int i = 0; i < 12; i += 4) {
+      String[] s = log.get(i).split("#");
+      String instance = s[1];
+      Assert.assertEquals(log.get(i), "prepare#" + instance);
+      Assert.assertEquals(log.get(i + 1), "test1#" + instance);
+      Assert.assertEquals(log.get(i + 2), "test2#" + instance);
+      Assert.assertEquals(log.get(i + 3), "clean#" + instance);
+    }
+  }
+} // DependentTest
+
diff --git a/src/test/java/test/dependent/DependentWithDataProviderSampleTest.java b/src/test/java/test/dependent/DependentWithDataProviderSampleTest.java
new file mode 100644
index 0000000..4291605
--- /dev/null
+++ b/src/test/java/test/dependent/DependentWithDataProviderSampleTest.java
@@ -0,0 +1,65 @@
+package test.dependent;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+
+import java.util.List;
+
+public class DependentWithDataProviderSampleTest
+{
+  public static List<String> m_log = Lists.newArrayList();
+  private String param;
+
+  @Factory( dataProvider = "prov" )
+  public DependentWithDataProviderSampleTest( String param )
+  {
+    this.param = param;
+  }
+
+  @DataProvider( name = "prov" )
+  public static Object[][] dataProvider()
+  {
+    return new Object[][] {
+      { "One" },
+      { "Two" },
+      { "Three" },
+    };
+  }
+
+  private void log(String s) {
+    m_log.add(s + "#" + param);
+  }
+
+  @BeforeClass
+  public void prepare()
+  {
+    log("prepare");
+  }
+
+  @Test
+  public void test1()
+  {
+    log("test1");
+  }
+
+  @Test( dependsOnMethods = "test1" )
+  public void test2()
+  {
+    log("test2");
+  }
+
+  @AfterClass
+  public void clean()
+  {
+    log("clean");
+  }
+
+  @Override
+  public String toString() {
+    return "[" + param + "]";
+  }
+}
diff --git a/src/test/java/test/dependent/DependsOnProtectedMethodTest.java b/src/test/java/test/dependent/DependsOnProtectedMethodTest.java
new file mode 100644
index 0000000..00fbb2a
--- /dev/null
+++ b/src/test/java/test/dependent/DependsOnProtectedMethodTest.java
@@ -0,0 +1,29 @@
+package test.dependent;

+

+import org.testng.Assert;

+import org.testng.annotations.BeforeMethod;

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class DependsOnProtectedMethodTest {

+  private boolean m_before1 = false;

+  private boolean m_before2 = false;

+

+  @BeforeMethod(dependsOnMethods = { "before2" })

+  protected void before() {

+    m_before1 = true;

+  }

+

+  @BeforeMethod

+  protected void before2() {

+    m_before2 = true;

+  }

+

+  @Test

+  public void verifyBeforeInvocations() {

+    Assert.assertTrue(m_before1 && m_before2, "Protected dependent @BeforeMethods should have been invoked");

+  }

+}

diff --git a/src/test/java/test/dependent/DepthDependencyTest.java b/src/test/java/test/dependent/DepthDependencyTest.java
new file mode 100644
index 0000000..4ad0816
--- /dev/null
+++ b/src/test/java/test/dependent/DepthDependencyTest.java
@@ -0,0 +1,21 @@
+package test.dependent;
+
+import org.testng.annotations.Test;
+
+public class DepthDependencyTest {
+
+  @Test(groups = { "1"} )
+  public void f1() {
+    throw new RuntimeException();
+  }
+
+  @Test(groups = { "2"}, dependsOnGroups = {"1"} )
+  public void f2() {
+
+  }
+
+  @Test(groups = { "3"}, dependsOnGroups = {"2"} )
+  public void f3() {
+
+  }
+}
diff --git a/src/test/java/test/dependent/DifferentClassDependsOnGroupsTest1.java b/src/test/java/test/dependent/DifferentClassDependsOnGroupsTest1.java
new file mode 100644
index 0000000..6c206bc
--- /dev/null
+++ b/src/test/java/test/dependent/DifferentClassDependsOnGroupsTest1.java
@@ -0,0 +1,18 @@
+package test.dependent;
+
+
+import static org.testng.Assert.assertTrue;
+
+import org.testng.annotations.Test;
+
+
+public class DifferentClassDependsOnGroupsTest1 {
+  @Test(groups = { "mainGroup" })
+  public void test0() {
+    assertTrue(1 == 0); // Force a failure
+  }
+
+  @Test(dependsOnGroups= {"mainGroup"})
+  public void test2() {
+  }
+}
diff --git a/src/test/java/test/dependent/DifferentClassDependsOnGroupsTest2.java b/src/test/java/test/dependent/DifferentClassDependsOnGroupsTest2.java
new file mode 100644
index 0000000..fea81c2
--- /dev/null
+++ b/src/test/java/test/dependent/DifferentClassDependsOnGroupsTest2.java
@@ -0,0 +1,11 @@
+package test.dependent;
+
+
+import org.testng.annotations.Test;
+
+
+public class DifferentClassDependsOnGroupsTest2 {
+  @Test(dependsOnGroups = { "mainGroup" })
+  public void test1() {
+  }
+}
diff --git a/src/test/java/test/dependent/GroupByInstancesSampleTest.java b/src/test/java/test/dependent/GroupByInstancesSampleTest.java
new file mode 100644
index 0000000..68bb9e4
--- /dev/null
+++ b/src/test/java/test/dependent/GroupByInstancesSampleTest.java
@@ -0,0 +1,46 @@
+package test.dependent;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+
+import java.util.List;
+
+public class GroupByInstancesSampleTest {
+  private String m_country;
+  public static List<String> m_log = Lists.newArrayList();
+
+  private static void log(String method, String country) {
+//    System.out.println("LOG:" + method + "#" + country + " " + Thread.currentThread().getId());
+    m_log.add(method + "#" + country);
+  }
+
+  @DataProvider
+  public Object[][] dp() {
+    return new Object[][] {
+        new Object[] { "usa" },
+        new Object[] { "uk" },
+    };
+  }
+
+  @Factory(dataProvider = "dp")
+  public GroupByInstancesSampleTest(String country) {
+    m_country = country;
+  }
+
+  @Test
+  public void signIn() {
+    log("signIn", m_country);
+  }
+
+  @Test(dependsOnMethods = "signIn")
+  public void signOut() {
+    log("signOut", m_country);
+  }
+
+  @Override
+  public String toString() {
+    return "[GroupByInstancesSampleTest: " + m_country + "]";
+  }
+}
diff --git a/src/test/java/test/dependent/GroupByInstancesTest.java b/src/test/java/test/dependent/GroupByInstancesTest.java
new file mode 100644
index 0000000..89afb33
--- /dev/null
+++ b/src/test/java/test/dependent/GroupByInstancesTest.java
@@ -0,0 +1,44 @@
+package test.dependent;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+
+import test.SimpleBaseTest;
+
+import java.util.List;
+
+public class GroupByInstancesTest extends SimpleBaseTest {
+
+  @Test
+  public void dontGroupByInstances() {
+    runTest(false);
+  }
+    
+  @Test
+  public void groupByInstances() {
+    runTest(true);
+  }
+
+  private void runTest(boolean group) {
+    TestNG tng = create(GroupByInstancesSampleTest.class);
+    GroupByInstancesSampleTest.m_log = Lists.newArrayList();
+    tng.setGroupByInstances(group);
+    tng.run();
+
+    List<String> log = GroupByInstancesSampleTest.m_log;
+    int i = 0;
+    if (group) {
+      Assert.assertTrue(log.get(i++).startsWith("signIn"));
+      Assert.assertTrue(log.get(i++).startsWith("signOut"));
+      Assert.assertTrue(log.get(i++).startsWith("signIn"));
+      Assert.assertTrue(log.get(i++).startsWith("signOut"));
+    } else {
+      Assert.assertTrue(log.get(i++).startsWith("signIn"));
+      Assert.assertTrue(log.get(i++).startsWith("signIn"));
+      Assert.assertTrue(log.get(i++).startsWith("signOut"));
+      Assert.assertTrue(log.get(i++).startsWith("signOut"));
+    }
+  }
+}
diff --git a/src/test/java/test/dependent/ImplicitGroupInclusion2SampleTest.java b/src/test/java/test/dependent/ImplicitGroupInclusion2SampleTest.java
new file mode 100644
index 0000000..1baaf17
--- /dev/null
+++ b/src/test/java/test/dependent/ImplicitGroupInclusion2SampleTest.java
@@ -0,0 +1,37 @@
+package test.dependent;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class ImplicitGroupInclusion2SampleTest {
+  private boolean m_m1, m_m2, m_m3;
+
+  @BeforeClass(groups = {"g2"})
+  public void init() {
+    m_m1 = m_m2 = m_m3 = false;
+  }
+
+  @Test (groups = {"g1"})
+  public void m1() {
+    m_m1 = true;
+ }
+
+ @Test (groups = {"g1"}, dependsOnMethods="m1")
+  public void m2() {
+     m_m2 = true;
+ }
+
+ @Test (groups = {"g2"})
+  public void m3() {
+     m_m3 = true;
+ }
+
+ @AfterClass(groups = {"g2"})
+ public void verify() {
+   Assert.assertFalse(m_m1, "Shouldn't have invoked m1()");
+   Assert.assertFalse(m_m2);
+   Assert.assertTrue(m_m3);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/test/dependent/ImplicitGroupInclusion3SampleTest.java b/src/test/java/test/dependent/ImplicitGroupInclusion3SampleTest.java
new file mode 100644
index 0000000..11ff860
--- /dev/null
+++ b/src/test/java/test/dependent/ImplicitGroupInclusion3SampleTest.java
@@ -0,0 +1,19 @@
+package test.dependent;
+
+import org.testng.annotations.Test;
+
+public class ImplicitGroupInclusion3SampleTest {
+ @Test( groups = {"inc"} )
+ public void test1() {}
+
+ @Test( groups = {"exc"} )
+ public void test2() {
+   throw new RuntimeException("exclude me");
+ }
+
+ @Test( groups = {"exc"}, dependsOnMethods={"test2"} )
+ public void test3() {
+   throw new RuntimeException("exclude me");
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/test/dependent/ImplicitGroupInclusion4SampleTest.java b/src/test/java/test/dependent/ImplicitGroupInclusion4SampleTest.java
new file mode 100644
index 0000000..b3b9c54
--- /dev/null
+++ b/src/test/java/test/dependent/ImplicitGroupInclusion4SampleTest.java
@@ -0,0 +1,43 @@
+package test.dependent;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class ImplicitGroupInclusion4SampleTest {
+  private boolean m_m1, m_m2, m_m3, m_m4;
+
+  @BeforeClass(groups = {"g2"})
+  public void init() {
+    m_m1 = m_m2 = m_m3 = m_m4 = false;
+  }
+
+  @Test (groups = {"g1"})
+  public void m1() {
+    m_m1 = true;
+ }
+
+ @Test (groups = {"g1"}, dependsOnMethods="m1")
+  public void m2() {
+     m_m2 = true;
+ }
+
+ @Test (groups = {"g2"})
+  public void m3() {
+     m_m3 = true;
+ }
+
+ @Test (groups = {"g2"}, dependsOnMethods="m3")
+ public void m4() {
+   m_m4 = true;
+ }
+
+ @AfterClass(groups = {"g2"})
+ public void verify() {
+   Assert.assertFalse(m_m1, "Shouldn't have invoked m1()");
+   Assert.assertFalse(m_m2);
+   Assert.assertTrue(m_m3);
+   Assert.assertTrue(m_m4);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/test/dependent/ImplicitGroupInclusionSampleTest.java b/src/test/java/test/dependent/ImplicitGroupInclusionSampleTest.java
new file mode 100644
index 0000000..edade57
--- /dev/null
+++ b/src/test/java/test/dependent/ImplicitGroupInclusionSampleTest.java
@@ -0,0 +1,21 @@
+package test.dependent;
+
+import org.testng.annotations.Test;
+
+public class ImplicitGroupInclusionSampleTest {
+
+  @Test(groups = "z")
+  public void z() {
+
+  }
+
+  @Test(groups = "a", dependsOnGroups = {"z"})
+  public void a() {
+
+  }
+
+  @Test(groups = "b", dependsOnGroups = {"a"})
+  public void b() {
+
+  }
+}
diff --git a/src/test/java/test/dependent/ImplicitGroupInclusionTest.java b/src/test/java/test/dependent/ImplicitGroupInclusionTest.java
new file mode 100644
index 0000000..c8804f8
--- /dev/null
+++ b/src/test/java/test/dependent/ImplicitGroupInclusionTest.java
@@ -0,0 +1,100 @@
+package test.dependent;
+
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+
+public class ImplicitGroupInclusionTest extends BaseTest {
+
+  @Test
+  public void verifyImplicitGroupInclusion() {
+    addClass("test.dependent.ImplicitGroupInclusionSampleTest");
+    addIncludedGroup("b");
+
+    run();
+    String[] passed = {
+        "a", "b", "z"
+     };
+    String[] failed = {
+    };
+    String[] skipped = {
+    };
+    verifyTests("Failed", failed, getFailedTests());
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Skipped", skipped, getSkippedTests());
+  }
+
+  @Test
+  public void verifyImplicitGroupInclusion2() {
+    addClass("test.dependent.ImplicitGroupInclusion2SampleTest");
+    addIncludedGroup("g2");
+
+    run();
+    String[] passed = {
+        "m3"
+     };
+    String[] failed = {
+    };
+    String[] skipped = {
+    };
+    verifyTests("Failed", failed, getFailedTests());
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Skipped", skipped, getSkippedTests());
+  }
+
+  @Test
+  public void verifyImplicitGroupInclusion4() {
+    addClass("test.dependent.ImplicitGroupInclusion4SampleTest");
+    addIncludedGroup("g2");
+
+    run();
+    String[] passed = {
+        "m3", "m4"
+     };
+    String[] failed = {
+    };
+    String[] skipped = {
+    };
+    verifyTests("Failed", failed, getFailedTests());
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Skipped", skipped, getSkippedTests());
+  }
+
+  @Test
+  public void verifyImplicitGroupInclusion3() {
+    addClass("test.dependent.ImplicitGroupInclusion3SampleTest");
+    addIncludedGroup("inc");
+    addExcludedGroup("exc");
+
+    run();
+    String[] passed = {
+        "test1"
+     };
+    String[] failed = {
+    };
+    String[] skipped = {
+    };
+    verifyTests("Failed", failed, getFailedTests());
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Skipped", skipped, getSkippedTests());
+  }
+
+  @Test
+  public void verifyImplicitMethodInclusion() {
+    addClass("test.dependent.ImplicitMethodInclusionSampleTest");
+    addIncludedGroup("windows");
+
+    run();
+    String[] passed = {
+        "a", "b"
+     };
+    String[] failed = {
+    };
+    String[] skipped = {
+    };
+    verifyTests("Failed", failed, getFailedTests());
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Skipped", skipped, getSkippedTests());
+  }
+
+}
diff --git a/src/test/java/test/dependent/ImplicitMethodInclusionSampleTest.java b/src/test/java/test/dependent/ImplicitMethodInclusionSampleTest.java
new file mode 100644
index 0000000..3f02969
--- /dev/null
+++ b/src/test/java/test/dependent/ImplicitMethodInclusionSampleTest.java
@@ -0,0 +1,20 @@
+package test.dependent;
+
+import org.testng.annotations.Test;
+
+public class ImplicitMethodInclusionSampleTest {
+  @Test(groups = {"linux"})
+  public void a() {
+//    ppp("A");
+  }
+
+  @Test(groups = {"linux", "windows"} , dependsOnMethods={"a"})
+  public void b() {
+//    ppp("B");
+  }
+
+  private void ppp(String string) {
+    System.out.println("[Implicit] " + string);
+  }
+
+}
diff --git a/src/test/java/test/dependent/InstanceSkipSampleTest.java b/src/test/java/test/dependent/InstanceSkipSampleTest.java
new file mode 100644
index 0000000..3f8d415
--- /dev/null
+++ b/src/test/java/test/dependent/InstanceSkipSampleTest.java
@@ -0,0 +1,55 @@
+package test.dependent;
+
+import java.util.List;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+
+/**
+ * Verify that only instances that fail cause dependency failures. In other words,
+ * when run, this test should show:
+ * passed = [f#1 f#3 g#1 g#3], failed = [f#2], skipped = [g#2]
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public class InstanceSkipSampleTest {
+
+  private int m_n;
+  public static List<String> m_list = Lists.newArrayList();
+
+  @Factory(dataProvider = "dp")
+  public InstanceSkipSampleTest(int n) {
+    m_n = n;
+  }
+
+  @DataProvider
+  public static Object[][] dp() {
+    return new Object[][] {
+        new Object[] { 1 },
+        new Object[] { 2 },
+        new Object[] { 3 },
+    };
+  }
+
+  @Test
+  public void f() {
+    if (m_n == 2) throw new RuntimeException();
+    log("f");
+  }
+
+  @Test(dependsOnMethods = "f")
+  public void g() {
+    log("g");
+  }
+
+  private void log(String s) {
+    m_list.add(s + "#" + m_n);
+  }
+
+  @Override
+  public String toString() {
+    return "" + m_n;
+  }
+}
diff --git a/src/test/java/test/dependent/MissingGroupSampleTest.java b/src/test/java/test/dependent/MissingGroupSampleTest.java
new file mode 100644
index 0000000..d7ba5b2
--- /dev/null
+++ b/src/test/java/test/dependent/MissingGroupSampleTest.java
@@ -0,0 +1,29 @@
+package test.dependent;
+
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.Parser;
+
+import java.io.ByteArrayInputStream;
+
+public class MissingGroupSampleTest {
+
+  @Test(dependsOnGroups = {"missing-group"})
+  public void shouldBeSkipped() {
+
+  }
+
+  @Test(dependsOnGroups = {"missing-group"}, ignoreMissingDependencies=true)
+  public void shouldNotBeSkipped() {
+
+  }
+
+  public static void main(String[] args) throws Exception {
+    TestNG tng = new TestNG();
+    String xml = "<suite name=\"dgf\" verbose=\"10\"><test name=\"dgf\"><classes><class name=\"test.dependent.MissingGroupSampleTest\"></class></classes></test></suite>";
+    System.out.println(xml);
+    ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes());
+    tng.setXmlSuites(new Parser(is).parseToList());
+    tng.run();
+  }
+}
diff --git a/src/test/java/test/dependent/MissingGroupTest.java b/src/test/java/test/dependent/MissingGroupTest.java
new file mode 100644
index 0000000..06448f0
--- /dev/null
+++ b/src/test/java/test/dependent/MissingGroupTest.java
@@ -0,0 +1,28 @@
+package test.dependent;
+
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+
+public class MissingGroupTest extends BaseTest {
+
+  @Test
+  public void verifyThatExceptionIsThrownIfMissingGroup() {
+    addClass("test.dependent.MissingGroupSampleTest");
+
+    run();
+    String[] passed = {
+        "shouldNotBeSkipped"
+     };
+    String[] failed = {
+    };
+    String[] skipped = {
+      "shouldBeSkipped"
+    };
+    verifyTests("Failed", failed, getFailedTests());
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Skipped", skipped, getSkippedTests());
+
+  }
+
+}
diff --git a/src/test/java/test/dependent/MissingMethodSampleTest.java b/src/test/java/test/dependent/MissingMethodSampleTest.java
new file mode 100644
index 0000000..786e891
--- /dev/null
+++ b/src/test/java/test/dependent/MissingMethodSampleTest.java
@@ -0,0 +1,29 @@
+package test.dependent;
+
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.Parser;
+
+import java.io.ByteArrayInputStream;
+
+public class MissingMethodSampleTest {
+
+  @Test(dependsOnMethods="missingMethod", ignoreMissingDependencies=true)
+  public void explicitlyIgnoreMissingMethod() {
+
+  }
+
+  @Test(dependsOnMethods="missingMethod", alwaysRun=true)
+  public void alwaysRunDespiteMissingMethod() {}
+
+  public static void main(String[] args) throws Exception {
+    TestNG tng = new TestNG();
+    String xml = "<suite name=\"dgf\" verbose=\"10\"><test name=\"dgf\"><classes>" +
+    		"<class name=\"test.dependent.MissingMethodSampleTest\"/>" +
+    		"</classes></test></suite>";
+    System.out.println(xml);
+    ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes());
+    tng.setXmlSuites(new Parser(is).parseToList());
+    tng.run();
+  }
+}
diff --git a/src/test/java/test/dependent/MissingMethodTest.java b/src/test/java/test/dependent/MissingMethodTest.java
new file mode 100644
index 0000000..f665edd
--- /dev/null
+++ b/src/test/java/test/dependent/MissingMethodTest.java
@@ -0,0 +1,28 @@
+package test.dependent;
+
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+
+public class MissingMethodTest extends BaseTest {
+
+  @Test
+  public void verifyThatExceptionIsThrownIfMissingMethod() {
+    addClass("test.dependent.MissingMethodSampleTest");
+
+    run();
+    String[] passed = {
+        "explicitlyIgnoreMissingMethod",
+        "alwaysRunDespiteMissingMethod"
+     };
+    String[] failed = {
+    };
+    String[] skipped = {
+    };
+    verifyTests("Failed", failed, getFailedTests());
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Skipped", skipped, getSkippedTests());
+
+  }
+
+}
diff --git a/src/test/java/test/dependent/MultiGroup1SampleTest.java b/src/test/java/test/dependent/MultiGroup1SampleTest.java
new file mode 100644
index 0000000..79b58c7
--- /dev/null
+++ b/src/test/java/test/dependent/MultiGroup1SampleTest.java
@@ -0,0 +1,16 @@
+package test.dependent;
+
+import org.testng.annotations.Test;
+
+@Test(groups = { "checkin" })
+public class MultiGroup1SampleTest {
+
+  @Test(groups = {"a"})
+  public void testA() {
+
+  }
+
+  public void test1() throws Exception {
+    throw new Exception("fail");
+  }
+}
diff --git a/src/test/java/test/dependent/MultiGroup2SampleTest.java b/src/test/java/test/dependent/MultiGroup2SampleTest.java
new file mode 100644
index 0000000..1b17042
--- /dev/null
+++ b/src/test/java/test/dependent/MultiGroup2SampleTest.java
@@ -0,0 +1,10 @@
+package test.dependent;
+
+import org.testng.annotations.Test;
+
+@Test(groups = {"integration"}, dependsOnGroups = {"checkin", "a"})
+public class MultiGroup2SampleTest {
+
+  public void test2() throws Exception {
+  }
+}
diff --git a/src/test/java/test/dependent/MultiGroupTest.java b/src/test/java/test/dependent/MultiGroupTest.java
new file mode 100644
index 0000000..bf56055
--- /dev/null
+++ b/src/test/java/test/dependent/MultiGroupTest.java
@@ -0,0 +1,27 @@
+package test.dependent;
+
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+
+public class MultiGroupTest extends BaseTest {
+  @Test
+  public void verifyDependsOnMultiGroups() {
+     addClass(MultiGroup1SampleTest.class.getName());
+     addClass(MultiGroup2SampleTest.class.getName());
+
+     run();
+     String[] passed = {
+         "testA",
+      };
+     String[] failed = {
+        "test1"
+     };
+     String[] skipped = {
+         "test2"
+     };
+     verifyTests("Passed", passed, getPassedTests());
+     verifyTests("Failed", failed, getFailedTests());
+     verifyTests("Skipped", skipped, getSkippedTests());
+  }
+}
diff --git a/src/test/java/test/dependent/MultipleDependentSampleTest.java b/src/test/java/test/dependent/MultipleDependentSampleTest.java
new file mode 100644
index 0000000..be4cea0
--- /dev/null
+++ b/src/test/java/test/dependent/MultipleDependentSampleTest.java
@@ -0,0 +1,21 @@
+package test.dependent;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class MultipleDependentSampleTest {
+
+  @Test
+  public void init() {}
+
+  @Test(dependsOnMethods = "init")
+  public void fail() {
+    Assert.fail();
+  }
+
+  @Test(dependsOnMethods = "fail")
+  public void skip1() {}
+
+  @Test(dependsOnMethods = "skip1")
+  public void skip2() {}
+}
diff --git a/src/test/java/test/dependent/OrderMethodTest.java b/src/test/java/test/dependent/OrderMethodTest.java
new file mode 100644
index 0000000..e49184e
--- /dev/null
+++ b/src/test/java/test/dependent/OrderMethodTest.java
@@ -0,0 +1,35 @@
+package test.dependent;
+
+import org.testng.annotations.Test;
+
+
+/**
+ * This class verifies that when methods have dependents, they are run
+ * in the correct order.
+ *
+ * @author Cedric Beust, Aug 19, 2004
+ *
+ */
+public class OrderMethodTest extends BaseOrderMethodTest {
+  @Test(groups = { "1.0" })
+  public void z_first0() {
+//    ppp("1.0");
+    m_group1[0] = true;
+  }
+
+  @Test(groups = { "2.1" }, dependsOnGroups = { "1.0", "1.1" })
+  public void a_second1() {
+//    ppp("2.1");
+    verifyGroup(2, m_group1);
+    m_group2[1] = true;
+  }
+
+  @Test(groups = { "1.1" })
+  public void z_premiere1() {
+//    ppp("1.1");
+    m_group1[1] = true;
+  }
+
+
+
+}
diff --git a/src/test/java/test/dependent/SD2.java b/src/test/java/test/dependent/SD2.java
new file mode 100644
index 0000000..372269f
--- /dev/null
+++ b/src/test/java/test/dependent/SD2.java
@@ -0,0 +1,36 @@
+package test.dependent;
+
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SD2 {
+  public static List<String> m_log = new ArrayList<>();
+
+  @Test(groups = { "one" })
+  public void oneA() {
+    m_log.add("oneA");
+  }
+
+  @Test
+  public void canBeRunAnytime() {
+    m_log.add("canBeRunAnytime");
+  }
+
+  @Test(dependsOnGroups = { "one" } )
+  public void secondA() {
+    m_log.add("secondA");
+  }
+
+  @Test(dependsOnMethods= { "secondA" })
+  public void thirdA() {
+    m_log.add("thirdA");
+  }
+
+  @Test(groups = { "one" })
+  public void oneB() {
+    m_log.add("oneB");
+  }
+
+}
\ No newline at end of file
diff --git a/src/test/java/test/dependent/SampleDependent1.java b/src/test/java/test/dependent/SampleDependent1.java
new file mode 100644
index 0000000..faf85ac
--- /dev/null
+++ b/src/test/java/test/dependent/SampleDependent1.java
@@ -0,0 +1,23 @@
+package test.dependent;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+
+/**
+ * This class exercises dependent groups
+ *
+ * @author Cedric Beust, Aug 19, 2004
+ *
+ */
+public class SampleDependent1 {
+
+  @Test(groups = { "fail" })
+  public void fail() {
+    Assert.assertTrue(false);
+  }
+
+  @Test(dependsOnGroups = { "fail" })
+  public void shouldBeSkipped() {
+  }
+}
diff --git a/src/test/java/test/dependent/SampleDependentConfigurationMethods.java b/src/test/java/test/dependent/SampleDependentConfigurationMethods.java
new file mode 100644
index 0000000..909b557
--- /dev/null
+++ b/src/test/java/test/dependent/SampleDependentConfigurationMethods.java
@@ -0,0 +1,26 @@
+package test.dependent;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class SampleDependentConfigurationMethods {
+  private boolean m_create = false;
+  private boolean m_first = false;
+
+  @BeforeMethod
+  public void createInstance() {
+    m_create = true;
+  }
+
+  @BeforeMethod(dependsOnMethods = { "createInstance"})
+  public void firstInvocation() {
+    assert m_create : "createInstance() was never called";
+    m_first = true;
+  }
+
+  @Test
+  public void verifyDependents() {
+    assert m_create : "createInstance() was never called";
+    assert m_first : "firstInvocation() was never called";
+  }
+}
diff --git a/src/test/java/test/dependent/SampleDependentMethods.java b/src/test/java/test/dependent/SampleDependentMethods.java
new file mode 100644
index 0000000..9d6157d
--- /dev/null
+++ b/src/test/java/test/dependent/SampleDependentMethods.java
@@ -0,0 +1,71 @@
+package test.dependent;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.Test;
+
+
+/**
+ * This class exercises dependent methods
+ *
+ * @author Cedric Beust, Aug 19, 2004
+ *
+ */
+public class SampleDependentMethods {
+  private boolean m_oneA = false;
+  private boolean m_oneB = false;
+  private boolean m_secondA = false;
+  private boolean m_thirdA = false;
+
+  @Test
+  public void oneA() {
+//    ppp("oneA");
+//    assert false : "TEMPORARY FAILURE";
+    assert ! m_secondA : "secondA shouldn't have been run yet";
+    m_oneA = true;
+  }
+
+  @Test
+  public void canBeRunAnytime() {
+
+  }
+
+  @Test(dependsOnMethods= { "oneA", "oneB" })
+  public void secondA() {
+//    ppp("secondA");
+    assert m_oneA : "oneA wasn't run";
+    assert m_oneB : "oneB wasn't run";
+    assert ! m_secondA : "secondA shouldn't have been run yet";
+    m_secondA = true;
+
+  }
+
+  @Test(dependsOnMethods= { "secondA" })
+  public void thirdA() {
+//    ppp("thirdA");
+    assert m_oneA : "oneA wasn't run";
+    assert m_oneB : "oneB wasn't run";
+    assert m_secondA : "secondA wasn't run";
+    assert ! m_thirdA : "thirdA shouldn't have been run yet";
+    m_thirdA = true;
+
+}
+
+  @Test
+  public void oneB() {
+//    ppp("oneB");
+    assert ! m_secondA : "secondA shouldn't have been run yet";
+    m_oneB = true;
+  }
+
+  @AfterClass
+  public void tearDown() {
+    assert m_oneA : "oneA wasn't run";
+    assert m_oneB : "oneB wasn't run";
+    assert m_secondA : "secondA wasn't run";
+    assert m_thirdA : "thirdA wasn't run";
+  }
+
+  public static void ppp(String s) {
+    System.out.println("[SampleDependentMethods] " + s);
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/dependent/SampleDependentMethods2.java b/src/test/java/test/dependent/SampleDependentMethods2.java
new file mode 100644
index 0000000..89fec89
--- /dev/null
+++ b/src/test/java/test/dependent/SampleDependentMethods2.java
@@ -0,0 +1,68 @@
+package test.dependent;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.Test;
+
+
+/**
+ * This class exercises dependent methods
+ *
+ * @author Cedric Beust, Aug 19, 2004
+ *
+ */
+public class SampleDependentMethods2 {
+  private boolean m_oneA = false;
+  private boolean m_oneB = false;
+  private boolean m_secondA = false;
+  private boolean m_thirdA = false;
+
+  @Test(groups = { "one" })
+  public void oneA() {
+//    ppp("oneA");
+    assert ! m_secondA : "secondA shouldn't have been run yet";
+    m_oneA = true;
+  }
+
+  @Test
+  public void canBeRunAnytime() {
+
+  }
+
+  @Test(dependsOnGroups = { "one" } )
+  public void secondA() {
+//    ppp("secondA");
+    assert m_oneA : "oneA wasn't run";
+    assert m_oneB : "oneB wasn't run";
+    assert ! m_secondA : "secondA shouldn't have been run yet";
+    m_secondA = true;
+  }
+
+  @Test(dependsOnMethods= { "secondA" })
+  public void thirdA() {
+//    ppp("thirdA");
+    assert m_oneA : "oneA wasn't run";
+    assert m_oneB : "oneB wasn't run";
+    assert m_secondA : "secondA wasn't run";
+    assert ! m_thirdA : "thirdA shouldn't have been run yet";
+    m_thirdA = true;
+  }
+
+  @Test(groups = { "one" })
+  public void oneB() {
+//    ppp("oneB");
+    assert ! m_secondA : "secondA shouldn't have been run yet";
+    m_oneB = true;
+  }
+
+  @AfterClass
+  public void tearDown() {
+    assert m_oneA : "oneA wasn't run";
+    assert m_oneB : "oneB wasn't run";
+    assert m_secondA : "secondA wasn't run";
+    assert m_thirdA : "thirdA wasn't run";
+  }
+
+  public static void ppp(String s) {
+    System.out.println("[SampleDependentMethods] " + s);
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/dependent/SampleDependentMethods3.java b/src/test/java/test/dependent/SampleDependentMethods3.java
new file mode 100644
index 0000000..5ccba7a
--- /dev/null
+++ b/src/test/java/test/dependent/SampleDependentMethods3.java
@@ -0,0 +1,54 @@
+package test.dependent;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+
+/**
+ * This class tests overloaded dependent methods
+ *
+ * @author Cedric Beust, Aug 19, 2004
+ *
+ */
+public class SampleDependentMethods3 {
+  private boolean m_oneA = false;
+  private boolean m_oneB = false;
+  private boolean m_secondA = false;
+
+  @Test
+  public void one() {
+//    ppp("oneA");
+    assert ! m_secondA : "secondA shouldn't have been run yet";
+    m_oneA = true;
+  }
+
+  @Parameters({ "foo" })
+  @Test
+  public void one(String s) {
+//    ppp("oneB");
+    assert ! m_secondA : "secondA shouldn't have been run yet";
+    assert "Cedric".equals(s) : "Expected parameter value Cedric but got " + s;
+    m_oneB = true;
+  }
+
+  @Test(dependsOnMethods = { "one" } )
+  public void secondA() {
+//    ppp("secondA");
+    assert m_oneA : "oneA wasn't run";
+    assert m_oneB : "oneB wasn't run";
+    assert ! m_secondA : "secondA shouldn't have been run yet";
+    m_secondA = true;
+  }
+
+  @AfterClass
+  public void tearDown() {
+    assert m_oneA : "oneA wasn't run";
+    assert m_oneB : "oneB wasn't run";
+    assert m_secondA : "secondA wasn't run";
+  }
+
+  public static void ppp(String s) {
+    System.out.println("[SampleDependentMethods] " + s);
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/dependent/SampleDependentMethods4.java b/src/test/java/test/dependent/SampleDependentMethods4.java
new file mode 100644
index 0000000..02b0159
--- /dev/null
+++ b/src/test/java/test/dependent/SampleDependentMethods4.java
@@ -0,0 +1,25 @@
+package test.dependent;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+/**
+ * This class
+ *
+ * @author cbeust
+ */
+public class SampleDependentMethods4 {
+
+  @Test
+  public void step1() {
+  }
+
+  @Test(dependsOnMethods = { "step1" })
+  public void step2() {
+    Assert.assertTrue(false, "Problem in step2");
+  }
+
+  @Test(dependsOnMethods = { "step2" })
+  public void step3() {
+  }
+}
diff --git a/src/test/java/test/dependent/SampleDependentMethods5.java b/src/test/java/test/dependent/SampleDependentMethods5.java
new file mode 100644
index 0000000..621e573
--- /dev/null
+++ b/src/test/java/test/dependent/SampleDependentMethods5.java
@@ -0,0 +1,19 @@
+package test.dependent;
+
+import org.testng.annotations.Test;
+
+/**
+ * This class
+ *
+ * @author cbeust
+ */
+public class SampleDependentMethods5 {
+
+  @Test
+  public void step1() {
+  }
+
+  @Test(dependsOnMethods = { "step1", "blablabla" })
+  public void step2() {
+  }
+}
diff --git a/src/test/java/test/dependent/SampleDependentMethods6.java b/src/test/java/test/dependent/SampleDependentMethods6.java
new file mode 100644
index 0000000..37c60d1
--- /dev/null
+++ b/src/test/java/test/dependent/SampleDependentMethods6.java
@@ -0,0 +1,18 @@
+package test.dependent;
+
+import org.testng.annotations.Test;
+
+/**
+ * This class
+ *
+ * @author cbeust
+ */
+public class SampleDependentMethods6 {
+  @Test(dependsOnMethods = { "step2" })
+  public void step1() {
+  }
+
+  @Test(dependsOnMethods = { "step1" })
+  public void step2() {
+  }
+}
diff --git a/src/test/java/test/dependent/SampleDependentTest.java b/src/test/java/test/dependent/SampleDependentTest.java
new file mode 100644
index 0000000..8f56d76
--- /dev/null
+++ b/src/test/java/test/dependent/SampleDependentTest.java
@@ -0,0 +1,40 @@
+package test.dependent;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+public class SampleDependentTest extends SimpleBaseTest {
+
+  @Test
+  public void test2() {
+    TestNG tng = create(SD2.class);
+    SD2.m_log.clear();
+    tng.run();
+
+    boolean oneA = false;
+    boolean oneB = false;
+    boolean secondA = false;
+
+    for (String s : SD2.m_log) {
+      if ("oneA".equals(s)) {
+        oneA = true;
+      }
+      if ("oneB".equals(s)) {
+        oneB = true;
+      }
+      if ("secondA".equals(s)) {
+        Assert.assertTrue(oneA);
+        Assert.assertTrue(oneB);
+        secondA = true;
+      }
+      if ("thirdA".equals(s)) {
+        Assert.assertTrue(oneA);
+        Assert.assertTrue(oneB);
+        Assert.assertTrue(secondA);
+      }
+    }
+  }
+}
diff --git a/src/test/java/test/dependent/Test1.java b/src/test/java/test/dependent/Test1.java
new file mode 100644
index 0000000..18ca788
--- /dev/null
+++ b/src/test/java/test/dependent/Test1.java
@@ -0,0 +1,5 @@
+package test.dependent;
+
+public class Test1 {
+
+}
diff --git a/src/test/java/test/dependent/functionality1/Config.java b/src/test/java/test/dependent/functionality1/Config.java
new file mode 100644
index 0000000..c4a4d2b
--- /dev/null
+++ b/src/test/java/test/dependent/functionality1/Config.java
@@ -0,0 +1,11 @@
+package test.dependent.functionality1;
+
+import org.testng.annotations.BeforeSuite;
+
+public class Config {
+
+   @BeforeSuite (groups = "other")
+   public void beforeSuite(){
+       System.out.println("BeforeSuite group 'other'");
+   }
+}
diff --git a/src/test/java/test/dependent/functionality1/Test1.java b/src/test/java/test/dependent/functionality1/Test1.java
new file mode 100644
index 0000000..4b06b55
--- /dev/null
+++ b/src/test/java/test/dependent/functionality1/Test1.java
@@ -0,0 +1,14 @@
+package test.dependent.functionality1;
+
+import org.testng.annotations.Test;
+
+@Test (groups = "tests.functional.package")
+public class Test1 {
+
+   public void test1_1(){
+       System.out.println("Test 1_1");
+   }
+   public void test1_2(){
+       System.out.println("Test 1_2");
+   }
+}
diff --git a/src/test/java/test/dependent/functionality1/Test2.java b/src/test/java/test/dependent/functionality1/Test2.java
new file mode 100644
index 0000000..2a45541
--- /dev/null
+++ b/src/test/java/test/dependent/functionality1/Test2.java
@@ -0,0 +1,15 @@
+package test.dependent.functionality1;
+
+import org.testng.annotations.Test;
+
+@Test(groups = "tests.functional.upload", dependsOnGroups = "tests.functional.package")
+public class Test2 {
+
+  public void test2_1() {
+    System.out.println("Test 2_1");
+  }
+
+  public void test2_2() {
+    System.out.println("Test 2_2");
+  }
+}
diff --git a/src/test/java/test/dependent/xml/GroupDependencySampleTest.java b/src/test/java/test/dependent/xml/GroupDependencySampleTest.java
new file mode 100644
index 0000000..2397898
--- /dev/null
+++ b/src/test/java/test/dependent/xml/GroupDependencySampleTest.java
@@ -0,0 +1,15 @@
+package test.dependent.xml;
+
+import org.testng.annotations.Test;
+
+public class GroupDependencySampleTest {
+
+  @Test(groups = "a")
+  public void a1() {}
+
+  @Test(groups = "b")
+  public void b1() {}
+
+  @Test(groups = "c")
+  public void c1() {}
+}
diff --git a/src/test/java/test/dependent/xml/GroupDependencyTest.java b/src/test/java/test/dependent/xml/GroupDependencyTest.java
new file mode 100644
index 0000000..a8603bd
--- /dev/null
+++ b/src/test/java/test/dependent/xml/GroupDependencyTest.java
@@ -0,0 +1,67 @@
+package test.dependent.xml;
+
+import org.testng.Assert;
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.SimpleBaseTest;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class GroupDependencyTest extends SimpleBaseTest {
+  @DataProvider
+  public Object[][] dp() {
+    return new Object[][] {
+      new Object[] { new String[] { "a", "b", "c", "a1", "b1", "c1" } },
+      new Object[] { new String[] { "a", "c", "b", "a1", "c1", "b1" } },
+      new Object[] { new String[] { "b", "a", "c", "b1", "a1", "c1" } },
+      new Object[] { new String[] { "b", "c", "a", "b1", "c1", "a1" } },
+      new Object[] { new String[] { "c", "b", "a", "c1", "b1", "a1" } },
+      new Object[] { new String[] { "c", "a", "b", "c1", "a1", "b1" } },
+    };
+  }
+
+  @Test(dataProvider = "dp")
+  public void verifyGroupSingle(String[] a) {
+    configureGroup(a, false /* single */);
+  }
+
+  @Test(dataProvider = "dp")
+  public void verifyGroupMulti(String[] a) {
+    configureGroup(a, true /* multi */);
+  }
+
+  private void configureGroup(String[] a, boolean multi) {
+    XmlSuite suite = createXmlSuite("Dependencies");
+    XmlTest test =
+        createXmlTest(suite, "DependencyTest", GroupDependencySampleTest.class.getName());
+    if (multi) {
+      test.addXmlDependencyGroup(a[2], a[1] + " " + a[0]);
+    } else {
+      test.addXmlDependencyGroup(a[2], a[1]);
+      test.addXmlDependencyGroup(a[1], a[0]);
+    }
+
+    TestNG tng = create();
+    tng.setXmlSuites(Arrays.asList(suite));
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+    List<ITestResult> t = tla.getPassedTests();
+    String method2 = t.get(2).getMethod().getMethodName();
+    if (multi) {
+      // When we have "a depends on groups b and c", the only certainty is that "a"
+      // will be run last
+      Assert.assertEquals(method2, a[5]);
+    } else {
+      assertTestResultsEqual(tla.getPassedTests(), Arrays.asList(a[3], a[4], a[5]));
+    }
+  }
+}
diff --git a/src/test/java/test/dependsongroup/DependsOnGroupsTest.java b/src/test/java/test/dependsongroup/DependsOnGroupsTest.java
new file mode 100644
index 0000000..6b27d86
--- /dev/null
+++ b/src/test/java/test/dependsongroup/DependsOnGroupsTest.java
@@ -0,0 +1,32 @@
+package test.dependsongroup;
+
+import org.testng.Assert;
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+public class DependsOnGroupsTest extends SimpleBaseTest {
+
+  @Test
+  public void methodsShouldBeGroupedByClasses() {
+    TestNG tng = create(new Class[] {
+        ZeroSampleTest.class, FirstSampleTest.class, SecondSampleTest.class
+    });
+
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+    String[] expected = new String[] {
+        "zeroA", "zeroB",
+        "firstA", "firstB",
+        "secondA", "secondB"
+    };
+    for (int i = 0; i < expected.length; i++) {
+      ITestResult testResult = tla.getPassedTests().get(i);
+      Assert.assertEquals(testResult.getMethod().getMethodName(), expected[i]);
+    }
+  }
+}
diff --git a/src/test/java/test/dependsongroup/FirstSampleTest.java b/src/test/java/test/dependsongroup/FirstSampleTest.java
new file mode 100644
index 0000000..2fb354b
--- /dev/null
+++ b/src/test/java/test/dependsongroup/FirstSampleTest.java
@@ -0,0 +1,19 @@
+package test.dependsongroup;
+
+import org.testng.annotations.Test;
+
+@Test(groups = { "first" }, dependsOnGroups = { "zero" })
+public class FirstSampleTest {
+
+  @Test
+  public void firstA() {
+//    System.out.println("firstA");
+  }
+
+  @Test
+  public void firstB() {
+//    System.out.println("firstB");
+  }
+
+}
+
diff --git a/src/test/java/test/dependsongroup/SecondSampleTest.java b/src/test/java/test/dependsongroup/SecondSampleTest.java
new file mode 100644
index 0000000..34a0d66
--- /dev/null
+++ b/src/test/java/test/dependsongroup/SecondSampleTest.java
@@ -0,0 +1,18 @@
+package test.dependsongroup;
+
+import org.testng.annotations.Test;
+
+@Test(groups = { "second" }, dependsOnGroups = { "zero" })
+public class SecondSampleTest {
+
+  @Test
+  public void secondA() {
+//    System.out.println("secondA");
+  }
+
+  @Test
+  public void secondB() {
+//    System.out.println("secondB");
+  }
+
+}
\ No newline at end of file
diff --git a/src/test/java/test/dependsongroup/TestFixture1.java b/src/test/java/test/dependsongroup/TestFixture1.java
new file mode 100644
index 0000000..74e2039
--- /dev/null
+++ b/src/test/java/test/dependsongroup/TestFixture1.java
@@ -0,0 +1,11 @@
+package test.dependsongroup;
+
+import org.testng.annotations.BeforeTest;
+
+
+public class TestFixture1 {
+  @BeforeTest(groups={"test", "testgroup"})
+  public void setup() {
+  }
+
+}
diff --git a/src/test/java/test/dependsongroup/TestFixture2.java b/src/test/java/test/dependsongroup/TestFixture2.java
new file mode 100644
index 0000000..3479d5a
--- /dev/null
+++ b/src/test/java/test/dependsongroup/TestFixture2.java
@@ -0,0 +1,15 @@
+package test.dependsongroup;
+
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+
+public class TestFixture2 {
+  @BeforeTest(groups={"test"}, dependsOnGroups={"testgroup"})
+  public void setup() {
+  }
+
+  @Test(groups={"test"})  //@@
+  public void testMethod() {
+  }
+}
diff --git a/src/test/java/test/dependsongroup/ZeroSampleTest.java b/src/test/java/test/dependsongroup/ZeroSampleTest.java
new file mode 100644
index 0000000..21ba07f
--- /dev/null
+++ b/src/test/java/test/dependsongroup/ZeroSampleTest.java
@@ -0,0 +1,19 @@
+package test.dependsongroup;
+
+import org.testng.annotations.Test;
+
+@Test(groups = { "zero" })
+public class ZeroSampleTest {
+
+  @Test
+  public void zeroA() {
+//    System.out.println("zeroA");
+  }
+
+  @Test
+  public void zeroB() {
+//    System.out.println("zeroB");
+  }
+
+}
+
diff --git a/src/test/java/test/dependsongroup/suite.xml b/src/test/java/test/dependsongroup/suite.xml
new file mode 100644
index 0000000..c5c182a
--- /dev/null
+++ b/src/test/java/test/dependsongroup/suite.xml
@@ -0,0 +1,15 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+
+<suite name="webpublic" verbose="10">
+    <test name="test">
+        <groups>
+            <run>
+                <include name="test" />
+            </run>
+        </groups>
+        
+        <packages>
+            <package name="test.dependsongroup" />
+        </packages>
+    </test>
+</suite>
\ No newline at end of file
diff --git a/src/test/java/test/distributed/DistributedTest.java b/src/test/java/test/distributed/DistributedTest.java
new file mode 100644
index 0000000..7713671
--- /dev/null
+++ b/src/test/java/test/distributed/DistributedTest.java
@@ -0,0 +1,174 @@
+package test.distributed;
+
+import org.testng.Assert;
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.remote.SuiteDispatcher;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.BaseDistributedTest;
+import testhelper.OutputDirectoryPatch;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Random;
+
+public class DistributedTest extends BaseDistributedTest {
+
+  private List<Thread> m_hostThreads = new ArrayList<>();
+
+  protected Thread startSlave(final String filename) {
+    Thread result = new Thread(new Runnable() {
+      @Override
+      public void run() {
+        TestNG.main(new String[] { "-slave", filename, "-d", OutputDirectoryPatch.getOutputDirectory() });
+      }
+    });
+    result.setName("Slave-" + filename);
+    result.start();
+    m_hostThreads.add(result);
+    return result;
+  }
+
+  private File createMasterProperties(String strategy)
+  throws IOException
+  {
+    String fileName = "remote";
+
+    File result = File.createTempFile(fileName, ".properties");
+    result.deleteOnExit();
+    Properties p = new Properties();
+    p.setProperty("testng.hosts", "localhost:" + m_ports[0] + " localhost:" + m_ports[1]);
+    p.setProperty("testng.master.strategy", strategy);
+    p.setProperty("testng.verbose", "0");
+    p.setProperty("testng.master.adpter", "org.testng.remote.adapter.DefaultMastertAdapter");
+    FileOutputStream fos = new FileOutputStream(result);
+    p.store(fos, "Automatically generated by tests");
+    fos.close();
+
+    return result;
+  }
+
+  private File createSlaveProperties(String port)
+  throws IOException
+  {
+    String fileName = "remote";
+
+    File result = File.createTempFile(fileName, ".properties");
+    result.deleteOnExit();
+    Properties p = new Properties();
+    p.setProperty("testng.verbose", "0");
+    p.setProperty("slave.port", port);
+    p.setProperty("testng.slave.adpter", "org.testng.remote.adapter.DefaultWorkerAdapter");
+    FileOutputStream fos = new FileOutputStream(result);
+    p.store(fos, "Automatically generated by tests");
+    fos.close();
+
+    return result;
+  }
+
+  private XmlSuite createSuite(String name, Class[] classes) {
+    XmlSuite result = new XmlSuite();
+    result.setName(name);
+
+    for (Class c : classes) {
+      XmlTest test1 = new XmlTest(result);
+      test1.setName(c.getName());
+      XmlClass class1 = new XmlClass(c);
+      test1.getXmlClasses().add(class1);
+    }
+
+    return result;
+  }
+
+//  @ Configuration(beforeTestClass = true)
+  private void startSlaves() throws IOException{
+	  int port = new Random().nextInt(50000) + 2000;
+	  m_ports = new String[] { Integer.toString(port), Integer.toString(port+1)};
+
+	  File slaveFile = createSlaveProperties(m_ports[0]);
+	  startSlave( slaveFile.getCanonicalPath());
+
+	  slaveFile = createSlaveProperties(m_ports[1]);
+	  startSlave( slaveFile.getCanonicalPath());
+  }
+
+  private String[] m_ports = new String[2];
+
+  public TestListenerAdapter twoHosts(String strategy) throws IOException {
+    TestNG tng = new TestNG();
+    tng.setOutputDirectory(OutputDirectoryPatch.getOutputDirectory());
+
+    File masterFile = createMasterProperties(strategy);
+    tng.setMaster(masterFile.getAbsolutePath());
+
+    XmlSuite suite = createSuite("DistributedSuite1", new Class[] { Test1.class, Test2.class });
+    tng.setXmlSuites(Arrays.asList(new XmlSuite[] { suite }));
+
+    TestListenerAdapter result = new TestListenerAdapter();
+    tng.addListener(result);
+    tng.run();
+
+    String[] passed = {
+        "f1", "f2"
+    };
+    String[] failed = {};
+    String[] skipped = {};
+
+    verifyTests("Passed", passed, toMap(result.getPassedTests()));
+    verifyTests("Failed", failed, toMap(result.getFailedTests()));
+    verifyTests("Skipped", skipped, toMap(result.getSkippedTests()));
+
+    return result;
+  }
+
+  @Test
+  public void twoHostsWithTestStrategy() throws IOException {
+    startSlaves();
+    TestListenerAdapter listener = twoHosts(SuiteDispatcher.STRATEGY_TEST);
+
+    boolean found1 = false;
+    boolean found2 = false;
+    for (ITestResult tr : listener.getPassedTests()) {
+      String host = tr.getHost();
+      if (! found1) {
+        found1 = host.contains(m_ports[0]);
+      }
+      if (! found2) {
+        found2 = host.contains(m_ports[1]);
+      }
+    }
+    Assert.assertTrue(found1, "No tests ran on port " + m_ports[0]);
+    Assert.assertTrue(found2, "No tests ran on port " + m_ports[1]);
+  }
+
+  @Test
+  public void twoHostsWithSuiteStrategy() throws IOException {
+    startSlaves();
+    twoHosts(SuiteDispatcher.STRATEGY_SUITE);
+  }
+
+  private Map<String, ITestResult> toMap(List<ITestResult> results) {
+    Map<String, ITestResult> result = new HashMap<>();
+    for (ITestResult tr : results) {
+      result.put(tr.getName(), tr);
+    }
+
+    return result;
+  }
+  private void ppp(String string) {
+    System.out.println("[DistributedTest] " + string);
+  }
+}
+
diff --git a/src/test/java/test/distributed/Test1.java b/src/test/java/test/distributed/Test1.java
new file mode 100644
index 0000000..ef0aa4b
--- /dev/null
+++ b/src/test/java/test/distributed/Test1.java
@@ -0,0 +1,15 @@
+package test.distributed;
+
+import org.testng.annotations.Test;
+
+public class Test1 {
+  @Test
+  public void f1() {
+//    ppp("f1");
+  }
+
+  private void ppp(String s) {
+    System.out.println("[Test1] " + s);
+  }
+}
+
diff --git a/src/test/java/test/distributed/Test2.java b/src/test/java/test/distributed/Test2.java
new file mode 100644
index 0000000..1b7b6bd
--- /dev/null
+++ b/src/test/java/test/distributed/Test2.java
@@ -0,0 +1,14 @@
+package test.distributed;
+
+import org.testng.annotations.Test;
+
+public class Test2 {
+  @Test
+  public void f2() {
+//    ppp("f2");
+  }
+
+  private void ppp(String s) {
+    System.out.println("[Test2] " + s);
+  }
+}
diff --git a/src/test/java/test/enable/A.java b/src/test/java/test/enable/A.java
new file mode 100644
index 0000000..59a79aa
--- /dev/null
+++ b/src/test/java/test/enable/A.java
@@ -0,0 +1,73 @@
+package test.enable;
+
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+public class A {
+
+  public void testA() {}
+
+  @Test
+  public void testA2() {}
+
+  @Test(enabled = true)
+  public void testA3() {}
+
+  @Test(enabled = false)
+  public void disabledA() {}
+
+  @BeforeSuite
+  public void beforeSuiteA() {}
+
+  @BeforeSuite(enabled = true)
+  public void beforeSuiteA2() {}
+
+  @BeforeSuite(enabled = false)
+  public void disabledBeforeSuiteA() {}
+
+  @BeforeSuite(alwaysRun = false)
+  public void beforeSuiteNoRunA() {}
+
+  @BeforeSuite(enabled = true, alwaysRun = false)
+  public void beforeSuiteNoRunA2() {}
+
+  @BeforeSuite(enabled = false, alwaysRun = false)
+  public void disabledBeforeSuiteNoRunA() {}
+
+  @BeforeSuite(alwaysRun = true)
+  public void beforeSuiteRunA() {}
+
+  @BeforeSuite(enabled = true, alwaysRun = true)
+  public void beforeSuiteRunA2() {}
+
+  @BeforeSuite(enabled = false, alwaysRun = true)
+  public void disabledBeforeSuiteRunA() {}
+
+  @AfterSuite
+  public void afterSuiteA() {}
+
+  @AfterSuite(enabled = true)
+  public void afterSuiteA2() {}
+
+  @AfterSuite(enabled = false)
+  public void disabledAfterSuiteA() {}
+
+  @AfterSuite(alwaysRun = false)
+  public void afterSuiteNoRunA() {}
+
+  @AfterSuite(enabled = true, alwaysRun = false)
+  public void afterSuiteNoRunA2() {}
+
+  @AfterSuite(enabled = false, alwaysRun = false)
+  public void disabledAfterSuiteNoRunA() {}
+
+  @AfterSuite(alwaysRun = true)
+  public void afterSuiteRunA() {}
+
+  @AfterSuite(enabled = true, alwaysRun = true)
+  public void afterSuiteRunA2() {}
+
+  @AfterSuite(enabled = false, alwaysRun = true)
+  public void disabledAfterSuiteRunA() {}
+}
diff --git a/src/test/java/test/enable/B.java b/src/test/java/test/enable/B.java
new file mode 100644
index 0000000..8c9c4f3
--- /dev/null
+++ b/src/test/java/test/enable/B.java
@@ -0,0 +1,74 @@
+package test.enable;
+
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+@Test(enabled = false)
+public class B {
+
+  public void testB() {}
+
+  @Test
+  public void testB2() {}
+
+  @Test(enabled = true)
+  public void testB3() {}
+
+  @Test(enabled = false)
+  public void disabledB() {}
+
+  @BeforeSuite(enabled = true)
+  public void disabledBeforeSuiteB() {}
+
+  @BeforeSuite
+  public void disabledBeforeSuiteB2() {}
+
+  @BeforeSuite(enabled = false)
+  public void disabledBeforeSuiteB3() {}
+
+  @BeforeSuite(alwaysRun = false)
+  public void beforeSuiteNoRunB() {}
+
+  @BeforeSuite(enabled = true, alwaysRun = false)
+  public void beforeSuiteNoRunB2() {}
+
+  @BeforeSuite(enabled = false, alwaysRun = false)
+  public void disabledBeforeSuiteNoRunB() {}
+
+  @BeforeSuite(alwaysRun = true)
+  public void beforeSuiteRunB() {}
+
+  @BeforeSuite(enabled = true, alwaysRun = true)
+  public void beforeSuiteRunB2() {}
+
+  @BeforeSuite(enabled = false, alwaysRun = true)
+  public void disabledBeforeSuiteRunB() {}
+
+  @AfterSuite
+  public void afterSuiteB() {}
+
+  @AfterSuite(enabled = true)
+  public void afterSuiteB2() {}
+
+  @AfterSuite(enabled = false)
+  public void disabledAfterSuiteB() {}
+
+  @AfterSuite(alwaysRun = false)
+  public void afterSuiteNoRunB() {}
+
+  @AfterSuite(enabled = true, alwaysRun = false)
+  public void afterSuiteNoRunB2() {}
+
+  @AfterSuite(enabled = false, alwaysRun = false)
+  public void disabledAfterSuiteNoRunB() {}
+
+  @AfterSuite(alwaysRun = true)
+  public void afterSuiteRunB() {}
+
+  @AfterSuite(enabled = true, alwaysRun = true)
+  public void afterSuiteRunB2() {}
+
+  @AfterSuite(enabled = false, alwaysRun = true)
+  public void disabledAfterSuiteRunB() {}
+}
diff --git a/src/test/java/test/enable/C.java b/src/test/java/test/enable/C.java
new file mode 100644
index 0000000..edf7a8b
--- /dev/null
+++ b/src/test/java/test/enable/C.java
@@ -0,0 +1,74 @@
+package test.enable;
+
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+@Test
+public class C {
+
+  public void testC() {}
+
+  @Test
+  public void testC2() {}
+
+  @Test(enabled = true)
+  public void testC3() {}
+
+  @Test(enabled = false)
+  public void disabledC() {}
+
+  @BeforeSuite
+  public void beforeSuiteC() {}
+
+  @BeforeSuite(enabled = true)
+  public void beforeSuiteC2() {}
+
+  @BeforeSuite(enabled = false)
+  public void disabledBeforeSuiteC() {}
+
+  @BeforeSuite(alwaysRun = false)
+  public void beforeSuiteNoRunC() {}
+
+  @BeforeSuite(enabled = true, alwaysRun = false)
+  public void beforeSuiteNoRunC2() {}
+
+  @BeforeSuite(enabled = false, alwaysRun = false)
+  public void disabledBeforeSuiteNoRunC() {}
+
+  @BeforeSuite(alwaysRun = true)
+  public void beforeSuiteRunC() {}
+
+  @BeforeSuite(enabled = true, alwaysRun = true)
+  public void beforeSuiteRunC2() {}
+
+  @BeforeSuite(enabled = false, alwaysRun = true)
+  public void disabledBeforeSuiteRunC() {}
+
+  @AfterSuite
+  public void afterSuiteC() {}
+
+  @AfterSuite(enabled = true)
+  public void afterSuiteC2() {}
+
+  @AfterSuite(enabled = false)
+  public void disabledAfterSuiteC() {}
+
+  @AfterSuite(alwaysRun = false)
+  public void afterSuiteNoRunC() {}
+
+  @AfterSuite(enabled = true, alwaysRun = false)
+  public void afterSuiteNoRunC2() {}
+
+  @AfterSuite(enabled = false, alwaysRun = false)
+  public void disabledAfterSuiteNoRunC() {}
+
+  @AfterSuite(alwaysRun = true)
+  public void afterSuiteRunC() {}
+
+  @AfterSuite(enabled = true, alwaysRun = true)
+  public void afterSuiteRunC2() {}
+
+  @AfterSuite(enabled = false, alwaysRun = true)
+  public void disabledAfterSuiteRunC() {}
+}
diff --git a/src/test/java/test/enable/EnableTest.java b/src/test/java/test/enable/EnableTest.java
new file mode 100644
index 0000000..299b0f5
--- /dev/null
+++ b/src/test/java/test/enable/EnableTest.java
@@ -0,0 +1,44 @@
+package test.enable;
+
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class EnableTest extends SimpleBaseTest {
+
+  @Test
+  public void disabled_methods_should_not_be_run() {
+    TestNG tng = create(A.class, B.class, C.class);
+    InvokedMethodListener listener = new InvokedMethodListener();
+    tng.addListener(listener);
+    tng.setPreserveOrder(true);
+    tng.run();
+
+    assertThat(listener.getInvokedMethods()).containsExactly(
+        "beforeSuiteA", "beforeSuiteA2", "beforeSuiteNoRunA", "beforeSuiteNoRunA2", "beforeSuiteRunA", "beforeSuiteRunA2",
+        "beforeSuiteRunB", "beforeSuiteRunB2",
+        "beforeSuiteC", "beforeSuiteC2", "beforeSuiteNoRunC", "beforeSuiteNoRunC2", "beforeSuiteRunC", "beforeSuiteRunC2",
+        "testA2", "testA3", "testB2", "testB3", "testC", "testC2", "testC3",
+        "afterSuiteA", "afterSuiteA2", "afterSuiteNoRunA", "afterSuiteNoRunA2", "afterSuiteRunA", "afterSuiteRunA2",
+        "afterSuiteRunB", "afterSuiteRunB2",
+        "afterSuiteC", "afterSuiteC2", "afterSuiteNoRunC", "afterSuiteNoRunC2", "afterSuiteRunC", "afterSuiteRunC2"
+    );
+  }
+
+  @Test(description = "https://github.com/cbeust/testng/issues/420")
+  public void issue420() {
+    TestNG tng = create(Issue420FirstSample.class, Issue420SecondSample.class);
+    InvokedMethodListener listener = new InvokedMethodListener();
+    tng.addListener(listener);
+    tng.run();
+
+    assertThat(listener.getInvokedMethods()).containsExactly(
+        "alwaysBeforeSuite", "beforeSuite",
+        "verifySomethingFirstSample", "verifySomethingSecondSample",
+        "afterSuite", "alwaysAfterSuite"
+    );
+  }
+}
diff --git a/src/test/java/test/enable/InvokedMethodListener.java b/src/test/java/test/enable/InvokedMethodListener.java
new file mode 100644
index 0000000..35b68a2
--- /dev/null
+++ b/src/test/java/test/enable/InvokedMethodListener.java
@@ -0,0 +1,27 @@
+package test.enable;
+
+import org.testng.IInvokedMethod;
+import org.testng.IInvokedMethodListener;
+import org.testng.ITestResult;
+import org.testng.collections.Lists;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class InvokedMethodListener implements IInvokedMethodListener {
+
+  private final List<String> invokedMethods = new ArrayList<>();
+
+  @Override
+  public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
+    invokedMethods.add(method.getTestMethod().getMethodName());
+  }
+
+  @Override
+  public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
+  }
+
+  public List<String> getInvokedMethods() {
+    return invokedMethods;
+  }
+}
diff --git a/src/test/java/test/enable/Issue420BaseTestCase.java b/src/test/java/test/enable/Issue420BaseTestCase.java
new file mode 100644
index 0000000..d605dfb
--- /dev/null
+++ b/src/test/java/test/enable/Issue420BaseTestCase.java
@@ -0,0 +1,19 @@
+package test.enable;
+
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.BeforeSuite;
+
+public abstract class Issue420BaseTestCase {
+
+  @BeforeSuite(alwaysRun = true)
+  public static void alwaysBeforeSuite() {}
+
+  @BeforeSuite(alwaysRun = false)
+  public static void beforeSuite() {}
+
+  @AfterSuite(alwaysRun = false)
+  public static void afterSuite() {}
+
+  @AfterSuite(alwaysRun = true)
+  public static void alwaysAfterSuite() {}
+}
diff --git a/src/test/java/test/enable/Issue420FirstSample.java b/src/test/java/test/enable/Issue420FirstSample.java
new file mode 100644
index 0000000..e18efdb
--- /dev/null
+++ b/src/test/java/test/enable/Issue420FirstSample.java
@@ -0,0 +1,10 @@
+package test.enable;
+
+import org.testng.annotations.Test;
+
+@Test(enabled = false)
+public class Issue420FirstSample extends Issue420BaseTestCase {
+
+  @Test
+  public void verifySomethingFirstSample() {}
+}
diff --git a/src/test/java/test/enable/Issue420SecondSample.java b/src/test/java/test/enable/Issue420SecondSample.java
new file mode 100644
index 0000000..1cc6cde
--- /dev/null
+++ b/src/test/java/test/enable/Issue420SecondSample.java
@@ -0,0 +1,10 @@
+package test.enable;
+
+import org.testng.annotations.Test;
+
+@Test
+public class Issue420SecondSample extends Issue420BaseTestCase {
+
+  @Test
+  public void verifySomethingSecondSample() {}
+}
diff --git a/src/test/java/test/expectedexceptions/ExpectedExceptionsTest.java b/src/test/java/test/expectedexceptions/ExpectedExceptionsTest.java
new file mode 100644
index 0000000..b106364
--- /dev/null
+++ b/src/test/java/test/expectedexceptions/ExpectedExceptionsTest.java
@@ -0,0 +1,27 @@
+package test.expectedexceptions;
+
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+
+public class ExpectedExceptionsTest extends BaseTest {
+
+  @Test
+  public void expectedExceptionsDeprecatedSyntax() {
+    runTest("test.expectedexceptions.SampleExceptions",
+        new String[] { "shouldPass" },
+        new String[] { "shouldFail1", "shouldFail2", "shouldFail3" },
+        new String[] {});
+  }
+
+  @Test
+  public void expectedExceptions() {
+    runTest("test.expectedexceptions.SampleExceptions2",
+        new String[] { "shouldPass", "shouldPass2", "shouldPass3", "shouldPass4" },
+        new String[] { "shouldFail1", "shouldFail2", "shouldFail3", "shouldFail4" },
+        new String[] {});
+  }
+
+}
+
+
diff --git a/src/test/java/test/expectedexceptions/ParametersExceptionTest.java b/src/test/java/test/expectedexceptions/ParametersExceptionTest.java
new file mode 100644
index 0000000..2734aa8
--- /dev/null
+++ b/src/test/java/test/expectedexceptions/ParametersExceptionTest.java
@@ -0,0 +1,31 @@
+package test.expectedexceptions;

+

+import org.testng.Assert;

+import org.testng.annotations.AfterMethod;

+import org.testng.annotations.DataProvider;

+import org.testng.annotations.Test;

+import org.xml.sax.SAXException;

+

+import java.io.IOException;

+import java.lang.reflect.Method;

+

+

+/**

+ * This class/interface

+ */

+public class ParametersExceptionTest {

+    @Test(dataProvider="A")

+    public void testA(Exception err) {

+      System.out.println("testA");

+    }

+

+    @DataProvider(name="A")

+    protected Object[][] dp() {

+      return new Object[][] { {new IOException(), new SAXException() } };

+    }

+

+    @AfterMethod

+    protected void verify(Method method) {

+      Assert.assertTrue(false, "forced failure");

+    }

+}

diff --git a/src/test/java/test/expectedexceptions/SampleExceptions.java b/src/test/java/test/expectedexceptions/SampleExceptions.java
new file mode 100644
index 0000000..0c72ab0
--- /dev/null
+++ b/src/test/java/test/expectedexceptions/SampleExceptions.java
@@ -0,0 +1,45 @@
+package test.expectedexceptions;
+
+import org.testng.annotations.ExpectedExceptions;
+import org.testng.annotations.Test;
+
+/**
+ * This class tests @ExpectedExceptions
+ *
+ * @author cbeust
+ */
+public class SampleExceptions {
+
+  @Test
+  @ExpectedExceptions({ NumberFormatException.class} )
+  public void shouldPass() {
+    throw new NumberFormatException();
+  }
+
+  @Test
+  @ExpectedExceptions({ NumberFormatException.class} )
+  public void shouldFail1() {
+    throw new RuntimeException();
+  }
+
+  @Test
+  @ExpectedExceptions({ NumberFormatException.class} )
+  public void shouldFail2() {
+  }
+
+  @Test
+  @ExpectedExceptions(RuntimeException.class)
+  public void shouldFail3() {
+  }
+//  @Test(expectedExceptions = NumberFormatException.class)
+//  public void throwWrongException() {
+//      throw new NullPointerException();
+//  }
+
+//  @Test
+//  @ExpectedExceptions({ FileNotFoundException.class, IOException.class })
+//  public void shouldPass2() throws Exception {
+//    throw new FileNotFoundException();
+//  }
+
+}
diff --git a/src/test/java/test/expectedexceptions/SampleExceptions2.java b/src/test/java/test/expectedexceptions/SampleExceptions2.java
new file mode 100644
index 0000000..da47a87
--- /dev/null
+++ b/src/test/java/test/expectedexceptions/SampleExceptions2.java
@@ -0,0 +1,51 @@
+package test.expectedexceptions;
+
+import org.testng.annotations.Test;
+
+/**
+ * This class tests @ExpectedExceptions
+ *
+ * @author cbeust
+ */
+public class SampleExceptions2 {
+
+  @Test(expectedExceptions = NumberFormatException.class )
+  public void shouldPass() {
+    throw new NumberFormatException();
+  }
+
+  @Test(expectedExceptions = NumberFormatException.class)
+  public void shouldFail1() {
+    throw new RuntimeException();
+  }
+
+  @Test(expectedExceptions = NumberFormatException.class)
+  public void shouldFail2() {
+  }
+
+  @Test(expectedExceptions = NumberFormatException.class,
+      expectedExceptionsMessageRegExp = ".*bomb.*")
+  public void shouldPass2() {
+    throw new NumberFormatException("This should not bomb at all");
+  }
+
+  @Test(expectedExceptions = NumberFormatException.class,
+      expectedExceptionsMessageRegExp = ".*bombc.*")
+  public void shouldFail3() {
+    throw new NumberFormatException("This should bomb for good");
+  }
+
+  @Test(expectedExceptions = NumberFormatException.class, expectedExceptionsMessageRegExp = ".*")
+  public void shouldPass3() {
+    throw new NumberFormatException(null);
+  }
+
+  @Test(expectedExceptions = NumberFormatException.class, expectedExceptionsMessageRegExp = "Multiline.*")
+  public void shouldPass4() {
+    throw new NumberFormatException("Multiline\nException");
+  }
+
+  @Test(expectedExceptions = RuntimeException.class)
+  public void shouldFail4() {
+  }
+}
diff --git a/src/test/java/test/expectedexceptions/WrappedExpectedExceptionTest.java b/src/test/java/test/expectedexceptions/WrappedExpectedExceptionTest.java
new file mode 100644
index 0000000..d99934f
--- /dev/null
+++ b/src/test/java/test/expectedexceptions/WrappedExpectedExceptionTest.java
@@ -0,0 +1,13 @@
+package test.expectedexceptions;
+
+
+import org.testng.annotations.ExpectedExceptions;
+import org.testng.annotations.Test;
+
+public class WrappedExpectedExceptionTest {
+  @Test(timeOut = 1000L)
+  @ExpectedExceptions({ IllegalStateException.class })
+  public void testTimeout() {
+    throw new IllegalStateException("expected failure");
+  }
+}
diff --git a/src/test/java/test/factory/BadFactoryMethodReturnTypeSample.java b/src/test/java/test/factory/BadFactoryMethodReturnTypeSample.java
new file mode 100644
index 0000000..9015792
--- /dev/null
+++ b/src/test/java/test/factory/BadFactoryMethodReturnTypeSample.java
@@ -0,0 +1,14 @@
+package test.factory;
+
+import org.testng.annotations.Factory;
+
+public class BadFactoryMethodReturnTypeSample {
+
+    @Factory
+    private Object createInstances() {
+        return new Object[] {
+                new BaseFactory(42),
+                new BaseFactory(43)
+        };
+    }
+}
diff --git a/src/test/java/test/factory/BaseFactory.java b/src/test/java/test/factory/BaseFactory.java
new file mode 100644
index 0000000..6ad2e0c
--- /dev/null
+++ b/src/test/java/test/factory/BaseFactory.java
@@ -0,0 +1,29 @@
+package test.factory;
+
+import org.testng.annotations.Test;
+
+public class BaseFactory {
+
+  private int m_n;
+
+  public BaseFactory(int n) {
+    m_n = n;
+  }
+
+  public int getN() {
+    return m_n;
+  }
+
+  @Test
+  public void f() {
+  }
+
+  /**
+   * @@@ for some reason, the test results get added in the wrong order if
+   * I don't define a toString() method. Need to investigate.
+   */
+  @Override
+  public String toString() {
+    return "[" + getClass().getName() + " " + getN() + "]";
+  }
+}
diff --git a/src/test/java/test/factory/DPClass.java b/src/test/java/test/factory/DPClass.java
new file mode 100644
index 0000000..fa6e034
--- /dev/null
+++ b/src/test/java/test/factory/DPClass.java
@@ -0,0 +1,14 @@
+package test.factory;
+
+import org.testng.annotations.DataProvider;
+
+public class DPClass {
+  @DataProvider
+  static public Object[][] dp() {
+    return new Object[][] {
+      new Object[] { 43 },
+      new Object[] { 44 },
+    };
+  }
+
+}
diff --git a/src/test/java/test/factory/DisabledFactorySampleTest.java b/src/test/java/test/factory/DisabledFactorySampleTest.java
new file mode 100644
index 0000000..39b8fc4
--- /dev/null
+++ b/src/test/java/test/factory/DisabledFactorySampleTest.java
@@ -0,0 +1,11 @@
+package test.factory;
+
+import org.testng.annotations.Factory;
+
+public class DisabledFactorySampleTest {
+
+  @Factory(enabled = false)
+  public Object[] factory() {
+    return new Object[] { new MyTest() };
+  }
+}
diff --git a/src/test/java/test/factory/DisabledFactoryTest.java b/src/test/java/test/factory/DisabledFactoryTest.java
new file mode 100644
index 0000000..82656fc
--- /dev/null
+++ b/src/test/java/test/factory/DisabledFactoryTest.java
@@ -0,0 +1,22 @@
+package test.factory;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+public class DisabledFactoryTest extends SimpleBaseTest {
+
+  @Test
+  public void disabledFactoryShouldNotRun() {
+    TestNG tng = create(DisabledFactorySampleTest.class);
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.run();
+
+    Assert.assertEquals(tla.getPassedTests().size(), 0);
+    Assert.assertEquals(tla.getSkippedTests().size(), 0);
+    Assert.assertEquals(tla.getFailedTests().size(), 0);
+  }
+}
diff --git a/src/test/java/test/factory/Factory2Test.java b/src/test/java/test/factory/Factory2Test.java
new file mode 100644
index 0000000..c7ba69f
--- /dev/null
+++ b/src/test/java/test/factory/Factory2Test.java
@@ -0,0 +1,23 @@
+package test.factory;
+
+import org.testng.annotations.Factory;
+
+/**
+ * Factory to test that setUp methods are correctly interleaved even
+ * when we use similar instances of a same test class.
+ *
+ * @author cbeust
+ */
+public class Factory2Test {
+
+  @Factory()
+  public Object[] createObjects()
+  {
+    return new Object[] { new Sample2(), new Sample2() };
+  }
+
+
+  private static void ppp(String s) {
+    System.out.println("[FactoryTest] " + s);
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/factory/FactoryAndTestMethodTest.java b/src/test/java/test/factory/FactoryAndTestMethodTest.java
new file mode 100644
index 0000000..a39d1da
--- /dev/null
+++ b/src/test/java/test/factory/FactoryAndTestMethodTest.java
@@ -0,0 +1,37 @@
+package test.factory;
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+
+@Test(groups = { "x" })
+public class FactoryAndTestMethodTest {
+
+  @Factory(dataProvider = "data")
+  public Object[] ohNo(String s) {
+    return makeNullArgTests(s);
+  }
+
+  public static class NullArgsTest {
+    public final String s;
+
+    public NullArgsTest(String s) {
+      this.s = s;
+    }
+
+    @Test
+    public void test() {
+      Assert.assertNotNull(s);
+    }
+  }
+
+  private Object[] makeNullArgTests(String s) {
+    return new Object[0];
+  }
+
+  @DataProvider(name = "data")
+  public Object[][] makeData() {
+    return new Object[][] { { "foo" } };
+  };
+}
diff --git a/src/test/java/test/factory/FactoryBase.java b/src/test/java/test/factory/FactoryBase.java
new file mode 100644
index 0000000..60e793c
--- /dev/null
+++ b/src/test/java/test/factory/FactoryBase.java
@@ -0,0 +1,13 @@
+package test.factory;
+
+import org.testng.annotations.Factory;
+
+public class FactoryBase {
+
+  @Factory
+  public Object[] create() {
+    return new Object[] {
+      new FactoryBaseSampleTest()
+    };
+  }
+}
diff --git a/src/test/java/test/factory/FactoryBaseSampleTest.java b/src/test/java/test/factory/FactoryBaseSampleTest.java
new file mode 100644
index 0000000..b1de892
--- /dev/null
+++ b/src/test/java/test/factory/FactoryBaseSampleTest.java
@@ -0,0 +1,9 @@
+package test.factory;
+
+import org.testng.annotations.Test;
+
+public class FactoryBaseSampleTest {
+
+  @Test
+  public void f() {}
+}
diff --git a/src/test/java/test/factory/FactoryChild.java b/src/test/java/test/factory/FactoryChild.java
new file mode 100644
index 0000000..83ffd2a
--- /dev/null
+++ b/src/test/java/test/factory/FactoryChild.java
@@ -0,0 +1,5 @@
+package test.factory;
+
+public class FactoryChild extends FactoryBase {
+
+}
diff --git a/src/test/java/test/factory/FactoryDataProviderSampleTest.java b/src/test/java/test/factory/FactoryDataProviderSampleTest.java
new file mode 100644
index 0000000..3ce8db6
--- /dev/null
+++ b/src/test/java/test/factory/FactoryDataProviderSampleTest.java
@@ -0,0 +1,31 @@
+package test.factory;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+
+public class FactoryDataProviderSampleTest extends BaseFactory {
+
+  @Factory(dataProvider = "dp")
+  public FactoryDataProviderSampleTest(int n) {
+    super(n);
+  }
+
+  @DataProvider
+  static public Object[][] dp() {
+    return new Object[][] {
+      new Object[] { 41 },
+      new Object[] { 42 },
+    };
+  }
+
+  @Override
+  public String toString() {
+    return "[FactoryDataProviderSampleTest " + getN() + "]";
+  }
+
+  @Test
+  public void f() {
+//    System.out.println("Test:" + getN());
+  }
+}
diff --git a/src/test/java/test/factory/FactoryDataProviderStaticSampleErrorTest.java b/src/test/java/test/factory/FactoryDataProviderStaticSampleErrorTest.java
new file mode 100644
index 0000000..75c2b41
--- /dev/null
+++ b/src/test/java/test/factory/FactoryDataProviderStaticSampleErrorTest.java
@@ -0,0 +1,20 @@
+package test.factory;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Factory;
+
+public class FactoryDataProviderStaticSampleErrorTest extends BaseFactory {
+  @Factory(dataProvider = "dp")
+  public FactoryDataProviderStaticSampleErrorTest(int n) {
+    super(n);
+  }
+
+  @DataProvider
+  public Object[][] dp() {
+    return new Object[][] {
+      new Object[] { 41 },
+      new Object[] { 42 },
+    };
+  }
+
+}
diff --git a/src/test/java/test/factory/FactoryDataProviderStaticSampleTest.java b/src/test/java/test/factory/FactoryDataProviderStaticSampleTest.java
new file mode 100644
index 0000000..46f499c
--- /dev/null
+++ b/src/test/java/test/factory/FactoryDataProviderStaticSampleTest.java
@@ -0,0 +1,11 @@
+package test.factory;
+
+import org.testng.annotations.Factory;
+
+public class FactoryDataProviderStaticSampleTest extends BaseFactory {
+
+  @Factory(dataProvider = "dp", dataProviderClass = DPClass.class)
+  public FactoryDataProviderStaticSampleTest(int n) {
+    super(n);
+  }
+}
diff --git a/src/test/java/test/factory/FactoryDataProviderTest.java b/src/test/java/test/factory/FactoryDataProviderTest.java
new file mode 100644
index 0000000..b8f91de
--- /dev/null
+++ b/src/test/java/test/factory/FactoryDataProviderTest.java
@@ -0,0 +1,53 @@
+package test.factory;
+
+import org.testng.Assert;
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.TestNGException;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+import java.util.Iterator;
+
+public class FactoryDataProviderTest extends SimpleBaseTest {
+
+  @Test(description = "Test @Factory(dataProvider) on a local static data provider")
+  public void factoryWithLocalDataProvider() {
+    runTest(FactoryDataProviderSampleTest.class, 41, 42);
+  }
+  
+  @Test(description = "Test @Factory(dataProvider) on a data provider in another class")
+  public void factoryWithStaticDataProvider() {
+    runTest(FactoryDataProviderStaticSampleTest.class, 43, 44);
+  }
+
+  @Test(description = "Test @Factory(dataProvider) on a non static data provider with no arg ctor")
+  public void factoryWithNonStaticDataProvider() {
+    runTest(FactoryDataProviderWithNoArgCtorSampleErrorTest.class, 45, 46);
+  }
+
+  @Test(expectedExceptions = TestNGException.class,
+      description = "Should fail because the data provider is not static")
+  public void factoryWithNonStaticDataProviderShouldFail() {
+    runTest(FactoryDataProviderStaticSampleErrorTest.class, 43, 44);
+  }
+
+  private void runTest(Class<?> cls, int n1, int n2) {
+    TestNG tng = create(cls);
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+    Assert.assertEquals(tla.getPassedTests().size(), 2);
+    Iterator<ITestResult> iterator = tla.getPassedTests().iterator();
+    BaseFactory t1 = (BaseFactory) iterator.next().getInstance();
+    BaseFactory t2 = (BaseFactory) iterator.next().getInstance();
+//    Assert.assertTrue(t1.getN() == n1 || t1.getN() == n2);
+//    Assert.assertTrue(t2.getN() == n1 || t2.getN() == n2);
+//    System.out.println("Results:" + t1.getN() + " " + t2.getN());
+    Assert.assertEquals(t1.getN(), n1);
+    Assert.assertEquals(t2.getN(), n2);
+  }
+}
diff --git a/src/test/java/test/factory/FactoryDataProviderWithNoArgCtorSampleErrorTest.java b/src/test/java/test/factory/FactoryDataProviderWithNoArgCtorSampleErrorTest.java
new file mode 100644
index 0000000..e8ac513
--- /dev/null
+++ b/src/test/java/test/factory/FactoryDataProviderWithNoArgCtorSampleErrorTest.java
@@ -0,0 +1,28 @@
+package test.factory;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Factory;
+
+public class FactoryDataProviderWithNoArgCtorSampleErrorTest extends BaseFactory {
+
+  public FactoryDataProviderWithNoArgCtorSampleErrorTest() {
+    super(0);
+  }
+
+  @Factory(dataProvider = "dp")
+  public FactoryDataProviderWithNoArgCtorSampleErrorTest(int n) {
+    super(n);
+  }
+
+  @DataProvider
+  public Object[][] dp() {
+    return new Object[][] {
+      new Object[] { 45 },
+      new Object[] { 46 },
+    };
+  }
+  @Override
+  public String toString() {
+    return "[FactoryDataProviderWithNoArgCtorSampleErrorTest " + getN() + "]";
+  }
+}
diff --git a/src/test/java/test/factory/FactoryFailureSampleTest.java b/src/test/java/test/factory/FactoryFailureSampleTest.java
new file mode 100644
index 0000000..4b0b949
--- /dev/null
+++ b/src/test/java/test/factory/FactoryFailureSampleTest.java
@@ -0,0 +1,16 @@
+package test.factory;
+
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+
+public class FactoryFailureSampleTest {
+
+  @Factory
+  public Object[] factory() {
+    throw new NullPointerException();
+  }
+
+  @Test
+  public void f() {
+  }
+}
diff --git a/src/test/java/test/factory/FactoryFailureTest.java b/src/test/java/test/factory/FactoryFailureTest.java
new file mode 100644
index 0000000..1307dd5
--- /dev/null
+++ b/src/test/java/test/factory/FactoryFailureTest.java
@@ -0,0 +1,23 @@
+package test.factory;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+public class FactoryFailureTest extends SimpleBaseTest {
+
+   @Test
+   public void factoryThrowingShouldNotRunTests() {
+     TestNG tng = create(FactoryFailureSampleTest.class);
+
+     try {
+       tng.run();
+       Assert.fail();
+     }
+     catch(Exception ex) {
+       // success
+     }
+   }
+}
diff --git a/src/test/java/test/factory/FactoryInSeparateClass.java b/src/test/java/test/factory/FactoryInSeparateClass.java
new file mode 100644
index 0000000..27cd278
--- /dev/null
+++ b/src/test/java/test/factory/FactoryInSeparateClass.java
@@ -0,0 +1,45 @@
+package test.factory;
+
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+
+/**
+ * this is like the FactoryTest, except it creates test instances in a separate
+ * class from the test class
+ */
+public class FactoryInSeparateClass {
+  static private boolean m_wasRun = false;
+  static private int m_checkSum = 0;
+
+  public static void addToSum(int i) {
+    m_checkSum += i;
+  }
+
+  @BeforeTest
+  public void beforeTest() {
+    m_wasRun = false;
+    m_checkSum = 0;
+  }
+
+  @Factory
+  public Object[] createObjects() {
+    return new Object[] {
+      new MyTest(1),
+      new MyTest(2),
+      new MyTest(3),
+    };
+  }
+
+   @Test(groups = "testMethodOnFactoryClass", dependsOnGroups={"MyTest"})
+    public void checkSum() {
+    m_wasRun = true;
+      assert (m_checkSum == 6) :
+        "Test instances made by factory did not invoke their test methods correctly.  expected 6 but got " + m_checkSum;
+    }
+
+    public static boolean wasRun() {
+      return m_wasRun;
+    }
+}
+
diff --git a/src/test/java/test/factory/FactoryInSuperClassTest.java b/src/test/java/test/factory/FactoryInSuperClassTest.java
new file mode 100644
index 0000000..d380dd9
--- /dev/null
+++ b/src/test/java/test/factory/FactoryInSuperClassTest.java
@@ -0,0 +1,21 @@
+package test.factory;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+public class FactoryInSuperClassTest extends SimpleBaseTest {
+
+  @Test
+  public void factoryInSuperClassShouldWork() {
+    TestNG tng = create(FactoryChild.class);
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+    Assert.assertEquals(tla.getPassedTests().size(), 1);
+  }
+}
diff --git a/src/test/java/test/factory/FactoryIntegrationTest.java b/src/test/java/test/factory/FactoryIntegrationTest.java
new file mode 100644
index 0000000..4ea492a
--- /dev/null
+++ b/src/test/java/test/factory/FactoryIntegrationTest.java
@@ -0,0 +1,47 @@
+package test.factory;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.TestNGException;
+import org.testng.annotations.Test;
+import test.SimpleBaseTest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
+
+public class FactoryIntegrationTest extends SimpleBaseTest {
+
+    @Test(description = "https://github.com/cbeust/testng/issues/876")
+    public void testExceptionWithNonStaticFactoryMethod() {
+        TestNG tng = create(GitHub876Sample.class);
+        try {
+            tng.run();
+            failBecauseExceptionWasNotThrown(TestNGException.class);
+        } catch (TestNGException e) {
+            assertThat(e).hasMessage("\nCan't invoke public java.lang.Object[] test.factory.GitHub876Sample.createInstances(): either make it static or add a no-args constructor to your class");
+        }
+    }
+
+    @Test
+    public void testNonPublicFactoryMethodShouldWork() {
+        TestNG tng = create(NonPublicFactoryMethodSample.class);
+        TestListenerAdapter tla = new TestListenerAdapter();
+        tng.addListener(tla);
+
+        tng.run();
+
+        Assert.assertEquals(tla.getPassedTests().size(), 2);
+    }
+
+    @Test
+    public void testExceptionWithBadFactoryMethodReturnType() {
+        TestNG tng = create(BadFactoryMethodReturnTypeSample.class);
+        try {
+            tng.run();
+            failBecauseExceptionWasNotThrown(TestNGException.class);
+        } catch (TestNGException e) {
+            assertThat(e).hasMessage("\ntest.factory.BadFactoryMethodReturnTypeSample.createInstances MUST return [ java.lang.Object[] or org.testng.IInstanceInfo[] ] but returns java.lang.Object");
+        }
+    }
+}
diff --git a/src/test/java/test/factory/FactoryInterleavingSampleA.java b/src/test/java/test/factory/FactoryInterleavingSampleA.java
new file mode 100644
index 0000000..f21ecda
--- /dev/null
+++ b/src/test/java/test/factory/FactoryInterleavingSampleA.java
@@ -0,0 +1,44 @@
+package test.factory;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class FactoryInterleavingSampleA {
+  public int m_n;
+
+  public FactoryInterleavingSampleA(int n) {
+    m_n = n;
+  }
+
+  private void log(Integer s) {
+    FactoryInterleavingTest.LOG.add(m_n * 10 + s);
+//    System.out.println(" Instance " + m_n + " " + s);
+  }
+
+  @Override
+  public String toString() {
+    return "[A n:" + m_n + "]";
+  }
+
+  @BeforeClass
+  public void bc() {
+    log(0);
+  }
+
+  @AfterClass
+  public void ac() {
+    log(3);
+  }
+
+  @Test
+  public void f1() {
+    log(1);
+  }
+
+  @Test
+  public void f2() {
+    log(2);
+  }
+
+}
diff --git a/src/test/java/test/factory/FactoryInterleavingSampleFactory.java b/src/test/java/test/factory/FactoryInterleavingSampleFactory.java
new file mode 100644
index 0000000..84a7580
--- /dev/null
+++ b/src/test/java/test/factory/FactoryInterleavingSampleFactory.java
@@ -0,0 +1,19 @@
+package test.factory;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Factory;
+
+public class FactoryInterleavingSampleFactory {
+  @Factory
+  public Object[] factory() {
+    return new Object[] {
+        new FactoryInterleavingSampleA(1), new FactoryInterleavingSampleA(2)
+    };
+  }
+
+  @BeforeClass
+  public void beforeB() {
+    System.out.println("Before B");
+  }
+}
+
diff --git a/src/test/java/test/factory/FactoryInterleavingTest.java b/src/test/java/test/factory/FactoryInterleavingTest.java
new file mode 100644
index 0000000..d3a1aaf
--- /dev/null
+++ b/src/test/java/test/factory/FactoryInterleavingTest.java
@@ -0,0 +1,37 @@
+package test.factory;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+
+import test.SimpleBaseTest;
+
+import java.util.List;
+
+public class FactoryInterleavingTest extends SimpleBaseTest {
+
+  public static List<Integer> LOG = Lists.newArrayList();
+
+  @Test
+  public void methodsShouldBeInterleaved() {
+    TestNG tng = create(FactoryInterleavingSampleFactory.class);
+    tng.run();
+    Integer[] valid1 = {
+        10, 11, 12, 13,
+        20, 21, 22, 23,
+    };
+
+    Integer[] valid2 = {
+        20, 21, 22, 23,
+        10, 11, 12, 13,
+    };
+    Integer[] logArray = LOG.toArray(new Integer[LOG.size()]);
+    if (! logArray.equals(valid1)) {
+      Assert.assertEquals(logArray, valid1);
+    } else if (! logArray.equals(valid2)) {
+      System.err.println(logArray + " " + valid2);
+      Assert.assertEquals(logArray, valid2);
+    }
+  }
+}
diff --git a/src/test/java/test/factory/FactoryOrderMainTest.java b/src/test/java/test/factory/FactoryOrderMainTest.java
new file mode 100644
index 0000000..21733a8
--- /dev/null
+++ b/src/test/java/test/factory/FactoryOrderMainTest.java
@@ -0,0 +1,27 @@
+package test.factory;
+
+import org.testng.Assert;
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+import java.util.List;
+
+public class FactoryOrderMainTest extends SimpleBaseTest {
+
+  @Test
+  public void factoriesShouldBeInvokedInTheOrderOfCreation() {
+    TestNG tng = create(FactoryOrderTest.class);
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+    List<ITestResult> passed = tla.getPassedTests();
+    for (int i = 0; i < passed.size(); i++) {
+      Assert.assertEquals(((FactoryOrderSampleTest) passed.get(i).getInstance()).getValue(), i);
+    }
+  }
+}
diff --git a/src/test/java/test/factory/FactoryOrderSampleTest.java b/src/test/java/test/factory/FactoryOrderSampleTest.java
new file mode 100644
index 0000000..8839832
--- /dev/null
+++ b/src/test/java/test/factory/FactoryOrderSampleTest.java
@@ -0,0 +1,44 @@
+package test.factory;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class FactoryOrderSampleTest {
+
+  int value;
+
+  public FactoryOrderSampleTest(int j) {
+    value = j;
+    log("classC constructor " + value);
+  }
+
+  private void log(String string) {
+//    System.out.println("[FactoryOrderSampleTest] " + string);
+  }
+
+  @BeforeClass(groups = { "s1ds" })
+  public void setup() {
+    log("classC.BeforeClass " + value);
+  }
+
+  @Test(groups = { "s1ds" })
+  public void methodC1() throws Exception {
+//    Thread.sleep(1000);
+    log("classC.methodC1 " + value);
+  }
+
+  @AfterClass(groups = { "s1ds" })
+  public void cleanup() {
+    log("classC.AfterClass " + value);
+  }
+
+  @Override
+  public String toString() {
+    return "[FactoryOrderSampleTest " + value + "]";
+  }
+
+  public int getValue() {
+    return value;
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/factory/FactoryOrderTest.java b/src/test/java/test/factory/FactoryOrderTest.java
new file mode 100644
index 0000000..fcbcdc7
--- /dev/null
+++ b/src/test/java/test/factory/FactoryOrderTest.java
@@ -0,0 +1,27 @@
+package test.factory;
+
+
+import org.testng.annotations.Factory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FactoryOrderTest {
+
+   public FactoryOrderTest() {
+//       System.out.println("inside testFactory constructor");
+   }
+
+   @Factory
+   public static Object[] testF()
+   throws Exception {
+       List result = new ArrayList();
+//       System.out.println("inside factory: ");
+       int i = 0;
+       while (i < 5) {
+           result.add(new FactoryOrderSampleTest(i));
+           i++;
+       }
+       return result.toArray();
+   }
+}
\ No newline at end of file
diff --git a/src/test/java/test/factory/FactoryTest.java b/src/test/java/test/factory/FactoryTest.java
new file mode 100644
index 0000000..f44f3e9
--- /dev/null
+++ b/src/test/java/test/factory/FactoryTest.java
@@ -0,0 +1,30 @@
+package test.factory;
+
+import static org.testng.Assert.assertFalse;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Parameters;
+
+public class FactoryTest {
+  static boolean m_invoked = false;
+
+  @Parameters({ "factory-param" })
+  @Factory
+  public Object[] createObjects(String param) {
+    Assert.assertEquals(param, "FactoryParam");
+    assertFalse(m_invoked, "Should only be invoked once");
+    m_invoked = true;
+
+    return new Object[] {
+        new FactoryTest2(42),
+        new FactoryTest2(43)
+    };
+  }
+
+  @AfterSuite
+  public void afterSuite() {
+    m_invoked = false;
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/factory/FactoryTest2.java b/src/test/java/test/factory/FactoryTest2.java
new file mode 100644
index 0000000..598ba03
--- /dev/null
+++ b/src/test/java/test/factory/FactoryTest2.java
@@ -0,0 +1,44 @@
+package test.factory;
+
+import org.testng.annotations.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class
+ *
+ * @author cbeust
+ */
+public class FactoryTest2 {
+  private static Map<Integer, Integer> m_numbers = new HashMap<>();
+  private int m_number;
+
+  public FactoryTest2() {
+    throw new RuntimeException("Shouldn't be invoked");
+  }
+
+  public static Map<Integer, Integer> getNumbers() {
+    return m_numbers;
+  }
+
+  public FactoryTest2(int n) {
+    m_number = n;
+  }
+
+  @Test(groups = { "first" })
+  public void testInt() {
+    Integer n = m_number;
+    m_numbers.put(n, n);
+  }
+
+  @Override
+  public String toString() {
+    return "[FactoryTest2 " + m_number + "]";
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[FactoryTest2] " + s);
+  }
+
+}
diff --git a/src/test/java/test/factory/FactoryWithDataProvider.java b/src/test/java/test/factory/FactoryWithDataProvider.java
new file mode 100644
index 0000000..982df60
--- /dev/null
+++ b/src/test/java/test/factory/FactoryWithDataProvider.java
@@ -0,0 +1,33 @@
+package test.factory;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Factory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FactoryWithDataProvider {
+
+  @DataProvider
+  public Object[][] dp() {
+    return new Object[][] {
+      new Object[] { new int[] { 3, 5 } },
+      new Object[] { new int [] { 7, 9 } },
+    };
+  }
+
+  @Factory(dataProvider = "dp")
+  public Object[] factory(int[] array) {
+    List<Object> result = new ArrayList<>();
+    for (int n : array) {
+      result.add(new OddTest(n));
+    }
+
+    return result.toArray();
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[FactoryWithDataProvider] " + s);
+  }
+
+}
diff --git a/src/test/java/test/factory/FactoryWithDataProviderTest.java b/src/test/java/test/factory/FactoryWithDataProviderTest.java
new file mode 100644
index 0000000..d346677
--- /dev/null
+++ b/src/test/java/test/factory/FactoryWithDataProviderTest.java
@@ -0,0 +1,29 @@
+package test.factory;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+public class FactoryWithDataProviderTest {
+
+  /**
+   * Verify that a factory can receive a data provider
+   */
+  @Test
+  public void verifyDataProvider() {
+    TestNG tng = new TestNG();
+    tng.setVerbose(0);
+    tng.setTestClasses(new Class[] { FactoryWithDataProvider.class });
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+    Assert.assertEquals(tla.getPassedTests().size(), 4);
+
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[FactoryWithDataProviderTest] " + s);
+  }
+}
diff --git a/src/test/java/test/factory/FactoryWithInstanceInfoTest.java b/src/test/java/test/factory/FactoryWithInstanceInfoTest.java
new file mode 100644
index 0000000..5f246a5
--- /dev/null
+++ b/src/test/java/test/factory/FactoryWithInstanceInfoTest.java
@@ -0,0 +1,39 @@
+package test.factory;
+
+import static org.testng.Assert.assertFalse;
+
+import org.testng.IInstanceInfo;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Parameters;
+import org.testng.internal.InstanceInfo;
+
+public class FactoryWithInstanceInfoTest {
+  static boolean m_invoked = false;
+
+  @Parameters({ "factory-param" })
+  @Factory
+  public IInstanceInfo[] createObjectsWithInstanceInfo(String param)
+  {
+    assert "FactoryParam".equals(param) : "Incorrect param: " + param;
+
+    assertFalse(m_invoked, "Should only be invoked once");
+    m_invoked = true;
+
+    return new IInstanceInfo[] {
+        new InstanceInfo(FactoryWithInstanceInfoTest2.class,
+            new FactoryWithInstanceInfoTest2(42)),
+        new InstanceInfo(FactoryWithInstanceInfoTest2.class,
+            new FactoryWithInstanceInfoTest2(43)),
+    };
+  }
+
+  @BeforeSuite
+  public void beforeSuite() {
+    m_invoked = false;
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[FactoryWithInstanceInfoTest] " + s);
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/factory/FactoryWithInstanceInfoTest2.java b/src/test/java/test/factory/FactoryWithInstanceInfoTest2.java
new file mode 100644
index 0000000..520af05
--- /dev/null
+++ b/src/test/java/test/factory/FactoryWithInstanceInfoTest2.java
@@ -0,0 +1,39 @@
+package test.factory;
+
+import org.testng.annotations.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class is created by FactoryWithInstanceInfoTest2
+ *
+ * @author cbeust
+ */
+public class FactoryWithInstanceInfoTest2 {
+  private static Map<Integer, Integer> m_numbers = new HashMap<>();
+  private int m_number;
+
+  public FactoryWithInstanceInfoTest2() {
+    throw new RuntimeException("Shouldn't be invoked");
+  }
+
+  public static Map<Integer, Integer> getNumbers() {
+    return m_numbers;
+  }
+
+  public FactoryWithInstanceInfoTest2(int n) {
+    m_number = n;
+  }
+
+  @Test(groups = { "first" })
+  public void testInt() {
+    Integer n = m_number;
+    m_numbers.put(n, n);
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[FactoryTest2] " + s);
+  }
+
+}
diff --git a/src/test/java/test/factory/GitHub876Sample.java b/src/test/java/test/factory/GitHub876Sample.java
new file mode 100644
index 0000000..9e01136
--- /dev/null
+++ b/src/test/java/test/factory/GitHub876Sample.java
@@ -0,0 +1,47 @@
+package test.factory;
+
+import org.testng.Assert;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+
+public class GitHub876Sample {
+
+    @Factory
+    public Object[] createInstances() {
+        return new Object[]{
+                new GitHub876Sample(new DataTest("foo", true)),
+                new GitHub876Sample(new DataTest("FOO", false))
+        };
+    }
+
+    private final DataTest dataTest;
+
+    public GitHub876Sample(DataTest dataTest) {
+        this.dataTest = dataTest;
+    }
+
+    @Test
+    public void test() {
+        switch (dataTest.s) {
+            case "FOO":
+                Assert.assertFalse(dataTest.b);
+                break;
+            case "foo":
+                Assert.assertTrue(dataTest.b);
+                break;
+            default:
+                Assert.fail("Unknown value");
+        }
+    }
+
+    public static class DataTest {
+
+        private final String s;
+        private final boolean b;
+
+        public DataTest(String s, boolean b) {
+            this.s = s;
+            this.b = b;
+        }
+    }
+}
diff --git a/src/test/java/test/factory/MyTest.java b/src/test/java/test/factory/MyTest.java
new file mode 100644
index 0000000..571a75b
--- /dev/null
+++ b/src/test/java/test/factory/MyTest.java
@@ -0,0 +1,30 @@
+package test.factory;
+
+import org.testng.annotations.Test;
+
+public class MyTest {
+  private int i;
+
+  // in this test, our default constructor sets s to a value that will cause a failure
+  // the valid test instances should come from the factory
+  public MyTest() {
+    i = 0;
+  }
+
+  public MyTest(int i) {
+    this.i = i;
+  }
+
+  @Test(groups = "MyTest")
+  public void testMethod() {
+    FactoryInSeparateClass.addToSum(i);
+    //    assert i > 0 : "MyTest was not constructed with correct params";
+    assert (i != 0) : "My test was not created by the factory";
+  }
+
+  @Test(dependsOnGroups = "testMethodOnFactoryClass")
+  public void verifyThatTestMethodOnFactoryClassWasRun() {
+    assert FactoryInSeparateClass.wasRun() : "Test method on factory class wasn't run";
+  }
+
+}
\ No newline at end of file
diff --git a/src/test/java/test/factory/NestedFactoryTest.java b/src/test/java/test/factory/NestedFactoryTest.java
new file mode 100644
index 0000000..c7143b9
--- /dev/null
+++ b/src/test/java/test/factory/NestedFactoryTest.java
@@ -0,0 +1,47 @@
+package test.factory;
+
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+
+public class NestedFactoryTest {
+  private int m_capacity = 2;
+  private float m_loadFactor = 0.4f;
+
+  public class NestedFactory {
+    @Factory
+    public Object[] createInstances() {
+      return new NestedFactoryTest[] {
+        new NestedFactoryTest(1, 0.1f),
+        new NestedFactoryTest(10, 0.5f),
+      };
+    }
+  };
+
+  private static int m_instanceCount = 0;
+  public NestedFactoryTest() {
+    m_instanceCount++;
+  }
+
+  public NestedFactoryTest(int capacity, float loadFactor) {
+    m_instanceCount++;
+   this.m_capacity=capacity;
+   this.m_loadFactor=loadFactor;
+  }
+
+  @Test
+  public void verify() {
+    // Should have three instances:  the default one created by TestNG
+    // and two created by the factory
+    assertEquals(m_instanceCount, 3);
+    assertTrue((m_capacity == 1 && m_loadFactor == 0.1f) ||
+        m_capacity == 10 && m_loadFactor == 0.5f);
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[NestedFactoryTest] " + s);
+  }
+}
diff --git a/src/test/java/test/factory/NestedStaticFactoryTest.java b/src/test/java/test/factory/NestedStaticFactoryTest.java
new file mode 100644
index 0000000..6602d15
--- /dev/null
+++ b/src/test/java/test/factory/NestedStaticFactoryTest.java
@@ -0,0 +1,47 @@
+package test.factory;
+
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+
+public class NestedStaticFactoryTest {
+  private int m_capacity = 2;
+  private float m_loadFactor = 0.4f;
+
+  static public class NestedStaticFactory {
+    @Factory
+    public Object[] createInstances() {
+      return new NestedStaticFactoryTest[] {
+        new NestedStaticFactoryTest(1, 0.1f),
+        new NestedStaticFactoryTest(10, 0.5f),
+      };
+    }
+  };
+
+  private static int m_instanceCount = 0;
+  public NestedStaticFactoryTest() {
+    m_instanceCount++;
+  }
+
+  public NestedStaticFactoryTest(int capacity, float loadFactor) {
+    m_instanceCount++;
+   this.m_capacity=capacity;
+   this.m_loadFactor=loadFactor;
+  }
+
+  @Test
+  public void verify() {
+    // Should have three instances:  the default one created by TestNG
+    // and two created by the factory
+    assertEquals(m_instanceCount, 2);
+    assertTrue((m_capacity == 1 && m_loadFactor == 0.1f) ||
+        m_capacity == 10 && m_loadFactor == 0.5f);
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[NestedStaticFactoryTest] " + s);
+  }
+}
diff --git a/src/test/java/test/factory/NonPublicFactoryMethodSample.java b/src/test/java/test/factory/NonPublicFactoryMethodSample.java
new file mode 100644
index 0000000..7feb6ef
--- /dev/null
+++ b/src/test/java/test/factory/NonPublicFactoryMethodSample.java
@@ -0,0 +1,14 @@
+package test.factory;
+
+import org.testng.annotations.Factory;
+
+public class NonPublicFactoryMethodSample {
+
+    @Factory
+    private Object[] createInstances() {
+        return new Object[] {
+                new BaseFactory(42),
+                new BaseFactory(43)
+        };
+    }
+}
diff --git a/src/test/java/test/factory/OddTest.java b/src/test/java/test/factory/OddTest.java
new file mode 100644
index 0000000..273c363
--- /dev/null
+++ b/src/test/java/test/factory/OddTest.java
@@ -0,0 +1,21 @@
+package test.factory;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class OddTest {
+  private int m_n;
+
+  public OddTest(int n) {
+    m_n = n;
+  }
+
+  @Test
+  public void verify() {
+    Assert.assertTrue(m_n % 2 != 0);
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[OddTest] " + s);
+  }
+}
diff --git a/src/test/java/test/factory/Sample2.java b/src/test/java/test/factory/Sample2.java
new file mode 100644
index 0000000..97a6dc9
--- /dev/null
+++ b/src/test/java/test/factory/Sample2.java
@@ -0,0 +1,85 @@
+package test.factory;
+
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test that setUp methods are correctly interleaved even
+ * when we use similar instances of a same test class.
+ *
+ * @author cbeust
+ */
+public class Sample2 {
+  private static List<String> m_methodList = new ArrayList<>();
+
+  @BeforeSuite
+  public void init() {
+    m_methodList = new ArrayList<>();
+  }
+
+  @BeforeMethod
+  public void setUp() {
+    ppp("SET UP");
+    m_methodList.add("setUp");
+  }
+
+  @AfterMethod
+  public void tearDown() {
+    ppp("TEAR DOWN");
+    m_methodList.add("tearDown");
+  }
+
+  @AfterSuite
+  public void afterSuite() {
+    String[] expectedStrings = {
+        "setUp",
+        "testInputImages",
+        "tearDown",
+        "setUp",
+        "testInputImages",
+        "tearDown",
+        "setUp",
+        "testImages",
+        "tearDown",
+        "setUp",
+        "testImages",
+        "tearDown",
+    };
+    List<String> expected = new ArrayList<>();
+    for (String s : expectedStrings) {
+      expected.add(s);
+    }
+
+    ppp("ORDER OF METHODS:");
+    for (String s : m_methodList) {
+      ppp("   " + s);
+    }
+
+    assertEquals(m_methodList, expected);
+  }
+
+  @Test
+  public void testInputImages() {
+    m_methodList.add("testInputImages");
+    ppp("TESTINPUTIMAGES");
+  }
+
+  @Test(dependsOnMethods={"testInputImages"})
+  public void testImages() {
+    m_methodList.add("testImages");
+  }
+
+  private static void ppp(String s) {
+    if (false) {
+      System.out.println("[Sample2] " + s);
+    }
+  }
+}
diff --git a/src/test/java/test/factory/TestClassAnnotationTest.java b/src/test/java/test/factory/TestClassAnnotationTest.java
new file mode 100644
index 0000000..506ee56
--- /dev/null
+++ b/src/test/java/test/factory/TestClassAnnotationTest.java
@@ -0,0 +1,44 @@
+package test.factory;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+
+/**
+ * Make sure that @Factory methods are not counted as @Test in the
+ * presence of a class-scoped @Test annotation.
+ *
+ * Created on Mar 30, 2006
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ */
+@Test
+public class TestClassAnnotationTest {
+
+  private int m_count;
+
+  @Factory
+  public Object[] createFixture() {
+    ppp("FACTORY");
+    m_count++;
+    return new Object[] { new Object[] { new Object() }};
+  }
+
+  public void testOne() {
+    ppp("TESTONE");
+    m_count++;
+  }
+
+  @AfterClass
+  public void verify() {
+    ppp("VERIFY");
+    Assert.assertEquals(m_count, 2);
+  }
+
+  private static void ppp(String s) {
+    if (false) {
+      System.err.println("[FactoryTest] " + s);
+    }
+  }
+
+}
diff --git a/src/test/java/test/factory/VerifyFactoryTest.java b/src/test/java/test/factory/VerifyFactoryTest.java
new file mode 100644
index 0000000..3ec6d56
--- /dev/null
+++ b/src/test/java/test/factory/VerifyFactoryTest.java
@@ -0,0 +1,20 @@
+package test.factory;
+
+import org.testng.annotations.Test;
+
+import java.util.Map;
+
+public class VerifyFactoryTest {
+  @Test(dependsOnGroups = { "first" } )
+  public void mainCheck() {
+    Map<Integer, Integer> numbers = FactoryTest2.getNumbers();
+    assert null != numbers.get(42)
+      : "Didn't find 42";
+    assert null != numbers.get(43)
+      : "Didn't find 43";
+    assert 2 == numbers.size()
+      : "Expected 2 numbers, found " + (numbers.size());
+  }
+
+
+}
diff --git a/src/test/java/test/factory/VerifyFactoryWithInstanceInfoTest.java b/src/test/java/test/factory/VerifyFactoryWithInstanceInfoTest.java
new file mode 100644
index 0000000..d9d52d6
--- /dev/null
+++ b/src/test/java/test/factory/VerifyFactoryWithInstanceInfoTest.java
@@ -0,0 +1,20 @@
+package test.factory;
+
+import org.testng.annotations.Test;
+
+import java.util.Map;
+
+public class VerifyFactoryWithInstanceInfoTest {
+  @Test(dependsOnGroups = { "first" } )
+  public void mainCheck() {
+    Map<Integer, Integer> numbers = FactoryWithInstanceInfoTest2.getNumbers();
+    assert null != numbers.get(42)
+      : "Didn't find 42";
+    assert null != numbers.get(43)
+      : "Didn't find 43";
+    assert 2 == numbers.size()
+      : "Expected 2 numbers, found " + (numbers.size());
+  }
+
+
+}
diff --git a/src/test/java/test/factory/classconf/XClassOrderWithFactory.java b/src/test/java/test/factory/classconf/XClassOrderWithFactory.java
new file mode 100644
index 0000000..74eacbb
--- /dev/null
+++ b/src/test/java/test/factory/classconf/XClassOrderWithFactory.java
@@ -0,0 +1,37 @@
+package test.factory.classconf;

+

+import org.testng.annotations.AfterClass;

+import org.testng.annotations.BeforeClass;

+import org.testng.annotations.Factory;

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class XClassOrderWithFactory {

+  public static final String EXPECTED_LOG= "BTABTABTA";

+  public static final StringBuffer LOG= new StringBuffer();

+

+  @Factory

+  public Object[] createInstances() throws Exception {

+      return new Object[] {

+              new XClassOrderTest(), new XClassOrderTest(), new XClassOrderTest()

+          };

+  }

+

+  public static class XClassOrderTest {

+    @BeforeClass

+    public void beforeClass() {

+      LOG.append("B");

+    }

+

+    public @Test void test() {

+      LOG.append("T");

+    }

+

+    public @AfterClass void afterClass() {

+      LOG.append("A");

+    }

+  }

+}

diff --git a/src/test/java/test/factory/classconf/XClassOrderWithFactoryTest.java b/src/test/java/test/factory/classconf/XClassOrderWithFactoryTest.java
new file mode 100644
index 0000000..62b65d6
--- /dev/null
+++ b/src/test/java/test/factory/classconf/XClassOrderWithFactoryTest.java
@@ -0,0 +1,23 @@
+package test.factory.classconf;

+

+import org.testng.Assert;

+import org.testng.TestListenerAdapter;

+import org.testng.TestNG;

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class XClassOrderWithFactoryTest {

+  @Test

+  public void testBeforeAfterClassInvocationsWithFactory() {

+    TestNG testng= new TestNG();

+    testng.setTestClasses(new Class[] {XClassOrderWithFactory.class});

+    TestListenerAdapter tla = new TestListenerAdapter();

+    testng.addListener(tla);

+    testng.setVerbose(0);

+    testng.run();

+    Assert.assertEquals(XClassOrderWithFactory.LOG.toString(), XClassOrderWithFactory.EXPECTED_LOG);

+  }

+}

diff --git a/src/test/java/test/failedreporter/FailedReporter2SampleTest.java b/src/test/java/test/failedreporter/FailedReporter2SampleTest.java
new file mode 100644
index 0000000..a238c3b
--- /dev/null
+++ b/src/test/java/test/failedreporter/FailedReporter2SampleTest.java
@@ -0,0 +1,22 @@
+package test.failedreporter;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class FailedReporter2SampleTest {
+  @DataProvider
+  public Object[][] dp() {
+    return new Object[][] {
+      new Object[] { 0 },
+      new Object[] { 1 },
+      new Object[] { 2 },
+    };
+  }
+
+  @Test(dataProvider = "dp")
+  public void f1(Integer ip) {
+    if (ip == 1) {
+      throw new RuntimeException();
+    }
+  }
+}
diff --git a/src/test/java/test/failedreporter/FailedReporterSampleTest.java b/src/test/java/test/failedreporter/FailedReporterSampleTest.java
new file mode 100644
index 0000000..77d2eb6
--- /dev/null
+++ b/src/test/java/test/failedreporter/FailedReporterSampleTest.java
@@ -0,0 +1,21 @@
+package test.failedreporter;
+
+import org.testng.SkipException;
+import org.testng.annotations.Test;
+
+public class FailedReporterSampleTest {
+  @Test
+  public void f2() {
+    throw new RuntimeException();
+  }
+
+  @Test
+  public void f1() {
+    throw new SkipException("Skipped");
+  }
+
+  @Test
+  public void f3() {
+  }
+
+}
diff --git a/src/test/java/test/failedreporter/FailedReporterTest.java b/src/test/java/test/failedreporter/FailedReporterTest.java
new file mode 100644
index 0000000..ce1bb00
--- /dev/null
+++ b/src/test/java/test/failedreporter/FailedReporterTest.java
@@ -0,0 +1,58 @@
+package test.failedreporter;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+
+import test.BaseTest;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+public class FailedReporterTest extends BaseTest {
+  private File mTempDirectory;
+
+  @BeforeMethod
+  public void setUp() {
+    File slashTmpDir = new File(System.getProperty("java.io.tmpdir"));
+    mTempDirectory = new File(slashTmpDir, "testng-tmp-" + System.currentTimeMillis() % 1000);
+    mTempDirectory.mkdirs();
+    mTempDirectory.deleteOnExit();
+  }
+
+  @AfterMethod
+  public void tearDown() {
+    deleteDir(mTempDirectory);
+  }
+
+  @Test
+  public void failedAndSkippedMethodsShouldBeIncluded() throws IOException {
+    testFailedReporter(FailedReporterSampleTest.class, new String[] { "f1", "f2" },
+        "<include name=\"%s\"" + "\"/>");   }
+
+  @Test
+  public void failedMethodWithDataProviderShouldHaveInvocationNumbers() throws IOException {
+    testFailedReporter(FailedReporter2SampleTest.class, new String[] { "f1" },
+        "<include invocationNumbers=\"1\" name=\"%s\"" + "\"/>");
+  }
+
+  private void testFailedReporter(Class<?> cls, String[] expectedMethods, String expectedLine) {
+    TestNG tng = new TestNG();
+    tng.setVerbose(0);
+    tng.setTestClasses(new Class[] { cls });
+    tng.setOutputDirectory(mTempDirectory.getAbsolutePath());
+    tng.run();
+
+    File failed = new File(mTempDirectory, "testng-failed.xml");
+    for (String s : expectedMethods) {
+      List<String> resultLines = Lists.newArrayList();
+      grep(failed, expectedLine.format(s), resultLines);
+      Assert.assertEquals(1, resultLines.size());
+    }
+
+  }
+}
diff --git a/src/test/java/test/failures/Base0.java b/src/test/java/test/failures/Base0.java
new file mode 100644
index 0000000..7181b8f
--- /dev/null
+++ b/src/test/java/test/failures/Base0.java
@@ -0,0 +1,12 @@
+package test.failures;
+
+import org.testng.annotations.Test;
+
+public class Base0 {
+  @Test
+  public void base1() {
+      assert true;
+  }
+
+
+}
diff --git a/src/test/java/test/failures/Base1.java b/src/test/java/test/failures/Base1.java
new file mode 100644
index 0000000..e3bb465
--- /dev/null
+++ b/src/test/java/test/failures/Base1.java
@@ -0,0 +1,15 @@
+package test.failures;
+
+import org.testng.annotations.Test;
+
+public class Base1 extends Base0 {
+   @Test
+   public void base2() {
+     assert true;
+   }
+
+   @Test
+   public void failFromBase() {
+     throw new RuntimeException("VOLUNTARILY FAILED");
+   }
+}
\ No newline at end of file
diff --git a/src/test/java/test/failures/BaseFailuresTest.java b/src/test/java/test/failures/BaseFailuresTest.java
new file mode 100644
index 0000000..7e1889e
--- /dev/null
+++ b/src/test/java/test/failures/BaseFailuresTest.java
@@ -0,0 +1,91 @@
+package test.failures;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.reporters.FailedReporter;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+public class BaseFailuresTest {
+
+//  protected TestNG run(Class[] classes, String outputDir) {
+//    return run(new TestNG(), classes, outputDir);
+//  }
+
+  protected String getSuiteName() {
+    return "TmpSuite";
+  }
+
+  protected TestNG run(TestNG result, Class[] classes, String outputDir) {
+     result.setVerbose(0);
+     result.setOutputDirectory(outputDir);
+     result.setTestClasses(classes);
+     result.run();
+
+     return result;
+  }
+
+  /**
+   * @param f
+   * @param regexps
+   * @return true if the file contains at least one occurrence of each regexp
+   */
+  protected boolean containsRegularExpressions(File f, String[] strRegexps) {
+    Pattern[] matchers = new Pattern[strRegexps.length];
+    boolean[] results = new boolean[strRegexps.length];
+    for (int i = 0; i < strRegexps.length; i++) {
+      matchers[i] = Pattern.compile(".*" + strRegexps[i] + ".*");
+      results[i] = false;
+    }
+
+    try {
+      FileReader fr = new FileReader(f);
+      BufferedReader br = new BufferedReader(fr);
+      String line = br.readLine();
+      while (line != null) {
+        for (int i = 0; i < strRegexps.length; i++) {
+          if (matchers[i].matcher(line).matches()) {
+            results[i] = true;
+          }
+        }
+        line = br.readLine();
+      }
+      fr.close();
+      br.close();
+    }
+    catch (FileNotFoundException e) {
+      e.printStackTrace();
+      return false;
+    }
+    catch (IOException e) {
+      e.printStackTrace();
+      return false;
+    }
+
+    for (int i = 0; i < results.length; i++) {
+      boolean result = results[i];
+      if (! result) {
+        throw new AssertionError("Couldn't find " + strRegexps[i]);
+      }
+    }
+
+    return true;
+  }
+
+  protected void verify(String outputDir, String[] expected) {
+    File f = new File(outputDir +
+        File.separatorChar + getSuiteName() +
+        File.separator + FailedReporter.TESTNG_FAILED_XML);
+     boolean passed = containsRegularExpressions(f, expected);
+     Assert.assertTrue(passed);
+
+     File tmpDir = new File(outputDir);
+     tmpDir.delete();
+  }
+
+}
diff --git a/src/test/java/test/failures/Child.java b/src/test/java/test/failures/Child.java
new file mode 100644
index 0000000..67a5620
--- /dev/null
+++ b/src/test/java/test/failures/Child.java
@@ -0,0 +1,16 @@
+package test.failures;
+
+import org.testng.annotations.Test;
+
+public class Child extends Base1 {
+
+  @Test
+  public void pass() {
+      assert true;
+  }
+
+  @Test
+  public void fail() {
+    throw new RuntimeException("VOLUNTARILY FAILED");
+  }
+}
diff --git a/src/test/java/test/failures/DependentTest.java b/src/test/java/test/failures/DependentTest.java
new file mode 100644
index 0000000..0a7dcae
--- /dev/null
+++ b/src/test/java/test/failures/DependentTest.java
@@ -0,0 +1,16 @@
+package test.failures;
+
+import org.testng.annotations.Test;
+
+public class DependentTest {
+
+  @Test
+  public void f1() {
+
+  }
+
+  @Test(dependsOnMethods = {"f1"}, dependsOnGroups = { "f" })
+  public void f2() {
+    throw new RuntimeException();
+  }
+}
diff --git a/src/test/java/test/failures/FailuresTest.java b/src/test/java/test/failures/FailuresTest.java
new file mode 100644
index 0000000..fcef49a
--- /dev/null
+++ b/src/test/java/test/failures/FailuresTest.java
@@ -0,0 +1,69 @@
+package test.failures;
+
+
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+
+import test.TestHelper;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class FailuresTest extends BaseFailuresTest {
+
+  @Test
+  public void shouldIncludeFailedMethodsFromBaseClass() {
+    XmlSuite suite = TestHelper.createSuite("test.failures.Child", getSuiteName());
+    TestNG tng = TestHelper.createTestNG(suite);
+    tng.run();
+
+     String[] expected = new String[] {
+       "<class name=\"test.failures.Child\">",
+       "<include name=\"fail\"/>",
+       "<include name=\"failFromBase\"/>",
+     };
+
+     verify(getOutputDir(), expected);
+  }
+
+  @Test(enabled = false)
+  public void shouldIncludeDependentMethods() {
+    XmlSuite suite = TestHelper.createSuite("test.failures.DependentTest", getSuiteName());
+    TestNG tng = TestHelper.createTestNG(suite);
+    tng.run();
+
+    String[] expected = new String[] {
+        "<include name=\"f1\"/>",
+        "<include name=\"f2\"/>"
+      };
+
+    verify(getOutputDir(), expected);
+  }
+
+  @Test(enabled = false)
+  public void shouldIncludeParameters() {
+    XmlSuite suite = TestHelper.createSuite("test.failures.Child", getSuiteName());
+    Map<String, String> params = new HashMap<>();
+    params.put("first-name", "Cedric");
+    params.put("last-name", "Beust");
+    suite.setParameters(params);
+
+    TestNG tng = TestHelper.createTestNG(suite);
+    tng.run();
+
+    String[] expected = new String[] {
+        "<parameter name=\"first-name\" value=\"Cedric\"/>",
+      };
+
+    verify(getOutputDir(), expected);
+  }
+
+  private String getOutputDir() {
+    return System.getProperty("java.io.tmpdir");
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[FailuresTest] " + s);
+  }
+}
diff --git a/src/test/java/test/groupbug/GroupBugTest.java b/src/test/java/test/groupbug/GroupBugTest.java
new file mode 100644
index 0000000..93d64ba
--- /dev/null
+++ b/src/test/java/test/groupbug/GroupBugTest.java
@@ -0,0 +1,28 @@
+package test.groupbug;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+
+import test.BaseTest;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class GroupBugTest extends BaseTest {
+
+  static List<String> passed = Lists.newArrayList();
+
+  @Test(groups = "broken",
+      description = "Comment out dependsOnGroups in ITCaseOne will fix the ordering, that's the bug")
+  public void shouldOrderByClass() {
+    passed.clear();
+    addClass(ITCaseOne.class);
+    addClass(ITCaseTwo.class);
+    run();
+    List<String> expected = Arrays.asList(
+        "one1", "one2", "two1", "two2"
+    );
+    Assert.assertEquals(passed, expected);
+  }
+}
diff --git a/src/test/java/test/groupbug/ITCaseOne.java b/src/test/java/test/groupbug/ITCaseOne.java
new file mode 100644
index 0000000..a0ac4b8
--- /dev/null
+++ b/src/test/java/test/groupbug/ITCaseOne.java
@@ -0,0 +1,34 @@
+package test.groupbug;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class ITCaseOne {
+
+  @BeforeClass
+  public void beforeClass() {
+    System.out.printf("RUN %s.beforeClass()\n", getClass());
+  }
+
+  @AfterClass(alwaysRun = true)
+  public void afterClass() {
+    System.out.printf("RUN %s.afterClass()\n", getClass());
+  }
+
+  @Test(groups = "std-one")
+  public void one1() {
+    GroupBugTest.passed.add("one1");
+    System.out.printf("RUN %s.one1()\n", getClass());
+  }
+
+  /**
+   * Commenting out dependsOnGroups fixes the ordering, that's the bug.
+   */
+  @Test(groups = "logic-one", dependsOnGroups = "std-one")
+  public void one2() {
+    GroupBugTest.passed.add("one2");
+    System.out.printf("RUN %s.one2()\n", getClass());
+  }
+
+}
\ No newline at end of file
diff --git a/src/test/java/test/groupbug/ITCaseTwo.java b/src/test/java/test/groupbug/ITCaseTwo.java
new file mode 100644
index 0000000..e782bdc
--- /dev/null
+++ b/src/test/java/test/groupbug/ITCaseTwo.java
@@ -0,0 +1,31 @@
+package test.groupbug;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class ITCaseTwo {
+
+  @BeforeClass
+  public void beforeClass() {
+    System.out.printf("RUN %s.beforeClass()\n", getClass());
+  }
+
+  @AfterClass(alwaysRun = true)
+  public void afterClass() {
+    System.out.printf("RUN %s.afterClass()\n", getClass());
+  }
+
+  @Test(groups = "std-two")
+  public void two1() {
+    GroupBugTest.passed.add("two1");
+    System.out.printf("RUN %s.two1()\n", getClass());
+  }
+
+  @Test(groups = "logic-two", dependsOnGroups = "std-two")
+  public void two2() {
+    GroupBugTest.passed.add("two2");
+    System.out.printf("RUN %s.two2()\n", getClass());
+  }
+
+}
\ No newline at end of file
diff --git a/src/test/java/test/groupinvocation/DummyTest.java b/src/test/java/test/groupinvocation/DummyTest.java
new file mode 100644
index 0000000..2259f4f
--- /dev/null
+++ b/src/test/java/test/groupinvocation/DummyTest.java
@@ -0,0 +1,46 @@
+package test.groupinvocation;

+

+import org.testng.Assert;

+import org.testng.annotations.AfterClass;

+import org.testng.annotations.Test;

+

+import java.util.HashMap;

+import java.util.Map;

+

+

+/**

+ * This class/interface

+ */

+public class DummyTest {

+  private static Map<String, Integer> s_externalClassGroups= new HashMap<>();

+

+  @Test(groups={"a"})

+  public void testA() {

+  }

+

+  @Test(groups={"b"})

+  public void testB() {

+  }

+

+  @Test(groups={"a", "b"})

+  public void testAB() {

+  }

+

+  @AfterClass(alwaysRun=true)

+  public void checkInvocations() {

+    Integer hashCode1= s_externalClassGroups.get("beforeGroups");

+    Assert.assertNotNull(hashCode1, "External @BeforeGroups not invoked");

+    Integer hashCode2= s_externalClassGroups.get("afterGroups");

+    Assert.assertNotNull(hashCode2, "External @AfterGroups not invoked");

+    Assert.assertEquals(hashCode1, hashCode2, "External @BeforeGroups and @AfterGroups were not invoked on the" +

+        " same class instance");

+  }

+

+  /**

+   * @param string

+   * @param i

+   */

+  public static void recordInvocation(String string, int i) {

+    s_externalClassGroups.put(string, i);

+  }

+}

diff --git a/src/test/java/test/groupinvocation/DummyTest2.java b/src/test/java/test/groupinvocation/DummyTest2.java
new file mode 100644
index 0000000..50c20a1
--- /dev/null
+++ b/src/test/java/test/groupinvocation/DummyTest2.java
@@ -0,0 +1,23 @@
+package test.groupinvocation;

+

+import org.testng.Assert;

+import org.testng.annotations.AfterClass;

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class DummyTest2 {

+  private boolean m_invoked= false;

+

+  @Test(groups={"A"})

+  public void dummyTest() {

+    m_invoked= true;

+  }

+

+  @AfterClass(alwaysRun=true)

+  public void checkInvocations() {

+    Assert.assertFalse(m_invoked, "@Test method invoked even if @BeforeGroups failed");

+  }

+}

diff --git a/src/test/java/test/groupinvocation/FailingBeforeGroupMethod.java b/src/test/java/test/groupinvocation/FailingBeforeGroupMethod.java
new file mode 100644
index 0000000..ae9e36b
--- /dev/null
+++ b/src/test/java/test/groupinvocation/FailingBeforeGroupMethod.java
@@ -0,0 +1,14 @@
+package test.groupinvocation;

+

+import org.testng.annotations.BeforeGroups;

+

+

+/**

+ * This class/interface

+ */

+public class FailingBeforeGroupMethod {

+  @BeforeGroups(groups={"A"})

+  public void beforeGroupA() {

+    throw new RuntimeException("Failing @BeforeGroups beforeGroupA method");

+  }

+}

diff --git a/src/test/java/test/groupinvocation/GroupConfiguration.java b/src/test/java/test/groupinvocation/GroupConfiguration.java
new file mode 100644
index 0000000..fea3510
--- /dev/null
+++ b/src/test/java/test/groupinvocation/GroupConfiguration.java
@@ -0,0 +1,20 @@
+package test.groupinvocation;

+

+import org.testng.annotations.AfterGroups;

+import org.testng.annotations.BeforeGroups;

+

+

+/**

+ * This class/interface

+ */

+public class GroupConfiguration {

+  @BeforeGroups(groups={"a"})

+  public void beforeGroups() {

+    DummyTest.recordInvocation("beforeGroups", hashCode());

+  }

+

+  @AfterGroups(groups={"a"})

+  public void afterGroups() {

+    DummyTest.recordInvocation("afterGroups", hashCode());

+  }

+}

diff --git a/src/test/java/test/groupinvocation/GroupSuiteSampleTest.java b/src/test/java/test/groupinvocation/GroupSuiteSampleTest.java
new file mode 100644
index 0000000..4f51bdd
--- /dev/null
+++ b/src/test/java/test/groupinvocation/GroupSuiteSampleTest.java
@@ -0,0 +1,16 @@
+package test.groupinvocation;
+
+import org.testng.annotations.Test;
+
+public class GroupSuiteSampleTest {
+
+  @Test(groups = "a")
+  public void a() {}
+
+  @Test(groups = "b")
+  public void b() {}
+
+  @Test
+  public void c() {}
+}
+
diff --git a/src/test/java/test/groupinvocation/GroupSuiteTest.java b/src/test/java/test/groupinvocation/GroupSuiteTest.java
new file mode 100644
index 0000000..ddba4de
--- /dev/null
+++ b/src/test/java/test/groupinvocation/GroupSuiteTest.java
@@ -0,0 +1,75 @@
+package test.groupinvocation;
+
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.SimpleBaseTest;
+
+import java.util.Arrays;
+
+/**
+ * Test that <suite> tags can have groups.
+ */
+@Test
+public class GroupSuiteTest extends SimpleBaseTest {
+
+  public void includeFromSuite0() {
+    runWithSuite(g(), g(), g("a", "b", "c"));
+  }
+
+  public void includeFromSuite1() {
+    runWithSuite(g("a"), g(), g("a"));
+  }
+  
+  public void includeFromSuite2() {
+    runWithSuite(g("a", "b"), g(), g("a", "b"));
+  }
+
+  public void excludeFromSuite1() {
+    runWithSuite(g(), g("a"), g("b", "c"));
+  }
+
+  public void excludeFromSuite2() {
+    runWithSuite(g(), g("a", "b"), g("c"));
+  }
+
+  @Test(description = "Include in both suite and test")
+  public void includeTestAndSuite1() {
+    runWithSuite(g("a"), g(), g("b"), g(), g("a", "b"));
+  }
+
+  @Test(description = "Include in suite, exclude in test")
+  public void excludeTestAndSuite2() {
+    runWithSuite(g(), g("a"), g(), g("a"), g("b", "c"));
+  }
+
+  private void runWithSuite(String[] suiteGroups, String[] excludedSuiteGroups,
+      String[] methods) {
+    runWithSuite(suiteGroups, excludedSuiteGroups, g(), g(), methods);
+  }
+
+  private void runWithSuite(String[] suiteGroups, String[] excludedSuiteGroups,
+      String[] testGroups, String[] excludedTestGroups,
+      String[] methods) {
+    XmlSuite s = createXmlSuite("Groups");
+    s.setIncludedGroups(Arrays.asList(suiteGroups));
+    s.setExcludedGroups(Arrays.asList(excludedSuiteGroups));
+    XmlTest t = createXmlTest(s, "Groups-test", GroupSuiteSampleTest.class.getName());
+    t.setIncludedGroups(Arrays.asList(testGroups));
+    t.setExcludedGroups(Arrays.asList(excludedTestGroups));
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG tng = create();
+    tng.addListener(tla);
+    tng.setXmlSuites(Arrays.asList(new XmlSuite[] { s }));
+    tng.run();
+
+    verifyPassedTests(tla, methods);
+  }
+
+  private String[] g(String... groups) {
+    return groups;
+  }
+}
diff --git a/src/test/java/test/groupinvocation/testng.xml b/src/test/java/test/groupinvocation/testng.xml
new file mode 100644
index 0000000..5d4c832
--- /dev/null
+++ b/src/test/java/test/groupinvocation/testng.xml
@@ -0,0 +1,33 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

+<suite name="Groups run" verbose="2">
+  <test name="External group invocation">

+		<groups>

+			<run>

+				<include name="a" />

+			</run>

+		</groups>

+		<classes>

+			<class name="test.groupinvocation.GroupConfiguration" />

+			<class name="test.groupinvocation.DummyTest" />

+		</classes>

+  </test>

+	<test name="Failing External BeforeGroups - Group run">

+		<groups>

+			<run>

+				<include name="A" />

+			</run>

+		</groups>

+		<classes>

+			<class name="test.groupinvocation.FailingBeforeGroupMethod" />

+			<class name="test.groupinvocation.DummyTest2" />

+		</classes>

+  </test>

+	

+	<test name="Failing External BeforeGroups - No group run">

+		<classes>

+			<class name="test.groupinvocation.FailingBeforeGroupMethod" />

+			<class name="test.groupinvocation.DummyTest2" />

+		</classes>

+  </test>

+</suite>

+

diff --git a/src/test/java/test/guice/ExampleSingleton.java b/src/test/java/test/guice/ExampleSingleton.java
new file mode 100644
index 0000000..19c791c
--- /dev/null
+++ b/src/test/java/test/guice/ExampleSingleton.java
@@ -0,0 +1,10 @@
+package test.guice;
+
+public class ExampleSingleton implements ISingleton {
+
+  @Override
+  public void doSomething() {
+//    System.out.println("Doing something");
+  }
+
+}
diff --git a/src/test/java/test/guice/Guice1Test.java b/src/test/java/test/guice/Guice1Test.java
new file mode 100644
index 0000000..d4e413e
--- /dev/null
+++ b/src/test/java/test/guice/Guice1Test.java
@@ -0,0 +1,23 @@
+package test.guice;
+
+import com.google.inject.Inject;
+
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+@Guice(modules = GuiceExampleModule.class)
+public class Guice1Test extends SimpleBaseTest {
+  static ISingleton m_object;
+
+  @Inject
+  ISingleton m_singleton;
+
+  @Test
+  public void singletonShouldWork() {
+    m_singleton.doSomething();
+    m_object = m_singleton;
+  }
+
+}
diff --git a/src/test/java/test/guice/Guice2Test.java b/src/test/java/test/guice/Guice2Test.java
new file mode 100644
index 0000000..ebe15f8
--- /dev/null
+++ b/src/test/java/test/guice/Guice2Test.java
@@ -0,0 +1,22 @@
+package test.guice;
+
+import com.google.inject.Inject;
+
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+@Guice(modules = GuiceExampleModule.class)
+public class Guice2Test extends SimpleBaseTest {
+  static ISingleton m_object;
+
+  @Inject
+  ISingleton m_singleton;
+
+  @Test
+  public void singletonShouldWork() {
+    m_object = m_singleton;
+  }
+
+}
diff --git a/src/test/java/test/guice/GuiceBase.java b/src/test/java/test/guice/GuiceBase.java
new file mode 100644
index 0000000..ab9baf2
--- /dev/null
+++ b/src/test/java/test/guice/GuiceBase.java
@@ -0,0 +1,8 @@
+package test.guice;
+
+import org.testng.annotations.Guice;
+
+@Guice(modules = GuiceExampleModule.class)
+public class GuiceBase {
+
+}
diff --git a/src/test/java/test/guice/GuiceExampleModule.java b/src/test/java/test/guice/GuiceExampleModule.java
new file mode 100644
index 0000000..c76ab9c
--- /dev/null
+++ b/src/test/java/test/guice/GuiceExampleModule.java
@@ -0,0 +1,14 @@
+package test.guice;
+
+import com.google.inject.Binder;
+import com.google.inject.Module;
+import com.google.inject.Singleton;
+
+public class GuiceExampleModule implements Module {
+
+  @Override
+  public void configure(Binder binder) {
+    binder.bind(ISingleton.class).to(ExampleSingleton.class).in(Singleton.class);
+  }
+
+}
diff --git a/src/test/java/test/guice/GuiceInheritanceTest.java b/src/test/java/test/guice/GuiceInheritanceTest.java
new file mode 100644
index 0000000..5772862
--- /dev/null
+++ b/src/test/java/test/guice/GuiceInheritanceTest.java
@@ -0,0 +1,16 @@
+package test.guice;
+
+import com.google.inject.Inject;
+
+import org.testng.annotations.Test;
+
+public class GuiceInheritanceTest extends GuiceBase {
+
+  @Inject
+  ISingleton m_singleton;
+
+  @Test
+  public void singletonShouldWork() {
+    m_singleton.doSomething();
+  }
+}
diff --git a/src/test/java/test/guice/GuiceModuleFactoryTest.java b/src/test/java/test/guice/GuiceModuleFactoryTest.java
new file mode 100644
index 0000000..548fc3e
--- /dev/null
+++ b/src/test/java/test/guice/GuiceModuleFactoryTest.java
@@ -0,0 +1,18 @@
+package test.guice;
+
+import com.google.inject.Inject;
+
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+@Guice(moduleFactory = ModuleFactory.class)
+public class GuiceModuleFactoryTest {
+
+  @Inject
+  ISingleton m_singleton;
+
+  @Test
+  public void singletonShouldWork() {
+    m_singleton.doSomething();
+  }
+}
diff --git a/src/test/java/test/guice/GuiceNoModuleTest.java b/src/test/java/test/guice/GuiceNoModuleTest.java
new file mode 100644
index 0000000..93bd8bb
--- /dev/null
+++ b/src/test/java/test/guice/GuiceNoModuleTest.java
@@ -0,0 +1,12 @@
+package test.guice;
+
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+@Guice
+public class GuiceNoModuleTest {
+
+  @Test
+  public void f() {
+  }
+}
diff --git a/src/test/java/test/guice/GuiceParentModule.java b/src/test/java/test/guice/GuiceParentModule.java
new file mode 100644
index 0000000..96da859
--- /dev/null
+++ b/src/test/java/test/guice/GuiceParentModule.java
@@ -0,0 +1,23 @@
+package test.guice;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.testng.ITestContext;
+
+public class GuiceParentModule extends AbstractModule {
+
+  private final ITestContext context;
+
+  public GuiceParentModule(ITestContext context) {
+    this.context = context;
+  }
+
+  @Override
+  protected void configure() {
+    bind(MyService.class).toProvider(MyServiceProvider.class);
+    bind(MyContext.class).to(MyContextImpl.class).in(Singleton.class);
+    bind(ITestContext.class).toInstance(context);
+  }
+}
diff --git a/src/test/java/test/guice/GuiceParentModuleTest.java b/src/test/java/test/guice/GuiceParentModuleTest.java
new file mode 100644
index 0000000..6b1c0d3
--- /dev/null
+++ b/src/test/java/test/guice/GuiceParentModuleTest.java
@@ -0,0 +1,28 @@
+package test.guice;
+
+import org.testng.Assert;
+import org.testng.ITestContext;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.google.inject.Inject;
+
+@Test
+@Guice(modules = GuiceTestModule.class)
+public class GuiceParentModuleTest {
+  @Inject
+  MySession mySession;
+  @Inject
+  MyService myService;
+  @Inject
+  ITestContext context;
+
+  public void testService() {
+    Assert.assertNotNull(myService);
+    Assert.assertNotNull(mySession);
+    myService.serve(mySession);
+    Assert.assertNotNull(context);
+    Assert.assertEquals(context.getName(), "Guice");
+    Assert.assertEquals(context.getSuite().getName(), "parent-module-suite");
+  }
+}
diff --git a/src/test/java/test/guice/GuiceTest.java b/src/test/java/test/guice/GuiceTest.java
new file mode 100644
index 0000000..223ac76
--- /dev/null
+++ b/src/test/java/test/guice/GuiceTest.java
@@ -0,0 +1,28 @@
+package test.guice;
+
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+import junit.framework.Assert;
+
+public class GuiceTest extends SimpleBaseTest {
+
+  @Test
+  public void guiceTest() {
+    TestNG tng = create(new Class[] { Guice1Test.class, Guice2Test.class});
+    Guice1Test.m_object = null;
+    Guice2Test.m_object = null;
+    tng.run();
+
+    Assert.assertNotNull(Guice1Test.m_object);
+    Assert.assertNotNull(Guice2Test.m_object);
+    Assert.assertEquals(Guice1Test.m_object, Guice2Test.m_object);
+  }
+
+  @Test
+  public void guiceWithNoModules() {
+    TestNG tng = create(new Class[] { GuiceNoModuleTest.class });
+    tng.run();
+  }
+}
diff --git a/src/test/java/test/guice/GuiceTestModule.java b/src/test/java/test/guice/GuiceTestModule.java
new file mode 100644
index 0000000..204404c
--- /dev/null
+++ b/src/test/java/test/guice/GuiceTestModule.java
@@ -0,0 +1,18 @@
+package test.guice;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+
+public class GuiceTestModule extends AbstractModule {
+  private final MyContext myContext;
+
+  @Inject
+  GuiceTestModule(MyContext myContext) {
+    this.myContext = myContext;
+  }
+
+  @Override
+  protected void configure() {
+    bind(MySession.class).toInstance(myContext.getSession());
+  }
+}
diff --git a/src/test/java/test/guice/ISingleton.java b/src/test/java/test/guice/ISingleton.java
new file mode 100644
index 0000000..c945f08
--- /dev/null
+++ b/src/test/java/test/guice/ISingleton.java
@@ -0,0 +1,6 @@
+package test.guice;
+
+public interface ISingleton {
+
+  void doSomething();
+}
diff --git a/src/test/java/test/guice/ModuleFactory.java b/src/test/java/test/guice/ModuleFactory.java
new file mode 100644
index 0000000..cfce806
--- /dev/null
+++ b/src/test/java/test/guice/ModuleFactory.java
@@ -0,0 +1,24 @@
+package test.guice;
+
+import com.google.inject.Module;
+
+import org.testng.IModuleFactory;
+import org.testng.ITestContext;
+
+public class ModuleFactory implements IModuleFactory {
+
+  @Override
+  public Module createModule(ITestContext context, Class<?> testClass) {
+    String parameter = context.getCurrentXmlTest().getParameter("inject");
+    String expected = "guice";
+    if (! expected.equals(parameter)) {
+      throw new RuntimeException("Excepted parameter to be " + expected + ", got " + parameter);
+    }
+    if (GuiceModuleFactoryTest.class == testClass) {
+      return new GuiceExampleModule();
+    } else {
+      throw new RuntimeException("Don't know how to create a module for class " + testClass);
+    }
+  }
+
+}
diff --git a/src/test/java/test/guice/MyContext.java b/src/test/java/test/guice/MyContext.java
new file mode 100644
index 0000000..7378395
--- /dev/null
+++ b/src/test/java/test/guice/MyContext.java
@@ -0,0 +1,6 @@
+package test.guice;
+
+public interface MyContext {
+
+  MySession getSession();
+}
diff --git a/src/test/java/test/guice/MyContextImpl.java b/src/test/java/test/guice/MyContextImpl.java
new file mode 100644
index 0000000..18c21fb
--- /dev/null
+++ b/src/test/java/test/guice/MyContextImpl.java
@@ -0,0 +1,11 @@
+package test.guice;
+
+
+public class MyContextImpl implements MyContext {
+  private final MySession mySession = new MySession();
+
+  @Override
+  public MySession getSession() {
+    return mySession;
+  }
+}
diff --git a/src/test/java/test/guice/MyService.java b/src/test/java/test/guice/MyService.java
new file mode 100644
index 0000000..ef782ae
--- /dev/null
+++ b/src/test/java/test/guice/MyService.java
@@ -0,0 +1,6 @@
+package test.guice;
+
+public interface MyService {
+
+  void serve(MySession mySession);
+}
diff --git a/src/test/java/test/guice/MyServiceProvider.java b/src/test/java/test/guice/MyServiceProvider.java
new file mode 100644
index 0000000..868f83c
--- /dev/null
+++ b/src/test/java/test/guice/MyServiceProvider.java
@@ -0,0 +1,15 @@
+package test.guice;
+
+import com.google.inject.Provider;
+
+public class MyServiceProvider implements Provider<MyService> {
+
+  @Override
+  public MyService get() {
+    return new MyService() {
+      @Override
+      public void serve(MySession mySession) {
+      }
+    };
+  }
+}
diff --git a/src/test/java/test/guice/MySession.java b/src/test/java/test/guice/MySession.java
new file mode 100644
index 0000000..515c55d
--- /dev/null
+++ b/src/test/java/test/guice/MySession.java
@@ -0,0 +1,5 @@
+package test.guice;
+
+public class MySession {
+
+}
diff --git a/src/test/java/test/hook/BaseConfigurable.java b/src/test/java/test/hook/BaseConfigurable.java
new file mode 100644
index 0000000..cb7c8a5
--- /dev/null
+++ b/src/test/java/test/hook/BaseConfigurable.java
@@ -0,0 +1,39 @@
+package test.hook;
+
+import org.testng.IConfigurable;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.BeforeTest;
+
+import java.lang.reflect.Method;
+
+abstract public class BaseConfigurable implements IConfigurable {
+  static int m_hookCount = 0;
+  static boolean m_bs = false;
+  static boolean m_bt = false;
+  static boolean m_bm = false;
+  static boolean m_bc = false;
+  static String m_methodName = null;
+
+  @BeforeSuite
+  public void bs() {
+    m_bs = true;
+  }
+
+  @BeforeTest
+  public void bt() {
+    m_bt = true;
+  }
+
+  @BeforeMethod
+  public void bm(Method m) {
+    m_bm = true;
+  }
+
+  @BeforeClass
+  public void bc() {
+    m_bc = true;
+  }
+
+}
diff --git a/src/test/java/test/hook/ConfigurableFailureTest.java b/src/test/java/test/hook/ConfigurableFailureTest.java
new file mode 100644
index 0000000..5d771a4
--- /dev/null
+++ b/src/test/java/test/hook/ConfigurableFailureTest.java
@@ -0,0 +1,25 @@
+package test.hook;
+
+import org.testng.Assert;
+import org.testng.IConfigurable;
+import org.testng.IConfigureCallBack;
+import org.testng.ITestResult;
+import org.testng.annotations.Test;
+
+/**
+ * Test harness for {@link IConfigurable}
+ */
+public class ConfigurableFailureTest extends BaseConfigurable {
+
+  @Override
+  public void run(IConfigureCallBack callBack, ITestResult testResult) {
+    m_hookCount++;
+    // Not calling the callback
+  }
+
+  @Test
+  public void hookWasNotRun() {
+    Assert.assertFalse(m_bc);
+    Assert.assertFalse(m_bm);
+  }
+}
diff --git a/src/test/java/test/hook/ConfigurableListener.java b/src/test/java/test/hook/ConfigurableListener.java
new file mode 100644
index 0000000..c449e35
--- /dev/null
+++ b/src/test/java/test/hook/ConfigurableListener.java
@@ -0,0 +1,23 @@
+package test.hook;
+
+import org.testng.IConfigurable;
+import org.testng.IConfigureCallBack;
+import org.testng.ITestResult;
+
+import java.lang.reflect.Method;
+
+public class ConfigurableListener implements IConfigurable {
+  static int m_hookCount = 0;
+  static String m_methodName;
+
+  @Override
+  public void run(IConfigureCallBack callBack, ITestResult testResult) {
+    m_hookCount++;
+    Object[] parameters = callBack.getParameters();
+    if (parameters.length > 0) {
+      m_methodName = ((Method) parameters[0]).getName();
+    }
+    callBack.runConfigurationMethod(testResult);
+  }
+
+}
diff --git a/src/test/java/test/hook/ConfigurableSuccessTest.java b/src/test/java/test/hook/ConfigurableSuccessTest.java
new file mode 100644
index 0000000..d07302b
--- /dev/null
+++ b/src/test/java/test/hook/ConfigurableSuccessTest.java
@@ -0,0 +1,31 @@
+package test.hook;
+
+import org.testng.IConfigurable;
+import org.testng.IConfigureCallBack;
+import org.testng.ITestResult;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Method;
+
+
+/**
+ * Test harness for {@link IConfigurable}
+ */
+public class ConfigurableSuccessTest extends BaseConfigurable {
+  @Override
+  public void run(IConfigureCallBack callBack, ITestResult testResult) {
+    m_hookCount++;
+    Object[] parameters = callBack.getParameters();
+    if (parameters.length > 0) {
+      m_methodName = ((Method) parameters[0]).getName();
+    }
+    callBack.runConfigurationMethod(testResult);
+  }
+
+  @Test
+  public void hookWasRun() {
+//    Assert.assertEquals(m_hookCount, 2);
+//    Assert.assertTrue(m_bc);
+//    Assert.assertTrue(m_bm);
+  }
+}
diff --git a/src/test/java/test/hook/ConfigurableSuccessWithListenerTest.java b/src/test/java/test/hook/ConfigurableSuccessWithListenerTest.java
new file mode 100644
index 0000000..5d2f155
--- /dev/null
+++ b/src/test/java/test/hook/ConfigurableSuccessWithListenerTest.java
@@ -0,0 +1,45 @@
+package test.hook;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Method;
+
+@Listeners(ConfigurableListener.class)
+public class ConfigurableSuccessWithListenerTest {
+  static boolean m_bm = false;
+  static boolean m_bc = false;
+  static boolean m_bt;
+  static boolean m_bs;
+
+  @BeforeSuite
+  public void bs() {
+    m_bs = true;
+  }
+
+  @BeforeMethod
+  public void bt() {
+    m_bt = true;
+  }
+
+  @BeforeMethod
+  public void bm(Method m) {
+    m_bm = true;
+  }
+
+  @BeforeClass
+  public void bc() {
+    m_bc = true;
+  }
+
+  @Test
+  public void hookWasRun() {
+    Assert.assertEquals(ConfigurableListener.m_hookCount, 2);
+    Assert.assertTrue(m_bc);
+    Assert.assertTrue(m_bm);
+  }
+}
diff --git a/src/test/java/test/hook/HookFailureTest.java b/src/test/java/test/hook/HookFailureTest.java
new file mode 100644
index 0000000..bc7124f
--- /dev/null
+++ b/src/test/java/test/hook/HookFailureTest.java
@@ -0,0 +1,34 @@
+package test.hook;
+
+import org.testng.IHookCallBack;
+import org.testng.IHookable;
+import org.testng.ITestResult;
+import org.testng.annotations.Test;
+
+public class HookFailureTest implements IHookable {
+  static boolean m_hook = false;
+  static boolean m_testWasRun = false;
+
+  @Override
+  public void run(IHookCallBack callBack, ITestResult testResult) {
+    m_hook = true;
+    // Not invoking the callback:  the method should not be run
+  }
+
+  @Test
+  public void verify() {
+    m_testWasRun = true;
+  }
+
+//  @AfterMethod
+//  public void tearDown() {
+//    Assert.assertTrue(m_hook);
+//    Assert.assertFalse(m_testWasRun);
+//  }
+
+  private void ppp(String string) {
+    System.out.println("[HookFailureTest] " + string);
+  }
+
+
+}
diff --git a/src/test/java/test/hook/HookListener.java b/src/test/java/test/hook/HookListener.java
new file mode 100644
index 0000000..85a30bf
--- /dev/null
+++ b/src/test/java/test/hook/HookListener.java
@@ -0,0 +1,16 @@
+package test.hook;
+
+import org.testng.IHookCallBack;
+import org.testng.IHookable;
+import org.testng.ITestResult;
+
+public class HookListener implements IHookable {
+  public static boolean m_hook = false;
+
+  @Override
+  public void run(IHookCallBack callBack, ITestResult testResult) {
+    m_hook = true;
+    callBack.runTestMethod(testResult);
+  }
+
+}
diff --git a/src/test/java/test/hook/HookSuccess599Test.java b/src/test/java/test/hook/HookSuccess599Test.java
new file mode 100644
index 0000000..307c23f
--- /dev/null
+++ b/src/test/java/test/hook/HookSuccess599Test.java
@@ -0,0 +1,43 @@
+package test.hook;
+
+import org.testng.IHookCallBack;
+import org.testng.IHookable;
+import org.testng.ITestResult;
+import org.testng.Reporter;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * Test harness for {@link IHookable}
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ * @since Aug 01, 2006
+ */
+public class HookSuccess599Test implements IHookable {
+  static boolean m_hook = false;
+  static boolean m_testWasRun = false;
+  static String m_parameter = null;
+
+  @Override
+  public void run(IHookCallBack callBack, ITestResult testResult) {
+    m_hook = true;
+    Object[] parameters = callBack.getParameters();
+    if (parameters.length > 0) {
+      m_parameter = parameters[0].toString();
+    }
+    callBack.runTestMethod(testResult);
+  }
+
+  @DataProvider
+  public Object[][] dp() {
+    return new Object[][] {
+        new Object[] { "foo" }
+    };
+  }
+
+  @Test(dataProvider = "dp", timeOut = 100)
+  public void verify(String name) {
+    m_testWasRun = true;
+    Reporter.log("output from hook test.verify");
+  }
+}
diff --git a/src/test/java/test/hook/HookSuccess862Test.java b/src/test/java/test/hook/HookSuccess862Test.java
new file mode 100644
index 0000000..e92f5e2
--- /dev/null
+++ b/src/test/java/test/hook/HookSuccess862Test.java
@@ -0,0 +1,40 @@
+package test.hook;
+
+import org.testng.*;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import javax.inject.Named;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+public class HookSuccess862Test implements IHookable {
+
+    @Override
+    public void run(IHookCallBack callBack, ITestResult testResult) {
+        Method method = testResult.getMethod().getConstructorOrMethod().getMethod();
+        for (int i = 0; i < callBack.getParameters().length; i++) {
+            Annotation[] annotations = method.getParameterAnnotations()[i];
+            for (Annotation annotation : annotations) {
+                if (annotation instanceof Named) {
+                    Named named = (Named) annotation;
+                    callBack.getParameters()[0] = callBack.getParameters()[0] + named.value();
+                }
+            }
+        }
+        callBack.runTestMethod(testResult);
+    }
+
+    @DataProvider
+    public Object[][] dp() {
+        return new Object[][]{
+                new Object[]{"foo", "test"}
+        };
+    }
+
+    @Test(dataProvider = "dp")
+    public void verify(@Named("bar") String bar, String test) {
+        Assert.assertEquals(bar, "foobar");
+        Assert.assertEquals(test, "test");
+    }
+}
diff --git a/src/test/java/test/hook/HookSuccessTest.java b/src/test/java/test/hook/HookSuccessTest.java
new file mode 100644
index 0000000..4c5e695
--- /dev/null
+++ b/src/test/java/test/hook/HookSuccessTest.java
@@ -0,0 +1,54 @@
+package test.hook;
+
+import org.testng.IHookCallBack;
+import org.testng.IHookable;
+import org.testng.ITestResult;
+import org.testng.Reporter;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * Test harness for {@link IHookable}
+ *
+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
+ * @since Aug 01, 2006
+ */
+public class HookSuccessTest implements IHookable {
+  static boolean m_hook = false;
+  static boolean m_testWasRun = false;
+  static String m_parameter = null;
+
+  @Override
+  public void run(IHookCallBack callBack, ITestResult testResult) {
+    m_hook = true;
+    Object[] parameters = callBack.getParameters();
+    if (parameters.length > 0) {
+      m_parameter = parameters[0].toString();
+    }
+    callBack.runTestMethod(testResult);
+  }
+
+  @DataProvider
+  public Object[][] dp() {
+    return new Object[][] {
+        new Object[] { "foo" }
+    };
+  }
+
+  @Test(dataProvider = "dp")
+  public void verify(String name) {
+    m_testWasRun = true;
+    Reporter.log("output from hook test.verify");
+  }
+
+//  @AfterMethod
+//  public void tearDown() {
+//    Assert.assertTrue(m_hook);
+//    Assert.assertTrue(m_testWasRun);
+//  }
+
+  private void ppp(String string) {
+    System.out.println("[HookTest] " + string);
+  }
+
+}
diff --git a/src/test/java/test/hook/HookSuccessWithListenerTest.java b/src/test/java/test/hook/HookSuccessWithListenerTest.java
new file mode 100644
index 0000000..9d6fb0f
--- /dev/null
+++ b/src/test/java/test/hook/HookSuccessWithListenerTest.java
@@ -0,0 +1,14 @@
+package test.hook;
+
+import org.testng.Assert;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+@Listeners(HookListener.class)
+public class HookSuccessWithListenerTest {
+
+  @Test
+  public void verify() {
+    Assert.assertTrue(HookListener.m_hook);
+  }
+}
diff --git a/src/test/java/test/hook/HookableTest.java b/src/test/java/test/hook/HookableTest.java
new file mode 100644
index 0000000..30787d4
--- /dev/null
+++ b/src/test/java/test/hook/HookableTest.java
@@ -0,0 +1,129 @@
+package test.hook;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+
+/**
+ * Because IHookable and IConfigurable are global, it's safer to run them in a
+ * sub-TestNG object, otherwise they will be run for your entire test suite...
+ *
+ * @author cbeust
+ */
+public class HookableTest extends BaseTest {
+
+  @BeforeMethod
+  public void bm() {
+    HookSuccessTest.m_hook = false;
+    HookSuccessTest.m_testWasRun = false;
+    HookSuccessTest.m_parameter = null;
+    HookSuccess599Test.m_hook = false;
+    HookSuccess599Test.m_testWasRun = false;
+    HookSuccess599Test.m_parameter = null;
+    HookFailureTest.m_hook = false;
+    HookFailureTest.m_testWasRun = false;
+    HookListener.m_hook = false;
+    BaseConfigurable.m_hookCount = 0;
+    BaseConfigurable.m_bc = false;
+    BaseConfigurable.m_bm = false;
+    BaseConfigurable.m_bs = false;
+    BaseConfigurable.m_bt = false;
+    BaseConfigurable.m_methodName = null;
+    ConfigurableListener.m_hookCount = 0;
+    ConfigurableListener.m_methodName = null;
+    ConfigurableSuccessWithListenerTest.m_bc = false;
+    ConfigurableSuccessWithListenerTest.m_bm = false;
+  }
+
+  @Test
+  public void hookSuccess() {
+    addClass(HookSuccessTest.class);
+    run();
+
+    verifyTests("Passed", new String[] { "verify" }, getPassedTests());
+    Assert.assertTrue(HookSuccessTest.m_hook);
+    Assert.assertTrue(HookSuccessTest.m_testWasRun);
+    Assert.assertEquals(HookSuccessTest.m_parameter, "foo");
+  }
+
+  @Test(description = "https://github.com/cbeust/testng/issues/599")
+  public void issue599() {
+    addClass(HookSuccess599Test.class);
+    run();
+
+    verifyTests("Passed", new String[] { "verify" }, getPassedTests());
+    Assert.assertTrue(HookSuccess599Test.m_hook);
+    Assert.assertTrue(HookSuccess599Test.m_testWasRun);
+    Assert.assertEquals(HookSuccess599Test.m_parameter, "foo");
+  }
+
+  @Test(description = "https://github.com/cbeust/testng/pull/862")
+  public void issue862() {
+    addClass(HookSuccess862Test.class);
+    run();
+
+    verifyTests("Passed", new String[] { "verify" }, getPassedTests());
+  }
+
+  @Test
+  public void hookSuccessWithListener() {
+    addClass(HookSuccessWithListenerTest.class);
+    run();
+
+    verifyTests("Passed", new String[] { "verify" }, getPassedTests());
+    Assert.assertTrue(HookListener.m_hook);
+  }
+
+  @Test
+  public void hookFailure() {
+    addClass(HookFailureTest.class);
+    run();
+
+    // To investigate: TestNG still thinks the test passed since it can't know whether
+    // the hook ended up invoking the test or not.
+//    verifyTests("Passed", new String[] { }, getPassedTests());
+    Assert.assertTrue(HookFailureTest.m_hook);
+    Assert.assertFalse(HookFailureTest.m_testWasRun);
+  }
+
+  @Test
+  public void configurableSuccess() {
+    addClass(ConfigurableSuccessTest.class);
+    run();
+
+    Assert.assertEquals(BaseConfigurable.m_hookCount, 4);
+    Assert.assertTrue(BaseConfigurable.m_bc);
+    Assert.assertTrue(BaseConfigurable.m_bm);
+    Assert.assertTrue(BaseConfigurable.m_bs);
+    Assert.assertTrue(BaseConfigurable.m_bt);
+    Assert.assertEquals(BaseConfigurable.m_methodName, "hookWasRun");
+  }
+
+  @Test
+  public void configurableSuccessWithListener() {
+    addClass(ConfigurableSuccessWithListenerTest.class);
+    run();
+
+    Assert.assertEquals(ConfigurableListener.m_hookCount, 4);
+    Assert.assertTrue(ConfigurableSuccessWithListenerTest.m_bs);
+    Assert.assertTrue(ConfigurableSuccessWithListenerTest.m_bt);
+    Assert.assertTrue(ConfigurableSuccessWithListenerTest.m_bc);
+    Assert.assertTrue(ConfigurableSuccessWithListenerTest.m_bm);
+    Assert.assertEquals(ConfigurableListener.m_methodName, "hookWasRun");
+  }
+
+  @Test
+  public void configurableFailure() {
+    addClass(ConfigurableFailureTest.class);
+    run();
+
+    Assert.assertEquals(BaseConfigurable.m_hookCount, 4);
+    Assert.assertFalse(BaseConfigurable.m_bc);
+    Assert.assertFalse(BaseConfigurable.m_bm);
+    Assert.assertFalse(BaseConfigurable.m_bs);
+    Assert.assertFalse(BaseConfigurable.m_bt);
+  }
+
+}
diff --git a/src/test/java/test/inheritance/BaseClassScope.java b/src/test/java/test/inheritance/BaseClassScope.java
new file mode 100644
index 0000000..a0343d0
--- /dev/null
+++ b/src/test/java/test/inheritance/BaseClassScope.java
@@ -0,0 +1,7 @@
+package test.inheritance;
+
+import org.testng.annotations.Test;
+
+@Test
+public class BaseClassScope {
+}
diff --git a/src/test/java/test/inheritance/Child_1.java b/src/test/java/test/inheritance/Child_1.java
new file mode 100644
index 0000000..f1cd9be
--- /dev/null
+++ b/src/test/java/test/inheritance/Child_1.java
@@ -0,0 +1,26 @@
+package test.inheritance;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+public class Child_1 extends ZBase_0 {
+
+  @BeforeMethod
+  public void initDialog() {
+    m_methodList.add("initDialog");
+    ppp("  INIT 1");
+  }
+
+  @AfterMethod
+  public void tearDownDialog() {
+    m_methodList.add("tearDownDialog");
+    ppp("  TEAR_DOWN 1");
+  }
+
+  private static void ppp(String s) {
+    if (m_verbose) {
+      System.out.println("[C1] " + s);
+    }
+  }
+
+}
diff --git a/src/test/java/test/inheritance/ClassScopeTest.java b/src/test/java/test/inheritance/ClassScopeTest.java
new file mode 100644
index 0000000..5a02695
--- /dev/null
+++ b/src/test/java/test/inheritance/ClassScopeTest.java
@@ -0,0 +1,18 @@
+package test.inheritance;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class ClassScopeTest extends BaseClassScope {
+
+  private boolean m_verify = false;
+
+  public void setVerify() {
+    m_verify = true;
+  }
+
+  @Test(dependsOnMethods = "setVerify")
+  public void verify() {
+    Assert.assertTrue(m_verify);
+  }
+}
diff --git a/src/test/java/test/inheritance/DChild_2.java b/src/test/java/test/inheritance/DChild_2.java
new file mode 100644
index 0000000..b5615e3
--- /dev/null
+++ b/src/test/java/test/inheritance/DChild_2.java
@@ -0,0 +1,40 @@
+package test.inheritance;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ * Make sure that configuration inherited methods are invoked in the right
+ * order.  The funny naming is to make sure that the class names do not
+ * play any role in the ordering of methods.
+ *
+ * Created on Sep 8, 2005
+ * @author cbeust
+ */
+public class DChild_2 extends Child_1 {
+
+  @BeforeMethod
+  public void initDialog2() {
+    m_methodList.add("initDialog2");
+    ppp("    INIT 2");
+  }
+
+  @AfterMethod
+  public void tearDownDialog2() {
+    m_methodList.add("tearDownDialog2");
+    ppp("    TEAR_DOWN 2");
+  }
+
+  @Test(groups = {"before" })
+  public void test() {
+    m_methodList.add("test");
+    ppp("      TEST");
+  }
+
+  private static void ppp(String s) {
+    if (m_verbose) {
+      System.out.println("[D2] " + s);
+    }
+  }
+}
diff --git a/src/test/java/test/inheritance/VerifyTest.java b/src/test/java/test/inheritance/VerifyTest.java
new file mode 100644
index 0000000..5a73b7e
--- /dev/null
+++ b/src/test/java/test/inheritance/VerifyTest.java
@@ -0,0 +1,33 @@
+package test.inheritance;
+
+import org.testng.AssertJUnit;
+import org.testng.annotations.Test;
+
+import java.util.List;
+
+public class VerifyTest {
+
+  @Test(dependsOnGroups = {"before"})
+  public void verify() {
+    String[] expected = {
+      "initApplication",
+      "initDialog",
+      "initDialog2",
+      "test",
+      "tearDownDialog2",
+      "tearDownDialog",
+      "tearDownApplication"
+    };
+
+    int i = 0;
+    List<String> l = ZBase_0.getMethodList();
+    AssertJUnit.assertEquals(expected.length, l.size());
+    for (String s : l) {
+      AssertJUnit.assertEquals(expected[i++], s);
+    }
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[VerifyTest] " + s);
+  }
+}
diff --git a/src/test/java/test/inheritance/ZBase_0.java b/src/test/java/test/inheritance/ZBase_0.java
new file mode 100644
index 0000000..0d79923
--- /dev/null
+++ b/src/test/java/test/inheritance/ZBase_0.java
@@ -0,0 +1,40 @@
+package test.inheritance;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeTest;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ZBase_0 {
+  protected static boolean m_verbose = false;
+  protected static List<String> m_methodList = new ArrayList<>();
+
+  @BeforeTest
+  public void beforeTest() {
+    m_methodList = new ArrayList<>();
+  }
+
+  @BeforeMethod
+  public void initApplication() {
+    m_methodList.add("initApplication");
+    ppp("INIT 0");
+  }
+
+  @AfterMethod
+  public void tearDownApplication() {
+    m_methodList.add("tearDownApplication");
+    ppp("TEAR DOWN 0");
+  }
+
+  private static void ppp(String s) {
+    if (m_verbose) {
+      System.out.println("[Z0] " + s);
+    }
+  }
+
+  public static List<String> getMethodList() {
+    return m_methodList;
+  }
+}
diff --git a/src/test/java/test/inheritance/testng234/ChildTest.java b/src/test/java/test/inheritance/testng234/ChildTest.java
new file mode 100644
index 0000000..72abebe
--- /dev/null
+++ b/src/test/java/test/inheritance/testng234/ChildTest.java
@@ -0,0 +1,20 @@
+package test.inheritance.testng234;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+
+public class ChildTest extends ParentTest {
+
+  @BeforeClass
+  public void beforeClassMethod() {
+    Assert.assertTrue(false, "This is so sad... I must skip all my tests ...");
+  }
+
+  @Override
+  @Test
+  public void polymorphicMethod() {
+  }
+
+}
diff --git a/src/test/java/test/inheritance/testng234/ParentTest.java b/src/test/java/test/inheritance/testng234/ParentTest.java
new file mode 100644
index 0000000..705dda8
--- /dev/null
+++ b/src/test/java/test/inheritance/testng234/ParentTest.java
@@ -0,0 +1,13 @@
+package test.inheritance.testng234;
+
+import org.testng.annotations.Test;
+
+public abstract class ParentTest {
+
+  @Test
+  public void executePolymorphicMethod() {
+  }
+
+  protected abstract void polymorphicMethod();
+
+}
diff --git a/src/test/java/test/inheritance/testng234/PolymorphicFailureTest.java b/src/test/java/test/inheritance/testng234/PolymorphicFailureTest.java
new file mode 100644
index 0000000..2113962
--- /dev/null
+++ b/src/test/java/test/inheritance/testng234/PolymorphicFailureTest.java
@@ -0,0 +1,28 @@
+package test.inheritance.testng234;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+import java.util.Arrays;
+
+public class PolymorphicFailureTest extends SimpleBaseTest {
+
+  @Test
+  public void superclassFailureShouldCauseFailure() {
+    TestNG tng = create(ChildTest.class);
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+    assertTestResultsEqual(tla.getSkippedTests(), Arrays.asList(
+        "polymorphicMethod",
+        "executePolymorphicMethod")
+        );
+    Assert.assertEquals(0, tla.getPassedTests().size());
+    Assert.assertEquals(0, tla.getFailedTests().size());
+  }
+}
diff --git a/src/test/java/test/inheritance/testng471/Class1.java b/src/test/java/test/inheritance/testng471/Class1.java
new file mode 100644
index 0000000..75a850b
--- /dev/null
+++ b/src/test/java/test/inheritance/testng471/Class1.java
@@ -0,0 +1,32 @@
+package test.inheritance.testng471;
+
+import org.junit.Assert;
+import org.testng.annotations.*;
+
+public class Class1 extends SuperClass1 {
+
+  @BeforeClass
+  public void beforeClass1() {
+  }
+
+  @AfterClass
+  public void afterClass1() {
+  }
+
+  @BeforeMethod
+  public void beforeMethodClass1() {
+  }
+
+  @AfterMethod
+  public void afterMethodClass1() {
+    Assert.fail();
+  }
+
+  @Test
+  public void test1_1() {
+  }
+
+  @Test
+  public void test1_2() {
+  }
+}
diff --git a/src/test/java/test/inheritance/testng471/Class2.java b/src/test/java/test/inheritance/testng471/Class2.java
new file mode 100644
index 0000000..ba60024
--- /dev/null
+++ b/src/test/java/test/inheritance/testng471/Class2.java
@@ -0,0 +1,30 @@
+package test.inheritance.testng471;
+
+import org.testng.annotations.*;
+
+public class Class2 extends SuperClass1 {
+
+  @BeforeClass
+  public void beforeClass2() {
+  }
+
+  @AfterClass
+  public void afterClass2() {
+  }
+
+  @BeforeMethod
+  public void beforeMethodClass2() {
+  }
+
+  @AfterMethod
+  public void afterMethodClass2() {
+  }
+
+  @Test
+  public void test2_1() {
+  }
+
+  @Test
+  public void test2_2() {
+  }
+}
diff --git a/src/test/java/test/inheritance/testng471/Class3.java b/src/test/java/test/inheritance/testng471/Class3.java
new file mode 100644
index 0000000..f8d94f0
--- /dev/null
+++ b/src/test/java/test/inheritance/testng471/Class3.java
@@ -0,0 +1,34 @@
+package test.inheritance.testng471;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class Class3 extends SuperClass2 {
+
+  @BeforeClass
+  public void beforeClass3() {
+  }
+
+  @AfterClass
+  public void afterClass3() {
+  }
+
+  @BeforeMethod
+  public void beforeMethodClass3() {
+  }
+
+  @AfterMethod
+  public void afterMethodClass3() {
+  }
+
+  @Test
+  public void test3_1() {
+  }
+
+  @Test
+  public void test3_2() {
+  }
+}
diff --git a/src/test/java/test/inheritance/testng471/SuperClass1.java b/src/test/java/test/inheritance/testng471/SuperClass1.java
new file mode 100644
index 0000000..3645efd
--- /dev/null
+++ b/src/test/java/test/inheritance/testng471/SuperClass1.java
@@ -0,0 +1,10 @@
+package test.inheritance.testng471;
+
+import org.testng.annotations.BeforeClass;
+
+public class SuperClass1 {
+
+  @BeforeClass
+  public void beforeSuperClass1() {
+  }
+}
diff --git a/src/test/java/test/inheritance/testng471/SuperClass2.java b/src/test/java/test/inheritance/testng471/SuperClass2.java
new file mode 100644
index 0000000..87ad3f7
--- /dev/null
+++ b/src/test/java/test/inheritance/testng471/SuperClass2.java
@@ -0,0 +1,10 @@
+package test.inheritance.testng471;
+
+import org.testng.annotations.BeforeClass;
+
+public class SuperClass2 {
+
+  @BeforeClass
+  public void beforeSuperClass2() {
+  }
+}
diff --git a/src/test/java/test/inheritance/testng471/TestNG471.java b/src/test/java/test/inheritance/testng471/TestNG471.java
new file mode 100644
index 0000000..3315140
--- /dev/null
+++ b/src/test/java/test/inheritance/testng471/TestNG471.java
@@ -0,0 +1,42 @@
+package test.inheritance.testng471;
+
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.InvokedMethodNameListener;
+import test.SimpleBaseTest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class TestNG471 extends SimpleBaseTest {
+
+  @Test
+  public void test_classes_should_not_be_skipped_when_a_after_method_fails() {
+    TestNG tng = create(Class1.class, Class2.class, Class3.class);
+    tng.setPreserveOrder(true);
+    tng.setVerbose(10);
+    InvokedMethodNameListener listener = new InvokedMethodNameListener();
+    tng.setPreserveOrder(true);
+    tng.addListener(listener);
+
+    tng.run();
+    assertThat(listener.getFailedMethodNames()).containsExactly("afterMethodClass1");
+    assertThat(listener.getSkippedMethodNames()).containsExactly("test1_2");
+    assertThat(listener.getSucceedMethodNames()).containsExactly(
+        "beforeSuperClass1",
+          "beforeClass1",
+            "beforeMethodClass1", "test1_1", // "afterMethodClass1" failed
+            // "beforeMethodClass1", "test1_2" and "afterMethodClass1" skipped
+            // "afterClass1" skipped
+        "beforeSuperClass1",
+          "beforeClass2",
+            "beforeMethodClass2", "test2_1", "afterMethodClass2",
+            "beforeMethodClass2", "test2_2", "afterMethodClass2",
+          "afterClass2",
+        "beforeSuperClass2",
+          "beforeClass3",
+            "beforeMethodClass3", "test3_1", "afterMethodClass3",
+            "beforeMethodClass3", "test3_2", "afterMethodClass3",
+          "afterClass3");
+  }
+}
diff --git a/src/test/java/test/inheritance/testng739/A.java b/src/test/java/test/inheritance/testng739/A.java
new file mode 100644
index 0000000..cb84ce4
--- /dev/null
+++ b/src/test/java/test/inheritance/testng739/A.java
@@ -0,0 +1,17 @@
+package test.inheritance.testng739;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class A extends BaseClass {
+
+  @BeforeClass
+  public void beforeClassA() {
+    Assert.fail();
+  }
+
+  @Test
+  public void testA() {
+  }
+}
diff --git a/src/test/java/test/inheritance/testng739/B.java b/src/test/java/test/inheritance/testng739/B.java
new file mode 100644
index 0000000..ede5fde
--- /dev/null
+++ b/src/test/java/test/inheritance/testng739/B.java
@@ -0,0 +1,10 @@
+package test.inheritance.testng739;
+
+import org.testng.annotations.Test;
+
+public class B extends BaseClass {
+
+  @Test
+  public void testB() {
+  }
+}
diff --git a/src/test/java/test/inheritance/testng739/BaseClass.java b/src/test/java/test/inheritance/testng739/BaseClass.java
new file mode 100644
index 0000000..8186cac
--- /dev/null
+++ b/src/test/java/test/inheritance/testng739/BaseClass.java
@@ -0,0 +1,9 @@
+package test.inheritance.testng739;
+
+import org.testng.annotations.BeforeClass;
+
+public class BaseClass {
+
+  @BeforeClass
+  public void beforeBaseClass() {}
+}
diff --git a/src/test/java/test/inheritance/testng739/TestNG739.java b/src/test/java/test/inheritance/testng739/TestNG739.java
new file mode 100644
index 0000000..f837e51
--- /dev/null
+++ b/src/test/java/test/inheritance/testng739/TestNG739.java
@@ -0,0 +1,26 @@
+package test.inheritance.testng739;
+
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.InvokedMethodNameListener;
+import test.SimpleBaseTest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class TestNG739 extends SimpleBaseTest {
+
+  @Test
+  public void test_classes_should_be_skipped_when_a_before_class_fails() {
+    TestNG tng = create(A.class, B.class);
+    tng.setPreserveOrder(true);
+    InvokedMethodNameListener listener = new InvokedMethodNameListener();
+    tng.setPreserveOrder(true);
+    tng.addListener(listener);
+
+    tng.run();
+    assertThat(listener.getSucceedMethodNames()).containsExactly("beforeBaseClass", "beforeBaseClass", "testB");
+    assertThat(listener.getFailedMethodNames()).containsExactly("beforeClassA");
+    assertThat(listener.getSkippedMethodNames()).containsExactly("testA");
+  }
+}
diff --git a/src/test/java/test/inject/InjectAfterMethodWithTestResultSampleTest.java b/src/test/java/test/inject/InjectAfterMethodWithTestResultSampleTest.java
new file mode 100644
index 0000000..c670ab2
--- /dev/null
+++ b/src/test/java/test/inject/InjectAfterMethodWithTestResultSampleTest.java
@@ -0,0 +1,47 @@
+package test.inject;
+
+import org.testng.ITestResult;
+import org.testng.SkipException;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Method;
+
+public class InjectAfterMethodWithTestResultSampleTest {
+  static int m_success;
+
+  @Test
+  public void pass() {}
+
+  @Test
+  public void fail() {
+    throw new RuntimeException();
+  }
+
+  @Test
+  public void skip() {
+    throw new SkipException("Skipped");
+  }
+
+  @BeforeClass
+  public void init() {
+    m_success = 3;
+  }
+
+  @BeforeMethod
+  public void before(Method m, ITestResult r) {
+    System.out.println("Before result: " + r);
+  }
+
+  @AfterMethod
+  public void after(Method m, ITestResult r) {
+    String name = m.getName();
+    if (("pass".equals(name) && r.getStatus() == ITestResult.SUCCESS)
+        || ("fail".equals(name) && r.getStatus() == ITestResult.FAILURE)
+        || ("skip".equals(name) && r.getStatus() == ITestResult.SKIP)) {
+          m_success--;
+        }
+  }
+}
diff --git a/src/test/java/test/inject/InjectAfterMethodWithTestResultTest.java b/src/test/java/test/inject/InjectAfterMethodWithTestResultTest.java
new file mode 100644
index 0000000..14c4c60
--- /dev/null
+++ b/src/test/java/test/inject/InjectAfterMethodWithTestResultTest.java
@@ -0,0 +1,18 @@
+package test.inject;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+public class InjectAfterMethodWithTestResultTest extends SimpleBaseTest {
+
+  @Test
+  public void verifyTestResultInjection() {
+    TestNG tng = create(InjectAfterMethodWithTestResultSampleTest.class);
+    tng.run();
+
+    Assert.assertEquals(0, InjectAfterMethodWithTestResultSampleTest.m_success);
+  }
+}
diff --git a/src/test/java/test/inject/InjectBeforeAndAfterMethodsWithTestResultSampleTest.java b/src/test/java/test/inject/InjectBeforeAndAfterMethodsWithTestResultSampleTest.java
new file mode 100644
index 0000000..1b5eb54
--- /dev/null
+++ b/src/test/java/test/inject/InjectBeforeAndAfterMethodsWithTestResultSampleTest.java
@@ -0,0 +1,53 @@
+package test.inject;
+
+import org.testng.Assert;
+import org.testng.ITestResult;
+import org.testng.SkipException;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Method;
+
+public class InjectBeforeAndAfterMethodsWithTestResultSampleTest {
+  static int m_success;
+  private ITestResult m_testResult;
+
+  @Test
+  public void pass() {
+    Assert.assertEquals(m_testResult.getAttribute("before"), 10);
+  }
+
+  @Test
+  public void fail() {
+    throw new RuntimeException();
+  }
+
+  @Test
+  public void skip() {
+    throw new SkipException("Skipped");
+  }
+
+  @BeforeClass
+  public void init() {
+    m_success = 3;
+  }
+
+  @BeforeMethod
+  public void before(Method m, ITestResult r) {
+    m_testResult = r;
+    r.setAttribute("before", 10);
+  }
+
+  @AfterMethod
+  public void after(Method m, ITestResult r) {
+    String name = m.getName();
+    Assert.assertEquals(r, m_testResult);
+    if (("pass".equals(name) && r.getStatus() == ITestResult.SUCCESS)
+        || ("fail".equals(name) && r.getStatus() == ITestResult.FAILURE)
+        || ("skip".equals(name) && r.getStatus() == ITestResult.SKIP)) {
+          m_success--;
+        }
+  }
+}
diff --git a/src/test/java/test/inject/InjectBeforeMethodTest.java b/src/test/java/test/inject/InjectBeforeMethodTest.java
new file mode 100644
index 0000000..f5f034a
--- /dev/null
+++ b/src/test/java/test/inject/InjectBeforeMethodTest.java
@@ -0,0 +1,48 @@
+package test.inject;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Method;
+
+public class InjectBeforeMethodTest {
+  private int m_beforeIndex = 0;
+  private int m_afterIndex = 0;
+  private static final Object[][] DATA = {
+      new Object[] { "a" },
+      new Object[] { "b" },
+  };
+
+  @BeforeMethod
+  public void before(Object[] parameters) {
+    Assert.assertEquals(DATA[m_beforeIndex], parameters);
+    m_beforeIndex++;
+  }
+
+  @BeforeMethod
+  public void before2(Object[] parameters, Method m) {
+  }
+
+  @BeforeMethod
+  public void before3(Method m, Object[] parameters) {
+  }
+
+  @DataProvider
+  public Object[][] dp() {
+    return DATA;
+  }
+
+  @AfterMethod
+  public void after(Object[] parameters) {
+    Assert.assertEquals(DATA[m_afterIndex], parameters);
+    m_afterIndex++;
+  }
+
+  @Test(dataProvider = "dp")
+  public void f(String a) {
+  }
+
+}
diff --git a/src/test/java/test/inject/InjectDataProviderTest.java b/src/test/java/test/inject/InjectDataProviderTest.java
new file mode 100644
index 0000000..df90ffa
--- /dev/null
+++ b/src/test/java/test/inject/InjectDataProviderTest.java
@@ -0,0 +1,59 @@
+package test.inject;
+
+import org.testng.ITestContext;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import test.dataprovider.MyIterator;
+
+import java.util.Iterator;
+
+/**
+ * Test that injection works for data providers.
+ *
+ * @author Cedric Beust, Mar 3, 2010
+ *
+ */
+public class InjectDataProviderTest {
+
+  @DataProvider
+  public Object[][] dp1() {
+    return new Object[][] {
+      new Object[] { 1, "a" },
+      new Object[] { 2, "b" },
+    };
+  }
+
+  @Test(dataProvider = "dp1", enabled = true)
+  public void dpObject1(Integer n, ITestContext ctx, String a) {
+  }
+
+  @Test(dataProvider = "dp1", enabled = true)
+  public void dpObject2(ITestContext ctx, Integer n, String a) {
+  }
+
+  @Test(dataProvider = "dp1", enabled = true)
+  public void dpObject3(Integer n, String a, ITestContext ctx) {
+  }
+
+  @DataProvider
+  public Iterator<Object[]> dp2() {
+    return new MyIterator(
+    new Object[][] {
+      new Object[] { 1, "a" },
+      new Object[] { 2, "b" },
+    });
+  }
+
+  @Test(dataProvider = "dp2", enabled = false)
+  public void dpIterator1(Integer n, ITestContext ctx, String a) {
+  }
+
+  @Test(dataProvider = "dp2", enabled = false)
+  public void dpIterator2(ITestContext ctx, Integer n, String a) {
+  }
+
+  @Test(dataProvider = "dp2", enabled = false)
+  public void dpIterator3(Integer n, String a, ITestContext ctx) {
+  }
+}
diff --git a/src/test/java/test/inject/InjectTestContextTest.java b/src/test/java/test/inject/InjectTestContextTest.java
new file mode 100644
index 0000000..99b3a41
--- /dev/null
+++ b/src/test/java/test/inject/InjectTestContextTest.java
@@ -0,0 +1,32 @@
+package test.inject;
+
+import org.testng.Assert;
+import org.testng.ITestContext;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlTest;
+
+import test.SimpleBaseTest;
+
+public class InjectTestContextTest extends SimpleBaseTest {
+
+  @Test(enabled = false)
+  public void verifyTestContextInjection(ITestContext tc, XmlTest xmlTest) {
+    TestNG tng = create();
+    tng.setTestClasses(new Class[] { Sample.class });
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+    Assert.assertEquals(xmlTest.getName(), "Injection");
+    Assert.assertEquals(tla.getPassedTests().size(), 1);
+    Assert.assertEquals(tla.getPassedTests().get(0).getMethod().getMethodName(), "f");
+  }
+
+  @Parameters("string")
+  @Test(enabled = true)
+  public void injectionAndParameters(String s, ITestContext ctx) {
+  }
+}
diff --git a/src/test/java/test/inject/InjectTestResultTest.java b/src/test/java/test/inject/InjectTestResultTest.java
new file mode 100644
index 0000000..5325a45
--- /dev/null
+++ b/src/test/java/test/inject/InjectTestResultTest.java
@@ -0,0 +1,18 @@
+package test.inject;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+public class InjectTestResultTest extends SimpleBaseTest {
+
+  @Test
+  public void verifyTestResultInjection() {
+    TestNG tng = create(InjectBeforeAndAfterMethodsWithTestResultSampleTest.class);
+    tng.run();
+
+    Assert.assertEquals(0, InjectBeforeAndAfterMethodsWithTestResultSampleTest.m_success);
+  }
+}
diff --git a/src/test/java/test/inject/NoInjectionTest.java b/src/test/java/test/inject/NoInjectionTest.java
new file mode 100644
index 0000000..9f1d6ba
--- /dev/null
+++ b/src/test/java/test/inject/NoInjectionTest.java
@@ -0,0 +1,37 @@
+package test.inject;
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.NoInjection;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Method;
+
+/**
+ * Test the @NoInjection annotation.
+ *
+ * @author cbeust
+ */
+public class NoInjectionTest {
+
+  @DataProvider(name = "provider")
+  public Object[][] provide() throws Exception {
+      return new Object[][] { { CC.class.getMethod("f") } };
+  }
+
+  @Test(dataProvider = "provider")
+  public void withoutInjection(@NoInjection Method m) {
+      Assert.assertEquals(m.getName(), "f");
+  }
+
+  @Test(dataProvider = "provider")
+  public void withInjection(Method m) {
+      Assert.assertEquals(m.getName(), "withInjection");
+  }
+}
+
+class CC {
+
+  public void f() {
+  }
+}
diff --git a/src/test/java/test/inject/Sample.java b/src/test/java/test/inject/Sample.java
new file mode 100644
index 0000000..8aca0fe
--- /dev/null
+++ b/src/test/java/test/inject/Sample.java
@@ -0,0 +1,18 @@
+package test.inject;
+
+import org.testng.Assert;
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+import org.testng.annotations.Test;
+
+public class Sample {
+
+  @Test
+  public void f(ITestContext tc) {
+    Assert.assertNotNull(tc);
+    ITestNGMethod[] allMethods = tc.getAllTestMethods();
+    Assert.assertEquals(allMethods.length, 1);
+    Assert.assertEquals(allMethods[0].getMethod().getName(),"f");
+
+  }
+}
diff --git a/src/test/java/test/interleavedorder/BaseTestClass.java b/src/test/java/test/interleavedorder/BaseTestClass.java
new file mode 100644
index 0000000..8d0f182
--- /dev/null
+++ b/src/test/java/test/interleavedorder/BaseTestClass.java
@@ -0,0 +1,25 @@
+package test.interleavedorder;
+
+import org.testng.annotations.Test;
+
+
+public class BaseTestClass {
+  @Test
+  public void testOne() {
+    ppp("test1");
+    InterleavedInvocationTest.LOG.add("test1");
+  }
+
+  @Test
+  public void testTwo() {
+    ppp("test2");
+    InterleavedInvocationTest.LOG.add("test2");
+  }
+
+  private void ppp(String s) {
+    if (false) {
+      System.out.println(getClass().toString() + " " + s);
+    }
+  }
+
+}
diff --git a/src/test/java/test/interleavedorder/InterleavedInvocationTest.java b/src/test/java/test/interleavedorder/InterleavedInvocationTest.java
new file mode 100644
index 0000000..e160964
--- /dev/null
+++ b/src/test/java/test/interleavedorder/InterleavedInvocationTest.java
@@ -0,0 +1,52 @@
+package test.interleavedorder;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+import testhelper.OutputDirectoryPatch;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class InterleavedInvocationTest extends BaseTest {
+  public static List<String> LOG = new ArrayList<>();
+
+  @BeforeTest
+  public void beforeTest() {
+    LOG = new ArrayList<>();
+  }
+
+  private void verifyInvocation(int number, List<String> log, int index) {
+    Assert.assertEquals(log.get(index), "beforeTestChild" + number + "Class");
+    Assert.assertTrue(("test1".equals(log.get(index + 1)) && "test2".equals(LOG.get(index + 2)))
+        || ("test2".equals(LOG.get(index + 1)) && "test1".equals(LOG.get(index + 2))),
+        "test methods were not invoked correctly");
+    Assert.assertEquals(log.get(index + 3), "afterTestChild" + number + "Class");
+  }
+
+  @Test
+  public void invocationOrder() {
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG testng = new TestNG();
+    testng.setOutputDirectory(OutputDirectoryPatch.getOutputDirectory());
+    testng.setTestClasses(new Class[] { TestChild1.class, TestChild2.class });
+    testng.addListener(tla);
+    testng.setVerbose(0);
+    testng.run();
+
+    Assert.assertEquals(LOG.size(), 8, LOG.toString());
+    int number1 = "beforeTestChild1Class".equals(LOG.get(0)) ? 1 : 2;
+    int number2 = number1 == 1 ? 2 : 1;
+    verifyInvocation(number1, LOG, 0);
+    verifyInvocation(number2, LOG, 4);
+  }
+
+  public static void ppp(String s) {
+    System.out.println("[InterleavedTest] " + s);
+  }
+}
diff --git a/src/test/java/test/interleavedorder/TestChild1.java b/src/test/java/test/interleavedorder/TestChild1.java
new file mode 100644
index 0000000..baf474a
--- /dev/null
+++ b/src/test/java/test/interleavedorder/TestChild1.java
@@ -0,0 +1,25 @@
+package test.interleavedorder;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+
+
+public class TestChild1 extends BaseTestClass {
+  @BeforeClass
+  public void beforeTestChildOneClass() {
+    ppp("beforeTestChild1Class");
+    InterleavedInvocationTest.LOG.add("beforeTestChild1Class");
+  }
+
+  @AfterClass
+  public void afterTestChildOneClass() {
+    ppp("afterTestChild1Class");
+    InterleavedInvocationTest.LOG.add("afterTestChild1Class");
+  }
+
+  private void ppp(String s) {
+    if (false) {
+      System.out.println("[TestChild1] " + s);
+    }
+  }
+}
diff --git a/src/test/java/test/interleavedorder/TestChild2.java b/src/test/java/test/interleavedorder/TestChild2.java
new file mode 100644
index 0000000..2786ad7
--- /dev/null
+++ b/src/test/java/test/interleavedorder/TestChild2.java
@@ -0,0 +1,26 @@
+package test.interleavedorder;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+
+
+public class TestChild2 extends BaseTestClass {
+  @BeforeClass
+  public void beforeTestChildTwoClass() {
+    ppp("beforeTestChild2Class");
+    InterleavedInvocationTest.LOG.add("beforeTestChild2Class");
+  }
+
+  @AfterClass
+  public void afterTestChildTwoClass() {
+    ppp("afterTestChild2Class");
+    InterleavedInvocationTest.LOG.add("afterTestChild2Class");
+  }
+
+  private void ppp(String s) {
+    if (false) {
+      System.out.println("[TestChild2] " + s);
+    }
+  }
+
+}
diff --git a/src/test/java/test/interleavedorder/testng-order.xml b/src/test/java/test/interleavedorder/testng-order.xml
new file mode 100644
index 0000000..0483b35
--- /dev/null
+++ b/src/test/java/test/interleavedorder/testng-order.xml
@@ -0,0 +1,11 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+  
+<suite name="Order" verbose="2" parallel="false" annotations="javadoc">
+
+  <test name="Test order invocation">
+    <classes>
+       <class name="test.interleavedorder.InterleavedInvocationTest" />
+    </classes>
+  </test>
+</suite>
+
diff --git a/src/test/java/test/invocationcount/DataProviderBase.java b/src/test/java/test/invocationcount/DataProviderBase.java
new file mode 100644
index 0000000..65dea0f
--- /dev/null
+++ b/src/test/java/test/invocationcount/DataProviderBase.java
@@ -0,0 +1,20 @@
+package test.invocationcount;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class DataProviderBase {
+  @Test(dataProvider = "dp")
+  public void f(Integer n) {
+  }
+
+  @DataProvider
+  public Object[][] dp() {
+    return new Integer[][] {
+        new Integer[] { 0 },
+        new Integer[] { 1 },
+        new Integer[] { 2 },
+    };
+  }
+
+}
diff --git a/src/test/java/test/invocationcount/DataProviderFalseFalseTest.java b/src/test/java/test/invocationcount/DataProviderFalseFalseTest.java
new file mode 100644
index 0000000..20fbe1e
--- /dev/null
+++ b/src/test/java/test/invocationcount/DataProviderFalseFalseTest.java
@@ -0,0 +1,12 @@
+package test.invocationcount;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+public class DataProviderFalseFalseTest extends DataProviderBase {
+  @BeforeMethod(firstTimeOnly = false)
+  public void beforeMethod() {}
+
+  @AfterMethod(lastTimeOnly = false)
+  public void afterMethod() {}
+}
diff --git a/src/test/java/test/invocationcount/DataProviderFalseTrueTest.java b/src/test/java/test/invocationcount/DataProviderFalseTrueTest.java
new file mode 100644
index 0000000..ff1f0a5
--- /dev/null
+++ b/src/test/java/test/invocationcount/DataProviderFalseTrueTest.java
@@ -0,0 +1,13 @@
+package test.invocationcount;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+public class DataProviderFalseTrueTest extends DataProviderBase {
+  @BeforeMethod(firstTimeOnly = false)
+  public void beforeMethod() {}
+
+  @AfterMethod(lastTimeOnly = true)
+  public void afterMethod() {}
+
+}
diff --git a/src/test/java/test/invocationcount/DataProviderTrueFalseTest.java b/src/test/java/test/invocationcount/DataProviderTrueFalseTest.java
new file mode 100644
index 0000000..38c8ad7
--- /dev/null
+++ b/src/test/java/test/invocationcount/DataProviderTrueFalseTest.java
@@ -0,0 +1,12 @@
+package test.invocationcount;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+public class DataProviderTrueFalseTest extends DataProviderBase {
+  @BeforeMethod(firstTimeOnly = true)
+  public void beforeMethod() {}
+
+  @AfterMethod(lastTimeOnly = false)
+  public void afterMethod() {}
+}
diff --git a/src/test/java/test/invocationcount/DataProviderTrueTrueTest.java b/src/test/java/test/invocationcount/DataProviderTrueTrueTest.java
new file mode 100644
index 0000000..e21706f
--- /dev/null
+++ b/src/test/java/test/invocationcount/DataProviderTrueTrueTest.java
@@ -0,0 +1,13 @@
+package test.invocationcount;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+public class DataProviderTrueTrueTest extends DataProviderBase {
+  @BeforeMethod(firstTimeOnly = true)
+  public void beforeMethod() {}
+
+  @AfterMethod(lastTimeOnly = true)
+  public void afterMethod() {}
+
+}
diff --git a/src/test/java/test/invocationcount/FailedInvocationCount.java b/src/test/java/test/invocationcount/FailedInvocationCount.java
new file mode 100644
index 0000000..43ab19e
--- /dev/null
+++ b/src/test/java/test/invocationcount/FailedInvocationCount.java
@@ -0,0 +1,21 @@
+package test.invocationcount;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class FailedInvocationCount {
+  int m_count;
+
+  @BeforeClass
+  public void setUp() {
+    m_count = 0;
+  }
+
+  @Test(invocationCount = 10)
+  public void f() {
+    if (m_count++ > 3) {
+      throw new RuntimeException();
+    }
+  }
+
+}
diff --git a/src/test/java/test/invocationcount/FailedInvocationCount2.java b/src/test/java/test/invocationcount/FailedInvocationCount2.java
new file mode 100644
index 0000000..d7861a7
--- /dev/null
+++ b/src/test/java/test/invocationcount/FailedInvocationCount2.java
@@ -0,0 +1,30 @@
+package test.invocationcount;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class FailedInvocationCount2 {
+  int m_count;
+  int m_count2;
+
+  @BeforeClass
+  public void setUp() {
+    m_count = 0;
+    m_count2 = 0;
+  }
+
+  @Test(invocationCount = 10, skipFailedInvocations = true)
+  public void shouldSkipFromAnnotation() {
+    if (m_count++ > 3) {
+      throw new RuntimeException();
+    }
+  }
+
+  @Test(invocationCount = 10, skipFailedInvocations = false)
+  public void shouldNotSkipFromAnnotation() {
+    if (m_count2++ > 3) {
+      throw new RuntimeException();
+    }
+  }
+
+}
diff --git a/src/test/java/test/invocationcount/FailedInvocationCountTest.java b/src/test/java/test/invocationcount/FailedInvocationCountTest.java
new file mode 100644
index 0000000..6c3a9d7
--- /dev/null
+++ b/src/test/java/test/invocationcount/FailedInvocationCountTest.java
@@ -0,0 +1,50 @@
+package test.invocationcount;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+public class FailedInvocationCountTest {
+
+  private void runTest(boolean skip,
+    int passed, int failed, int skipped)
+  {
+    TestNG testng = new TestNG();
+    testng.setVerbose(0);
+    testng.setSkipFailedInvocationCounts(skip);
+    testng.setTestClasses(new Class[] { FailedInvocationCount.class });
+    TestListenerAdapter tla = new TestListenerAdapter();
+    testng.addListener(tla);
+    testng.run();
+
+    Assert.assertEquals(tla.getPassedTests().size(), passed);
+    Assert.assertEquals(tla.getFailedTests().size(), failed);
+    Assert.assertEquals(tla.getSkippedTests().size(), skipped);
+  }
+
+  @Test
+  public void verifyGloballyShouldStop() {
+    runTest(true, 4, 1, 5);
+  }
+
+  @Test
+  public void verifyGloballyShouldNotStop() {
+    runTest(false, 4, 6, 0);
+  }
+
+  @Test
+  public void verifyAttributeShouldStop() {
+    TestNG testng = new TestNG();
+    testng.setVerbose(0);
+    testng.setTestClasses(new Class[] { FailedInvocationCount2.class });
+    TestListenerAdapter tla = new TestListenerAdapter();
+    testng.addListener(tla);
+    testng.run();
+
+    Assert.assertEquals(tla.getPassedTests().size(), 8);
+    Assert.assertEquals(tla.getFailedTests().size(), 7);
+    Assert.assertEquals(tla.getSkippedTests().size(), 5);
+
+  }
+}
diff --git a/src/test/java/test/invocationcount/FirstAndLastTimeTest.java b/src/test/java/test/invocationcount/FirstAndLastTimeTest.java
new file mode 100644
index 0000000..8d31a06
--- /dev/null
+++ b/src/test/java/test/invocationcount/FirstAndLastTimeTest.java
@@ -0,0 +1,119 @@
+package test.invocationcount;
+
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import java.util.List;
+
+import test.InvokedMethodNameListener;
+import test.SimpleBaseTest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Test various combination of @BeforeMethod(firstTimeOnly = true/false) and
+ * @AfterMethod(lastTimeOnly = true/false) with invocation counts and data
+ * providers.
+ * @author cbeust@google.com
+ *
+ */
+public class FirstAndLastTimeTest extends SimpleBaseTest {
+  @Test
+  public void verifyDataProviderFalseFalse() {
+    List<String> invokedMethodNames = run(DataProviderFalseFalseTest.class);
+
+    assertThat(invokedMethodNames).containsExactly(
+        "beforeMethod", "f", "afterMethod",
+        "beforeMethod", "f", "afterMethod",
+        "beforeMethod", "f", "afterMethod"
+    );
+  }
+
+  @Test
+  public void verifyDataProviderTrueFalse() {
+    List<String> invokedMethodNames = run(DataProviderTrueFalseTest.class);
+
+    assertThat(invokedMethodNames).containsExactly(
+        "beforeMethod", "f", "afterMethod",
+        "f", "afterMethod",
+        "f", "afterMethod"
+    );
+  }
+
+  @Test
+  public void verifyDataProviderFalseTrue() {
+    List<String> invokedMethodNames = run(DataProviderFalseTrueTest.class);
+
+    assertThat(invokedMethodNames).containsExactly(
+        "beforeMethod", "f",
+        "beforeMethod", "f",
+        "beforeMethod", "f", "afterMethod"
+    );
+  }
+
+  @Test
+  public void verifyDataProviderTrueTrue() {
+    List<String> invokedMethodNames = run(DataProviderTrueTrueTest.class);
+
+    assertThat(invokedMethodNames).containsExactly(
+        "beforeMethod", "f",
+        "f",
+        "f", "afterMethod"
+    );
+  }
+
+  @Test
+  public void verifyInvocationCountFalseFalse() {
+    List<String> invokedMethodNames = run(InvocationCountFalseFalseTest.class);
+
+    assertThat(invokedMethodNames).containsExactly(
+        "beforeMethod", "f", "afterMethod",
+        "beforeMethod", "f", "afterMethod",
+        "beforeMethod", "f", "afterMethod"
+    );
+  }
+
+  @Test
+  public void verifyInvocationCountTrueFalse() {
+    List<String> invokedMethodNames = run(InvocationCountTrueFalseTest.class);
+
+    assertThat(invokedMethodNames).containsExactly(
+        "beforeMethod", "f", "afterMethod",
+        "f", "afterMethod",
+        "f", "afterMethod"
+    );
+  }
+
+  @Test
+  public void verifyInvocationCountFalseTrue() {
+    List<String> invokedMethodNames = run(InvocationCountFalseTrueTest.class);
+
+    assertThat(invokedMethodNames).containsExactly(
+        "beforeMethod", "f",
+        "beforeMethod", "f",
+        "beforeMethod", "f", "afterMethod"
+    );
+  }
+
+  @Test
+  public void verifyInvocationCountTrueTrue() {
+    List<String> invokedMethodNames = run(InvocationCountTrueTrueTest.class);
+
+    assertThat(invokedMethodNames).containsExactly(
+        "beforeMethod", "f",
+        "f",
+        "f", "afterMethod"
+    );
+  }
+
+  private static List<String> run(Class<?> cls) {
+    TestNG tng = create(cls);
+    InvokedMethodNameListener listener = new InvokedMethodNameListener();
+    tng.addListener(listener);
+
+    tng.run();
+
+    return listener.getInvokedMethodNames();
+  }
+
+}
diff --git a/src/test/java/test/invocationcount/InvocationBase.java b/src/test/java/test/invocationcount/InvocationBase.java
new file mode 100644
index 0000000..fe80bf8
--- /dev/null
+++ b/src/test/java/test/invocationcount/InvocationBase.java
@@ -0,0 +1,9 @@
+package test.invocationcount;
+
+import org.testng.annotations.Test;
+
+public class InvocationBase {
+  @Test(invocationCount = 3)
+  public void f() {
+  }
+}
diff --git a/src/test/java/test/invocationcount/InvocationCountFalseFalseTest.java b/src/test/java/test/invocationcount/InvocationCountFalseFalseTest.java
new file mode 100644
index 0000000..e325f09
--- /dev/null
+++ b/src/test/java/test/invocationcount/InvocationCountFalseFalseTest.java
@@ -0,0 +1,12 @@
+package test.invocationcount;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+public class InvocationCountFalseFalseTest extends InvocationBase {
+  @BeforeMethod(firstTimeOnly = false)
+  public void beforeMethod() {}
+
+  @AfterMethod(lastTimeOnly = false)
+  public void afterMethod() {}
+}
diff --git a/src/test/java/test/invocationcount/InvocationCountFalseTrueTest.java b/src/test/java/test/invocationcount/InvocationCountFalseTrueTest.java
new file mode 100644
index 0000000..7e5306b
--- /dev/null
+++ b/src/test/java/test/invocationcount/InvocationCountFalseTrueTest.java
@@ -0,0 +1,12 @@
+package test.invocationcount;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+public class InvocationCountFalseTrueTest extends InvocationBase {
+  @BeforeMethod(firstTimeOnly = false)
+  public void beforeMethod() {}
+
+  @AfterMethod(lastTimeOnly = true)
+  public void afterMethod() {}
+}
diff --git a/src/test/java/test/invocationcount/InvocationCountTrueFalseTest.java b/src/test/java/test/invocationcount/InvocationCountTrueFalseTest.java
new file mode 100644
index 0000000..06a5bbb
--- /dev/null
+++ b/src/test/java/test/invocationcount/InvocationCountTrueFalseTest.java
@@ -0,0 +1,12 @@
+package test.invocationcount;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+public class InvocationCountTrueFalseTest extends InvocationBase {
+  @BeforeMethod(firstTimeOnly = true)
+  public void beforeMethod() {}
+
+  @AfterMethod(lastTimeOnly = false)
+  public void afterMethod() {}
+}
diff --git a/src/test/java/test/invocationcount/InvocationCountTrueTrueTest.java b/src/test/java/test/invocationcount/InvocationCountTrueTrueTest.java
new file mode 100644
index 0000000..f2c1d5c
--- /dev/null
+++ b/src/test/java/test/invocationcount/InvocationCountTrueTrueTest.java
@@ -0,0 +1,12 @@
+package test.invocationcount;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+public class InvocationCountTrueTrueTest extends InvocationBase {
+  @BeforeMethod(firstTimeOnly = true)
+  public void beforeMethod() {}
+
+  @AfterMethod(lastTimeOnly = true)
+  public void afterMethod() {}
+}
diff --git a/src/test/java/test/invokedmethodlistener/A.java b/src/test/java/test/invokedmethodlistener/A.java
new file mode 100644
index 0000000..24fadc3
--- /dev/null
+++ b/src/test/java/test/invokedmethodlistener/A.java
@@ -0,0 +1,9 @@
+package test.invokedmethodlistener;
+
+import org.testng.annotations.BeforeSuite;
+
+public class A {
+
+  @BeforeSuite(alwaysRun=false)
+  public static void someMethod1() {}
+}
diff --git a/src/test/java/test/invokedmethodlistener/B.java b/src/test/java/test/invokedmethodlistener/B.java
new file mode 100644
index 0000000..8f2eb36
--- /dev/null
+++ b/src/test/java/test/invokedmethodlistener/B.java
@@ -0,0 +1,13 @@
+package test.invokedmethodlistener;
+
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+@Test(enabled=false)
+public class B extends A{
+
+  @BeforeSuite
+  public static void someMethod2() {}
+
+  public void someTest() {}
+}
diff --git a/src/test/java/test/invokedmethodlistener/Base.java b/src/test/java/test/invokedmethodlistener/Base.java
new file mode 100644
index 0000000..67980df
--- /dev/null
+++ b/src/test/java/test/invokedmethodlistener/Base.java
@@ -0,0 +1,56 @@
+package test.invokedmethodlistener;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+public class Base {
+  private boolean m_fail;
+
+  public Base(boolean fail) {
+    m_fail = fail;
+  }
+
+  @BeforeMethod
+  public void beforeMethod() {
+  }
+
+  @AfterMethod
+  public void afterMethod() {}
+
+  @BeforeTest
+  public void beforeTest() {}
+
+  @AfterTest
+  public void afterTest() {}
+
+  @BeforeClass
+  public void beforeClass() {}
+
+  @AfterClass
+  public void afterClass() {}
+
+  @BeforeSuite
+  public void beforeSuite() {}
+
+  @AfterSuite
+  public void afterSuite() {
+    if (m_fail) {
+      throw new RuntimeException("After Suite FAILING");
+    }
+  }
+
+  @Test
+  public void a() {
+     if (m_fail) {
+      throw new IllegalArgumentException("Test Method FAILING");
+    }
+  }
+
+}
diff --git a/src/test/java/test/invokedmethodlistener/C.java b/src/test/java/test/invokedmethodlistener/C.java
new file mode 100644
index 0000000..335ba00
--- /dev/null
+++ b/src/test/java/test/invokedmethodlistener/C.java
@@ -0,0 +1,13 @@
+package test.invokedmethodlistener;
+
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+@Test
+public class C extends A{
+
+  @BeforeSuite
+  public static void someMethod3() {}
+
+  public void someTest() {}
+}
diff --git a/src/test/java/test/invokedmethodlistener/Failure.java b/src/test/java/test/invokedmethodlistener/Failure.java
new file mode 100644
index 0000000..c28f842
--- /dev/null
+++ b/src/test/java/test/invokedmethodlistener/Failure.java
@@ -0,0 +1,8 @@
+package test.invokedmethodlistener;
+
+public class Failure extends Base {
+  public Failure() {
+    super(true);
+  }
+
+}
diff --git a/src/test/java/test/invokedmethodlistener/InvokedMethodListener.java b/src/test/java/test/invokedmethodlistener/InvokedMethodListener.java
new file mode 100644
index 0000000..e25014a
--- /dev/null
+++ b/src/test/java/test/invokedmethodlistener/InvokedMethodListener.java
@@ -0,0 +1,26 @@
+package test.invokedmethodlistener;
+
+import org.testng.IInvokedMethod;
+import org.testng.IInvokedMethodListener;
+import org.testng.ITestResult;
+import org.testng.collections.Lists;
+
+import java.util.List;
+
+public class InvokedMethodListener implements IInvokedMethodListener {
+
+  private final List<IInvokedMethod> m_methods = Lists.newArrayList();
+
+  @Override
+  public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
+    m_methods.add(method);
+  }
+
+  @Override
+  public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
+  }
+
+  public List<IInvokedMethod> getInvokedMethods() {
+    return m_methods;
+  }
+}
diff --git a/src/test/java/test/invokedmethodlistener/InvokedMethodListenerTest.java b/src/test/java/test/invokedmethodlistener/InvokedMethodListenerTest.java
new file mode 100644
index 0000000..52edbf7
--- /dev/null
+++ b/src/test/java/test/invokedmethodlistener/InvokedMethodListenerTest.java
@@ -0,0 +1,139 @@
+package test.invokedmethodlistener;
+
+import org.testng.Assert;
+import org.testng.IInvokedMethod;
+import org.testng.IInvokedMethodListener;
+import org.testng.ITestResult;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+
+import test.SimpleBaseTest;
+
+import java.util.List;
+
+public class InvokedMethodListenerTest extends SimpleBaseTest {
+
+  private static void run(Class[] classes, IInvokedMethodListener l) {
+    TestNG tng = create();
+    tng.setTestClasses(classes);
+
+    tng.addInvokedMethodListener(l);
+    tng.run();
+  }
+
+  private static void assertMethodCount(MyListener l) {
+    Assert.assertEquals(l.getBeforeCount(), 9);
+    Assert.assertEquals(l.getAfterCount(), 9);
+  }
+
+  @Test
+  public void withSuccess() {
+    MyListener l = new MyListener();
+    run(new Class[]{Success.class}, l);
+    assertMethodCount(l);
+  }
+
+  @Test
+  public void withFailure() {
+    MyListener l = new MyListener();
+    run(new Class[] { Failure.class }, l);
+    assertMethodCount(l);
+    Assert.assertEquals(l.getSuiteStatus(), ITestResult.FAILURE);
+    Assert.assertTrue(null != l.getSuiteThrowable());
+    Assert.assertTrue(l.getSuiteThrowable().getClass() == RuntimeException.class);
+
+    Assert.assertEquals(l.getMethodStatus(), ITestResult.FAILURE);
+    Assert.assertTrue(null != l.getMethodThrowable());
+    Assert.assertTrue(l.getMethodThrowable().getClass() == IllegalArgumentException.class);
+  }
+
+  /**
+   * Fix for:
+   * https://github.com/juherr/testng-googlecode/issues/7
+   * https://github.com/juherr/testng-googlecode/issues/86
+   * https://github.com/cbeust/testng/issues/93
+   */
+  @Test
+  public void sameMethodInvokedMultipleTimesShouldHaveDifferentTimeStamps() {
+    TestNG tng = create(Sample.class);
+    InvokedMethodListener listener = new InvokedMethodListener();
+    tng.addListener(listener);
+    tng.run();
+    List<IInvokedMethod> m = listener.getInvokedMethods();
+    IInvokedMethod beforeSuite = m.get(0);
+    Assert.assertFalse(beforeSuite.getTestMethod().isAfterMethodConfiguration());
+    Assert.assertTrue(beforeSuite.isConfigurationMethod());
+    IInvokedMethod after1 = m.get(2);
+    Assert.assertTrue(after1.getTestMethod().isAfterMethodConfiguration());
+    Assert.assertTrue(after1.isConfigurationMethod());
+    IInvokedMethod after2 = m.get(4);
+    Assert.assertTrue(after2.getTestMethod().isAfterMethodConfiguration());
+    Assert.assertTrue(after2.isConfigurationMethod());
+    Assert.assertTrue(after1.getDate() != after2.getDate());
+  }
+
+  @Test(description = "Test methods with expected exceptions should show up as pass" +
+  		" in IInvokedMethodListener's afterInvocation method")
+  public void testMethodsWithExpectedExceptionsShouldShowUpAsPass() {
+    TestNG tng = create(Sample2.class);
+    Sample2.Sample2InvokedMethodListener l = new Sample2().new Sample2InvokedMethodListener();
+    tng.addListener(l);
+    tng.run();
+
+    Assert.assertTrue(l.isSuccess);
+  }
+
+  @Test(description = "Invoked method does not recognize configuration method")
+  public void issue629_InvokedMethodDoesNotRecognizeConfigurationMethod() {
+    InvokedMethodNameListener l = new InvokedMethodNameListener();
+    run(new Class[]{Success.class}, l);
+
+    Assert.assertEquals(l.testMethods.size(), 1);
+    Assert.assertTrue(l.testMethods.contains("a"));
+
+    Assert.assertEquals(l.testMethodsFromTM.size(), 1);
+    Assert.assertTrue(l.testMethodsFromTM.contains("a"));
+
+    Assert.assertEquals(l.configurationMethods.size(), 8);
+    Assert.assertTrue(l.configurationMethods.contains("beforeMethod"));
+    Assert.assertTrue(l.configurationMethods.contains("afterMethod"));
+    Assert.assertTrue(l.configurationMethods.contains("beforeTest"));
+    Assert.assertTrue(l.configurationMethods.contains("afterTest"));
+    Assert.assertTrue(l.configurationMethods.contains("beforeClass"));
+    Assert.assertTrue(l.configurationMethods.contains("afterClass"));
+    Assert.assertTrue(l.configurationMethods.contains("beforeSuite"));
+    Assert.assertTrue(l.configurationMethods.contains("afterSuite"));
+
+    Assert.assertEquals(l.configurationMethodsFromTM.size(), 8);
+    Assert.assertTrue(l.configurationMethodsFromTM.contains("beforeMethod"));
+    Assert.assertTrue(l.configurationMethodsFromTM.contains("afterMethod"));
+    Assert.assertTrue(l.configurationMethodsFromTM.contains("beforeTest"));
+    Assert.assertTrue(l.configurationMethodsFromTM.contains("afterTest"));
+    Assert.assertTrue(l.configurationMethodsFromTM.contains("beforeClass"));
+    Assert.assertTrue(l.configurationMethodsFromTM.contains("afterClass"));
+    Assert.assertTrue(l.configurationMethodsFromTM.contains("beforeSuite"));
+    Assert.assertTrue(l.configurationMethodsFromTM.contains("afterSuite"));
+  }
+
+  @Test
+  public void issue87_method_orderning_with_disable_test_class() {
+    assertIssue87(A.class, B.class, C.class);
+    assertIssue87(A.class, C.class, B.class);
+    assertIssue87(B.class, A.class, C.class);
+  }
+
+  private void assertIssue87(Class<?>... tests) {
+    TestNG tng = create(tests);
+    tng.setParallel(XmlSuite.ParallelMode.FALSE);
+    tng.setPreserveOrder(true);
+    InvokedMethodListener listener = new InvokedMethodListener();
+    tng.addListener(listener);
+    tng.run();
+    List<IInvokedMethod> m = listener.getInvokedMethods();
+    Assert.assertEquals(m.get(0).getTestMethod().getMethodName(), "someMethod1");
+    Assert.assertEquals(m.get(1).getTestMethod().getMethodName(), "someMethod3");
+    Assert.assertEquals(m.get(2).getTestMethod().getMethodName(), "someTest");
+    Assert.assertEquals(m.size(), 3);
+  }
+}
diff --git a/src/test/java/test/invokedmethodlistener/InvokedMethodNameListener.java b/src/test/java/test/invokedmethodlistener/InvokedMethodNameListener.java
new file mode 100644
index 0000000..be1df42
--- /dev/null
+++ b/src/test/java/test/invokedmethodlistener/InvokedMethodNameListener.java
@@ -0,0 +1,45 @@
+package test.invokedmethodlistener;
+
+import org.testng.IInvokedMethod;
+import org.testng.IInvokedMethodListener;
+import org.testng.ITestResult;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class InvokedMethodNameListener implements IInvokedMethodListener {
+
+  final Set<String> testMethods = new HashSet<>();
+  final Set<String> configurationMethods = new HashSet<>();
+  final Set<String> testMethodsFromTM = new HashSet<>();
+  final Set<String> configurationMethodsFromTM = new HashSet<>();
+
+  @Override
+  public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
+  }
+
+  @Override
+  public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
+    String methodName = method.getTestMethod().getMethodName();
+
+    if (method.isTestMethod()) {
+      testMethods.add(methodName);
+    }
+    if (method.isConfigurationMethod()) {
+      configurationMethods.add(methodName);
+    }
+    if (method.getTestMethod().isTest()) {
+      testMethodsFromTM.add(methodName);
+    }
+    if (method.getTestMethod().isBeforeMethodConfiguration() ||
+        method.getTestMethod().isAfterMethodConfiguration() ||
+        method.getTestMethod().isBeforeTestConfiguration() ||
+        method.getTestMethod().isAfterTestConfiguration() ||
+        method.getTestMethod().isBeforeClassConfiguration() ||
+        method.getTestMethod().isAfterClassConfiguration() ||
+        method.getTestMethod().isBeforeSuiteConfiguration() ||
+        method.getTestMethod().isAfterSuiteConfiguration()) {
+      configurationMethodsFromTM.add(methodName);
+    }
+  }
+}
diff --git a/src/test/java/test/invokedmethodlistener/MyListener.java b/src/test/java/test/invokedmethodlistener/MyListener.java
new file mode 100644
index 0000000..6d8705b
--- /dev/null
+++ b/src/test/java/test/invokedmethodlistener/MyListener.java
@@ -0,0 +1,58 @@
+package test.invokedmethodlistener;
+
+import org.testng.IInvokedMethod;
+import org.testng.IInvokedMethodListener;
+import org.testng.ITestResult;
+
+public class MyListener implements IInvokedMethodListener {
+  private int m_beforeCount = 0;
+  private int m_afterCount = 0;
+
+  private Throwable suiteThrowable;
+  private int suiteStatus = 0;
+  private Throwable methodThrowable;
+  private int methodStatus = 0;
+
+  @Override
+  public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
+    m_afterCount++;
+    if (method.getTestMethod().isAfterSuiteConfiguration()) {
+      suiteStatus = testResult.getStatus();
+      suiteThrowable = testResult.getThrowable();
+    }
+    if (method.getTestMethod().isTest()) {
+      methodStatus = testResult.getStatus();
+      methodThrowable = testResult.getThrowable();
+    }
+  }
+
+  @Override
+  public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
+    m_beforeCount++;
+  }
+
+  public int getBeforeCount() {
+    return m_beforeCount;
+  }
+
+  public int getAfterCount() {
+    return m_afterCount;
+  }
+
+  public Throwable getSuiteThrowable() {
+    return suiteThrowable;
+  }
+
+  public int getSuiteStatus() {
+    return suiteStatus;
+  }
+
+  public Throwable getMethodThrowable() {
+    return methodThrowable;
+  }
+
+  public int getMethodStatus() {
+    return methodStatus;
+  }
+
+}
diff --git a/src/test/java/test/invokedmethodlistener/Sample.java b/src/test/java/test/invokedmethodlistener/Sample.java
new file mode 100644
index 0000000..10a48ff
--- /dev/null
+++ b/src/test/java/test/invokedmethodlistener/Sample.java
@@ -0,0 +1,32 @@
+package test.invokedmethodlistener;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+public class Sample {
+
+  @Test
+  public void t1() {
+    try {
+      Thread.sleep(100);
+    } catch (InterruptedException handled) {
+      Thread.currentThread().interrupt();
+    }
+  }
+
+  @Test
+  public void t2() {
+    try {
+      Thread.sleep(100);
+    } catch (InterruptedException handled) {
+      Thread.currentThread().interrupt();
+    }
+  }
+
+  @AfterMethod
+  public void am() {}
+
+  @BeforeSuite
+  public void bs() {}
+}
diff --git a/src/test/java/test/invokedmethodlistener/Sample2.java b/src/test/java/test/invokedmethodlistener/Sample2.java
new file mode 100644
index 0000000..a16ee69
--- /dev/null
+++ b/src/test/java/test/invokedmethodlistener/Sample2.java
@@ -0,0 +1,42 @@
+package test.invokedmethodlistener;
+
+import org.testng.IInvokedMethod;
+import org.testng.IInvokedMethodListener;
+import org.testng.ITestResult;
+import org.testng.annotations.Test;
+
+public class Sample2 {
+
+  @Test(expectedExceptions = IllegalArgumentException.class)
+  public void t1() {
+    throw new IllegalArgumentException("Throw this exception on purpose in test");
+  }
+
+  public class Sample2InvokedMethodListener implements IInvokedMethodListener
+  {
+
+     boolean isSuccess = false;
+
+     /**
+      * {@inheritDoc}
+      */
+     @Override
+    public void afterInvocation(IInvokedMethod m, ITestResult tr)
+     {
+        isSuccess = tr.isSuccess();
+     }
+
+     /**
+      * {@inheritDoc}
+      */
+     @Override
+    public void beforeInvocation(IInvokedMethod arg0, ITestResult arg1)
+     {
+        // no need to implement this right now
+     }
+
+     public boolean isSuccess() {
+        return isSuccess;
+     }
+  }
+}
diff --git a/src/test/java/test/invokedmethodlistener/Success.java b/src/test/java/test/invokedmethodlistener/Success.java
new file mode 100644
index 0000000..84a8903
--- /dev/null
+++ b/src/test/java/test/invokedmethodlistener/Success.java
@@ -0,0 +1,8 @@
+package test.invokedmethodlistener;
+
+public class Success extends Base {
+  public Success() {
+    super(false);
+  }
+
+}
diff --git a/src/test/java/test/issue107/Issue107Test.java b/src/test/java/test/issue107/Issue107Test.java
new file mode 100644
index 0000000..e390b46
--- /dev/null
+++ b/src/test/java/test/issue107/Issue107Test.java
@@ -0,0 +1,50 @@
+package test.issue107;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+import test.SimpleBaseTest;
+
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * @author Vladislav.Rassokhin
+ */
+public class Issue107Test extends SimpleBaseTest {
+
+  @Test(description = "GITHUB-107, Check that suite parameters set from listener does not affects tests count")
+  public void testSuiteParameterModificationFromListener() throws Exception {
+    final XmlSuite suite = createXmlSuite("Simple suite");
+
+    final Map<String, String> parameters = suite.getParameters();
+    parameters.put(TestTestngCounter.PARAMETER_NAME, "some value that must be overriden in listener");
+    suite.setParameters(parameters);
+
+    runTest(suite);
+  }
+
+  @Test(description = "GITHUB-107, Check that suite parameters modification from listener does not affects tests count")
+  public void testSuiteParameterSetFromListener() throws Exception {
+    final XmlSuite suite = createXmlSuite("Simple suite");
+
+    runTest(suite);
+  }
+
+  private void runTest(XmlSuite suite) {
+    final XmlTest test = createXmlTest(suite, "Simple Test", TestTestngCounter.class.getName());
+    suite.setTests(Arrays.asList(test));
+
+    final TestListenerAdapter tla = new TestListenerAdapter();
+    final TestNG tng = create();
+    tng.setXmlSuites(Arrays.asList(suite));
+    tng.addListener(tla);
+    tng.run();
+
+    Assert.assertEquals(tla.getFailedTests().size(), 0);
+    Assert.assertEquals(tla.getPassedTests().size(), 2);
+  }
+}
diff --git a/src/test/java/test/issue107/MySuiteListener.java b/src/test/java/test/issue107/MySuiteListener.java
new file mode 100644
index 0000000..b26b688
--- /dev/null
+++ b/src/test/java/test/issue107/MySuiteListener.java
@@ -0,0 +1,19 @@
+package test.issue107;
+
+import org.testng.ISuite;
+import org.testng.ISuiteListener;
+import org.testng.xml.XmlSuite;
+
+import java.util.Map;
+
+public class MySuiteListener implements ISuiteListener {
+  public void onFinish(ISuite suite) {
+  }
+
+  public void onStart(ISuite suite) {
+    final XmlSuite xmlSuite = suite.getXmlSuite();
+    final Map<String, String> parameters = xmlSuite.getParameters();
+    parameters.put(TestTestngCounter.PARAMETER_NAME, TestTestngCounter.EXPECTED_VALUE);
+    xmlSuite.setParameters(parameters);
+  }
+}
diff --git a/src/test/java/test/issue107/TestTestngCounter.java b/src/test/java/test/issue107/TestTestngCounter.java
new file mode 100644
index 0000000..f926c93
--- /dev/null
+++ b/src/test/java/test/issue107/TestTestngCounter.java
@@ -0,0 +1,26 @@
+package test.issue107;
+
+import org.testng.Assert;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+@Listeners(MySuiteListener.class)
+public class TestTestngCounter {
+
+  public static final String PARAMETER_NAME = "key1";
+  public static final String EXPECTED_VALUE = "zzz";
+
+  @Parameters({PARAMETER_NAME})
+  @Test
+  public void testParameter(String key) {
+    Assert.assertEquals(key, EXPECTED_VALUE);
+  }
+
+  @Parameters({PARAMETER_NAME})
+  @Test
+  public void testParameterAsOptional(@Optional("Unknown") String key) {
+    Assert.assertEquals(key, EXPECTED_VALUE);
+  }
+}
diff --git a/src/test/java/test/jarpackages/JarPackagesTest.java b/src/test/java/test/jarpackages/JarPackagesTest.java
new file mode 100644
index 0000000..d9a6591
--- /dev/null
+++ b/src/test/java/test/jarpackages/JarPackagesTest.java
@@ -0,0 +1,49 @@
+package test.jarpackages;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+import java.io.File;
+
+public class JarPackagesTest extends SimpleBaseTest {
+  private TestListenerAdapter init(String jarFile) {
+    TestNG tng = create();
+    File currentDir = new File(".");
+    String path = currentDir.getAbsolutePath();
+    char s = File.separatorChar;
+    path = path + s + "test" + s + "src" + s + "test" + s + "jarpackages" + s;
+    String finalPath = path + jarFile;
+    tng.setTestJar(finalPath);
+    TestListenerAdapter result = new TestListenerAdapter();
+    tng.addListener(result);
+    tng.run();
+
+    return result;
+  }
+
+  @Test
+  public void jarWithTestngXml() {
+    TestListenerAdapter tla = init("withtestngxml.jar");
+    Assert.assertEquals(tla.getPassedTests().size(), 2);
+    String first = tla.getPassedTests().get(0).getName();
+    String second = tla.getPassedTests().get(1).getName();
+    boolean fThenG = "f".equals(first) && "g".equals(second);
+    boolean gThenF = "g".equals(first) && "f".equals(second);
+    Assert.assertTrue(fThenG || gThenF);
+  }
+
+  @Test
+  public void jarWithoutTestngXml() {
+    TestListenerAdapter tla = init("withouttestngxml.jar");
+    Assert.assertEquals(tla.getPassedTests().size(), 2);
+    String first = tla.getPassedTests().get(0).getName();
+    String second = tla.getPassedTests().get(1).getName();
+    boolean fThenG = "f".equals(first) && "g".equals(second);
+    boolean gThenF = "g".equals(first) && "f".equals(second);
+    Assert.assertTrue(fThenG || gThenF);
+  }
+}
diff --git a/src/test/java/test/jarpackages/testng.xml b/src/test/java/test/jarpackages/testng.xml
new file mode 100644
index 0000000..6def727
--- /dev/null
+++ b/src/test/java/test/jarpackages/testng.xml
@@ -0,0 +1,10 @@
+<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
+  
+<suite name="JarPackagesTestSuite" verbose="0">
+  <test name="JarPackagesTest">
+    <packages>
+      <package name="test.jarpackages.tests" />
+    </packages>
+  </test>
+</suite>
+
diff --git a/src/test/java/test/jarpackages/tests/A.java b/src/test/java/test/jarpackages/tests/A.java
new file mode 100644
index 0000000..19d25c2
--- /dev/null
+++ b/src/test/java/test/jarpackages/tests/A.java
@@ -0,0 +1,9 @@
+package test.jarpackages.tests;
+
+import org.testng.annotations.Test;
+
+public class A {
+
+  @Test
+  public void f() {}
+}
diff --git a/src/test/java/test/jarpackages/tests/B.java b/src/test/java/test/jarpackages/tests/B.java
new file mode 100644
index 0000000..7097ddf
--- /dev/null
+++ b/src/test/java/test/jarpackages/tests/B.java
@@ -0,0 +1,9 @@
+package test.jarpackages.tests;
+
+import org.testng.annotations.Test;
+
+public class B {
+
+  @Test
+  public void g() {}
+}
diff --git a/src/test/java/test/jason/Main.java b/src/test/java/test/jason/Main.java
new file mode 100644
index 0000000..7dd3850
--- /dev/null
+++ b/src/test/java/test/jason/Main.java
@@ -0,0 +1,17 @@
+package test.jason;

+

+import org.testng.annotations.AfterClass;

+import org.testng.annotations.Test;

+

+public class Main extends MainBase {

+  public static boolean m_passed = false;

+

+  @AfterClass

+  public void afterClass() {

+    m_passed = true;

+  }

+

+  @Test(description = "This test is run")

+  public void test1() throws InterruptedException {

+  }

+}

diff --git a/src/test/java/test/jason/MainBase.java b/src/test/java/test/jason/MainBase.java
new file mode 100644
index 0000000..0710dcd
--- /dev/null
+++ b/src/test/java/test/jason/MainBase.java
@@ -0,0 +1,10 @@
+package test.jason;

+

+import org.testng.annotations.Test;

+

+public class MainBase {

+

+  @Test(description = "This test is never run but prevents AfterClass")

+  public void checkReportsExist() {

+  }

+}

diff --git a/src/test/java/test/jason/MainTest.java b/src/test/java/test/jason/MainTest.java
new file mode 100644
index 0000000..cebe61e
--- /dev/null
+++ b/src/test/java/test/jason/MainTest.java
@@ -0,0 +1,30 @@
+package test.jason;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlInclude;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.SimpleBaseTest;
+
+import java.util.Arrays;
+
+public class MainTest extends SimpleBaseTest {
+
+  @Test
+  public void afterClassShouldRun() {
+    XmlSuite s = createXmlSuite("S");
+    XmlTest t = createXmlTest(s, "T", Main.class.getName());
+    XmlClass c = t.getXmlClasses().get(0);
+    c.getIncludedMethods().add(new XmlInclude("test1"));
+    t.setPreserveOrder("true");
+    TestNG tng = create();
+    tng.setXmlSuites(Arrays.asList(new XmlSuite[] { s }));
+    Main.m_passed = false;
+    tng.run();
+    Assert.assertTrue(Main.m_passed);
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/junit/BaseTest.java b/src/test/java/test/junit/BaseTest.java
new file mode 100644
index 0000000..a27c043
--- /dev/null
+++ b/src/test/java/test/junit/BaseTest.java
@@ -0,0 +1,41 @@
+package test.junit;
+
+import junit.framework.TestCase;
+
+/**
+ * Base JUnit test case to verify TestNG handles
+ * TestCase hierarchies properly.
+ *
+ * @author mperham
+ */
+public abstract class BaseTest extends TestCase {
+
+  private static int setUpInvokeCount = 0;
+  private static int tearDownInvokeCount = 0;
+
+  public BaseTest(String name) {
+    super(name);
+  }
+
+  @Override
+  protected void setUp() throws Exception {
+    setUpInvokeCount++;
+  }
+
+  @Override
+  protected void tearDown() throws Exception {
+    tearDownInvokeCount++;
+  }
+
+  public abstract void testA();
+  public abstract void testB();
+
+  public static int getSetUpInvokeCount() {
+    return setUpInvokeCount;
+  }
+
+  public static int getTearDownInvokeCount() {
+    return tearDownInvokeCount;
+  }
+
+}
diff --git a/src/test/java/test/junit/JUnitConstructorTest.java b/src/test/java/test/junit/JUnitConstructorTest.java
new file mode 100644
index 0000000..fcc92f7
--- /dev/null
+++ b/src/test/java/test/junit/JUnitConstructorTest.java
@@ -0,0 +1,52 @@
+package test.junit;
+
+import junit.framework.TestCase;
+
+
+/**
+ * Test that the correct number of constructors is called
+ *
+ * Created on Aug 9, 2005
+ * @author cbeust
+ */
+public class JUnitConstructorTest extends TestCase {
+  private static int m_constructorCount = 0;
+  private static int m_createCount = 0;
+  private static int m_queryCount = 0;
+
+  public JUnitConstructorTest(/*String string */) {
+//    super(string);
+//    setName(string);
+    ppp("CONSTRUCTING");
+    m_constructorCount++;
+  }
+
+//  public void test1() {
+//    ppp("TEST1");
+//  }
+//
+//  public void test2() {
+//    ppp("TEST2");
+//  }
+
+  public void testCreate() {
+    ppp("TEST_CREATE");
+    m_createCount++;
+  }
+
+  public void testQuery() {
+    ppp("TEST_QUERY");
+    m_queryCount++;
+  }
+
+  @Override
+  public void tearDown() {
+    assertEquals(3, m_constructorCount);
+    assertTrue((1 == m_createCount && 0 == m_queryCount) ||
+        (0 == m_createCount && 1 == m_queryCount));
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[JUnitHierarchyTest] " + s);
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/junit/JUnitEmptyTest.java b/src/test/java/test/junit/JUnitEmptyTest.java
new file mode 100644
index 0000000..33584e5
--- /dev/null
+++ b/src/test/java/test/junit/JUnitEmptyTest.java
@@ -0,0 +1,17 @@
+package test.junit;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+public class JUnitEmptyTest extends TestCase {
+
+  public JUnitEmptyTest(String name) {
+    super(name);
+  }
+
+  public static Test suite() {
+    TestSuite s = new TestSuite(JUnitEmptyTest.class);
+    return s;
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/junit/MainSuite.java b/src/test/java/test/junit/MainSuite.java
new file mode 100644
index 0000000..267b258
--- /dev/null
+++ b/src/test/java/test/junit/MainSuite.java
@@ -0,0 +1,17 @@
+package test.junit;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class MainSuite {
+  public static Test suite() {
+    TestSuite suite = new TestSuite("MainSuite");
+
+    suite.addTest(Suite1.suite());
+    suite.addTest(Suite2.suite());
+    suite.addTest(Suite3.suite());
+
+    return suite;
+  }
+
+}
diff --git a/src/test/java/test/junit/SetNameTest.java b/src/test/java/test/junit/SetNameTest.java
new file mode 100644
index 0000000..e816906
--- /dev/null
+++ b/src/test/java/test/junit/SetNameTest.java
@@ -0,0 +1,38 @@
+package test.junit;
+
+import org.testng.Assert;
+
+import java.io.Serializable;
+
+import junit.framework.TestCase;
+
+public class SetNameTest extends TestCase implements Serializable {
+  public static int m_ctorCount = 0;
+
+  public SetNameTest() {
+    ppp("CTOR");
+    m_ctorCount++;
+  }
+
+  @Override
+  public void setName(String name) {
+    ppp("SETNAME " + name);
+    super.setName(name);
+  }
+
+  public void testFoo() {
+    Assert.assertEquals("testFoo", getName());
+    ppp("FOO");
+  }
+
+  public void testBar() {
+    Assert.assertEquals("testBar", getName());
+    ppp("BAR");
+  }
+
+  private void ppp(String string) {
+    if (false) {
+      System.out.println("[FooBarTest#" + hashCode() + "] " + string);
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/junit/SetUpExceptionSampleTest.java b/src/test/java/test/junit/SetUpExceptionSampleTest.java
new file mode 100644
index 0000000..5e4ebde
--- /dev/null
+++ b/src/test/java/test/junit/SetUpExceptionSampleTest.java
@@ -0,0 +1,19 @@
+package test.junit;
+
+import junit.framework.TestCase;
+
+public class SetUpExceptionSampleTest extends TestCase {
+
+  @Override
+  protected void setUp() throws Exception {
+    throw new RuntimeException();
+  }
+
+  public void testM1() {
+  }
+
+  @Override
+  protected void tearDown() throws Exception {
+  }
+
+}
\ No newline at end of file
diff --git a/src/test/java/test/junit/SetUpExceptionTest.java b/src/test/java/test/junit/SetUpExceptionTest.java
new file mode 100644
index 0000000..2c0ee46
--- /dev/null
+++ b/src/test/java/test/junit/SetUpExceptionTest.java
@@ -0,0 +1,24 @@
+package test.junit;
+
+import org.testng.annotations.Test;
+
+public class SetUpExceptionTest extends test.BaseTest {
+
+  @Test
+  public void setUpFailingShouldCauseMethodsToBeSkipped() {
+    addClass("test.junit.SetUpExceptionSampleTest");
+    setJUnit(true);
+    run();
+    String[] passed = {
+    };
+    String[] failed = {
+      "setUp"
+    };
+    String[] skipped = {
+      "testM1", "tearDown"
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Skipped", skipped, getSkippedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+}
diff --git a/src/test/java/test/junit/Suite1.java b/src/test/java/test/junit/Suite1.java
new file mode 100644
index 0000000..6071c36
--- /dev/null
+++ b/src/test/java/test/junit/Suite1.java
@@ -0,0 +1,17 @@
+package test.junit;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class Suite1 {
+  public Suite1(String s) {
+    // dummy
+  }
+
+  public static Test suite() {
+    TestSuite suite = new TestSuite("JUnitSuite1");
+    suite.addTestSuite(TestAa.class);
+    suite.addTestSuite(TestAb.class);
+    return suite;
+  }
+}
diff --git a/src/test/java/test/junit/Suite2.java b/src/test/java/test/junit/Suite2.java
new file mode 100644
index 0000000..3963704
--- /dev/null
+++ b/src/test/java/test/junit/Suite2.java
@@ -0,0 +1,14 @@
+package test.junit;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class Suite2 {
+  public static Test suite() {
+    TestSuite suite = new TestSuite("Suite2");
+    suite.addTestSuite(TestAc.class);
+    suite.addTestSuite(TestAd.class);
+    suite.addTest(Suite3.suite());
+    return suite;
+  }
+}
diff --git a/src/test/java/test/junit/Suite3.java b/src/test/java/test/junit/Suite3.java
new file mode 100644
index 0000000..56c41b9
--- /dev/null
+++ b/src/test/java/test/junit/Suite3.java
@@ -0,0 +1,12 @@
+package test.junit;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class Suite3 {
+  public static Test suite() {
+    TestSuite suite = new TestSuite("Suite3");
+    suite.addTest(Suite4.suite());
+    return suite;
+  }
+}
diff --git a/src/test/java/test/junit/Suite4.java b/src/test/java/test/junit/Suite4.java
new file mode 100644
index 0000000..40c18b0
--- /dev/null
+++ b/src/test/java/test/junit/Suite4.java
@@ -0,0 +1,13 @@
+package test.junit;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class Suite4 {
+  public static Test suite() {
+    TestSuite suite = new TestSuite("Suite4");
+    suite.addTestSuite(TestAe.class);
+    suite.addTestSuite(TestAf.class);
+    return suite;
+  }
+}
diff --git a/src/test/java/test/junit/TestAa.java b/src/test/java/test/junit/TestAa.java
new file mode 100644
index 0000000..63ff7f9
--- /dev/null
+++ b/src/test/java/test/junit/TestAa.java
@@ -0,0 +1,10 @@
+package test.junit;
+
+import junit.framework.TestCase;
+
+public class TestAa extends TestCase {
+
+  public void testAa1() {
+
+  }
+}
diff --git a/src/test/java/test/junit/TestAb.java b/src/test/java/test/junit/TestAb.java
new file mode 100644
index 0000000..320de8d
--- /dev/null
+++ b/src/test/java/test/junit/TestAb.java
@@ -0,0 +1,10 @@
+package test.junit;
+
+import junit.framework.TestCase;
+
+public class TestAb extends TestCase {
+
+  public void testAb1() {
+
+  }
+}
diff --git a/src/test/java/test/junit/TestAc.java b/src/test/java/test/junit/TestAc.java
new file mode 100644
index 0000000..c4305c6
--- /dev/null
+++ b/src/test/java/test/junit/TestAc.java
@@ -0,0 +1,10 @@
+package test.junit;
+
+import junit.framework.TestCase;
+
+public class TestAc extends TestCase {
+
+  public void testAc1() {
+
+  }
+}
diff --git a/src/test/java/test/junit/TestAd.java b/src/test/java/test/junit/TestAd.java
new file mode 100644
index 0000000..216379f
--- /dev/null
+++ b/src/test/java/test/junit/TestAd.java
@@ -0,0 +1,10 @@
+package test.junit;
+
+import junit.framework.TestCase;
+
+public class TestAd extends TestCase {
+
+  public void testAd1() {
+
+  }
+}
diff --git a/src/test/java/test/junit/TestAe.java b/src/test/java/test/junit/TestAe.java
new file mode 100644
index 0000000..e7cfcb9
--- /dev/null
+++ b/src/test/java/test/junit/TestAe.java
@@ -0,0 +1,10 @@
+package test.junit;
+
+import junit.framework.TestCase;
+
+public class TestAe extends TestCase {
+
+  public void testAe1() {
+
+  }
+}
diff --git a/src/test/java/test/junit/TestAf.java b/src/test/java/test/junit/TestAf.java
new file mode 100644
index 0000000..b7761d4
--- /dev/null
+++ b/src/test/java/test/junit/TestAf.java
@@ -0,0 +1,10 @@
+package test.junit;
+
+import junit.framework.TestCase;
+
+public class TestAf extends TestCase {
+
+  public void testAf1() {
+
+  }
+}
diff --git a/src/test/java/test/junit/testsetup/ATest.java b/src/test/java/test/junit/testsetup/ATest.java
new file mode 100644
index 0000000..9c2fa9f
--- /dev/null
+++ b/src/test/java/test/junit/testsetup/ATest.java
@@ -0,0 +1,13 @@
+package test.junit.testsetup;
+
+import junit.framework.TestCase;
+
+public class ATest extends TestCase
+{
+	public void testIt()
+	{
+    System.out.println("A.testIt()");
+		Data d = TestSuiteContainerWrapper.getData();
+		assertEquals(3,d.i);
+	}
+}
diff --git a/src/test/java/test/junit/testsetup/Data.java b/src/test/java/test/junit/testsetup/Data.java
new file mode 100644
index 0000000..fd2f86e
--- /dev/null
+++ b/src/test/java/test/junit/testsetup/Data.java
@@ -0,0 +1,6 @@
+package test.junit.testsetup;
+
+public class Data
+{
+	public int i = 3;
+}
diff --git a/src/test/java/test/junit/testsetup/LayerATestSuite.java b/src/test/java/test/junit/testsetup/LayerATestSuite.java
new file mode 100644
index 0000000..8a38411
--- /dev/null
+++ b/src/test/java/test/junit/testsetup/LayerATestSuite.java
@@ -0,0 +1,17 @@
+package test.junit.testsetup;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class LayerATestSuite
+{
+	public static Test suite()
+	{
+		TestSuite suite = new TestSuite( "Layer A Test Suite" );
+
+		suite.addTestSuite( ATest.class );
+
+		TestSuiteContainerWrapper wrapper = new TestSuiteContainerWrapper(suite, Data.class);
+		return wrapper;
+	}
+}
diff --git a/src/test/java/test/junit/testsetup/LoggingTestSuite.java b/src/test/java/test/junit/testsetup/LoggingTestSuite.java
new file mode 100644
index 0000000..87e61cc
--- /dev/null
+++ b/src/test/java/test/junit/testsetup/LoggingTestSuite.java
@@ -0,0 +1,26 @@
+package test.junit.testsetup;
+
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+import junit.framework.TestSuite;
+
+public class LoggingTestSuite extends TestSuite
+{
+	public LoggingTestSuite( String string )
+	{
+		super( string );
+	}
+
+	@Override
+  public void run( TestResult result )
+	{
+		super.run( result );
+	}
+
+	@Override
+  public void runTest( Test test, TestResult result )
+	{
+		super.runTest( test, result );
+	}
+}
diff --git a/src/test/java/test/junit/testsetup/SmokeSuite.java b/src/test/java/test/junit/testsetup/SmokeSuite.java
new file mode 100644
index 0000000..3732ecb
--- /dev/null
+++ b/src/test/java/test/junit/testsetup/SmokeSuite.java
@@ -0,0 +1,31 @@
+package test.junit.testsetup;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class SmokeSuite extends LoggingTestSuite
+{
+	public static void main( String[] args )
+	{
+		junit.textui.TestRunner.run( suite() );
+	}
+
+	public static Test suite()
+	{
+		TestSuite suite = new TestSuite( "Smoke Test Suite" );
+
+		suite.addTest(LayerATestSuite.suite());
+
+		return suite;
+	}
+
+//	public SmokeSuite()
+//	{
+//		this("SmokeSuite");
+//	}
+
+	public SmokeSuite( String name )
+	{
+		super( name );
+	}
+}
diff --git a/src/test/java/test/junit/testsetup/TestSuiteContainerWrapper.java b/src/test/java/test/junit/testsetup/TestSuiteContainerWrapper.java
new file mode 100644
index 0000000..7c2903f
--- /dev/null
+++ b/src/test/java/test/junit/testsetup/TestSuiteContainerWrapper.java
@@ -0,0 +1,36 @@
+package test.junit.testsetup;
+
+import junit.extensions.TestSetup;
+import junit.framework.TestSuite;
+
+public class TestSuiteContainerWrapper extends TestSetup {
+  private static Data INSTANCE = null;
+  private static TestSuite _test = null;
+  private static Class dataImpl = null;
+
+  public TestSuiteContainerWrapper(TestSuite testSuite, Class dataImpl) {
+    super(testSuite);
+    _test = testSuite;
+    TestSuiteContainerWrapper.dataImpl = dataImpl;
+  }
+
+  public static Data getData() {
+    return INSTANCE;
+  }
+
+  @Override
+  protected void setUp() throws Exception {
+    System.out.println("setup");
+    INSTANCE = (Data) dataImpl.newInstance();
+  }
+
+  @Override
+  protected void tearDown() throws Exception {
+    System.out.println("teardown");
+
+    INSTANCE = null;
+
+    System.out.println(_test.countTestCases() + " test cases defined by \""
+        + _test.getName() + "\" were executed.");
+  }
+}
diff --git a/src/test/java/test/junit4/InheritedTest.java b/src/test/java/test/junit4/InheritedTest.java
new file mode 100644
index 0000000..670ae7c
--- /dev/null
+++ b/src/test/java/test/junit4/InheritedTest.java
@@ -0,0 +1,9 @@
+package test.junit4;
+
+/**
+ *
+ * @author lukas
+ */
+public class InheritedTest extends JUnit4Sample1 {
+
+}
diff --git a/src/test/java/test/junit4/JUnit4Child.java b/src/test/java/test/junit4/JUnit4Child.java
new file mode 100644
index 0000000..82cd216
--- /dev/null
+++ b/src/test/java/test/junit4/JUnit4Child.java
@@ -0,0 +1,12 @@
+package test.junit4;
+
+import org.junit.runners.Suite;
+
+/**
+ *
+ * @author lukas
+ */
+@Suite.SuiteClasses({JUnit4Sample1.class})
+public class JUnit4Child extends JUnit4SampleSuite {
+    public static final String[] EXPECTED = {"t1"};
+}
diff --git a/src/test/java/test/junit4/JUnit4ParameterizedTest.java b/src/test/java/test/junit4/JUnit4ParameterizedTest.java
new file mode 100644
index 0000000..fa35a24
--- /dev/null
+++ b/src/test/java/test/junit4/JUnit4ParameterizedTest.java
@@ -0,0 +1,57 @@
+package test.junit4;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class JUnit4ParameterizedTest {
+
+    public static final String[] EXPECTED = {"t2[0]", "t2[1]", "t4[0]"};
+    public static final String[] SKIPPED = {"t3[0]", "t3[1]", "ta[0]", "ta[1]"};
+    public static final String[] FAILED = {"t4[1]", "tf[0]", "tf[1]"};
+
+    private int param;
+
+    @Parameters
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] {{1}, {5}});
+    }
+
+    public JUnit4ParameterizedTest(int param) {
+        this.param = param;
+    }
+
+    @Test
+    public void t2() {
+    }
+
+    @Test
+    @Ignore
+    public void t3() {
+    }
+
+    @Test
+    public void t4() {
+        if (param == 5) {
+            Assert.fail("a test");
+        }
+    }
+
+    @Test
+    public void tf() {
+        Assert.fail("a test");
+    }
+
+    @Test
+    public void ta() {
+        Assume.assumeTrue(false);
+    }
+}
diff --git a/src/test/java/test/junit4/JUnit4Sample1.java b/src/test/java/test/junit4/JUnit4Sample1.java
new file mode 100644
index 0000000..938e262
--- /dev/null
+++ b/src/test/java/test/junit4/JUnit4Sample1.java
@@ -0,0 +1,15 @@
+package test.junit4;
+
+import org.junit.Test;
+
+/**
+ *
+ * @author lukas
+ */
+public class JUnit4Sample1 {
+
+    @Test
+    public void t1() {
+
+    }
+}
diff --git a/src/test/java/test/junit4/JUnit4Sample2.java b/src/test/java/test/junit4/JUnit4Sample2.java
new file mode 100644
index 0000000..0209f82
--- /dev/null
+++ b/src/test/java/test/junit4/JUnit4Sample2.java
@@ -0,0 +1,40 @@
+package test.junit4;
+
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ *
+ * @author lukas
+ */
+public class JUnit4Sample2 {
+
+    public static final String[] EXPECTED = {"t2", "t4"};
+    public static final String[] SKIPPED = {"t3", "ta"};
+    public static final String[] FAILED = {"tf"};
+
+    @Test
+    public void t2() {
+    }
+
+    @Test
+    @Ignore
+    public void t3() {
+    }
+
+    @Test
+    public void t4() {
+    }
+
+    @Test
+    public void tf() {
+        Assert.fail("a test");
+    }
+
+    @Test
+    public void ta() {
+        Assume.assumeTrue(false);
+    }
+}
diff --git a/src/test/java/test/junit4/JUnit4SampleSuite.java b/src/test/java/test/junit4/JUnit4SampleSuite.java
new file mode 100644
index 0000000..19e93b0
--- /dev/null
+++ b/src/test/java/test/junit4/JUnit4SampleSuite.java
@@ -0,0 +1,20 @@
+package test.junit4;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/**
+ *
+ * @author lukas
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+    JUnit4Sample1.class,
+    JUnit4Sample2.class
+})
+public class JUnit4SampleSuite {
+
+    public static final String[] EXPECTED = {"t1", "t2", "t4"};
+    public static final String[] SKIPPED = {"t3", "ta"};
+    public static final String[] FAILED = {"tf"};
+}
diff --git a/src/test/java/test/justin/BaseTestCase.java b/src/test/java/test/justin/BaseTestCase.java
new file mode 100644
index 0000000..e62a7cb
--- /dev/null
+++ b/src/test/java/test/justin/BaseTestCase.java
@@ -0,0 +1,53 @@
+package test.justin;
+
+import org.testng.annotations.Test;
+
+/**
+ * @author <a href="mailto:jlee@antwerkz.com">Justin Lee</a> Date: Aug 15, 2004
+ */
+@Test
+public abstract class BaseTestCase {
+    protected static final String TEST_PASSWORD = "testPassword";
+
+    public BaseTestCase() {
+        init();
+    }
+
+    public BaseTestCase(String name) {
+        this();
+    }
+
+    private void init() {
+        setSessionUser(null);
+    }
+
+    protected void commit() {
+    }
+
+    protected void tearDown() throws Exception {
+        commit();
+    }
+
+    protected Object createCustomer() throws Exception {
+      return null;
+    }
+
+    protected Object createProject() throws Exception {
+      return null;
+    }
+
+    protected Object createTimeEntry() throws Exception {
+        return null;
+    }
+
+    protected Object createUser(String name) throws Exception {
+      return null;
+    }
+
+    protected Object createUserGroup() throws Exception {
+      return null;
+    }
+
+    protected void setSessionUser(Object user) {
+    }
+}
diff --git a/src/test/java/test/justin/MonthTest.java b/src/test/java/test/justin/MonthTest.java
new file mode 100644
index 0000000..93194ba
--- /dev/null
+++ b/src/test/java/test/justin/MonthTest.java
@@ -0,0 +1,27 @@
+package test.justin;
+
+import org.testng.annotations.Test;
+
+import java.text.ParseException;
+
+/**
+ * Created Jul 10, 2005
+ *
+ * @author <a href="mailto:jlee@antwerkz.com">Justin Lee</a>
+ */
+public class MonthTest extends BaseTestCase {
+    public MonthTest() {
+    }
+
+    public MonthTest(String name) {
+        super(name);
+    }
+
+    @Test(groups = {"bean-tests"})
+    public void july2005() throws ParseException {
+    }
+
+    @Test
+    public void weekendDay() throws ParseException {
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/test/listeners/AggregateSampleTest.java b/src/test/java/test/listeners/AggregateSampleTest.java
new file mode 100644
index 0000000..e460a9d
--- /dev/null
+++ b/src/test/java/test/listeners/AggregateSampleTest.java
@@ -0,0 +1,16 @@
+package test.listeners;
+
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+@Listeners(L2.class)
+public class AggregateSampleTest extends BaseAggregate {
+  static int m_count = 0;
+  static public void incrementCount() {
+    m_count++;
+  }
+
+  @Test
+  public void f() {
+  }
+}
diff --git a/src/test/java/test/listeners/AlterSuiteListenerTest.java b/src/test/java/test/listeners/AlterSuiteListenerTest.java
new file mode 100644
index 0000000..438be8e
--- /dev/null
+++ b/src/test/java/test/listeners/AlterSuiteListenerTest.java
@@ -0,0 +1,81 @@
+package test.listeners;
+
+import org.testng.Assert;
+import org.testng.IAlterSuiteListener;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+import test.SimpleBaseTest;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class AlterSuiteListenerTest extends SimpleBaseTest {
+
+    public static final String ALTER_SUITE_LISTENER = "AlterSuiteListener";
+
+    @Test
+    public void executionListenerWithXml() {
+        XmlSuite suite = runTest(AlterSuiteListener1SampleTest.class, AlterSuiteNameListener.class.getName());
+        Assert.assertEquals(suite.getName(), AlterSuiteNameListener.class.getSimpleName());
+    }
+
+    @Test
+    public void executionListenerWithoutListener() {
+        XmlSuite suite = runTest(AlterSuiteListener1SampleTest.class, null/*Donot add the listener*/);
+        Assert.assertEquals(suite.getName(), ALTER_SUITE_LISTENER);
+    }
+
+    @Test
+    public void executionListenerWithXml2() {
+        XmlSuite suite = runTest(AlterSuiteListener1SampleTest.class, AlterXmlTestsInSuiteListener.class.getName());
+        Assert.assertEquals(suite.getTests().size(), 2);
+    }
+
+
+    private XmlSuite runTest(Class<?> listenerClass, String listenerName) {
+        XmlSuite s = createXmlSuite(ALTER_SUITE_LISTENER);
+        createXmlTest(s, "Test", listenerClass.getName());
+        boolean addListener = (listenerName != null);
+
+        if (addListener) {
+            s.addListener(listenerName);
+        }
+        TestNG tng = create();
+        tng.setXmlSuites(Arrays.asList(s));
+        tng.run();
+        return s;
+    }
+
+    public static class AlterSuiteListener1SampleTest {
+        @Test
+        public void foo() {
+        }
+    }
+
+
+    public static class AlterSuiteNameListener implements IAlterSuiteListener {
+
+        @Override
+        public void alter(List<XmlSuite> suites) {
+            XmlSuite suite = suites.get(0);
+            suite.setName(getClass().getSimpleName());
+        }
+    }
+
+
+    public static class AlterXmlTestsInSuiteListener implements IAlterSuiteListener {
+
+        @Override
+        public void alter(List<XmlSuite> suites) {
+            XmlSuite suite = suites.get(0);
+            List<XmlTest> tests = suite.getTests();
+            XmlTest test = tests.get(0);
+            XmlTest anotherTest = new XmlTest(suite);
+            anotherTest.setName("foo");
+            anotherTest.setClasses(test.getClasses());
+        }
+    }
+
+}
diff --git a/src/test/java/test/listeners/BaseAggregate.java b/src/test/java/test/listeners/BaseAggregate.java
new file mode 100644
index 0000000..f4bfc9b
--- /dev/null
+++ b/src/test/java/test/listeners/BaseAggregate.java
@@ -0,0 +1,8 @@
+package test.listeners;
+
+import org.testng.annotations.Listeners;
+
+@Listeners(L1.class)
+public class BaseAggregate {
+
+}
diff --git a/src/test/java/test/listeners/BaseListener.java b/src/test/java/test/listeners/BaseListener.java
new file mode 100644
index 0000000..9968fff
--- /dev/null
+++ b/src/test/java/test/listeners/BaseListener.java
@@ -0,0 +1,38 @@
+package test.listeners;
+
+import org.testng.ITestContext;
+import org.testng.ITestListener;
+import org.testng.ITestResult;
+
+public class BaseListener implements ITestListener {
+
+  @Override
+  public void onTestStart(ITestResult result) {
+    AggregateSampleTest.incrementCount();
+  }
+
+  @Override
+  public void onTestSuccess(ITestResult result) {
+  }
+
+  @Override
+  public void onTestFailure(ITestResult result) {
+  }
+
+  @Override
+  public void onTestSkipped(ITestResult result) {
+  }
+
+  @Override
+  public void onTestFailedButWithinSuccessPercentage(ITestResult result) {
+  }
+
+  @Override
+  public void onStart(ITestContext context) {
+  }
+
+  @Override
+  public void onFinish(ITestContext context) {
+  }
+}
+
diff --git a/src/test/java/test/listeners/BaseWithListener.java b/src/test/java/test/listeners/BaseWithListener.java
new file mode 100644
index 0000000..1cdfa92
--- /dev/null
+++ b/src/test/java/test/listeners/BaseWithListener.java
@@ -0,0 +1,12 @@
+package test.listeners;
+
+import org.testng.annotations.Listeners;
+
+@Listeners(value = {L3.class, SuiteListener.class, MyInvokedMethodListener.class})
+class BaseWithListener {
+  static int m_count = 0;
+
+  public static void incrementCount() {
+    m_count++;
+  }
+}
diff --git a/src/test/java/test/listeners/ClassListenerSample.java b/src/test/java/test/listeners/ClassListenerSample.java
new file mode 100644
index 0000000..d589980
--- /dev/null
+++ b/src/test/java/test/listeners/ClassListenerSample.java
@@ -0,0 +1,11 @@
+package test.listeners;
+
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+@Listeners(MyClassListener.class)
+public class ClassListenerSample {
+
+  @Test
+  public void test() {}
+}
diff --git a/src/test/java/test/listeners/ConfigurationListenerFailSampleTest.java b/src/test/java/test/listeners/ConfigurationListenerFailSampleTest.java
new file mode 100644
index 0000000..8a096e6
--- /dev/null
+++ b/src/test/java/test/listeners/ConfigurationListenerFailSampleTest.java
@@ -0,0 +1,16 @@
+package test.listeners;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class ConfigurationListenerFailSampleTest {
+
+  @BeforeMethod
+  public void bmShouldFail() {
+    throw new RuntimeException();
+  }
+
+  @Test
+  public void f() {
+  }
+}
diff --git a/src/test/java/test/listeners/ConfigurationListenerSkipSampleTest.java b/src/test/java/test/listeners/ConfigurationListenerSkipSampleTest.java
new file mode 100644
index 0000000..c876c24
--- /dev/null
+++ b/src/test/java/test/listeners/ConfigurationListenerSkipSampleTest.java
@@ -0,0 +1,20 @@
+package test.listeners;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class ConfigurationListenerSkipSampleTest {
+
+  @BeforeMethod
+  public void bmShouldFail() {
+    throw new RuntimeException();
+  }
+
+  @BeforeMethod(dependsOnMethods = "bmShouldFail")
+  public void bm() {
+  }
+
+  @Test
+  public void f() {
+  }
+}
diff --git a/src/test/java/test/listeners/ConfigurationListenerSucceedSampleTest.java b/src/test/java/test/listeners/ConfigurationListenerSucceedSampleTest.java
new file mode 100644
index 0000000..eb072d1
--- /dev/null
+++ b/src/test/java/test/listeners/ConfigurationListenerSucceedSampleTest.java
@@ -0,0 +1,14 @@
+package test.listeners;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class ConfigurationListenerSucceedSampleTest {
+
+  @BeforeMethod
+  public void bmShouldSucceed() {}
+
+  @Test
+  public void f() {
+  }
+}
diff --git a/src/test/java/test/listeners/ConfigurationListenerTest.java b/src/test/java/test/listeners/ConfigurationListenerTest.java
new file mode 100644
index 0000000..08b19c4
--- /dev/null
+++ b/src/test/java/test/listeners/ConfigurationListenerTest.java
@@ -0,0 +1,63 @@
+package test.listeners;
+
+import org.testng.Assert;
+import org.testng.IConfigurationListener2;
+import org.testng.ITestResult;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+public class ConfigurationListenerTest extends SimpleBaseTest {
+
+  static public class CL implements IConfigurationListener2 {
+
+    private static int m_status = 0;
+
+    @Override
+    public void beforeConfiguration(ITestResult tr) {
+      m_status += 1;
+    }
+
+    @Override
+    public void onConfigurationSuccess(ITestResult itr) {
+      m_status += 3;
+    }
+
+    @Override
+    public void onConfigurationFailure(ITestResult itr) {
+      m_status += 5;
+    }
+
+    @Override
+    public void onConfigurationSkip(ITestResult itr) {
+      m_status += 7;
+    }
+
+  }
+
+  private void runTest(Class<?> cls, int expected) {
+    TestNG tng = create(cls);
+    CL listener = new CL();
+    CL.m_status = 0;
+    tng.addListener(listener);
+    tng.run();
+
+    Assert.assertEquals(CL.m_status, expected);
+  }
+
+  @Test
+  public void shouldSucceed() {
+    runTest(ConfigurationListenerSucceedSampleTest.class, 1 + 3);
+  }
+
+  @Test
+  public void shouldFail() {
+    runTest(ConfigurationListenerFailSampleTest.class, 1 + 5);
+  }
+
+  @Test
+  public void shouldSkip() {
+    runTest(ConfigurationListenerSkipSampleTest.class, 1 + 5 + 7); // fail + skip
+  }
+}
diff --git a/src/test/java/test/listeners/Derived1.java b/src/test/java/test/listeners/Derived1.java
new file mode 100644
index 0000000..b349e62
--- /dev/null
+++ b/src/test/java/test/listeners/Derived1.java
@@ -0,0 +1,9 @@
+package test.listeners;
+
+import org.testng.annotations.Test;
+
+class Derived1 extends BaseWithListener {
+  @Test
+  public void t() {
+  }
+}
diff --git a/src/test/java/test/listeners/Derived2.java b/src/test/java/test/listeners/Derived2.java
new file mode 100644
index 0000000..3acaac1
--- /dev/null
+++ b/src/test/java/test/listeners/Derived2.java
@@ -0,0 +1,9 @@
+package test.listeners;
+
+import org.testng.annotations.Test;
+
+class Derived2 extends BaseWithListener {
+  @Test
+  public void s() {
+  }
+}
diff --git a/src/test/java/test/listeners/EndMillisShouldNotBeZeroTest.java b/src/test/java/test/listeners/EndMillisShouldNotBeZeroTest.java
new file mode 100644
index 0000000..d1eec10
--- /dev/null
+++ b/src/test/java/test/listeners/EndMillisShouldNotBeZeroTest.java
@@ -0,0 +1,50 @@
+package test.listeners;
+
+import org.testng.IInvokedMethod;
+import org.testng.IInvokedMethodListener;
+import org.testng.ITestResult;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+import test.listeners.EndMillisShouldNotBeZeroTest.MyInvokedMethodListener;
+import junit.framework.Assert;
+
+@Listeners(MyInvokedMethodListener.class)
+public class EndMillisShouldNotBeZeroTest {
+  private static long m_end;
+
+  public static class MyInvokedMethodListener implements IInvokedMethodListener {
+
+    @Override
+    public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
+    }
+
+    @Override
+    public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
+      m_end = testResult.getEndMillis();
+    }
+    
+  }
+
+  @BeforeClass
+  public void bm() {
+    m_end = 0;
+  }
+
+  @Test
+  public void f1()
+  {
+    try {
+      Thread.sleep(1);
+    } catch (InterruptedException handled) {
+      Thread.currentThread().interrupt();
+    }
+  }
+
+  @Test(description = "Make sure that ITestResult#getEndMillis is properly set",
+      dependsOnMethods = "f1")
+  public void f2() {
+    Assert.assertTrue(m_end > 0);
+  }
+}
diff --git a/src/test/java/test/listeners/ExecutionListener1SampleTest.java b/src/test/java/test/listeners/ExecutionListener1SampleTest.java
new file mode 100644
index 0000000..b95d142
--- /dev/null
+++ b/src/test/java/test/listeners/ExecutionListener1SampleTest.java
@@ -0,0 +1,10 @@
+package test.listeners;
+
+import org.testng.annotations.Test;
+
+public class ExecutionListener1SampleTest {
+  @Test
+  public void f() {}
+}
+
+
diff --git a/src/test/java/test/listeners/ExecutionListener2SampleTest.java b/src/test/java/test/listeners/ExecutionListener2SampleTest.java
new file mode 100644
index 0000000..2e438cb
--- /dev/null
+++ b/src/test/java/test/listeners/ExecutionListener2SampleTest.java
@@ -0,0 +1,11 @@
+package test.listeners;
+
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+@Listeners(ExecutionListenerTest.ExecutionListener.class)
+public class ExecutionListener2SampleTest {
+  @Test
+  public void f() {}
+
+}
diff --git a/src/test/java/test/listeners/ExecutionListenerTest.java b/src/test/java/test/listeners/ExecutionListenerTest.java
new file mode 100644
index 0000000..15cb775
--- /dev/null
+++ b/src/test/java/test/listeners/ExecutionListenerTest.java
@@ -0,0 +1,64 @@
+package test.listeners;
+
+import org.testng.Assert;
+import org.testng.IExecutionListener;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.SimpleBaseTest;
+
+import java.util.Arrays;
+
+public class ExecutionListenerTest extends SimpleBaseTest {
+
+  public static class ExecutionListener implements IExecutionListener {
+    public static boolean m_start = false;
+    public static boolean m_finish = false;
+
+    @Override
+    public void onExecutionStart() {
+      m_start = true;
+    }
+
+    @Override
+    public void onExecutionFinish() {
+      m_finish = true;
+    }
+  }
+
+  @Test
+  public void executionListenerWithXml() {
+    runTest(ExecutionListener1SampleTest.class, true /* add listener */, true /* should run */);
+  }
+
+  @Test
+  public void executionListenerWithoutListener() {
+    runTest(ExecutionListener1SampleTest.class, false /* don't add listener */,
+        false /* should not run */);
+  }
+
+  @Test
+  public void executionListenerAnnotation() {
+    runTest(ExecutionListener2SampleTest.class, false /* don't add listener */,
+        true /* should run */);
+  }
+
+  private void runTest(Class<?> listenerClass, boolean addListener, boolean expected) {
+    XmlSuite s = createXmlSuite("ExecutionListener");
+    XmlTest t = createXmlTest(s, "Test", listenerClass.getName());
+
+    if (addListener) {
+      s.addListener(ExecutionListener.class.getName());
+    }
+    TestNG tng = create();
+    tng.setXmlSuites(Arrays.asList(s));
+    ExecutionListener.m_start = false;
+    ExecutionListener.m_finish = false;
+    tng.run();
+
+    Assert.assertEquals(ExecutionListener.m_start, expected);
+    Assert.assertEquals(ExecutionListener.m_finish, expected);
+  }
+}
diff --git a/src/test/java/test/listeners/FailingSampleTest.java b/src/test/java/test/listeners/FailingSampleTest.java
new file mode 100644
index 0000000..86102a7
--- /dev/null
+++ b/src/test/java/test/listeners/FailingSampleTest.java
@@ -0,0 +1,20 @@
+package test.listeners;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+@Listeners(SimpleListener.class)
+public class FailingSampleTest {
+  @AfterMethod
+  public void am() {
+    SimpleListener.m_list.add(6);
+  }
+
+  @Test
+  public void a1() {
+    SimpleListener.m_list.add(4);
+    throw new RuntimeException();
+  }
+
+}
diff --git a/src/test/java/test/listeners/L1.java b/src/test/java/test/listeners/L1.java
new file mode 100644
index 0000000..8e171bf
--- /dev/null
+++ b/src/test/java/test/listeners/L1.java
@@ -0,0 +1,4 @@
+package test.listeners;
+
+public class L1 extends BaseListener {
+}
diff --git a/src/test/java/test/listeners/L2.java b/src/test/java/test/listeners/L2.java
new file mode 100644
index 0000000..3b8600d
--- /dev/null
+++ b/src/test/java/test/listeners/L2.java
@@ -0,0 +1,4 @@
+package test.listeners;
+
+public class L2 extends BaseListener {
+}
diff --git a/src/test/java/test/listeners/L3.java b/src/test/java/test/listeners/L3.java
new file mode 100644
index 0000000..36cf282
--- /dev/null
+++ b/src/test/java/test/listeners/L3.java
@@ -0,0 +1,11 @@
+package test.listeners;
+
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+
+public class L3 extends TestListenerAdapter {
+  @Override
+  public void onTestStart(ITestResult result) {
+    BaseWithListener.incrementCount();
+  }
+}
diff --git a/src/test/java/test/listeners/LListener.java b/src/test/java/test/listeners/LListener.java
new file mode 100644
index 0000000..7347ffc
--- /dev/null
+++ b/src/test/java/test/listeners/LListener.java
@@ -0,0 +1,19 @@
+package test.listeners;
+
+import org.testng.IInvokedMethod;
+import org.testng.IInvokedMethodListener;
+import org.testng.ITestResult;
+
+public class LListener implements IInvokedMethodListener {
+  public static boolean invoked = false;
+
+  @Override
+  public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
+    invoked = true;
+  }
+
+  @Override
+  public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
+  }
+
+}
diff --git a/src/test/java/test/listeners/LSampleTest.java b/src/test/java/test/listeners/LSampleTest.java
new file mode 100644
index 0000000..ba10646
--- /dev/null
+++ b/src/test/java/test/listeners/LSampleTest.java
@@ -0,0 +1,9 @@
+package test.listeners;
+
+import org.testng.annotations.Test;
+
+public class LSampleTest {
+
+  @Test
+  public void dummy() {}
+}
diff --git a/src/test/java/test/listeners/ListenerAssert.java b/src/test/java/test/listeners/ListenerAssert.java
new file mode 100644
index 0000000..dfcae08
--- /dev/null
+++ b/src/test/java/test/listeners/ListenerAssert.java
@@ -0,0 +1,20 @@
+package test.listeners;
+
+import org.testng.Assert;
+import org.testng.ITestNGListener;
+
+import java.util.List;
+
+public final class ListenerAssert {
+
+    private ListenerAssert() {}
+
+    public static void assertListenerType(List<? extends ITestNGListener> listeners, Class<? extends ITestNGListener> clazz) {
+        for (ITestNGListener listener : listeners) {
+            if (clazz.isInstance(listener)) {
+                return;
+            }
+        }
+        Assert.fail();
+    }
+}
diff --git a/src/test/java/test/listeners/ListenerInXmlTest.java b/src/test/java/test/listeners/ListenerInXmlTest.java
new file mode 100644
index 0000000..363f3d6
--- /dev/null
+++ b/src/test/java/test/listeners/ListenerInXmlTest.java
@@ -0,0 +1,21 @@
+package test.listeners;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+import java.util.Arrays;
+
+public class ListenerInXmlTest extends SimpleBaseTest {
+
+  @Test(description = "Make sure that listeners defined in testng.xml are invoked")
+  public void listenerInXmlShouldBeInvoked() {
+    TestNG tng = create();
+    tng.setTestSuites(Arrays.asList(getPathToResource("listener-in-xml.xml")));
+    LListener.invoked = false;
+    tng.run();
+    Assert.assertTrue(LListener.invoked);
+  }
+}
diff --git a/src/test/java/test/listeners/ListenerTest.java b/src/test/java/test/listeners/ListenerTest.java
new file mode 100644
index 0000000..8c36586
--- /dev/null
+++ b/src/test/java/test/listeners/ListenerTest.java
@@ -0,0 +1,142 @@
+package test.listeners;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+import test.SimpleBaseTest;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+
+public class ListenerTest extends SimpleBaseTest {
+
+  @BeforeMethod
+  public void bm() {
+    SimpleListener.m_list = Lists.newArrayList();
+  }
+
+  @Test(description = "Ensure that if a listener is present, we get test(), onSuccess()," +
+  		" afterMethod()")
+  public void listenerShouldBeCalledBeforeConfiguration() {
+    TestNG tng = create(OrderedListenerSampleTest.class);
+    tng.run();
+    Assert.assertEquals(SimpleListener.m_list, Arrays.asList(1, 2, 3, 4));
+  }
+
+  @Test(description = "TESTNG-400: onTestFailure should be called before @AfterMethod")
+  public void failureBeforeAfterMethod() {
+    TestNG tng = create(FailingSampleTest.class);
+    tng.run();
+    Assert.assertEquals(SimpleListener.m_list, Arrays.asList(4, 5, 6));
+  }
+
+  @Test(description = "Inherited @Listeners annotations should aggregate")
+  public void aggregateListeners() {
+    TestNG tng = create(AggregateSampleTest.class);
+    AggregateSampleTest.m_count = 0;
+    tng.run();
+    Assert.assertEquals(AggregateSampleTest.m_count, 2);
+  }
+
+  @Test(description = "Should attach only one instance of the same @Listener class per test")
+  public void shouldAttachOnlyOneInstanceOfTheSameListenerClassPerTest() {
+    TestNG tng = create(Derived1.class, Derived2.class);
+    BaseWithListener.m_count = 0;
+    tng.run();
+    Assert.assertEquals(BaseWithListener.m_count, 2);
+  }
+
+  @Test(description = "@Listeners with an ISuiteListener")
+  public void suiteListenersShouldWork() {
+    TestNG tng = create(SuiteListenerSample.class);
+    SuiteListener.start = 0;
+    SuiteListener.finish = 0;
+    tng.run();
+    Assert.assertEquals(SuiteListener.start, 1);
+    Assert.assertEquals(SuiteListener.finish, 1);
+  }
+
+  @Test(description = "GITHUB-767: ISuiteListener called twice when @Listeners")
+  public void suiteListenerInListernersAnnotationShouldBeRunOnce() {
+    TestNG tng = createTests("Suite", SuiteListenerSample2.class);
+    SuiteListener2.start = 0;
+    SuiteListener2.finish = 0;
+    tng.run();
+    Assert.assertEquals(SuiteListener2.start, 1);
+    Assert.assertEquals(SuiteListener2.finish, 1);
+  }
+
+  @Test(description = "GITHUB-171")
+  public void suiteListenersShouldBeOnlyRunOnceWithManyTests() {
+    TestNG tng = createTests("suite", Derived1.class, Derived2.class);
+    SuiteListener.start = 0;
+    SuiteListener.finish = 0;
+    tng.run();
+    Assert.assertEquals(SuiteListener.start, 1);
+    Assert.assertEquals(SuiteListener.finish, 1);
+  }
+
+  @Test(description = "GITHUB-795")
+  public void suiteListenersShouldBeOnlyRunOnceWithManyIdenticalTests() {
+    TestNG tng = createTests("suite", Derived1.class, Derived1.class);
+    SuiteListener.start = 0;
+    SuiteListener.finish = 0;
+    tng.run();
+    Assert.assertEquals(SuiteListener.start, 1);
+    Assert.assertEquals(SuiteListener.finish, 1);
+  }
+
+  @Test(description = "GITHUB-169")
+  public void invokedMethodListenersShouldBeOnlyRunOnceWithManyTests() {
+    TestNG tng = createTests("suite", Derived1.class, Derived2.class);
+    MyInvokedMethodListener.beforeInvocation.clear();
+    MyInvokedMethodListener.afterInvocation.clear();
+    tng.run();
+    assertThat(MyInvokedMethodListener.beforeInvocation).containsOnly(
+            entry("t", 1), entry("s", 1)
+    );
+    assertThat(MyInvokedMethodListener.afterInvocation).containsOnly(
+            entry("t", 1), entry("s", 1)
+    );
+  }
+
+  @Test(description = "GITHUB-154: MethodInterceptor will be called twice")
+  public void methodInterceptorShouldBeRunOnce() {
+    TestNG tng = create(SuiteListenerSample.class);
+    MyMethodInterceptor interceptor = new MyMethodInterceptor();
+    tng.addListener(interceptor);
+    tng.run();
+    Assert.assertEquals(interceptor.getCount(), 1);
+  }
+
+  @Test(description = "GITHUB-356: Add listeners for @BeforeClass/@AfterClass")
+  public void classListenerShouldWork() {
+    MyClassListener.beforeNames.clear();
+    MyClassListener.afterNames.clear();
+    TestNG tng = create(Derived1.class, Derived2.class);
+    MyClassListener listener = new MyClassListener();
+    tng.addListener(listener);
+    tng.run();
+    assertThat(MyClassListener.beforeNames).containsExactly("Derived1", "Derived2");
+    assertThat(MyClassListener.afterNames).containsExactly("Derived1", "Derived2");
+  }
+
+  @Test(description = "GITHUB-356: Add listeners for @BeforeClass/@AfterClass")
+  public void classListenerShouldWorkFromAnnotation() {
+    MyClassListener.beforeNames.clear();
+    MyClassListener.afterNames.clear();
+    TestNG tng = create(ClassListenerSample.class);
+    tng.run();
+    assertThat(MyClassListener.beforeNames).containsExactly("ClassListenerSample");
+    assertThat(MyClassListener.afterNames).containsExactly("ClassListenerSample");
+  }
+}
diff --git a/src/test/java/test/listeners/MyClassListener.java b/src/test/java/test/listeners/MyClassListener.java
new file mode 100644
index 0000000..75b2356
--- /dev/null
+++ b/src/test/java/test/listeners/MyClassListener.java
@@ -0,0 +1,24 @@
+package test.listeners;
+
+import org.testng.IClassListener;
+import org.testng.IMethodInstance;
+import org.testng.ITestClass;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MyClassListener implements IClassListener {
+
+  public static final List<String> beforeNames = new ArrayList<>();
+  public static final List<String> afterNames = new ArrayList<>();
+
+  @Override
+  public void onBeforeClass(ITestClass testClass, IMethodInstance mi) {
+    beforeNames.add(testClass.getRealClass().getSimpleName());
+  }
+
+  @Override
+  public void onAfterClass(ITestClass testClass, IMethodInstance mi) {
+    afterNames.add(testClass.getRealClass().getSimpleName());
+  }
+}
diff --git a/src/test/java/test/listeners/MyInvokedMethodListener.java b/src/test/java/test/listeners/MyInvokedMethodListener.java
new file mode 100644
index 0000000..8f0170e
--- /dev/null
+++ b/src/test/java/test/listeners/MyInvokedMethodListener.java
@@ -0,0 +1,34 @@
+package test.listeners;
+
+import org.testng.IInvokedMethod;
+import org.testng.IInvokedMethodListener;
+import org.testng.ITestResult;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MyInvokedMethodListener implements IInvokedMethodListener {
+
+    public static Map<String, Integer> beforeInvocation = new HashMap<>();
+    public static Map<String, Integer> afterInvocation = new HashMap<>();
+
+
+    @Override
+    public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
+        increments(beforeInvocation, method);
+    }
+
+    @Override
+    public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
+        increments(afterInvocation, method);
+    }
+
+    private static void increments(Map<String, Integer> map, IInvokedMethod method) {
+        String stringValue = method.getTestMethod().getMethodName();
+        Integer count = map.get(stringValue);
+        if (count == null) {
+            count = 0;
+        }
+        map.put(stringValue, count+1);
+    }
+}
diff --git a/src/test/java/test/listeners/MyMethodInterceptor.java b/src/test/java/test/listeners/MyMethodInterceptor.java
new file mode 100644
index 0000000..f118045
--- /dev/null
+++ b/src/test/java/test/listeners/MyMethodInterceptor.java
@@ -0,0 +1,22 @@
+package test.listeners;
+
+import org.testng.IMethodInstance;
+import org.testng.IMethodInterceptor;
+import org.testng.ITestContext;
+
+import java.util.List;
+
+public class MyMethodInterceptor implements IMethodInterceptor {
+
+    private int count = 0;
+
+    @Override
+    public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) {
+        count++;
+        return methods;
+    }
+
+    public int getCount() {
+        return count;
+    }
+}
diff --git a/src/test/java/test/listeners/OrderedListenerSampleTest.java b/src/test/java/test/listeners/OrderedListenerSampleTest.java
new file mode 100644
index 0000000..e5d0725
--- /dev/null
+++ b/src/test/java/test/listeners/OrderedListenerSampleTest.java
@@ -0,0 +1,28 @@
+package test.listeners;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+@Listeners(SimpleListener.class)
+public class OrderedListenerSampleTest extends SimpleBaseTest {
+
+  @BeforeMethod
+  public void bm() {
+    SimpleListener.m_list.add(1);
+  }
+
+  @Test
+  public void f() {
+    SimpleListener.m_list.add(2);
+  }
+
+  @AfterMethod
+  public void am() {
+    SimpleListener.m_list.add(4);
+  }
+}
+
diff --git a/src/test/java/test/listeners/ResultContextListener.java b/src/test/java/test/listeners/ResultContextListener.java
new file mode 100644
index 0000000..0b62188
--- /dev/null
+++ b/src/test/java/test/listeners/ResultContextListener.java
@@ -0,0 +1,36 @@
+package test.listeners;
+
+import org.testng.ITestContext;
+import org.testng.ITestListener;
+import org.testng.ITestResult;
+
+public class ResultContextListener implements ITestListener {
+	
+	public static boolean contextProvided = false;
+	
+	public void onTestStart(ITestResult result) {
+		ITestContext context = result.getTestContext();
+		if (context != null) {
+			contextProvided = true;
+		}
+	}
+
+	public void onTestSuccess(ITestResult result) {
+	}
+
+	public void onTestFailure(ITestResult result) {
+	}
+
+	public void onTestSkipped(ITestResult result) {
+	}
+
+	public void onTestFailedButWithinSuccessPercentage(ITestResult result) {
+	}
+
+	public void onStart(ITestContext context) {
+	}
+
+	public void onFinish(ITestContext context) {
+	}
+
+}
diff --git a/src/test/java/test/listeners/ResultContextListenerSample.java b/src/test/java/test/listeners/ResultContextListenerSample.java
new file mode 100644
index 0000000..bcc963c
--- /dev/null
+++ b/src/test/java/test/listeners/ResultContextListenerSample.java
@@ -0,0 +1,13 @@
+package test.listeners;
+
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+@Listeners(ResultContextListener.class)
+public class ResultContextListenerSample {
+	
+	@Test
+	public void f() {
+	}
+	
+}
diff --git a/src/test/java/test/listeners/ResultContextTest.java b/src/test/java/test/listeners/ResultContextTest.java
new file mode 100644
index 0000000..e48caad
--- /dev/null
+++ b/src/test/java/test/listeners/ResultContextTest.java
@@ -0,0 +1,19 @@
+package test.listeners;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+public class ResultContextTest extends SimpleBaseTest {
+	
+	@Test
+	public void testResultContext() {
+		TestNG tng = create(ResultContextListenerSample.class);
+	    tng.run();
+	    Assert.assertTrue(ResultContextListener.contextProvided, 
+	    		"Test context was not provided to the listener");
+	}
+	
+}
diff --git a/src/test/java/test/listeners/ResultEndMillisTest.java b/src/test/java/test/listeners/ResultEndMillisTest.java
new file mode 100644
index 0000000..81687e4
--- /dev/null
+++ b/src/test/java/test/listeners/ResultEndMillisTest.java
@@ -0,0 +1,20 @@
+package test.listeners;
+
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+import test.sample.Sample1;
+import junit.framework.Assert;
+
+public class ResultEndMillisTest extends SimpleBaseTest {
+
+  @Test
+  public void endMillisShouldBeNonNull() {
+    TestNG tng = create(Sample1.class);
+    tng.addListener(new ResultListener());
+    tng.run();
+
+    Assert.assertTrue(ResultListener.m_end > 0);
+  }
+}
diff --git a/src/test/java/test/listeners/ResultListener.java b/src/test/java/test/listeners/ResultListener.java
new file mode 100644
index 0000000..f1a9a9f
--- /dev/null
+++ b/src/test/java/test/listeners/ResultListener.java
@@ -0,0 +1,73 @@
+package test.listeners;
+
+import org.testng.ITestContext;
+import org.testng.ITestResult;
+import org.testng.internal.IResultListener2;
+
+public class ResultListener implements IResultListener2 {
+
+  public static long m_end = 0;
+
+  @Override
+  public void onTestStart(ITestResult result) {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void beforeConfiguration(ITestResult tr) {
+  }
+
+  @Override
+  public void onTestSuccess(ITestResult result) {
+    m_end = result.getEndMillis();
+  }
+
+  @Override
+  public void onTestFailure(ITestResult result) {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void onTestSkipped(ITestResult result) {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void onTestFailedButWithinSuccessPercentage(ITestResult result) {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void onStart(ITestContext context) {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void onFinish(ITestContext context) {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void onConfigurationSuccess(ITestResult itr) {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void onConfigurationFailure(ITestResult itr) {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void onConfigurationSkip(ITestResult itr) {
+    // TODO Auto-generated method stub
+
+  }
+}
diff --git a/src/test/java/test/listeners/SimpleListener.java b/src/test/java/test/listeners/SimpleListener.java
new file mode 100644
index 0000000..dd86dd1
--- /dev/null
+++ b/src/test/java/test/listeners/SimpleListener.java
@@ -0,0 +1,22 @@
+package test.listeners;
+
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+
+import java.util.List;
+
+public class SimpleListener extends TestListenerAdapter {
+  public static List<Integer> m_list;
+
+  @Override
+  public void onTestSuccess(ITestResult tr) {
+    m_list.add(3);
+    super.onTestSuccess(tr);
+  }
+
+  @Override
+  public void onTestFailure(ITestResult tr) {
+    m_list.add(5);
+    super.onTestSuccess(tr);
+  }
+}
diff --git a/src/test/java/test/listeners/SuiteAndConfigurationListenerTest.java b/src/test/java/test/listeners/SuiteAndConfigurationListenerTest.java
new file mode 100644
index 0000000..f4130aa
--- /dev/null
+++ b/src/test/java/test/listeners/SuiteAndConfigurationListenerTest.java
@@ -0,0 +1,58 @@
+package test.listeners;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.testng.Assert;
+import org.testng.IConfigurationListener;
+import org.testng.ISuite;
+import org.testng.ISuiteListener;
+import org.testng.ITestResult;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+import test.listeners.SuiteAndConfigurationListenerTest.MyListener;
+
+/**
+ * Check that if a listener implements IConfigurationListener additionally to
+ * ISuiteListener, ISuiteListener gets invoked exactly once.
+ *
+ * @author Mihails Volkovs
+ */
+@Listeners(MyListener.class)
+public class SuiteAndConfigurationListenerTest {
+  public static class MyListener implements ISuiteListener, IConfigurationListener {
+
+    private static volatile AtomicInteger started = new AtomicInteger(0);
+
+    public MyListener() {
+    }
+
+    @Override
+    public void onStart(ISuite suite) {
+      started.incrementAndGet();
+    }
+
+    @Override
+    public void onFinish(ISuite suite) {
+    }
+
+    @Override
+    public void onConfigurationSuccess(ITestResult itr) {
+    }
+
+    @Override
+    public void onConfigurationFailure(ITestResult itr) {
+    }
+
+    @Override
+    public void onConfigurationSkip(ITestResult itr) {
+    }
+
+  }
+
+  @Test
+  public void bothListenersShouldRun() {
+    Assert.assertEquals(MyListener.started.get(), 1, "ISuiteListener was not invoked exactly once:");
+  }
+
+}
diff --git a/src/test/java/test/listeners/SuiteAndInvokedMethodListenerTest.java b/src/test/java/test/listeners/SuiteAndInvokedMethodListenerTest.java
new file mode 100644
index 0000000..fcd1232
--- /dev/null
+++ b/src/test/java/test/listeners/SuiteAndInvokedMethodListenerTest.java
@@ -0,0 +1,56 @@
+package test.listeners;
+
+import org.testng.Assert;
+import org.testng.IInvokedMethod;
+import org.testng.IInvokedMethodListener;
+import org.testng.ISuite;
+import org.testng.ISuiteListener;
+import org.testng.ITestResult;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+import test.listeners.SuiteAndInvokedMethodListenerTest.MyListener;
+
+/**
+ * Make sure that if a listener implements both IInvokedMethodListener
+ * and ISuiteListener, both listeners get invoked.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+@Listeners(MyListener.class)
+public class SuiteAndInvokedMethodListenerTest {
+  public static class MyListener implements IInvokedMethodListener, ISuiteListener {
+
+    private static boolean m_before = false;
+    private static boolean m_start = false;
+
+    public MyListener() {
+    }
+
+    @Override
+    public void beforeInvocation(IInvokedMethod method, ITestResult result) {
+      m_before = true;
+    }
+
+    @Override
+    public void afterInvocation(IInvokedMethod method, ITestResult result) {
+    }
+
+    @Override
+    public void onStart(ISuite suite) {
+      m_start = true;
+    }
+
+    @Override
+    public void onFinish(ISuite suite) {
+    }
+
+  }
+
+  @Test
+  public void bothListenersShouldRun() {
+    Assert.assertTrue(MyListener.m_before, "IInvokedMethodListener was not invoked");
+    Assert.assertTrue(MyListener.m_start, "ISuiteListener was not invoked");
+  }
+
+}
diff --git a/src/test/java/test/listeners/SuiteListener.java b/src/test/java/test/listeners/SuiteListener.java
new file mode 100644
index 0000000..cafed1c
--- /dev/null
+++ b/src/test/java/test/listeners/SuiteListener.java
@@ -0,0 +1,20 @@
+package test.listeners;
+
+import org.testng.ISuite;
+import org.testng.ISuiteListener;
+
+public class SuiteListener implements ISuiteListener {
+  public static int start = 0;
+  public static int finish = 0;
+
+  @Override
+  public void onFinish(ISuite suite) {
+    finish++;
+  }
+
+  @Override
+  public void onStart(ISuite suite) {
+    start++;
+  }
+
+}
diff --git a/src/test/java/test/listeners/SuiteListener2.java b/src/test/java/test/listeners/SuiteListener2.java
new file mode 100644
index 0000000..2537796
--- /dev/null
+++ b/src/test/java/test/listeners/SuiteListener2.java
@@ -0,0 +1,88 @@
+package test.listeners;
+
+import org.testng.*;
+import org.testng.annotations.ITestAnnotation;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.List;
+
+public class SuiteListener2 implements IAnnotationTransformer, IInvokedMethodListener, ITestListener,  ISuiteListener, IExecutionListener, IMethodInterceptor {
+    public static int start = 0;
+    public static int finish = 0;
+
+    @Override
+    public void onFinish(ISuite suite) {
+        finish++;
+    }
+
+    @Override
+    public void onStart(ISuite suite) {
+        start++;
+    }
+
+    @Override
+    public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
+
+    }
+
+    @Override
+    public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
+
+    }
+
+    @Override
+    public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
+
+    }
+
+    @Override
+    public void onExecutionStart() {
+
+    }
+
+    @Override
+    public void onExecutionFinish() {
+
+    }
+
+    @Override
+    public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) {
+        return methods;
+    }
+
+    @Override
+    public void onTestStart(ITestResult result) {
+
+    }
+
+    @Override
+    public void onTestSuccess(ITestResult result) {
+
+    }
+
+    @Override
+    public void onTestFailure(ITestResult result) {
+
+    }
+
+    @Override
+    public void onTestSkipped(ITestResult result) {
+
+    }
+
+    @Override
+    public void onTestFailedButWithinSuccessPercentage(ITestResult result) {
+
+    }
+
+    @Override
+    public void onStart(ITestContext context) {
+
+    }
+
+    @Override
+    public void onFinish(ITestContext context) {
+
+    }
+}
diff --git a/src/test/java/test/listeners/SuiteListenerSample.java b/src/test/java/test/listeners/SuiteListenerSample.java
new file mode 100644
index 0000000..710c79c
--- /dev/null
+++ b/src/test/java/test/listeners/SuiteListenerSample.java
@@ -0,0 +1,11 @@
+package test.listeners;
+
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+@Listeners(SuiteListener.class)
+public class SuiteListenerSample {
+
+    @Test
+    public void foo(){}
+}
diff --git a/src/test/java/test/listeners/SuiteListenerSample2.java b/src/test/java/test/listeners/SuiteListenerSample2.java
new file mode 100644
index 0000000..54eeb2a
--- /dev/null
+++ b/src/test/java/test/listeners/SuiteListenerSample2.java
@@ -0,0 +1,11 @@
+package test.listeners;
+
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+@Listeners(SuiteListener2.class)
+public class SuiteListenerSample2 {
+
+    @Test
+    public void foo(){}
+}
diff --git a/src/test/java/test/mannotation/MAnnotation2SampleTest.java b/src/test/java/test/mannotation/MAnnotation2SampleTest.java
new file mode 100644
index 0000000..877c21a
--- /dev/null
+++ b/src/test/java/test/mannotation/MAnnotation2SampleTest.java
@@ -0,0 +1,123 @@
+package test.mannotation;
+
+import org.testng.Assert;
+import org.testng.annotations.Configuration;
+import org.testng.annotations.IConfigurationAnnotation;
+import org.testng.annotations.ITestAnnotation;
+import org.testng.annotations.Test;
+import org.testng.internal.IConfiguration;
+import org.testng.internal.annotations.IAnnotationFinder;
+
+import java.lang.reflect.Method;
+
+public class MAnnotation2SampleTest {
+  private IConfiguration m_configuration = new org.testng.internal.Configuration();
+  private IAnnotationFinder m_finder;
+
+  @Configuration(beforeTestClass = true, enabled = true, groups="current")
+  public void init() {
+    m_finder = m_configuration.getAnnotationFinder();
+  }
+
+  @Test
+  public void verifyTestGroupsInheritance()
+    throws SecurityException, NoSuchMethodException
+  {
+    {
+      Method method = MTest3.class.getMethod("groups1", new Class[0]);
+      ITestAnnotation test1 = (ITestAnnotation) m_finder.findAnnotation(method, ITestAnnotation.class);
+      Assert.assertEqualsNoOrder(new String[] { "method-test3", "child-class-test3", "base-class" },
+          test1.getGroups());
+    }
+
+    {
+      Method method = MTest3.class.getMethod("groups2", new Class[0]);
+      ITestAnnotation test1 = (ITestAnnotation) m_finder.findAnnotation(method, ITestAnnotation.class);
+      Assert.assertEqualsNoOrder(new String[] { "child-class-test3", "base-class" },
+          test1.getGroups());
+    }
+  }
+
+  @Test
+  public void verifyTestDependsOnGroupsInheritance()
+    throws SecurityException, NoSuchMethodException
+  {
+    {
+      Method method = MTest3.class.getMethod("dependsOnGroups1", new Class[0]);
+      ITestAnnotation test1 = (ITestAnnotation) m_finder.findAnnotation(method, ITestAnnotation.class);
+      Assert.assertEqualsNoOrder(new String[] { "dog2", "dog1", "dog3" },
+          test1.getDependsOnGroups());
+    }
+
+    {
+      Method method = MTest3.class.getMethod("dependsOnGroups2", new Class[0]);
+      ITestAnnotation test1 = (ITestAnnotation) m_finder.findAnnotation(method, ITestAnnotation.class);
+      Assert.assertEqualsNoOrder(new String[] { "dog1", "dog3" },
+          test1.getDependsOnGroups());
+    }
+
+  }
+
+  @Test
+  public void verifyTestDependsOnMethodsInheritance()
+    throws SecurityException, NoSuchMethodException
+  {
+    {
+      Method method = MTest3.class.getMethod("dependsOnMethods1", new Class[0]);
+      ITestAnnotation test1 = (ITestAnnotation) m_finder.findAnnotation(method, ITestAnnotation.class);
+      Assert.assertEqualsNoOrder(new String[] { "dom2", "dom3", "dom1" },
+          test1.getDependsOnMethods());
+    }
+
+    {
+      Method method = MTest3.class.getMethod("dependsOnMethods2", new Class[0]);
+      ITestAnnotation test1 = (ITestAnnotation) m_finder.findAnnotation(method, ITestAnnotation.class);
+      Assert.assertEqualsNoOrder(new String[] { "dom1", "dom3" },
+          test1.getDependsOnMethods());
+    }
+
+  }
+
+
+  @Test
+  public void verifyConfigurationGroupsInheritance()
+    throws SecurityException, NoSuchMethodException
+  {
+    Method method = MTest3.class.getMethod("beforeSuite", new Class[0]);
+    IConfigurationAnnotation test1 = (IConfigurationAnnotation) m_finder.findAnnotation(method, IConfigurationAnnotation.class);
+    Assert.assertEqualsNoOrder(new String[] { "method-test3", "child-class-test3", "base-class" },
+        test1.getGroups());
+  }
+
+  @Test(groups="current")
+  public void verifyTestEnabledInheritance()
+    throws SecurityException, NoSuchMethodException
+  {
+    {
+      Method method = MTest3.class.getMethod("enabled1", new Class[0]);
+      ITestAnnotation test1 = (ITestAnnotation) m_finder.findAnnotation(method, ITestAnnotation.class);
+      Assert.assertFalse(test1.getEnabled());
+    }
+
+    {
+      Method method = MTest3.class.getMethod("enabled2", new Class[0]);
+      ITestAnnotation test1 = (ITestAnnotation) m_finder.findAnnotation(method, ITestAnnotation.class);
+      Assert.assertTrue(test1.getEnabled());
+    }
+
+  }
+
+//  @Test(groups = "current")
+//  public void verifyCapture()
+//    throws SecurityException, NoSuchMethodException
+//  {
+//    {
+//      Method method = MChildCaptureTest.class.getMethod("shouldBelongToGroupChild", new Class[0]);
+//      ITest test1 = (ITest) m_finder.findAnnotation(method, ITest.class);
+//      Assert.assertEqualsNoOrder(new String[] { "child" },
+//          test1.getGroups());
+//    }
+//  }
+
+
+}
diff --git a/src/test/java/test/mannotation/MAnnotationSampleTest.java b/src/test/java/test/mannotation/MAnnotationSampleTest.java
new file mode 100644
index 0000000..b0fecd6
--- /dev/null
+++ b/src/test/java/test/mannotation/MAnnotationSampleTest.java
@@ -0,0 +1,253 @@
+package test.mannotation;
+
+import org.testng.Assert;
+import org.testng.annotations.Configuration;
+import org.testng.annotations.IConfigurationAnnotation;
+import org.testng.annotations.IDataProviderAnnotation;
+import org.testng.annotations.IExpectedExceptionsAnnotation;
+import org.testng.annotations.IFactoryAnnotation;
+import org.testng.annotations.IParametersAnnotation;
+import org.testng.annotations.ITestAnnotation;
+import org.testng.annotations.Test;
+import org.testng.internal.IConfiguration;
+import org.testng.internal.annotations.IAfterSuite;
+import org.testng.internal.annotations.IAnnotationFinder;
+import org.testng.internal.annotations.IBeforeSuite;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+@Test(enabled = true)
+public class MAnnotationSampleTest {
+  private IConfiguration m_configuration = new org.testng.internal.Configuration();
+  private IAnnotationFinder m_finder;
+
+  @Configuration(beforeTestClass = true, enabled = true)
+  public void init() {
+    m_finder = m_configuration.getAnnotationFinder();
+  }
+
+  public void verifyTestClassLevel() {
+    //
+    // Tests on MTest1SampleTest
+    //
+    ITestAnnotation test1 = (ITestAnnotation) m_finder.findAnnotation(MTest1.class, ITestAnnotation.class);
+    Assert.assertTrue(test1.getEnabled());
+    Assert.assertEquals(test1.getGroups(), new String[] { "group1", "group2" });
+    Assert.assertTrue(test1.getAlwaysRun());
+    Assert.assertEquals(test1.getParameters(), new String[] { "param1", "param2" });
+    Assert.assertEqualsNoOrder(test1.getDependsOnGroups(), new String[] { "dg1", "dg2" },  "depends on groups");
+    Assert.assertEqualsNoOrder( test1.getDependsOnMethods(), new String[] { "dm1", "dm2" });
+    Assert.assertEquals(test1.getTimeOut(), 42);
+    Assert.assertEquals(test1.getInvocationCount(), 43);
+    Assert.assertEquals(test1.getSuccessPercentage(), 44);
+    Assert.assertEquals(test1.getThreadPoolSize(), 3);
+    Assert.assertEquals(test1.getDataProvider(), "dp");
+    Assert.assertEquals(test1.getDescription(), "Class level description");
+
+    //
+    // Tests on MTest1SampleTest (test defaults)
+    //
+    ITestAnnotation test2 = (ITestAnnotation) m_finder.findAnnotation(MTest2.class, ITestAnnotation.class);
+    // test default for enabled
+    Assert.assertTrue(test2.getEnabled());
+    Assert.assertFalse(test2.getAlwaysRun());
+    Assert.assertEquals(test2.getTimeOut(), 0);
+    Assert.assertEquals(test2.getInvocationCount(), 1);
+    Assert.assertEquals(test2.getSuccessPercentage(), 100);
+    Assert.assertEquals(test2.getDataProvider(), "");
+  }
+
+  public void verifyTestMethodLevel() throws SecurityException, NoSuchMethodException
+  {
+    //
+    // Tests on MTest1SampleTest
+    //
+    Method method = MTest1.class.getMethod("f", new Class[0]);
+    ITestAnnotation test1 = (ITestAnnotation) m_finder.findAnnotation(method, ITestAnnotation.class);
+    Assert.assertTrue(test1.getEnabled());
+    Assert.assertEqualsNoOrder(test1.getGroups(), new String[] { "group1", "group3", "group4", "group2" });
+    Assert.assertTrue(test1.getAlwaysRun());
+    Assert.assertEquals(test1.getParameters(), new String[] { "param3", "param4" });
+    Assert.assertEqualsNoOrder(test1.getDependsOnGroups(), new String[] { "dg1", "dg2", "dg3", "dg4" });
+    Assert.assertEqualsNoOrder(test1.getDependsOnMethods(), new String[] { "dm1", "dm2", "dm3", "dm4" });
+    Assert.assertEquals(test1.getTimeOut(), 142);
+    Assert.assertEquals(test1.getInvocationCount(), 143);
+    Assert.assertEquals(test1.getSuccessPercentage(), 61);
+    Assert.assertEquals(test1.getDataProvider(), "dp2");
+    Assert.assertEquals(test1.getDescription(), "Method description");
+    Class[] exceptions = test1.getExpectedExceptions();
+    Assert.assertEquals(exceptions.length, 1);
+    Assert.assertEquals(exceptions[0], NullPointerException.class);
+  }
+
+  public void verifyTestConstructorLevel() throws SecurityException, NoSuchMethodException
+  {
+    //
+    // Tests on MTest1SampleTest
+    //
+    Constructor constructor = MTest1.class.getConstructor(new Class[0]);
+    ITestAnnotation test1 = (ITestAnnotation) m_finder.findAnnotation(constructor, ITestAnnotation.class);
+    Assert.assertNotNull(test1);
+    Assert.assertTrue(test1.getEnabled());
+    Assert.assertEqualsNoOrder(test1.getGroups(), new String[] { "group5", "group1", "group6", "group2" });
+    Assert.assertTrue(test1.getAlwaysRun());
+    Assert.assertEquals(test1.getParameters(), new String[] { "param5", "param6" });
+    Assert.assertEqualsNoOrder(test1.getDependsOnGroups(), new String[] { "dg1", "dg2", "dg5", "dg6" });
+    Assert.assertEqualsNoOrder(test1.getDependsOnMethods(), new String[] { "dm1", "dm2", "dm5", "dm6" });
+    Assert.assertEquals(test1.getTimeOut(), 242);
+    Assert.assertEquals(test1.getInvocationCount(), 243);
+    Assert.assertEquals(test1.getSuccessPercentage(), 62);
+    Assert.assertEquals(test1.getDataProvider(), "dp3");
+    Assert.assertEquals(test1.getDescription(), "Constructor description");
+    Class[] exceptions = test1.getExpectedExceptions();
+    Assert.assertEquals(exceptions.length, 1);
+    Assert.assertEquals(exceptions[0], NumberFormatException.class);
+  }
+
+  public void verifyConfigurationBefore() throws SecurityException, NoSuchMethodException
+  {
+    Method method = MTest1.class.getMethod("before", new Class[0]);
+    IConfigurationAnnotation configuration =
+      (IConfigurationAnnotation) m_finder.findAnnotation(method, IConfigurationAnnotation.class);
+    Assert.assertNotNull(configuration);
+    Assert.assertTrue(configuration.getBeforeSuite());
+    Assert.assertTrue(configuration.getBeforeTestMethod());
+    Assert.assertTrue(configuration.getBeforeTest());
+    Assert.assertTrue(configuration.getBeforeTestClass());
+    Assert.assertFalse(configuration.getAfterSuite());
+    Assert.assertFalse(configuration.getAfterTestMethod());
+    Assert.assertFalse(configuration.getAfterTest());
+    Assert.assertFalse(configuration.getAfterTestClass());
+    Assert.assertEquals(0, configuration.getAfterGroups().length);
+    String[] bg = configuration.getBeforeGroups();
+    Assert.assertEquals(bg.length, 2);
+    Assert.assertEqualsNoOrder(bg, new String[] {"b1", "b2"});
+
+    // Default values
+    Assert.assertTrue(configuration.getEnabled());
+    Assert.assertTrue(configuration.getInheritGroups());
+    Assert.assertFalse(configuration.getAlwaysRun());
+  }
+
+  public void verifyConfigurationAfter() throws SecurityException, NoSuchMethodException
+  {
+    Method method = MTest1.class.getMethod("after", new Class[0]);
+    IConfigurationAnnotation configuration =
+      (IConfigurationAnnotation) m_finder.findAnnotation(method, IConfigurationAnnotation.class);
+    Assert.assertNotNull(configuration);
+    Assert.assertFalse(configuration.getBeforeSuite());
+    Assert.assertFalse(configuration.getBeforeTestMethod());
+    Assert.assertFalse(configuration.getBeforeTest());
+    Assert.assertFalse(configuration.getBeforeTestClass());
+    Assert.assertTrue(configuration.getAfterSuite());
+    Assert.assertTrue(configuration.getAfterTestMethod());
+    Assert.assertTrue(configuration.getAfterTest());
+    Assert.assertTrue(configuration.getAfterTestClass());
+    Assert.assertEquals(0, configuration.getBeforeGroups().length);
+    String[] ag = configuration.getAfterGroups();
+    Assert.assertEquals(ag.length, 2);
+    Assert.assertEqualsNoOrder(ag, new String[] {"a1", "a2"});
+
+    // Default values
+    Assert.assertTrue(configuration.getEnabled());
+    Assert.assertTrue(configuration.getInheritGroups());
+    Assert.assertFalse(configuration.getAlwaysRun());
+  }
+
+  public void verifyConfigurationOthers() throws SecurityException, NoSuchMethodException
+  {
+    Method method = MTest1.class.getMethod("otherConfigurations", new Class[0]);
+    IConfigurationAnnotation configuration =
+      (IConfigurationAnnotation) m_finder.findAnnotation(method, IConfigurationAnnotation.class);
+    Assert.assertNotNull(configuration);
+    Assert.assertFalse(configuration.getBeforeSuite());
+    Assert.assertFalse(configuration.getBeforeTestMethod());
+    Assert.assertFalse(configuration.getBeforeTest());
+    Assert.assertFalse(configuration.getBeforeTestClass());
+    Assert.assertFalse(configuration.getAfterSuite());
+    Assert.assertFalse(configuration.getAfterTestMethod());
+    Assert.assertFalse(configuration.getAfterTest());
+    Assert.assertFalse(configuration.getAfterTestClass());
+
+    Assert.assertFalse(configuration.getEnabled());
+    Assert.assertEquals(configuration.getParameters(), new String[] { "oparam1", "oparam2" });
+    Assert.assertEqualsNoOrder(configuration.getGroups(), new String[] { "group1", "ogroup1", "ogroup2", "group2" }, "groups");
+    Assert.assertEqualsNoOrder(configuration.getDependsOnGroups(), new String[] { "odg1", "odg2" }, "depends on groups");
+    Assert.assertEqualsNoOrder(configuration.getDependsOnMethods(), new String[] { "odm1", "odm2" }, "depends on methods");
+    Assert.assertFalse(configuration.getInheritGroups());
+    Assert.assertTrue(configuration.getAlwaysRun());
+    Assert.assertEquals(configuration.getDescription(), "beforeSuite description");
+  }
+
+  public void verifyDataProvider() throws SecurityException, NoSuchMethodException
+  {
+    Method method = MTest1.class.getMethod("otherConfigurations", new Class[0]);
+    IDataProviderAnnotation dataProvider =
+      (IDataProviderAnnotation) m_finder.findAnnotation(method, IDataProviderAnnotation.class);
+    Assert.assertNotNull(dataProvider);
+    Assert.assertEquals(dataProvider.getName(), "dp4");
+  }
+
+  public void verifyExpectedExceptions() throws SecurityException, NoSuchMethodException
+  {
+    Method method = MTest1.class.getMethod("otherConfigurations", new Class[0]);
+    IExpectedExceptionsAnnotation exceptions=
+      (IExpectedExceptionsAnnotation) m_finder.findAnnotation(method, IExpectedExceptionsAnnotation.class);
+
+    Assert.assertNotNull(exceptions);
+    Assert.assertEquals(exceptions.getValue(), new Class[] { MTest1.class, MTest2.class });
+  }
+
+  public void verifyFactory() throws SecurityException, NoSuchMethodException
+  {
+    Method method = MTest1.class.getMethod("factory", new Class[0]);
+    IFactoryAnnotation factory=
+      (IFactoryAnnotation) m_finder.findAnnotation(method, IFactoryAnnotation.class);
+
+    Assert.assertNotNull(factory);
+    Assert.assertEquals(factory.getParameters(), new String[] { "pf1", "pf2" });
+  }
+
+  public void verifyParameters() throws SecurityException, NoSuchMethodException
+  {
+    Method method = MTest1.class.getMethod("parameters", new Class[0]);
+    IParametersAnnotation parameters =
+      (IParametersAnnotation) m_finder.findAnnotation(method, IParametersAnnotation.class);
+
+    Assert.assertNotNull(parameters);
+    Assert.assertEquals(parameters.getValue(), new String[] { "pp1", "pp2", "pp3" });
+  }
+
+  public void verifyNewConfigurationBefore() throws SecurityException, NoSuchMethodException
+  {
+    Method method = MTest1.class.getMethod("newBefore", new Class[0]);
+    IConfigurationAnnotation configuration =
+      (IConfigurationAnnotation) m_finder.findAnnotation(method, IBeforeSuite.class);
+    Assert.assertNotNull(configuration);
+    Assert.assertTrue(configuration.getBeforeSuite());
+
+    // Default values
+    Assert.assertTrue(configuration.getEnabled());
+    Assert.assertTrue(configuration.getInheritGroups());
+    Assert.assertFalse(configuration.getAlwaysRun());
+  }
+
+  public void verifyNewConfigurationAfter() throws SecurityException, NoSuchMethodException
+  {
+    Method method = MTest1.class.getMethod("newAfter", new Class[0]);
+    IConfigurationAnnotation configuration =
+      (IConfigurationAnnotation) m_finder.findAnnotation(method, IAfterSuite.class);
+    Assert.assertNotNull(configuration);
+    Assert.assertTrue(configuration.getAfterSuite());
+
+    // Default values
+    Assert.assertTrue(configuration.getEnabled());
+    Assert.assertTrue(configuration.getInheritGroups());
+    Assert.assertFalse(configuration.getAlwaysRun());
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[MAnnotationSampleTest] " + s);
+  }
+}
diff --git a/src/test/java/test/mannotation/MBase.java b/src/test/java/test/mannotation/MBase.java
new file mode 100644
index 0000000..932f33a
--- /dev/null
+++ b/src/test/java/test/mannotation/MBase.java
@@ -0,0 +1,11 @@
+package test.mannotation;
+
+import org.testng.annotations.Test;
+
+@Test(groups = "base-class", dependsOnGroups="dog3", dependsOnMethods="dom3",
+    enabled=true)
+public class MBase {
+
+  public void baseTest() {}
+
+}
diff --git a/src/test/java/test/mannotation/MBaseCapture.java b/src/test/java/test/mannotation/MBaseCapture.java
new file mode 100644
index 0000000..acb85c3
--- /dev/null
+++ b/src/test/java/test/mannotation/MBaseCapture.java
@@ -0,0 +1,18 @@
+package test.mannotation;
+
+import org.testng.annotations.Test;
+
+/**
+ * Make sure that if a method is declared in the base class and a child class
+ * adds a class-scoped group to it, that method receives it as well.
+ *
+ * @author cbeust
+ * @date Mar 22, 2006
+ */
+public class MBaseCapture {
+
+  @Test
+  public void shouldBelongToGroupChild() {
+
+  }
+}
diff --git a/src/test/java/test/mannotation/MChildCaptureTest.java b/src/test/java/test/mannotation/MChildCaptureTest.java
new file mode 100644
index 0000000..1f0e592
--- /dev/null
+++ b/src/test/java/test/mannotation/MChildCaptureTest.java
@@ -0,0 +1,11 @@
+package test.mannotation;
+
+import org.testng.annotations.Test;
+
+@Test(groups = "child")
+public class MChildCaptureTest extends MBaseCapture {
+
+  public void f() {
+
+  }
+}
diff --git a/src/test/java/test/mannotation/MTest1.java b/src/test/java/test/mannotation/MTest1.java
new file mode 100644
index 0000000..a4b8bde
--- /dev/null
+++ b/src/test/java/test/mannotation/MTest1.java
@@ -0,0 +1,84 @@
+package test.mannotation;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterGroups;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeGroups;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Configuration;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.ExpectedExceptions;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+@Test(enabled = true, groups = {"group1", "group2"},
+    alwaysRun = true, parameters = {"param1", "param2"},
+    dependsOnGroups = {"dg1", "dg2"}, dependsOnMethods = {"dm1", "dm2"},
+    timeOut = 42, invocationCount = 43, successPercentage = 44,
+    threadPoolSize = 3,
+    dataProvider = "dp", description = "Class level description")
+public class MTest1 {
+
+  @Test(enabled = true, groups = {"group5", "group6"},
+      alwaysRun = true, parameters = {"param5", "param6"},
+      dependsOnGroups = {"dg5", "dg6"}, dependsOnMethods = {"dm5", "dm6"},
+      timeOut = 242, invocationCount = 243, successPercentage = 62,
+      dataProvider = "dp3", description = "Constructor description",
+      expectedExceptions = NumberFormatException.class)
+  public MTest1() {}
+
+  @Test(enabled = true, groups = {"group3", "group4"},
+      alwaysRun = true, parameters = {"param3", "param4"},
+      dependsOnGroups = {"dg3", "dg4"}, dependsOnMethods = {"dm3", "dm4"},
+      timeOut = 142, invocationCount = 143, successPercentage = 61,
+      dataProvider = "dp2", description = "Method description",
+      expectedExceptions = NullPointerException.class)
+  public void f() {}
+
+  @Configuration(beforeSuite = true, beforeTestMethod = true,
+      beforeTest = true, beforeTestClass = true,
+      beforeGroups = { "b1", "b2"})
+  public void before() {}
+
+  @Configuration(afterSuite = true, afterTestMethod = true,
+      afterTest = true, afterTestClass = true,
+      afterGroups = {"a1", "a2"})
+  public void after() {}
+
+  @Configuration(parameters = {"oparam1", "oparam2"},
+      enabled = false, groups = {"ogroup1", "ogroup2"},
+      dependsOnGroups = {"odg1","odg2"},
+      dependsOnMethods = {"odm1", "odm2"}, alwaysRun = true,
+      inheritGroups = false,
+      description = "beforeSuite description")
+   @DataProvider(name = "dp4")
+   @ExpectedExceptions({MTest1.class, MTest2.class })
+  public void otherConfigurations() {}
+
+  @Factory(parameters = {"pf1", "pf2"})
+  public void factory() {}
+
+  @Parameters({"pp1", "pp2", "pp3"})
+  public void parameters() {}
+
+  @BeforeSuite
+  @BeforeTest
+  @BeforeGroups
+  @BeforeClass
+  @BeforeMethod
+  public void newBefore() {}
+
+  @AfterSuite
+  @AfterTest
+  @AfterGroups
+  @AfterClass
+  @AfterMethod
+  public void newAfter() {}
+
+}
diff --git a/src/test/java/test/mannotation/MTest2.java b/src/test/java/test/mannotation/MTest2.java
new file mode 100644
index 0000000..2a7b9ff
--- /dev/null
+++ b/src/test/java/test/mannotation/MTest2.java
@@ -0,0 +1,8 @@
+package test.mannotation;
+
+import org.testng.annotations.Test;
+
+@Test(alwaysRun = false)
+public class MTest2 {
+
+}
diff --git a/src/test/java/test/mannotation/MTest3.java b/src/test/java/test/mannotation/MTest3.java
new file mode 100644
index 0000000..68d50ca
--- /dev/null
+++ b/src/test/java/test/mannotation/MTest3.java
@@ -0,0 +1,37 @@
+package test.mannotation;
+
+import org.testng.annotations.Configuration;
+import org.testng.annotations.Test;
+
+@Test(groups = "child-class-test3", dependsOnGroups = "dog1", dependsOnMethods = "dom1")
+public class MTest3 extends MBase {
+
+  @Test(groups = "method-test3")
+  public void groups1() {}
+
+  @Test
+  public void groups2() {}
+
+  @Test(dependsOnGroups = "dog2")
+  public void dependsOnGroups1() {}
+
+  @Test
+  public void dependsOnGroups2() {}
+
+  @Test(dependsOnMethods = "dom2")
+  public void dependsOnMethods1() {}
+
+  @Test
+  public void dependsOnMethods2() {}
+
+  @Test(enabled = false)
+  public void enabled1() {}
+
+  @Test
+  public void enabled2() {}
+
+  @Configuration(beforeSuite = true, groups = "method-test3")
+  public void beforeSuite() {
+  }
+
+}
diff --git a/src/test/java/test/methodinterceptors/FastTestsFirstInterceptor.java b/src/test/java/test/methodinterceptors/FastTestsFirstInterceptor.java
new file mode 100644
index 0000000..87d515c
--- /dev/null
+++ b/src/test/java/test/methodinterceptors/FastTestsFirstInterceptor.java
@@ -0,0 +1,35 @@
+package test.methodinterceptors;
+
+import org.testng.IMethodInstance;
+import org.testng.IMethodInterceptor;
+import org.testng.ITestContext;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class FastTestsFirstInterceptor implements IMethodInterceptor {
+  @Override
+  public List<IMethodInstance> intercept(List<IMethodInstance> methods,
+      ITestContext context)
+  {
+    List<IMethodInstance> result = new ArrayList<>();
+    for (IMethodInstance m : methods) {
+      Test test = m.getMethod().getMethod().getAnnotation(Test.class);
+      Set<String> groups = new HashSet<>();
+      for (String group : test.groups()) {
+        groups.add(group);
+      }
+      if (groups.contains("fast")) {
+        result.add(0, m);
+      }
+      else {
+        result.add(m);
+      }
+    }
+    return result;
+  }
+
+}
diff --git a/src/test/java/test/methodinterceptors/FooTest.java b/src/test/java/test/methodinterceptors/FooTest.java
new file mode 100644
index 0000000..af35201
--- /dev/null
+++ b/src/test/java/test/methodinterceptors/FooTest.java
@@ -0,0 +1,15 @@
+package test.methodinterceptors;
+
+import org.testng.annotations.Test;
+
+public class FooTest {
+
+  @Test(groups = "fast")
+  public void zzzfast() {}
+
+  @Test
+  public void slow() {}
+
+  @Test
+  public void a() {}
+}
diff --git a/src/test/java/test/methodinterceptors/Issue392.java b/src/test/java/test/methodinterceptors/Issue392.java
new file mode 100644
index 0000000..c843918
--- /dev/null
+++ b/src/test/java/test/methodinterceptors/Issue392.java
@@ -0,0 +1,17 @@
+package test.methodinterceptors;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.Test;
+
+public class Issue392 {
+
+  @AfterClass
+  public void afterClass() {}
+
+  @Test
+  public void test1() {}
+
+  @Test
+  public void test2() {}
+
+}
diff --git a/src/test/java/test/methodinterceptors/Issue392Test.java b/src/test/java/test/methodinterceptors/Issue392Test.java
new file mode 100644
index 0000000..a337ed6
--- /dev/null
+++ b/src/test/java/test/methodinterceptors/Issue392Test.java
@@ -0,0 +1,43 @@
+package test.methodinterceptors;
+
+import org.testng.IMethodInstance;
+import org.testng.IMethodInterceptor;
+import org.testng.ITestContext;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import test.InvokedMethodNameListener;
+import test.SimpleBaseTest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class Issue392Test extends SimpleBaseTest {
+
+  @Test(description = "test for https://github.com/cbeust/testng/issues/392")
+  public void AfterClass_method_should_be_fired_when_IMethodInterceptor_removes_test_methods() {
+    TestNG tng = create(Issue392.class);
+    tng.setMethodInterceptor(new IMethodInterceptor() {
+      @Override
+      public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) {
+        List<IMethodInstance> instances = new ArrayList<>();
+        for (IMethodInstance instance : methods) {
+          if (!instance.getMethod().getMethodName().equals("test1")) {
+            instances.add(instance);
+          }
+        }
+        return instances;
+      }
+    });
+
+    InvokedMethodNameListener listener = new InvokedMethodNameListener();
+    tng.addListener(listener);
+
+    tng.run();
+
+    assertThat(listener.getInvokedMethodNames()).containsExactly("test2", "afterClass");
+  }
+
+}
diff --git a/src/test/java/test/methodinterceptors/Issue521.java b/src/test/java/test/methodinterceptors/Issue521.java
new file mode 100644
index 0000000..98d5921
--- /dev/null
+++ b/src/test/java/test/methodinterceptors/Issue521.java
@@ -0,0 +1,17 @@
+package test.methodinterceptors;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class Issue521 {
+
+  @BeforeClass
+  public void beforeClass() {}
+
+  @Test
+  public void test1() {}
+
+  @Test
+  public void test2() {}
+
+}
diff --git a/src/test/java/test/methodinterceptors/Issue521Test.java b/src/test/java/test/methodinterceptors/Issue521Test.java
new file mode 100644
index 0000000..60a661d
--- /dev/null
+++ b/src/test/java/test/methodinterceptors/Issue521Test.java
@@ -0,0 +1,43 @@
+package test.methodinterceptors;
+
+import org.testng.IMethodInstance;
+import org.testng.IMethodInterceptor;
+import org.testng.ITestContext;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import test.InvokedMethodNameListener;
+import test.SimpleBaseTest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class Issue521Test extends SimpleBaseTest {
+
+  @Test(description = "test for https://github.com/cbeust/testng/issues/521")
+  public void BeforeClass_method_should_be_fired_when_IMethodInterceptor_removes_test_methods() {
+    TestNG tng = create(Issue521.class);
+    tng.setMethodInterceptor(new IMethodInterceptor() {
+      @Override
+      public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) {
+        List<IMethodInstance> instances = new ArrayList<>();
+        for (IMethodInstance instance : methods) {
+          if (!instance.getMethod().getMethodName().equals("test1")) {
+            instances.add(instance);
+          }
+        }
+        return instances;
+      }
+    });
+
+    InvokedMethodNameListener listener = new InvokedMethodNameListener();
+    tng.addListener(listener);
+
+    tng.run();
+
+    assertThat(listener.getInvokedMethodNames()).containsExactly("beforeClass", "test2");
+  }
+
+}
diff --git a/src/test/java/test/methodinterceptors/LockUpInterceptorSampleTest.java b/src/test/java/test/methodinterceptors/LockUpInterceptorSampleTest.java
new file mode 100644
index 0000000..a93c482
--- /dev/null
+++ b/src/test/java/test/methodinterceptors/LockUpInterceptorSampleTest.java
@@ -0,0 +1,28 @@
+package test.methodinterceptors;
+
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+
+@Listeners({RemoveAMethodInterceptor.class})
+public class LockUpInterceptorSampleTest {
+
+    @Test
+    public void one() {
+        log("one");
+    }
+    
+    @Test
+    public void two() {
+        log("two");
+    }
+    
+    @Test
+    public void three() {
+        log("three");
+    }
+
+    private static void log(String s) {
+//      System.out.println("[MITest] " + s);
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/test/methodinterceptors/MethodInterceptorTest.java b/src/test/java/test/methodinterceptors/MethodInterceptorTest.java
new file mode 100644
index 0000000..ae56dd6
--- /dev/null
+++ b/src/test/java/test/methodinterceptors/MethodInterceptorTest.java
@@ -0,0 +1,119 @@
+package test.methodinterceptors;
+
+import org.testng.Assert;
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.Parser;
+import org.testng.xml.XmlSuite;
+import org.xml.sax.SAXException;
+
+import test.SimpleBaseTest;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.List;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+public class MethodInterceptorTest extends SimpleBaseTest {
+
+  private String XML =
+    "<!DOCTYPE suite SYSTEM \"http://beust.com/testng/testng-1.0.dtd\" >" +
+    "" +
+    "<suite name=\"Single\" verbose=\"0\">" +
+    "" +
+    "<listeners>" +
+    "  <listener class-name=\"test.methodinterceptors.NullMethodInterceptor\" />" +
+    "</listeners>" +
+    "" +
+    "  <test name=\"Single\" >" +
+    "    <classes>" +
+    "      <class name=\"test.methodinterceptors.FooTest\" />" +
+    "     </classes>" +
+    "  </test>" +
+    "" +
+    "</suite>";
+
+  @Test
+  public void noMethodsShouldRun() {
+    TestNG tng = create();
+    tng.setTestClasses(new Class[] { FooTest.class });
+    testNullInterceptor(tng);
+  }
+
+  private void testNullInterceptor(TestNG tng) {
+    tng.setMethodInterceptor(new NullMethodInterceptor());
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+    Assert.assertEquals(tla.getPassedTests().size(), 0);
+    Assert.assertEquals(tla.getFailedTests().size(), 0);
+    Assert.assertEquals(tla.getSkippedTests().size(), 0);
+  }
+
+  private void testFast(boolean useInterceptor) {
+    TestNG tng = create();
+    tng.setTestClasses(new Class[] { FooTest.class });
+    if (useInterceptor) {
+      tng.setMethodInterceptor(new FastTestsFirstInterceptor());
+    }
+    TestListenerAdapter tla = new TestListenerAdapter();
+//    tng.setParallel("methods");
+    tng.addListener(tla);
+    tng.run();
+
+    Assert.assertEquals(tla.getPassedTests().size(), 3);
+    ITestResult first = tla.getPassedTests().get(0);
+
+    String method = "zzzfast";
+    if (useInterceptor) {
+      Assert.assertEquals(first.getMethod().getMethodName(), method);
+    } else {
+      Assert.assertNotSame(first.getMethod().getMethodName(), method);
+    }
+  }
+
+  @Test
+  public void fastShouldRunFirst() {
+    testFast(true /* use interceptor */);
+  }
+
+  @Test
+  public void fastShouldNotRunFirst() {
+    testFast(false /* don't use interceptor */);
+  }
+
+  @Test
+  public void nullMethodInterceptorWorksInTestngXml()
+      throws IOException, ParserConfigurationException, SAXException {
+
+    File f = File.createTempFile("testng-tests-", "");
+    f.deleteOnExit();
+    BufferedWriter bw = new BufferedWriter(new FileWriter(f));
+    bw.write(XML);
+    bw.close();
+
+    try {
+      List<XmlSuite> xmlSuites = new Parser(f.getAbsolutePath()).parseToList();
+
+      TestNG tng = create();
+      tng.setXmlSuites(xmlSuites);
+      testNullInterceptor(tng);
+    }
+    catch(Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+
+  @Test(timeOut = 1000)
+  public void shouldNotLockUpWithInterceptorThatRemovesMethods() {
+    TestNG tng = create(LockUpInterceptorSampleTest.class);
+    tng.setParallel(XmlSuite.ParallelMode.METHODS);
+    tng.run();
+  }
+}
diff --git a/src/test/java/test/methodinterceptors/NullMethodInterceptor.java b/src/test/java/test/methodinterceptors/NullMethodInterceptor.java
new file mode 100644
index 0000000..79936b5
--- /dev/null
+++ b/src/test/java/test/methodinterceptors/NullMethodInterceptor.java
@@ -0,0 +1,15 @@
+package test.methodinterceptors;
+
+import org.testng.IMethodInstance;
+import org.testng.IMethodInterceptor;
+import org.testng.ITestContext;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class NullMethodInterceptor implements IMethodInterceptor {
+  @Override
+  public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) {
+    return new ArrayList<>();
+  }
+}
diff --git a/src/test/java/test/methodinterceptors/RemoveAMethodInterceptor.java b/src/test/java/test/methodinterceptors/RemoveAMethodInterceptor.java
new file mode 100644
index 0000000..b5f9d6f
--- /dev/null
+++ b/src/test/java/test/methodinterceptors/RemoveAMethodInterceptor.java
@@ -0,0 +1,51 @@
+package test.methodinterceptors;
+
+import org.testng.IMethodInstance;
+import org.testng.IMethodInterceptor;
+import org.testng.ITestContext;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An interceptor that removes the method called "two".
+ */
+public class RemoveAMethodInterceptor implements IMethodInterceptor {
+
+  @Override
+  public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) {
+    List<IMethodInstance> result = new ArrayList<>();
+
+    for (IMethodInstance method : methods) {
+      String name = method.getMethod().getMethodName();
+      if (!name.equals("two")) {
+        result.add(method);
+      }
+    }
+
+    log(this, methods, result);
+
+    return result;
+  }
+
+  public static void log(IMethodInterceptor listener, List<IMethodInstance> input,
+      List<IMethodInstance> output) {
+    StringBuilder msg = new StringBuilder().append(listener.getClass().getName())
+        .append(" - Input:").append(getMethodNames(input)).append(" ").append(input.size())
+        .append(" methods.").append(" Output:").append(getMethodNames(output)).append(" ")
+        .append(output.size()).append(" methods");
+    log(msg.toString());
+  }
+
+  public static List<String> getMethodNames(List<IMethodInstance> methods) {
+    List<String> names = new ArrayList<>();
+    for (IMethodInstance m : methods) {
+      names.add(m.getMethod().getMethodName());
+    }
+    return names;
+  }
+
+  private static void log(String s) {
+//    System.out.println("[MI2] " + s);
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/methodinterceptors/multipleinterceptors/FirstInterceptor.java b/src/test/java/test/methodinterceptors/multipleinterceptors/FirstInterceptor.java
new file mode 100644
index 0000000..5f785ba
--- /dev/null
+++ b/src/test/java/test/methodinterceptors/multipleinterceptors/FirstInterceptor.java
@@ -0,0 +1,8 @@
+package test.methodinterceptors.multipleinterceptors;
+
+public class FirstInterceptor extends MethodNameFilterInterceptor {
+
+    public FirstInterceptor() {
+        super("a");
+    }
+}
diff --git a/src/test/java/test/methodinterceptors/multipleinterceptors/FooTest.java b/src/test/java/test/methodinterceptors/multipleinterceptors/FooTest.java
new file mode 100644
index 0000000..9e05fd2
--- /dev/null
+++ b/src/test/java/test/methodinterceptors/multipleinterceptors/FooTest.java
@@ -0,0 +1,22 @@
+package test.methodinterceptors.multipleinterceptors;
+
+import org.testng.annotations.Test;
+
+public class FooTest {
+
+  @Test
+  public void a() {
+  }
+
+  @Test
+  public void b() {
+  }
+
+  @Test
+  public void c() {
+  }
+
+  @Test
+  public void d() {
+  }
+}
diff --git a/src/test/java/test/methodinterceptors/multipleinterceptors/MethodNameFilterInterceptor.java b/src/test/java/test/methodinterceptors/multipleinterceptors/MethodNameFilterInterceptor.java
new file mode 100644
index 0000000..6f48258
--- /dev/null
+++ b/src/test/java/test/methodinterceptors/multipleinterceptors/MethodNameFilterInterceptor.java
@@ -0,0 +1,37 @@
+package test.methodinterceptors.multipleinterceptors;
+
+import org.testng.IMethodInstance;
+import org.testng.IMethodInterceptor;
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class MethodNameFilterInterceptor implements IMethodInterceptor {
+
+  private final String methodName;
+
+  protected MethodNameFilterInterceptor(String methodName) {
+    this.methodName = methodName;
+  }
+
+  @Override
+  public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) {
+    List<IMethodInstance> result = new ArrayList<>();
+    for (IMethodInstance methodInstance : methods) {
+      ITestNGMethod method = methodInstance.getMethod();
+      String name = method.getMethodName();
+      if (!name.equals(methodName)) {
+        result.add(methodInstance);
+        String currentDescription = method.getDescription();
+        if (currentDescription == null) {
+          method.setDescription(methodName);
+        } else {
+          method.setDescription(currentDescription + methodName);
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/src/test/java/test/methodinterceptors/multipleinterceptors/MultipleInterceptorsTest.java b/src/test/java/test/methodinterceptors/multipleinterceptors/MultipleInterceptorsTest.java
new file mode 100644
index 0000000..b6b1e90
--- /dev/null
+++ b/src/test/java/test/methodinterceptors/multipleinterceptors/MultipleInterceptorsTest.java
@@ -0,0 +1,37 @@
+package test.methodinterceptors.multipleinterceptors;
+
+import java.util.Collections;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+public class MultipleInterceptorsTest extends SimpleBaseTest {
+    
+    @Test
+    public void testMultipleInterceptors(){
+      TestNG tng = create(FooTest.class);
+      tng.setMethodInterceptor(new FirstInterceptor());
+      tng.setMethodInterceptor(new SecondInterceptor());
+      tng.setMethodInterceptor(new ThirdInterceptor());
+      TestListenerAdapter tla = new TestListenerAdapter();
+      tng.addListener(tla);
+      tng.run();
+      Assert.assertEquals(tla.getPassedTests().size(), 1);
+      Assert.assertEquals(tla.getPassedTests().get(0).getName(), "d");
+    }
+
+    @Test
+    public void testMultipleInterceptorsWithPreserveOrder() {
+      TestNG tng = create();
+      tng.setTestSuites(Collections.singletonList(
+          getPathToResource("/methodinterceptors/multipleinterceptors/multiple-interceptors.xml")));
+      TestListenerAdapter tla = new TestListenerAdapter();
+      tng.addListener(tla);
+      tng.run();
+      Assert.assertEquals(tla.getPassedTests().get(0).getMethod().getDescription(), "abc");
+    }
+}
diff --git a/src/test/java/test/methodinterceptors/multipleinterceptors/SecondInterceptor.java b/src/test/java/test/methodinterceptors/multipleinterceptors/SecondInterceptor.java
new file mode 100644
index 0000000..df4a465
--- /dev/null
+++ b/src/test/java/test/methodinterceptors/multipleinterceptors/SecondInterceptor.java
@@ -0,0 +1,8 @@
+package test.methodinterceptors.multipleinterceptors;
+
+public class SecondInterceptor extends MethodNameFilterInterceptor {
+
+    public SecondInterceptor() {
+        super("b");
+    }
+}
diff --git a/src/test/java/test/methodinterceptors/multipleinterceptors/ThirdInterceptor.java b/src/test/java/test/methodinterceptors/multipleinterceptors/ThirdInterceptor.java
new file mode 100644
index 0000000..90e3a53
--- /dev/null
+++ b/src/test/java/test/methodinterceptors/multipleinterceptors/ThirdInterceptor.java
@@ -0,0 +1,8 @@
+package test.methodinterceptors.multipleinterceptors;
+
+public class ThirdInterceptor extends MethodNameFilterInterceptor {
+
+    public ThirdInterceptor() {
+        super("c");
+    }
+}
diff --git a/src/test/java/test/methods/SampleMethod1.java b/src/test/java/test/methods/SampleMethod1.java
new file mode 100644
index 0000000..668f697
--- /dev/null
+++ b/src/test/java/test/methods/SampleMethod1.java
@@ -0,0 +1,52 @@
+package test.methods;
+
+import org.testng.annotations.Test;
+
+/**
+ * This class is used to test invocation of methods
+ *
+ * @author cbeust
+ */
+public class SampleMethod1 {
+  private static boolean m_ok1 = false;
+  private static boolean m_ok2 = false;
+  private static boolean m_ok3 = true;
+  private static boolean m_ok4 = true;
+
+  public static void reset() {
+    m_ok1 = false;
+    m_ok2 = false;
+    m_ok3 = true;
+    m_ok4 = true;
+  }
+
+  @Test(groups = { "sample1" })
+  public void shouldRun1() {
+    m_ok1 = true;
+  }
+
+  @Test(groups = { "sample1" })
+  public void shouldRun2() {
+    m_ok2 = true;
+  }
+
+  @Test
+  public void shouldNotRun1() {
+    m_ok3 = false;
+  }
+
+  @Test
+  public void shouldNotRun2() {
+    m_ok4 = false;
+  }
+
+  public static void verify() {
+    assert m_ok1 && m_ok2 && m_ok3 && m_ok4 :
+      "All booleans should be true: " + m_ok1 + " " + m_ok2
+      + " " + m_ok3 + " " + m_ok4;
+  }
+  static private void ppp(String s) {
+    System.out.println("[SampleMethod1] " + s);
+  }
+
+}
diff --git a/src/test/java/test/methods/VerifyMethod1.java b/src/test/java/test/methods/VerifyMethod1.java
new file mode 100644
index 0000000..0ebf712
--- /dev/null
+++ b/src/test/java/test/methods/VerifyMethod1.java
@@ -0,0 +1,24 @@
+package test.methods;
+
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+/**
+ * This class verifies that the correct methods were run
+ *
+ * @author cbeust
+ */
+@Test(dependsOnGroups = { "sample1" })
+public class VerifyMethod1 {
+
+  @BeforeSuite
+  public void init() {
+    SampleMethod1.reset();
+  }
+
+  @Test
+  public void verify() {
+    SampleMethod1.verify();
+  }
+
+}
diff --git a/src/test/java/test/methodselectors/AllTestsMethodSelector.java b/src/test/java/test/methodselectors/AllTestsMethodSelector.java
new file mode 100644
index 0000000..fbb1e53
--- /dev/null
+++ b/src/test/java/test/methodselectors/AllTestsMethodSelector.java
@@ -0,0 +1,34 @@
+package test.methodselectors;
+
+import org.testng.IMethodSelector;
+import org.testng.IMethodSelectorContext;
+import org.testng.ITestNGMethod;
+
+import java.util.List;
+
+public class AllTestsMethodSelector implements IMethodSelector {
+
+  /**
+   *
+   */
+  private static final long serialVersionUID = 8059117082807260868L;
+
+  @Override
+  public boolean includeMethod(IMethodSelectorContext context,
+      ITestNGMethod method, boolean isTestMethod)
+  {
+    context.setStopped(true);
+    return true;
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[MyMethodSelector] " + s);
+  }
+
+  @Override
+  public void setTestMethods(List<ITestNGMethod> testMethods) {
+    // TODO Auto-generated method stub
+
+  }
+
+}
diff --git a/src/test/java/test/methodselectors/BeanShellTest.java b/src/test/java/test/methodselectors/BeanShellTest.java
new file mode 100644
index 0000000..ccd1522
--- /dev/null
+++ b/src/test/java/test/methodselectors/BeanShellTest.java
@@ -0,0 +1,26 @@
+package test.methodselectors;
+
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+
+public class BeanShellTest extends BaseTest {
+
+  @Test
+  public void onlyGroup1() {
+    addClass("test.methodselectors.SampleTest");
+    setBeanShellExpression("groups.\n     containsKey   \t    (\"test1\")");
+    run();
+    String[] passed = {
+        "test1",
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  public static void ppp(String s) {
+    System.out.println("[MethodSelectorTest] " + s);
+  }
+}
diff --git a/src/test/java/test/methodselectors/CommandLineTest.java b/src/test/java/test/methodselectors/CommandLineTest.java
new file mode 100644
index 0000000..04e693a
--- /dev/null
+++ b/src/test/java/test/methodselectors/CommandLineTest.java
@@ -0,0 +1,184 @@
+package test.methodselectors;
+
+import org.testng.Assert;
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+import testhelper.OutputDirectoryPatch;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CommandLineTest extends SimpleBaseTest {
+
+  private String[] ARG_WITHOUT_CLASSES =
+    new String[]{
+      "-log", "0",
+      "-d", OutputDirectoryPatch.getOutputDirectory(),
+      "-methodselectors", "",
+      ""
+  };
+
+  private String[] ARG_WITH_GROUPS =
+    new String[]{
+      "-log", "0",
+      "-d", OutputDirectoryPatch.getOutputDirectory(),
+      "-testclass", "test.methodselectors.SampleTest",
+      "-methodselectors", "",
+      "-groups", ""
+  };
+
+  private String[] ARG_WITHOUT_GROUPS =
+    new String[]{
+      "-log", "0",
+      "-d", OutputDirectoryPatch.getOutputDirectory(),
+      "-testclass", "test.methodselectors.SampleTest",
+      "-methodselectors", "",
+  };
+
+  private TestListenerAdapter tla;
+
+  @BeforeMethod
+  public void setup() {
+    ppp("setup()");
+    tla = new TestListenerAdapter();
+  }
+
+  @Test
+  public void commandLineNegativePriorityAllGroups() {
+    ppp("commandLineNegativePriorityAllGroups()");
+    ARG_WITHOUT_GROUPS[7] = "test.methodselectors.AllTestsMethodSelector:-1";
+    TestNG.privateMain(ARG_WITHOUT_GROUPS, tla);
+    String[] passed = {
+        "test1", "test2", "test3"
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, tla.getPassedTests());
+    verifyTests("Failed", failed, tla.getFailedTests());
+  }
+
+  @Test
+  public void commandLineNegativePriorityGroup2() {
+    ppp("commandLineNegativePriorityGroup2()");
+    ARG_WITHOUT_GROUPS[7] = "test.methodselectors.Test2MethodSelector:-1";
+    TestNG.privateMain(ARG_WITHOUT_GROUPS, tla);
+    String[] passed = {
+        "test2"
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, tla.getPassedTests());
+    verifyTests("Failed", failed, tla.getFailedTests());
+  }
+
+  @Test
+  public void commandLineLessThanPriorityTest1Test() {
+    ppp("commandLineLessThanPriorityTest1Test()");
+    ARG_WITH_GROUPS[7] = "test.methodselectors.Test2MethodSelector:5";
+    ARG_WITH_GROUPS[9] = "test1";
+    TestNG.privateMain(ARG_WITH_GROUPS, tla);
+    String[] passed = {
+        "test1", "test2"
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, tla.getPassedTests());
+    verifyTests("Failed", failed, tla.getFailedTests());
+  }
+
+  @Test
+  public void commandLineGreaterThanPriorityTest1Test2() {
+    ppp("commandLineGreaterThanPriorityTest1Test2()");
+    ARG_WITH_GROUPS[7] = "test.methodselectors.Test2MethodSelector:15";
+    ARG_WITH_GROUPS[9] = "test1";
+    TestNG.privateMain(ARG_WITH_GROUPS, tla);
+    String[] passed = {
+        "test2"
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, tla.getPassedTests());
+    verifyTests("Failed", failed, tla.getFailedTests());
+  }
+  @Test
+  public void commandLineLessThanPriorityAllTests() {
+    ppp("commandLineLessThanPriorityAllTests()");
+    ARG_WITH_GROUPS[7] = "test.methodselectors.AllTestsMethodSelector:5";
+    ARG_WITH_GROUPS[9] = "test1";
+    TestNG.privateMain(ARG_WITH_GROUPS, tla);
+    String[] passed = {
+        "test1", "test2", "test3"
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, tla.getPassedTests());
+    verifyTests("Failed", failed, tla.getFailedTests());
+  }
+
+  @Test
+  public void commandLineMultipleSelectors() {
+    ppp("commandLineMultipleSelectors()");
+    ARG_WITH_GROUPS[7] = "test.methodselectors.NoTestSelector:7,test.methodselectors.Test2MethodSelector:5";
+    ARG_WITH_GROUPS[9] = "test1";
+    TestNG.privateMain(ARG_WITH_GROUPS, tla);
+    String[] passed = {
+        "test1", "test2"
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, tla.getPassedTests());
+    verifyTests("Failed", failed, tla.getFailedTests());
+  }
+
+  @Test
+  public void commandLineNoTest1Selector() {
+    ppp("commandLineNoTest1Selector()");
+    ARG_WITHOUT_GROUPS[7] = "test.methodselectors.NoTest1MethodSelector:5";
+    TestNG.privateMain(ARG_WITHOUT_GROUPS, tla);
+    String[] passed = {
+        "test2", "test3"
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, tla.getPassedTests());
+    verifyTests("Failed", failed, tla.getFailedTests());
+  }
+
+  @Test
+  public void commandLineTestWithXmlFile() {
+    ppp("commandLineTestWithXmlFile()");
+    ARG_WITHOUT_CLASSES[5] = "test.methodselectors.NoTest1MethodSelector:5";
+    ARG_WITHOUT_CLASSES[6] = getPathToResource("testng-methodselectors.xml");
+    TestNG.privateMain(ARG_WITHOUT_CLASSES, tla);
+    String[] passed = {
+        "test2", "test3"
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, tla.getPassedTests());
+    verifyTests("Failed", failed, tla.getFailedTests());
+  }
+
+  private void verifyTests(String title, String[] expected, List<ITestResult> found) {
+    List<String> resultMethods = new ArrayList<>();
+    for( ITestResult result : found ) {
+      resultMethods.add( result.getName() );
+    }
+
+    Assert.assertEquals(resultMethods.size(), expected.length, "wrong number of " + title + " tests");
+
+    for(String e : expected) {
+      Assert.assertTrue(resultMethods.contains(e), "Expected to find method " + e + " in "
+          + title + " but didn't find it.");
+    }
+  }
+
+  public static void ppp(String s) {
+    //System.out.println("[CommandLineTest] " + s);
+  }
+}
diff --git a/src/test/java/test/methodselectors/MethodSelectorInSuiteTest.java b/src/test/java/test/methodselectors/MethodSelectorInSuiteTest.java
new file mode 100644
index 0000000..4bb205b
--- /dev/null
+++ b/src/test/java/test/methodselectors/MethodSelectorInSuiteTest.java
@@ -0,0 +1,78 @@
+package test.methodselectors;
+
+import org.testng.Assert;
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlMethodSelector;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.SimpleBaseTest;
+import testhelper.OutputDirectoryPatch;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class MethodSelectorInSuiteTest  extends SimpleBaseTest{
+
+  private TestListenerAdapter m_tla;
+
+  @BeforeMethod
+  public void setup() {
+    m_tla = new TestListenerAdapter();
+  }
+
+  @Test
+  public void programmaticXmlSuite() {
+    TestNG tng = create();
+    XmlSuite suite = new XmlSuite();
+    XmlMethodSelector methodSelector = new XmlMethodSelector();
+    methodSelector.setName("test.methodselectors.Test2MethodSelector");
+    methodSelector.setPriority(-1);
+    List<XmlMethodSelector> methodSelectors = Lists.newArrayList();
+    methodSelectors.add(methodSelector);
+    suite.setMethodSelectors(methodSelectors);
+    XmlTest test = new XmlTest(suite);
+    XmlClass testClass = new XmlClass(test.methodselectors.SampleTest.class);
+    test.setXmlClasses(Arrays.asList(testClass));
+    tng.setXmlSuites(Arrays.asList(suite));
+    tng.addListener(m_tla);
+    tng.run();
+
+    validate(new String[] { "test2" });
+  }
+
+  @Test
+  public void xmlXmlSuite() {
+    TestNG tng = create();
+    tng.setTestSuites(Arrays.asList(getPathToResource("methodselector-in-xml.xml")));
+    tng.addListener(m_tla);
+    tng.run();
+
+    validate(new String[] { "test2" });
+  }
+
+  @Test
+  public void fileOnCommandLine() {
+    String[] args = new String[] {
+        "-d", OutputDirectoryPatch.getOutputDirectory(),
+        getPathToResource("methodselector-in-xml.xml") };
+    TestNG.privateMain(args, m_tla);
+
+    validate(new String[] { "test2" });
+  }
+
+  private void validate(String[] expectPassed) {
+    List<ITestResult> passed = m_tla.getPassedTests();
+    Assert.assertEquals(passed.size(), expectPassed.length);
+    // doing this index based is probably not the best
+    for (int i = 0; i < expectPassed.length; i++) {
+      Assert.assertEquals(passed.get(i).getName(), expectPassed[i]);
+    }
+  }
+}
diff --git a/src/test/java/test/methodselectors/MethodSelectorTest.java b/src/test/java/test/methodselectors/MethodSelectorTest.java
new file mode 100644
index 0000000..0542627
--- /dev/null
+++ b/src/test/java/test/methodselectors/MethodSelectorTest.java
@@ -0,0 +1,85 @@
+package test.methodselectors;
+
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+
+public class MethodSelectorTest extends BaseTest {
+
+  @Test
+  public void negativePriorityAllGroups() {
+    addClass("test.methodselectors.SampleTest");
+    addMethodSelector("test.methodselectors.AllTestsMethodSelector", -1);
+    run();
+    String[] passed = {
+        "test1", "test2", "test3",
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void negativePriorityGroup2() {
+    addClass("test.methodselectors.SampleTest");
+    addMethodSelector("test.methodselectors.Test2MethodSelector", -1);
+    run();
+    String[] passed = {
+        "test2",
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void lessThanPriorityTest1Test() {
+    addClass("test.methodselectors.SampleTest");
+    addIncludedGroup("test1");
+    addMethodSelector("test.methodselectors.Test2MethodSelector", 5);
+    run();
+    String[] passed = {
+        "test1", "test2",
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void greaterThanPriorityTest1Test2() {
+    addClass("test.methodselectors.SampleTest");
+    addIncludedGroup("test1");
+    addMethodSelector("test.methodselectors.Test2MethodSelector", 15);
+    run();
+    String[] passed = {
+        "test2",
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void lessThanPriorityAllTests() {
+    addClass("test.methodselectors.SampleTest");
+    addIncludedGroup("test1");
+    addMethodSelector("test.methodselectors.AllTestsMethodSelector", 5);
+    run();
+    String[] passed = {
+        "test1", "test2", "test3"
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  public static void ppp(String s) {
+    System.out.println("[MethodSelectorTest] " + s);
+  }
+}
diff --git a/src/test/java/test/methodselectors/NoTest.java b/src/test/java/test/methodselectors/NoTest.java
new file mode 100644
index 0000000..55e1a89
--- /dev/null
+++ b/src/test/java/test/methodselectors/NoTest.java
@@ -0,0 +1,4 @@
+package test.methodselectors;

+

+public @interface NoTest {

+}

diff --git a/src/test/java/test/methodselectors/NoTest1MethodSelector.java b/src/test/java/test/methodselectors/NoTest1MethodSelector.java
new file mode 100644
index 0000000..14095fd
--- /dev/null
+++ b/src/test/java/test/methodselectors/NoTest1MethodSelector.java
@@ -0,0 +1,40 @@
+package test.methodselectors;
+
+import org.testng.IMethodSelector;
+import org.testng.IMethodSelectorContext;
+import org.testng.ITestNGMethod;
+
+import java.util.List;
+
+public class NoTest1MethodSelector implements IMethodSelector {
+
+  /**
+   *
+   */
+  private static final long serialVersionUID = 6706869410370733524L;
+
+  @Override
+  public boolean includeMethod(IMethodSelectorContext context,
+      ITestNGMethod method, boolean isTestMethod)
+  {
+    for (String group : method.getGroups()) {
+      if (group.equals("test1")) {
+        ppp( method.getMethodName() + " is group test1, don't include" );
+        context.setStopped(true);
+        return false;
+      }
+    }
+    ppp( method.getMethodName() + " is not in group test1" );
+    return true;
+  }
+
+  @Override
+  public void setTestMethods(List<ITestNGMethod> testMethods) {
+
+  }
+
+  public static void ppp(String s) {
+    //System.out.println("[NoTest1MethodSelector] " + s);
+  }
+
+}
diff --git a/src/test/java/test/methodselectors/NoTestSelector.java b/src/test/java/test/methodselectors/NoTestSelector.java
new file mode 100644
index 0000000..20ee653
--- /dev/null
+++ b/src/test/java/test/methodselectors/NoTestSelector.java
@@ -0,0 +1,31 @@
+package test.methodselectors;

+

+import org.testng.IMethodSelector;

+import org.testng.IMethodSelectorContext;

+import org.testng.ITestNGMethod;

+

+import java.util.List;

+

+public class NoTestSelector implements IMethodSelector {

+

+  /**

+   *

+   */

+  private static final long serialVersionUID = -7751190578710846487L;

+

+  @Override

+  public boolean includeMethod(IMethodSelectorContext context,

+      ITestNGMethod method, boolean isTestMethod)

+  {

+    ppp("NOTEST RETURNING FALSE FOR " + method);

+    return false;

+  }

+

+  @Override

+  public void setTestMethods(List<ITestNGMethod> testMethods) {

+  }

+

+  private static void ppp(String s) {

+//    System.out.println("[NoTestSelector] " + s);

+  }

+}

diff --git a/src/test/java/test/methodselectors/PrioritySampleTest.java b/src/test/java/test/methodselectors/PrioritySampleTest.java
new file mode 100644
index 0000000..6d66bd7
--- /dev/null
+++ b/src/test/java/test/methodselectors/PrioritySampleTest.java
@@ -0,0 +1,21 @@
+package test.methodselectors;

+

+import org.testng.annotations.Test;

+

+public class PrioritySampleTest {

+

+  @Test

+  public void alwaysRun() {

+    ppp("ALWAYS");

+  }

+

+  @Test

+  @NoTest

+  public void neverRun() {

+    ppp("NEVER");

+  }

+

+  private static void ppp(String s) {

+    System.out.println("[PrioritySampleTest] " + s);

+  }

+}

diff --git a/src/test/java/test/methodselectors/PriorityTest.java b/src/test/java/test/methodselectors/PriorityTest.java
new file mode 100644
index 0000000..2453129
--- /dev/null
+++ b/src/test/java/test/methodselectors/PriorityTest.java
@@ -0,0 +1,55 @@
+package test.methodselectors;

+

+import org.testng.Assert;

+import org.testng.ITestResult;

+import org.testng.TestListenerAdapter;

+import org.testng.TestNG;

+import org.testng.annotations.Test;

+

+import java.util.List;

+

+public class PriorityTest {

+

+  private void runTest(int priority, String[] passedTests) {

+    TestNG tng = new TestNG();

+    tng.setVerbose(0);

+    tng.setTestClasses(new Class[] { PrioritySampleTest.class });

+    tng.addMethodSelector("test.methodselectors.NoTestSelector", priority);

+    TestListenerAdapter tla = new TestListenerAdapter();

+    tng.addListener(tla);

+    tng.run();

+

+    List<ITestResult> passed = tla.getPassedTests();

+    Assert.assertEquals(passedTests.length, passed.size());

+    if (passedTests.length == 1) {

+      String passed0 = passed.get(0).getName();

+      Assert.assertEquals(passed0, passedTests[0]);

+    }

+    if (passedTests.length == 2) {

+      String passed0 = passed.get(0).getName();

+      String passed1 = passed.get(1).getName();

+      Assert.assertTrue(passed0.equals(passedTests[0])

+        || passed0.equals(passedTests[1]));

+      Assert.assertTrue(passed1.equals(passedTests[0])

+          || passed1.equals(passedTests[1]));

+

+    }

+  }

+

+//  @Test

+  public void negativePriority() {

+    runTest(-5, new String[] {});

+  }

+

+  @Test

+  public void lessThanTenPriority() {

+    runTest(5, new String[] { "alwaysRun" });

+  }

+

+//  @Test

+  public void greaterThanTenPriority() {

+    runTest(15, new String[] { "alwaysRun", "neverRun" });

+  }

+

+

+}

diff --git a/src/test/java/test/methodselectors/SampleTest.java b/src/test/java/test/methodselectors/SampleTest.java
new file mode 100644
index 0000000..6882d99
--- /dev/null
+++ b/src/test/java/test/methodselectors/SampleTest.java
@@ -0,0 +1,28 @@
+package test.methodselectors;
+
+import org.testng.annotations.Test;
+
+public class SampleTest {
+
+  @Test(groups = { "test1" })
+  public void test1() {
+    ppp("TEST1");
+
+  }
+
+  @Test(groups = { "test2" })
+  public void test2() {
+    ppp("TEST2");
+  }
+
+  @Test(groups = { "test3" })
+  public void test3() {
+    ppp("TEST3");
+  }
+
+  private static void ppp(String s) {
+    if (false) {
+      System.out.println("[SampleTest] " + s);
+    }
+  }
+}
diff --git a/src/test/java/test/methodselectors/Test2MethodSelector.java b/src/test/java/test/methodselectors/Test2MethodSelector.java
new file mode 100644
index 0000000..3533238
--- /dev/null
+++ b/src/test/java/test/methodselectors/Test2MethodSelector.java
@@ -0,0 +1,36 @@
+package test.methodselectors;
+
+import org.testng.IMethodSelector;
+import org.testng.IMethodSelectorContext;
+import org.testng.ITestNGMethod;
+
+import java.util.List;
+
+public class Test2MethodSelector implements IMethodSelector {
+
+  /**
+   *
+   */
+  private static final long serialVersionUID = 4166247968392649912L;
+
+  @Override
+  public boolean includeMethod(IMethodSelectorContext context,
+      ITestNGMethod method, boolean isTestMethod)
+  {
+    for (String group : method.getGroups()) {
+      if (group.equals("test2")) {
+        context.setStopped(true);
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  @Override
+  public void setTestMethods(List<ITestNGMethod> testMethods) {
+    // TODO Auto-generated method stub
+
+  }
+
+}
diff --git a/src/test/java/test/mixed/JUnit3Test1.java b/src/test/java/test/mixed/JUnit3Test1.java
new file mode 100644
index 0000000..48aeb0f
--- /dev/null
+++ b/src/test/java/test/mixed/JUnit3Test1.java
@@ -0,0 +1,22 @@
+package test.mixed;
+
+import junit.framework.TestCase;
+
+/**
+ *
+ * @author lukas
+ */
+public class JUnit3Test1 extends TestCase {
+
+    public JUnit3Test1(String name) {
+        super(name);
+    }
+
+    public void testA() {
+        
+    }
+
+    public void testB() {
+
+    }
+}
diff --git a/src/test/java/test/mixed/JUnit4Test1.java b/src/test/java/test/mixed/JUnit4Test1.java
new file mode 100644
index 0000000..a494354
--- /dev/null
+++ b/src/test/java/test/mixed/JUnit4Test1.java
@@ -0,0 +1,20 @@
+package test.mixed;
+
+import org.junit.Test;
+
+/**
+ *
+ * @author lukas
+ */
+public class JUnit4Test1 {
+
+    @Test
+    public void atest() {
+
+    }
+
+    @Test
+    public void bTest() {
+
+    }
+}
diff --git a/src/test/java/test/mixed/MixedTest.java b/src/test/java/test/mixed/MixedTest.java
new file mode 100644
index 0000000..a424a82
--- /dev/null
+++ b/src/test/java/test/mixed/MixedTest.java
@@ -0,0 +1,62 @@
+package test.mixed;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import test.BaseTest;
+import testhelper.OutputDirectoryPatch;
+
+/**
+ *
+ * @author lukas
+ */
+public class MixedTest extends BaseTest {
+    @Test
+    public void mixedWithExcludedGroups() {
+        String[] argv = {
+                "-d", OutputDirectoryPatch.getOutputDirectory(),
+                "-log", "0",
+                "-mixed",
+                "-groups", "unit",
+                "-excludegroups", "ignore",
+                "-testclass", "test.mixed.JUnit3Test1,test.mixed.JUnit4Test1,test.mixed.TestNGTest1,test.mixed.TestNGGroups"
+        };
+        TestListenerAdapter tla = new TestListenerAdapter();
+        TestNG.privateMain(argv, tla);
+
+        Assert.assertEquals(tla.getPassedTests().size(), 5); //2 from junit3test1, 2 from junit4test1, 0 from testngtest1 (no groups), 1 from testnggroups (1 is included, 1 is excluded)
+        Assert.assertEquals(tla.getFailedTests().size(), 0);
+
+    }
+
+    @Test
+    public void mixedClasses() {
+        String[] argv = {
+            "-d", OutputDirectoryPatch.getOutputDirectory(),
+            "-log", "0",
+            "-mixed",
+            "-testclass", "test.mixed.JUnit3Test1,test.mixed.JUnit4Test1,test.mixed.TestNGTest1"
+        };
+        TestListenerAdapter tla = new TestListenerAdapter();
+        TestNG.privateMain(argv, tla);
+
+        Assert.assertEquals(tla.getPassedTests().size(), 6);
+        Assert.assertEquals(tla.getFailedTests().size(), 0);
+    }
+
+    @Test
+    public void mixedMethods() {
+        String[] argv = {
+            "-d", OutputDirectoryPatch.getOutputDirectory(),
+            "-mixed",
+            "-log", "0",
+            "-methods", "test.mixed.JUnit3Test1.testB,test.mixed.JUnit4Test1.atest,test.mixed.TestNGTest1.tngCustomTest1"
+        };
+        TestListenerAdapter tla = new TestListenerAdapter();
+        TestNG.privateMain(argv, tla);
+
+        Assert.assertEquals(tla.getPassedTests().size(), 3);
+        Assert.assertEquals(tla.getFailedTests().size(), 0);
+    }
+}
diff --git a/src/test/java/test/mixed/TestNGGroups.java b/src/test/java/test/mixed/TestNGGroups.java
new file mode 100644
index 0000000..2618be0
--- /dev/null
+++ b/src/test/java/test/mixed/TestNGGroups.java
@@ -0,0 +1,17 @@
+package test.mixed;
+
+import org.testng.annotations.Test;
+
+@Test(groups = {"unit"})
+public class TestNGGroups {
+    @Test
+    public void tngTest1() {
+
+    }
+
+    @Test(groups = {"ignore"})
+    public void tngShouldBeIgnored() {
+
+    }
+
+}
diff --git a/src/test/java/test/mixed/TestNGTest1.java b/src/test/java/test/mixed/TestNGTest1.java
new file mode 100644
index 0000000..01ded8f
--- /dev/null
+++ b/src/test/java/test/mixed/TestNGTest1.java
@@ -0,0 +1,21 @@
+package test.mixed;
+
+import org.testng.annotations.Test;
+
+
+/**
+ *
+ * @author lukas
+ */
+public class TestNGTest1 {
+
+    @Test
+    public void tngTest1() {
+
+    }
+
+    @Test
+    public void tngCustomTest1() {
+
+    }
+}
diff --git a/src/test/java/test/morten/SampleTest.java b/src/test/java/test/morten/SampleTest.java
new file mode 100644
index 0000000..95f2fae
--- /dev/null
+++ b/src/test/java/test/morten/SampleTest.java
@@ -0,0 +1,37 @@
+package test.morten;
+
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+
+public class SampleTest {
+    private int capacity = 10;
+    private float loadFactor = 0.3f;
+
+    public class SampleTestTestFactory {
+      public SampleTestTestFactory() {} // CTR necessary ?
+      @Factory public Object[] createInstances() {
+      return new SampleTest[] {
+        new SampleTest(1, 0.1f),
+        new SampleTest(10, 0.5f),
+      };
+      }
+    };
+
+    public SampleTest() {
+
+    }
+
+    public SampleTest(int capacity, float loadFactor)
+    {
+      System.out.println("CREATING TEST WITH " + capacity);
+     this.capacity=capacity;
+     this.loadFactor=loadFactor;
+    }
+
+    @Test public void testPut()
+    {
+      //FIXME: This test does nothing
+      //HashMap hashTable = new HashMap(capacity, loadFactor);
+      // ...
+    }
+}
diff --git a/src/test/java/test/morten/SampleTestFactory.java b/src/test/java/test/morten/SampleTestFactory.java
new file mode 100644
index 0000000..203d5ec
--- /dev/null
+++ b/src/test/java/test/morten/SampleTestFactory.java
@@ -0,0 +1,13 @@
+package test.morten;
+
+import org.testng.annotations.Factory;
+
+public class SampleTestFactory {
+  public SampleTestFactory() {} // CTR necessary ?
+  @Factory public Object[] createInstances() {
+  return new SampleTest[] {
+    new SampleTest(1, 0.1f),
+    new SampleTest(10, 0.5f),
+  };
+  }
+};
diff --git a/src/test/java/test/multiple/Test1.java b/src/test/java/test/multiple/Test1.java
new file mode 100644
index 0000000..9014e94
--- /dev/null
+++ b/src/test/java/test/multiple/Test1.java
@@ -0,0 +1,28 @@
+package test.multiple;
+
+import static org.testng.Assert.assertTrue;
+
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.Test;
+
+public class Test1 {
+  private static int m_count = 0;
+
+  @Test
+  public void f1() {
+//    ppp("INVOKING f1 " + m_count);
+    assertTrue(m_count < 1, "FAILING");
+    m_count++;
+  }
+
+  @AfterTest
+  public void cleanUp() {
+    m_count = 0;
+  }
+
+
+  private static void ppp(String s) {
+    System.out.println("[Test1] " + s);
+  }
+
+}
diff --git a/src/test/java/test/multiple/TestMultiple.java b/src/test/java/test/multiple/TestMultiple.java
new file mode 100644
index 0000000..e4b847b
--- /dev/null
+++ b/src/test/java/test/multiple/TestMultiple.java
@@ -0,0 +1,25 @@
+package test.multiple;
+
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+
+public class TestMultiple extends BaseTest {
+
+  private static final String CLASS_NAME = "test.multiple.ThisFactory";
+
+  @Test(groups = { "current" })
+  public void multiple() {
+    addClass(CLASS_NAME);
+    run();
+    String[] passed = {
+      "f1",
+    };
+    String[] failed = {
+      "f1","f1","f1","f1","f1", "f1","f1","f1","f1",
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+}
diff --git a/src/test/java/test/multiple/ThisFactory.java b/src/test/java/test/multiple/ThisFactory.java
new file mode 100644
index 0000000..b3ff37d
--- /dev/null
+++ b/src/test/java/test/multiple/ThisFactory.java
@@ -0,0 +1,25 @@
+package test.multiple;
+
+import org.testng.annotations.Factory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class
+ *
+ * @author cbeust
+ */
+public class ThisFactory {
+
+  @Factory
+  public Object[] create() {
+    List result = new ArrayList();
+    for (int i = 0; i < 10; i++) {
+      result.add(new Test1());
+    }
+
+    return result.toArray();
+  }
+
+}
diff --git a/src/test/java/test/multiplelisteners/SimpleReporter.java b/src/test/java/test/multiplelisteners/SimpleReporter.java
new file mode 100755
index 0000000..b4f140e
--- /dev/null
+++ b/src/test/java/test/multiplelisteners/SimpleReporter.java
@@ -0,0 +1,35 @@
+package test.multiplelisteners;

+

+import org.testng.IReporter;

+import org.testng.ISuite;

+import org.testng.SuiteRunner;

+import org.testng.TestListenerAdapter;

+import org.testng.internal.IConfiguration;

+import org.testng.xml.XmlSuite;

+import test.listeners.ListenerAssert;

+

+import java.lang.reflect.Field;

+import java.util.List;

+

+public class SimpleReporter implements IReporter

+{

+  @Override

+  public void generateReport(final List<XmlSuite> xmlSuites, final List<ISuite> suites,

+      final String outputDirectory)

+  {

+    for (final ISuite iSuite : suites)

+    {

+      try

+      {

+        final Field field = SuiteRunner.class.getDeclaredField("m_configuration");

+        field.setAccessible(true);

+        final IConfiguration conf = (IConfiguration) field.get(iSuite);

+        ListenerAssert.assertListenerType(conf.getConfigurationListeners(), TestListenerAdapter.class);

+      }

+      catch (final Exception e)

+      {

+        throw new RuntimeException(e);

+      }

+    }

+  }

+}

diff --git a/src/test/java/test/multiplelisteners/Test1.java b/src/test/java/test/multiplelisteners/Test1.java
new file mode 100755
index 0000000..e42d086
--- /dev/null
+++ b/src/test/java/test/multiplelisteners/Test1.java
@@ -0,0 +1,12 @@
+package test.multiplelisteners;

+

+import org.testng.annotations.Test;

+

+@Test

+public class Test1

+{

+  public void test()

+  {

+//    Reporter.log("test1", true);

+  }

+}

diff --git a/src/test/java/test/multiplelisteners/TestMaker.java b/src/test/java/test/multiplelisteners/TestMaker.java
new file mode 100755
index 0000000..15795dc
--- /dev/null
+++ b/src/test/java/test/multiplelisteners/TestMaker.java
@@ -0,0 +1,53 @@
+package test.multiplelisteners;

+

+import org.testng.TestListenerAdapter;

+import org.testng.TestNG;

+import org.testng.annotations.Test;

+import org.testng.collections.Lists;

+import org.testng.xml.XmlClass;

+import org.testng.xml.XmlSuite;

+import org.testng.xml.XmlTest;

+

+import java.util.Arrays;

+import java.util.Collections;

+import java.util.List;

+

+public class TestMaker

+{

+  @Test(description = "Make sure only one listener is created and not 2^3")

+  public void run()

+  {

+    final TestNG tng = new TestNG();

+    tng.setUseDefaultListeners(false);

+    tng.setListenerClasses(Arrays.<Class> asList(TestListenerAdapter.class, SimpleReporter.class));

+    final List<XmlSuite> suites = createSuites();

+    tng.setXmlSuites(suites);

+    tng.setVerbose(0);

+    tng.run();

+

+//    Reporter.log(tng.getSuiteListeners().size() + "", true);

+//    for (final XmlSuite xmlSuite : suites)

+//    {

+//      Reporter.log(xmlSuite.getName() + ": " + xmlSuite.getListeners().size(), true);

+//    }

+  }

+

+  private List<XmlSuite> createSuites()

+  {

+    final List<XmlSuite> ret = Lists.newArrayList();

+    for (int i = 0; i < 3; i++)

+    {

+      ret.add(createSuite(i));

+    }

+    return ret;

+  }

+

+  private XmlSuite createSuite(final int nr)

+  {

+    final XmlSuite suite = new XmlSuite();

+    suite.setName("Suite_" + nr);

+

+    new XmlTest(suite).setXmlClasses(Collections.singletonList(new XmlClass(Test1.class)));

+    return suite;

+  }

+}

diff --git a/src/test/java/test/mustache/MustacheTest.java b/src/test/java/test/mustache/MustacheTest.java
new file mode 100644
index 0000000..3ca4631
--- /dev/null
+++ b/src/test/java/test/mustache/MustacheTest.java
@@ -0,0 +1,102 @@
+package test.mustache;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import org.testng.collections.Maps;
+import org.testng.mustache.Mustache;
+
+@Test
+public class MustacheTest {
+
+  public static class Person {
+    public String name;
+    public Person(String n) {
+      name = n;
+    }
+  }
+
+  public static class Age {
+    public int age;
+    public Age(int a) {
+      this.age = a;
+    }
+  }
+
+  private static final List<Person> PEOPLE = new ArrayList<>(
+          Arrays.asList(new Person("Carl"), new Person("Christopher")));
+
+  private static final List<Age> AGES = new ArrayList<>(
+          Arrays.asList(new Age(42), new Age(43)));
+
+  @DataProvider
+  public Object[][] dp() {
+    return new Object[][] {
+        // Simple
+        new Object[] {
+            create("one", "ello", "two", "orld"),
+            "H{{one}} W{{two}}",
+            "Hello World"
+        },
+        // Null condition
+        new Object[] {
+            Collections.emptyMap(),
+            "E{{#foo}}xxx{{/foo}}lephant",
+            "Elephant"
+        },
+        // Null condition with new line
+        new Object[] {
+            Collections.emptyMap(),
+            "Hello\n{{#foo}}@\n{{/foo}}World",
+            "Hello\nWorld"
+        },
+        // Simple scope
+        new Object[] {
+            create("person", new Person("John"), "day", "Monday"),
+            "Hello {{#person}}{{name}}{{/person}}, {{day}}",
+            "Hello John, Monday"
+        },
+        // Scope with shadowing
+        new Object[] {
+            create("person", new Person("John"), "name", "Carl"),
+            "Hello {{#person}}{{name}}{{/person}}, {{name}}",
+            "Hello John, Carl"
+        },
+        // Test iteration
+        new Object[] {
+            create("people", PEOPLE),
+            "People:@{{#people}}-{{/people}}!",
+            "People:@--!",
+        },
+        // Nested scopes
+        new Object[] {
+            create("people", PEOPLE, "ages", AGES),
+            ":@{{#people}}{{name}}{{#ages}}{{age}}{{/ages}}@{{/people}}!_",
+            ":@Carl4243@Christopher4243@!_",
+        },
+    };
+  }
+
+  private Map<String, Object> create(Object... objects) {
+    Map<String, Object> result = Maps.newHashMap();
+    for (int i = 0; i < objects.length; i += 2) {
+      result.put((String) objects[i], objects[i + 1]);
+    }
+    return result;
+  }
+
+  @Test(dataProvider = "dp")
+  public void runTest(Map<String, Object> model, String template, String expected)
+      throws IOException {
+//    InputStream is = new StringInputStream(template);
+    Assert.assertEquals(new Mustache().run(template, model), expected);
+  }
+
+}
diff --git a/src/test/java/test/name/ITestSample.java b/src/test/java/test/name/ITestSample.java
new file mode 100644
index 0000000..430be2c
--- /dev/null
+++ b/src/test/java/test/name/ITestSample.java
@@ -0,0 +1,43 @@
+package test.name;
+
+import org.testng.Assert;
+import org.testng.ITest;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+// From http://stackoverflow.com/q/33404335/4234729
+public class ITestSample implements ITest {
+
+  public ThreadLocal<String> testName = new ThreadLocal<>();
+
+  @DataProvider(name = "dp", parallel = true)
+  public Object[][] getTests() {
+    return new Object[][]{new Object[]{"test1"},
+                          new Object[]{"test2"},
+                          new Object[]{"test3"},
+                          new Object[]{"test4"},
+                          new Object[]{"test5"}};
+  }
+
+  @Test(dataProvider = "dp")
+  public void run(String testName) {
+    Assert.assertEquals(testName, this.testName.get());
+  }
+
+  @BeforeMethod
+  public void init(Object[] testArgs) {
+    testName.set((String) testArgs[0]);
+  }
+
+  @AfterMethod
+  public void tearDown(Object[] testArgs) {
+    Assert.assertEquals((String) testArgs[0], this.testName.get());
+  }
+
+  @Override
+  public String getTestName() {
+    return testName.get();
+  }
+}
diff --git a/src/test/java/test/name/NameSample.java b/src/test/java/test/name/NameSample.java
new file mode 100644
index 0000000..5bc0f34
--- /dev/null
+++ b/src/test/java/test/name/NameSample.java
@@ -0,0 +1,13 @@
+package test.name;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+@Test(testName = "NAME")
+public class NameSample {
+
+  @Test
+  public void test() {
+    Assert.assertTrue(true);
+  }
+}
diff --git a/src/test/java/test/name/NameTest.java b/src/test/java/test/name/NameTest.java
new file mode 100644
index 0000000..6e98ecd
--- /dev/null
+++ b/src/test/java/test/name/NameTest.java
@@ -0,0 +1,146 @@
+package test.name;
+
+import org.testng.Assert;
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import test.SimpleBaseTest;
+
+public class NameTest extends SimpleBaseTest {
+
+  @Test
+  public void itestTest() {
+    TestNG tng = create(SimpleITestSample.class);
+    TestListenerAdapter adapter = new TestListenerAdapter();
+    tng.addListener(adapter);
+
+    tng.run();
+
+    Assert.assertTrue(adapter.getFailedTests().isEmpty());
+    Assert.assertTrue(adapter.getSkippedTests().isEmpty());
+    Assert.assertEquals(adapter.getPassedTests().size(), 1);
+    ITestResult result = adapter.getPassedTests().get(0);
+    Assert.assertEquals(result.getMethod().getMethodName(), "test");
+    Assert.assertEquals(result.getName(), "NAME");
+    Assert.assertEquals(result.getTestName(), "NAME");
+  }
+
+  @Test
+  public void itestTestWithXml() {
+    TestNG tng = createTests("suite", SimpleITestSample.class);
+    TestListenerAdapter adapter = new TestListenerAdapter();
+    tng.addListener(adapter);
+
+    tng.run();
+
+    Assert.assertTrue(adapter.getFailedTests().isEmpty());
+    Assert.assertTrue(adapter.getSkippedTests().isEmpty());
+    Assert.assertEquals(adapter.getPassedTests().size(), 1);
+    ITestResult result = adapter.getPassedTests().get(0);
+    Assert.assertEquals(result.getMethod().getMethodName(), "test");
+    Assert.assertEquals(result.getName(), "NAME");
+    Assert.assertEquals(result.getTestName(), "NAME");
+  }
+
+  @Test
+  public void testNameTest() {
+    TestNG tng = create(NameSample.class);
+    TestListenerAdapter adapter = new TestListenerAdapter();
+    tng.addListener(adapter);
+
+    tng.run();
+
+    Assert.assertTrue(adapter.getFailedTests().isEmpty());
+    Assert.assertTrue(adapter.getSkippedTests().isEmpty());
+    Assert.assertEquals(adapter.getPassedTests().size(), 1);
+    ITestResult result = adapter.getPassedTests().get(0);
+    Assert.assertEquals(result.getMethod().getMethodName(), "test");
+    Assert.assertEquals(result.getName(), "NAME");
+    Assert.assertEquals(result.getTestName(), "NAME");
+  }
+
+  @Test
+  public void testNameTestWithXml() {
+    TestNG tng = createTests("suite", NameSample.class);
+    TestListenerAdapter adapter = new TestListenerAdapter();
+    tng.addListener(adapter);
+
+    tng.run();
+
+    Assert.assertTrue(adapter.getFailedTests().isEmpty());
+    Assert.assertTrue(adapter.getSkippedTests().isEmpty());
+    Assert.assertEquals(adapter.getPassedTests().size(), 1);
+    ITestResult result = adapter.getPassedTests().get(0);
+    Assert.assertEquals(result.getMethod().getMethodName(), "test");
+    Assert.assertEquals(result.getName(), "NAME");
+    Assert.assertEquals(result.getTestName(), "NAME");
+  }
+
+  @Test
+  public void noNameTest() {
+    TestNG tng = create(NoNameSample.class);
+    TestListenerAdapter adapter = new TestListenerAdapter();
+    tng.addListener(adapter);
+
+    tng.run();
+
+    Assert.assertTrue(adapter.getFailedTests().isEmpty());
+    Assert.assertTrue(adapter.getSkippedTests().isEmpty());
+    Assert.assertEquals(adapter.getPassedTests().size(), 1);
+    ITestResult result = adapter.getPassedTests().get(0);
+    Assert.assertEquals(result.getMethod().getMethodName(), "test");
+    Assert.assertEquals(result.getName(), "test");
+    Assert.assertEquals(result.getTestName(), null);
+  }
+
+  @Test
+  public void noNameTestWithXml() {
+    TestNG tng = createTests("suite", NoNameSample.class);
+    TestListenerAdapter adapter = new TestListenerAdapter();
+    tng.addListener(adapter);
+
+    tng.run();
+
+    Assert.assertTrue(adapter.getFailedTests().isEmpty());
+    Assert.assertTrue(adapter.getSkippedTests().isEmpty());
+    Assert.assertEquals(adapter.getPassedTests().size(), 1);
+    ITestResult result = adapter.getPassedTests().get(0);
+    Assert.assertEquals(result.getMethod().getMethodName(), "test");
+    Assert.assertEquals(result.getName(), "test");
+    Assert.assertEquals(result.getTestName(), null);
+  }
+
+  @Test
+  public void complexITestTest() {
+    TestNG tng = create(ITestSample.class);
+    TestListenerAdapter adapter = new TestListenerAdapter();
+    tng.addListener(adapter);
+
+    tng.run();
+
+    Assert.assertTrue(adapter.getFailedTests().isEmpty());
+    Assert.assertTrue(adapter.getSkippedTests().isEmpty());
+    Assert.assertEquals(adapter.getPassedTests().size(), 5);
+    List<String> testNames = new ArrayList<>(Arrays.asList("test1", "test2", "test3", "test4", "test5"));
+    for (ITestResult testResult : adapter.getPassedTests()) {
+      Assert.assertTrue(testNames.remove(testResult.getName()),
+                        "Duplicate test names " + getNames(adapter.getPassedTests()));
+    }
+    Assert.assertEquals(testNames, Collections.emptyList());
+  }
+
+  private static List<String> getNames(List<ITestResult> results) {
+    List<String> names = new ArrayList<>(results.size());
+    for (ITestResult result : results) {
+      names.add(result.getName());
+    }
+    return names;
+  }
+}
diff --git a/src/test/java/test/name/NoNameSample.java b/src/test/java/test/name/NoNameSample.java
new file mode 100644
index 0000000..66d397e
--- /dev/null
+++ b/src/test/java/test/name/NoNameSample.java
@@ -0,0 +1,12 @@
+package test.name;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class NoNameSample {
+
+  @Test
+  public void test() {
+    Assert.assertTrue(true);
+  }
+}
diff --git a/src/test/java/test/name/SimpleITestSample.java b/src/test/java/test/name/SimpleITestSample.java
new file mode 100644
index 0000000..14edf13
--- /dev/null
+++ b/src/test/java/test/name/SimpleITestSample.java
@@ -0,0 +1,18 @@
+package test.name;
+
+import org.testng.Assert;
+import org.testng.ITest;
+import org.testng.annotations.Test;
+
+public class SimpleITestSample implements ITest {
+
+  @Test
+  public void test() {
+    Assert.assertTrue(true);
+  }
+
+  @Override
+  public String getTestName() {
+    return "NAME";
+  }
+}
diff --git a/src/test/java/test/nested/GarfTest.java b/src/test/java/test/nested/GarfTest.java
new file mode 100644
index 0000000..17ea7ed
--- /dev/null
+++ b/src/test/java/test/nested/GarfTest.java
@@ -0,0 +1,17 @@
+package test.nested;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import test.nested.foo.AccountTypeEnum;
+
+@Test(groups = { "unittest" }, enabled = true )
+public class GarfTest {
+
+  @Test()
+  public void testGarf() {
+    AccountTypeEnum foo = AccountTypeEnum.ClearingMember;
+    Assert.assertEquals(foo,AccountTypeEnum.ClearingMember);
+  }
+
+}
diff --git a/src/test/java/test/nested/foo/AccountTypeEnum.java b/src/test/java/test/nested/foo/AccountTypeEnum.java
new file mode 100644
index 0000000..783f3f3
--- /dev/null
+++ b/src/test/java/test/nested/foo/AccountTypeEnum.java
@@ -0,0 +1,7 @@
+package test.nested.foo;
+
+public class AccountTypeEnum {
+
+  public static final AccountTypeEnum ClearingMember = null;
+
+}
diff --git a/src/test/java/test/nested2/TmpA.java b/src/test/java/test/nested2/TmpA.java
new file mode 100644
index 0000000..dd4373c
--- /dev/null
+++ b/src/test/java/test/nested2/TmpA.java
@@ -0,0 +1,26 @@
+package test.nested2;
+
+import org.testng.annotations.Test;
+
+public class TmpA {
+  public static class NestedAWithTest {
+    @Test
+    public void nestedA() {}
+  }
+
+  public static class NestedAWithoutTest {
+    public NestedAWithoutTest() {
+      throw new RuntimeException("TestNG should not instantiate me");
+    }
+
+    public void nestedA() {}
+  }
+
+  @Test
+  public static class DummyBase {}
+
+  public static class NestedAWithInheritedTest extends DummyBase {
+    public void nestedWithInheritedTest() {}
+  }
+
+}
\ No newline at end of file
diff --git a/src/test/java/test/objectfactory/BadMethodFactoryFactory.java b/src/test/java/test/objectfactory/BadMethodFactoryFactory.java
new file mode 100644
index 0000000..921f12b
--- /dev/null
+++ b/src/test/java/test/objectfactory/BadMethodFactoryFactory.java
@@ -0,0 +1,16 @@
+package test.objectfactory;
+
+import org.testng.annotations.ObjectFactory;
+
+/**
+ * @author Hani Suleiman
+ *         Date: Mar 6, 2007
+ *         Time: 5:03:19 PM
+ */
+public class BadMethodFactoryFactory
+{
+  @ObjectFactory
+  public Object create() {
+    return new LoggingObjectFactory();
+  }
+}
diff --git a/src/test/java/test/objectfactory/ClassObjectFactory.java b/src/test/java/test/objectfactory/ClassObjectFactory.java
new file mode 100644
index 0000000..6ed3da5
--- /dev/null
+++ b/src/test/java/test/objectfactory/ClassObjectFactory.java
@@ -0,0 +1,21 @@
+package test.objectfactory;
+
+import org.testng.IObjectFactory2;
+
+import java.lang.reflect.Constructor;
+
+public class ClassObjectFactory implements IObjectFactory2 {
+
+  @Override
+  public Object newInstance(Class<?> cls) {
+    try {
+      Constructor ctor = cls.getConstructors()[0];
+      return ctor.newInstance(new Object[] { 42 });
+    }
+    catch(Exception ex) {
+      ex.printStackTrace();
+      return null;
+    }
+  }
+
+}
diff --git a/src/test/java/test/objectfactory/ClassObjectFactorySampleTest.java b/src/test/java/test/objectfactory/ClassObjectFactorySampleTest.java
new file mode 100644
index 0000000..6cbd1ae
--- /dev/null
+++ b/src/test/java/test/objectfactory/ClassObjectFactorySampleTest.java
@@ -0,0 +1,20 @@
+package test.objectfactory;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class ClassObjectFactorySampleTest {
+  public static int m_n = 0;
+
+  /**
+   * Will be invoked with 42 by the factory.
+   */
+  public ClassObjectFactorySampleTest(int n) {
+    m_n = n;
+  }
+
+  @Test
+  public void f() {
+    Assert.assertEquals(42, m_n);
+  }
+}
diff --git a/src/test/java/test/objectfactory/CombinedTestAndObjectFactorySample.java b/src/test/java/test/objectfactory/CombinedTestAndObjectFactorySample.java
new file mode 100644
index 0000000..2408768
--- /dev/null
+++ b/src/test/java/test/objectfactory/CombinedTestAndObjectFactorySample.java
@@ -0,0 +1,37 @@
+package test.objectfactory;

+

+import org.testng.Assert;

+import org.testng.IObjectFactory;

+import org.testng.annotations.ObjectFactory;

+import org.testng.annotations.Test;

+

+import java.lang.reflect.Constructor;

+

+@SuppressWarnings("serial")

+public class CombinedTestAndObjectFactorySample implements IObjectFactory{

+  private boolean configured = false;

+

+  @ObjectFactory public IObjectFactory create() {

+    return new CombinedTestAndObjectFactorySample();

+  }

+

+  @Test public void isConfigured() {

+    Assert.assertTrue(configured, "Should have been configured by object factory");

+  }

+

+  @Override

+  @SuppressWarnings("unchecked")

+  public Object newInstance(Constructor constructor, Object... params)  {

+    Object o;

+    try {

+      o = constructor.newInstance(params);

+    } catch (Exception e) {

+      throw new RuntimeException(e);

+    }

+    if (CombinedTestAndObjectFactorySample.class.equals(o.getClass())) {

+      CombinedTestAndObjectFactorySample s = (CombinedTestAndObjectFactorySample) o;

+      s.configured = true;

+    }

+    return o;

+  }

+}

diff --git a/src/test/java/test/objectfactory/CombinedTestAndObjectFactoryTest.java b/src/test/java/test/objectfactory/CombinedTestAndObjectFactoryTest.java
new file mode 100644
index 0000000..c821618
--- /dev/null
+++ b/src/test/java/test/objectfactory/CombinedTestAndObjectFactoryTest.java
@@ -0,0 +1,24 @@
+package test.objectfactory;

+

+import org.testng.TestNG;

+import org.testng.annotations.Test;

+

+import test.BaseTest;

+

+public class CombinedTestAndObjectFactoryTest extends BaseTest {

+  @Test

+  void combinedTestAndObjectFactory() {

+    addClass(CombinedTestAndObjectFactorySample.class.getName());

+    run();

+    verifyTests("Should have passed", new String[]{"isConfigured"}, getPassedTests());

+    verifyTests("Failures", new String[0], getFailedTests());

+    verifyTests("Skipped", new String[0], getSkippedTests());

+  }

+

+  public static void main(String[] args) {

+    TestNG tng = new TestNG();

+    tng.setTestClasses(new Class[] {CombinedTestAndObjectFactorySample.class});

+    tng.setVerbose(10);

+    tng.run();

+  }

+}

diff --git a/src/test/java/test/objectfactory/ContextAwareFactoryFactory.java b/src/test/java/test/objectfactory/ContextAwareFactoryFactory.java
new file mode 100644
index 0000000..b6dfe49
--- /dev/null
+++ b/src/test/java/test/objectfactory/ContextAwareFactoryFactory.java
@@ -0,0 +1,20 @@
+package test.objectfactory;
+
+import org.testng.IObjectFactory;
+import org.testng.ITestContext;
+import org.testng.annotations.ObjectFactory;
+import org.testng.internal.ObjectFactoryImpl;
+
+/**
+ * @author Hani Suleiman
+ *         Date: Mar 8, 2007
+ *         Time: 10:05:55 PM
+ */
+public class ContextAwareFactoryFactory
+{
+  @ObjectFactory
+  public IObjectFactory create(ITestContext context) {
+    assert context != null;
+    return new ObjectFactoryImpl();
+  }
+}
diff --git a/src/test/java/test/objectfactory/CustomFactoryTest.java b/src/test/java/test/objectfactory/CustomFactoryTest.java
new file mode 100644
index 0000000..7582b25
--- /dev/null
+++ b/src/test/java/test/objectfactory/CustomFactoryTest.java
@@ -0,0 +1,69 @@
+package test.objectfactory;
+
+import org.testng.TestNG;
+import org.testng.TestNGException;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlSuite;
+import test.TestHelper;
+
+/**
+ * @author Hani Suleiman Date: Mar 6, 2007 Time: 3:52:19 PM
+ */
+public class CustomFactoryTest {
+  @Test
+  public void setFactoryOnTestNG() {
+    XmlSuite suite = TestHelper.createSuite("test.objectfactory.Simple", "objectfactory");
+    // suite.setObjectFactory(new LoggingObjectFactory());
+    TestNG tng = TestHelper.createTestNG(suite);
+    tng.setObjectFactory(LoggingObjectFactory.class);
+    tng.run();
+    assert LoggingObjectFactory.invoked == 1 : "Logging factory invoked "
+        + LoggingObjectFactory.invoked + " times";
+  }
+
+  @AfterMethod
+  public void resetCount() {
+    LoggingObjectFactory.invoked = 0;
+  }
+
+  @Test
+  public void setFactoryOnSuite() {
+    XmlSuite suite = TestHelper.createSuite("test.objectfactory.Simple", "objectfactory");
+    suite.setObjectFactory(new LoggingObjectFactory());
+    TestNG tng = TestHelper.createTestNG(suite);
+    tng.run();
+    assert LoggingObjectFactory.invoked == 1 : "Logging factory invoked "
+        + LoggingObjectFactory.invoked + " times";
+  }
+
+  @Test(enabled = false, description = "This broke after I made the change to enable AbstractTest")
+  public void setFactoryByAnnotation() {
+    XmlSuite suite = TestHelper.createSuite("test.objectfactory.Simple", "objectfactory");
+    suite.getTests().get(0).getXmlClasses()
+        .add(new XmlClass("test.objectfactory.MyFactoryFactory"));
+    TestNG tng = TestHelper.createTestNG(suite);
+    tng.run();
+    assert LoggingObjectFactory.invoked == 1 : "Logging factory invoked "
+        + LoggingObjectFactory.invoked + " times";
+  }
+
+  @Test
+  public void factoryReceivesContext() {
+    XmlSuite suite = TestHelper.createSuite("test.objectfactory.Simple", "objectfactory");
+    suite.getTests().get(0).getXmlClasses()
+        .add(new XmlClass("test.objectfactory.ContextAwareFactoryFactory"));
+    TestNG tng = TestHelper.createTestNG(suite);
+    tng.run();
+  }
+
+  @Test(expectedExceptions = TestNGException.class)
+  public void setInvalidMethodFactoryByAnnotation() {
+    XmlSuite suite = TestHelper.createSuite("test.objectfactory.Simple", "objectfactory");
+    suite.getTests().get(0).getXmlClasses()
+        .add(new XmlClass("test.objectfactory.BadMethodFactoryFactory"));
+    TestNG tng = TestHelper.createTestNG(suite);
+    tng.run();
+  }
+}
diff --git a/src/test/java/test/objectfactory/LoggingObjectFactory.java b/src/test/java/test/objectfactory/LoggingObjectFactory.java
new file mode 100644
index 0000000..34cd615
--- /dev/null
+++ b/src/test/java/test/objectfactory/LoggingObjectFactory.java
@@ -0,0 +1,26 @@
+package test.objectfactory;
+
+import org.testng.internal.ObjectFactoryImpl;
+
+import java.lang.reflect.Constructor;
+
+/**
+ * @author Hani Suleiman
+ *         Date: Mar 6, 2007
+ *         Time: 3:53:28 PM
+ */
+public class LoggingObjectFactory extends ObjectFactoryImpl
+{
+  /**
+   *
+   */
+  private static final long serialVersionUID = -395096650866727480L;
+  public static int invoked;
+
+  @Override
+  public Object newInstance(Constructor constructor, Object... params)
+  {
+    invoked++;
+    return super.newInstance(constructor, params);
+  }
+}
diff --git a/src/test/java/test/objectfactory/MyFactoryFactory.java b/src/test/java/test/objectfactory/MyFactoryFactory.java
new file mode 100644
index 0000000..40f7b94
--- /dev/null
+++ b/src/test/java/test/objectfactory/MyFactoryFactory.java
@@ -0,0 +1,17 @@
+package test.objectfactory;
+
+import org.testng.IObjectFactory;
+import org.testng.annotations.ObjectFactory;
+
+/**
+ * @author Hani Suleiman
+ *         Date: Mar 6, 2007
+ *         Time: 5:03:19 PM
+ */
+public class MyFactoryFactory
+{
+  @ObjectFactory
+  public IObjectFactory create() {
+    return new LoggingObjectFactory();
+  }
+}
diff --git a/src/test/java/test/objectfactory/ObjectFactory2Test.java b/src/test/java/test/objectfactory/ObjectFactory2Test.java
new file mode 100644
index 0000000..e03132a
--- /dev/null
+++ b/src/test/java/test/objectfactory/ObjectFactory2Test.java
@@ -0,0 +1,39 @@
+package test.objectfactory;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+
+import test.TestHelper;
+
+/**
+ * Test IObjectFactory2, which is an object factory that receives just the Class in parameter.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public class ObjectFactory2Test {
+
+  private void testFactory(boolean onSuite) {
+    XmlSuite suite = TestHelper.createSuite(ClassObjectFactorySampleTest.class.getName(),
+        "Test IObjectFactory2");
+    TestNG tng = TestHelper.createTestNG(suite);
+
+    if (onSuite) suite.setObjectFactory(new ClassObjectFactory());
+    else tng.setObjectFactory(ClassObjectFactory.class);
+
+    ClassObjectFactorySampleTest.m_n = 0;
+    tng.run();
+    Assert.assertEquals(ClassObjectFactorySampleTest.m_n, 42);
+  }
+
+  @Test
+  public void factoryOnSuiteShouldWork() {
+    testFactory(true /* on suite object */);
+  }
+
+  @Test
+  public void factoryOnTestNGShouldWork() {
+    testFactory(false /* on TestNG object */);
+  }
+}
diff --git a/src/test/java/test/objectfactory/Simple.java b/src/test/java/test/objectfactory/Simple.java
new file mode 100644
index 0000000..26a519e
--- /dev/null
+++ b/src/test/java/test/objectfactory/Simple.java
@@ -0,0 +1,16 @@
+package test.objectfactory;
+
+import org.testng.annotations.Test;
+
+/**
+ * @author Hani Suleiman
+ *         Date: Mar 6, 2007
+ *         Time: 3:56:57 PM
+ */
+public class Simple
+{
+  @Test
+  public void dummy() {
+
+  }
+}
diff --git a/src/test/java/test/override/OverrideSampleTest.java b/src/test/java/test/override/OverrideSampleTest.java
new file mode 100644
index 0000000..c58a7b1
--- /dev/null
+++ b/src/test/java/test/override/OverrideSampleTest.java
@@ -0,0 +1,15 @@
+package test.override;
+
+import org.testng.annotations.Test;
+
+public class OverrideSampleTest {
+
+  @Test(groups = "goodGroup")
+  public void good() {
+  }
+
+  @Test(groups = "badGroup")
+  public void bad() {
+    throw new RuntimeException("Should not happen");
+  }
+}
diff --git a/src/test/java/test/override/OverrideTest.java b/src/test/java/test/override/OverrideTest.java
new file mode 100644
index 0000000..d0a8345
--- /dev/null
+++ b/src/test/java/test/override/OverrideTest.java
@@ -0,0 +1,63 @@
+package test.override;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.internal.Utils;
+import org.xml.sax.SAXException;
+
+import test.SimpleBaseTest;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Verify that command line switches override parameters in testng.xml.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public class OverrideTest extends SimpleBaseTest {
+
+  private void runTest(String include, String exclude) {
+    File f = Utils.createTempFile(
+        "<suite name=\"S\">"
+        + "  <test name=\"T\">"
+        + "    <classes>"
+        + "      <class name=\"test.override.OverrideSampleTest\" />"
+        + "    </classes>"
+        + "  </test>"
+        + "</suite>"
+        );
+    TestNG tng = create();
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    if (include != null) tng.setGroups(include);
+    if (exclude != null) tng.setExcludedGroups(exclude);
+    tng.setTestSuites(Arrays.asList(f.getAbsolutePath()));
+    tng.run();
+
+    Assert.assertEquals(tla.getPassedTests().size(), 1);
+  }
+
+  @Test(description = "Override -groups")
+  public void overrideIncludeShouldWork()
+      throws ParserConfigurationException, SAXException, IOException {
+    runTest("goodGroup", null);
+  }
+
+  @Test(description = "Override -excludegroups")
+  public void overrideExcludeShouldWork()
+      throws ParserConfigurationException, SAXException, IOException {
+    runTest(null, "badGroup");
+  }
+
+  @Test(description = "Override -groups and -excludegroups")
+  public void overrideIncludeAndExcludeShouldWork()
+      throws ParserConfigurationException, SAXException, IOException {
+    runTest("goodGroup", "badGroup");
+  }
+}
diff --git a/src/test/java/test/parameters/BeforeSampleTest.java b/src/test/java/test/parameters/BeforeSampleTest.java
new file mode 100644
index 0000000..6d7dfdf
--- /dev/null
+++ b/src/test/java/test/parameters/BeforeSampleTest.java
@@ -0,0 +1,27 @@
+package test.parameters;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+import junit.framework.Assert;
+
+public class BeforeSampleTest {
+   @BeforeMethod
+   @Parameters("parameter")
+   public static void beforeMethod(String parameter) {
+     Assert.assertEquals("parameter value", parameter);
+   }
+
+   @DataProvider(name = "dataProvider")
+   public static Object[][] dataProvider() {
+     return new Object[][]{{"abc", "def"}};
+   }
+
+   @Test(dataProvider = "dataProvider")
+   public static void testExample(String a, String b) {
+     Assert.assertEquals("abc", a);
+     Assert.assertEquals("def", b);
+   }
+}
\ No newline at end of file
diff --git a/src/test/java/test/parameters/InheritFromSuiteChild1.java b/src/test/java/test/parameters/InheritFromSuiteChild1.java
new file mode 100644
index 0000000..1775ab6
--- /dev/null
+++ b/src/test/java/test/parameters/InheritFromSuiteChild1.java
@@ -0,0 +1,24 @@
+package test.parameters;
+
+import org.testng.Assert;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+/**
+ * Checks to see if the parameters from parent suite are passed onto the
+ * child suite (referred by <suite-file>)
+ * @author nullin
+ *
+ */
+public class InheritFromSuiteChild1
+{
+   @Test
+   @Parameters({"parameter1", "parameter2", "parameter3", "parameter4"})
+   public void inheritedparameter(String p1, String p2, @Optional("foobar")String p3, String p4) {
+      Assert.assertEquals(p1, "p1");
+      Assert.assertEquals(p2, "c1p2");
+      Assert.assertEquals(p3, "foobar");
+      Assert.assertEquals(p4, "c1p4");
+   }
+}
diff --git a/src/test/java/test/parameters/InheritFromSuiteChild2.java b/src/test/java/test/parameters/InheritFromSuiteChild2.java
new file mode 100644
index 0000000..d2faebd
--- /dev/null
+++ b/src/test/java/test/parameters/InheritFromSuiteChild2.java
@@ -0,0 +1,24 @@
+package test.parameters;
+
+import org.testng.Assert;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+/**
+ * Checks to see if the parameters from parent suite are passed onto the
+ * child suite (referred by <suite-file>)
+ * @author nullin
+ *
+ */
+public class InheritFromSuiteChild2
+{
+   @Test
+   @Parameters({"parameter1", "parameter2", "parameter3", "parameter4"})
+   public void inheritedparameter(String p1, String p2, String p3, @Optional("abc")String p4) {
+      Assert.assertEquals(p1, "p1");
+      Assert.assertEquals(p2, "c2p2");
+      Assert.assertEquals(p3, "c2p3");
+      Assert.assertEquals(p4, "abc");
+   }
+}
diff --git a/src/test/java/test/parameters/InheritFromSuiteChild3.java b/src/test/java/test/parameters/InheritFromSuiteChild3.java
new file mode 100644
index 0000000..196164e
--- /dev/null
+++ b/src/test/java/test/parameters/InheritFromSuiteChild3.java
@@ -0,0 +1,23 @@
+package test.parameters;
+
+import org.testng.Assert;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+/**
+ * Checks to see if the parameters from parent suite are passed onto the
+ * child suite (referred by <suite-file>)
+ * @author nullin
+ *
+ */
+public class InheritFromSuiteChild3
+{
+   @Test
+   @Parameters({"parameter1", "parameter2", "parameter3", "parameter4"})
+   public void inheritedparameter(String p1, String p2, String p3, String p4) {
+      Assert.assertEquals(p1, "p1");
+      Assert.assertEquals(p2, "c3p2");
+      Assert.assertEquals(p3, "c2p3");
+      Assert.assertEquals(p4, "c3p4");
+   }
+}
diff --git a/src/test/java/test/parameters/OptionalParameterTest.java b/src/test/java/test/parameters/OptionalParameterTest.java
new file mode 100644
index 0000000..f327039
--- /dev/null
+++ b/src/test/java/test/parameters/OptionalParameterTest.java
@@ -0,0 +1,11 @@
+package test.parameters;
+
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+
+public class OptionalParameterTest {
+
+  @Parameters("optional")
+  public OptionalParameterTest(@Optional String optional) {}
+
+}
diff --git a/src/test/java/test/parameters/Override1Sample.java b/src/test/java/test/parameters/Override1Sample.java
new file mode 100644
index 0000000..b3f133a
--- /dev/null
+++ b/src/test/java/test/parameters/Override1Sample.java
@@ -0,0 +1,25 @@
+package test.parameters;
+
+import org.testng.Assert;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+@Test
+public class Override1Sample {
+
+  @Parameters({"InheritedFromSuite", "InheritedFromTest", "InheritedFromClass"})
+  public void g(String suite, String test, String cls) {
+    Assert.assertEquals(suite, "InheritedFromSuite");
+    Assert.assertEquals(test, "InheritedFromTest");
+    Assert.assertEquals(cls, "InheritedFromClass");
+  }
+
+  public void h() {
+    System.out.println("h()");
+  }
+
+  @Parameters("a")
+  public void f(String p) {
+    Assert.assertEquals(p, "Correct");
+  }
+}
diff --git a/src/test/java/test/parameters/ParamInheritanceTest.java b/src/test/java/test/parameters/ParamInheritanceTest.java
new file mode 100644
index 0000000..fab48c5
--- /dev/null
+++ b/src/test/java/test/parameters/ParamInheritanceTest.java
@@ -0,0 +1,63 @@
+package test.parameters;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Arrays;
+
+public class ParamInheritanceTest extends SimpleBaseTest {
+
+  @Test(description = "When verbose is set to >1, TNG prints test results on CLI which are printed "
+      + "using SuiteResultCounts.calculateResultCounts(). This method has been throwing NPE "
+      + "because it's unable to find SuiteRunner in HashMap, because the list of parameters in "
+      + "SuiteRunner changed" + " during execution. This test makes sure we dont run into any NPEs")
+  public void noNPEInCountingResults() {
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG tng = create();
+    tng.setTestSuites(Arrays.asList(getPathToResource("param-inheritance/parent-suite.xml")));
+    tng.setVerbose(2);
+    tng.addListener(tla);
+    OutputStream os = new ByteArrayOutputStream();
+    PrintStream out = System.out;
+    PrintStream err = System.err;
+    try {
+      /*
+       * Changing system print streams so that exception or results stmt is not logged
+       * while running test (avoid confusing person running tests)
+       */
+      System.setOut(new PrintStream(os));
+      System.setErr(new PrintStream(os));
+      tng.run();
+      Assert.assertEquals(tla.getPassedTests().size(), 1);
+    }
+    finally {
+      try {
+        os.close();
+      }
+      catch (IOException e) {
+        //no need to handle this
+      }
+      System.setOut(out);
+      System.setErr(err);
+    }
+  }
+
+  @Test(description = "Checks to make sure parameters are inherited and overridden properly")
+  public void parameterInheritanceAndOverriding() {
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG tng = create();
+    tng.setUseDefaultListeners(false);
+    tng.setTestSuites(Arrays.asList(getPathToResource("parametertest/parent-suite.xml")));
+    tng.addListener(tla);
+    tng.run();
+    Assert.assertEquals(tla.getPassedTests().size(), 3);
+  }
+}
diff --git a/src/test/java/test/parameters/ParameterOverrideTest.java b/src/test/java/test/parameters/ParameterOverrideTest.java
new file mode 100644
index 0000000..1d1331d
--- /dev/null
+++ b/src/test/java/test/parameters/ParameterOverrideTest.java
@@ -0,0 +1,92 @@
+package test.parameters;
+
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlInclude;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.SimpleBaseTest;
+
+import java.util.Arrays;
+
+public class ParameterOverrideTest extends SimpleBaseTest {
+  enum S {
+    FAIL,
+    PASS_TEST,
+    PASS_CLASS,
+    PASS_INCLUDE,
+  };
+
+  @Test
+  public void testOverrideSuite() {
+    privateTestOverrideSuite(S.PASS_TEST);
+  }
+
+  @Test(expectedExceptions = AssertionError.class)
+  public void testOverrideSuiteNegative() {
+    privateTestOverrideSuite(S.FAIL);
+  }
+
+  @Test
+  public void classOverrideSuite() {
+    privateTestOverrideSuite(S.PASS_CLASS);
+  }
+
+  @Test(expectedExceptions = AssertionError.class)
+  public void classOverrideSuiteNegative() {
+    privateTestOverrideSuite(S.FAIL);
+  }
+
+  @Test
+  public void includeOverrideClass() {
+    privateTestOverrideSuite(S.PASS_INCLUDE);
+  }
+
+  @Test(expectedExceptions = AssertionError.class)
+  public void includeOverrideClassNegative() {
+    privateTestOverrideSuite(S.FAIL);
+  }
+
+  public void privateTestOverrideSuite(S status) {
+    XmlSuite s = createXmlSuite("s");
+    s.getParameters().put("a", "Incorrect");
+    s.getParameters().put("InheritedFromSuite", "InheritedFromSuite");
+    XmlTest t = createXmlTest(s, "t");
+    t.getLocalParameters().put("InheritedFromTest", "InheritedFromTest");
+    if (status == S.PASS_TEST) {
+      t.getLocalParameters().put("a", "Correct");
+    }
+
+    {
+      XmlClass c1 = new XmlClass(Override1Sample.class.getName());
+      c1.getLocalParameters().put("InheritedFromClass", "InheritedFromClass");
+      if (status == S.PASS_CLASS) {
+        c1.getLocalParameters().put("a", "Correct");
+      }
+      t.getXmlClasses().add(c1);
+
+      for (String method : new String[] { "f", "g" }) {
+        XmlInclude include1 = new XmlInclude(method);
+        if (status == S.PASS_INCLUDE) {
+          include1.getLocalParameters().put("a", "Correct");
+        }
+        include1.setXmlClass(c1);
+        c1.getIncludedMethods().add(include1);
+      }
+
+    }
+
+    TestNG tng = create();
+    tng.setXmlSuites(Arrays.asList(s));
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+//    System.out.println(s.toXml());
+//    tng.setVerbose(10);
+    tng.run();
+
+    assertTestResultsEqual(tla.getPassedTests(), Arrays.asList("f", "g"));
+  }
+}
diff --git a/src/test/java/test/parameters/ParameterSample.java b/src/test/java/test/parameters/ParameterSample.java
new file mode 100644
index 0000000..8937362
--- /dev/null
+++ b/src/test/java/test/parameters/ParameterSample.java
@@ -0,0 +1,52 @@
+package test.parameters;
+
+import org.testng.TestNG;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+import org.testng.xml.Parser;
+
+import java.io.ByteArrayInputStream;
+
+/**
+ * This class
+ *
+ * @author Cedric Beust, Jul 22, 2004
+ *
+ */
+
+public class ParameterSample {
+
+  @Parameters({ "first-name" })
+  @BeforeMethod
+  public void beforeTest(String firstName) {
+//    System.out.println("[ParameterSample] Invoked beforeTestMethod with: " + firstName);
+    assert "Cedric".equals(firstName)
+     : "Expected Cedric, got " + firstName;
+  }
+
+
+  @Parameters({ "first-name" })
+  @Test(groups = { "singleString"})
+  public void testSingleString(String firstName) {
+//    System.out.println("[ParameterSample] Invoked testString " + firstName);
+    assert "Cedric".equals(firstName);
+  }
+
+  @Parameters({"this parameter doesn't exist"})
+  @Test
+  public void testNonExistentParameter(@Optional String foo) {
+
+  }
+
+  public static void main(String[] args) throws Exception {
+    TestNG tng = new TestNG();
+    String xml = "<suite name=\"dgf\" verbose=\"10\"><parameter name=\"first-name\" value=\"Cedric\" /><test name=\"dgf\"><classes><class name=\"test.parameters.ParameterSample\"></class></classes></test></suite>";
+    System.out.println(xml);
+    ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes());
+    tng.setXmlSuites(new Parser(is).parseToList());
+    tng.run();
+  }
+
+}
diff --git a/src/test/java/test/parameters/ParameterTest.java b/src/test/java/test/parameters/ParameterTest.java
new file mode 100644
index 0000000..c4e0b71
--- /dev/null
+++ b/src/test/java/test/parameters/ParameterTest.java
@@ -0,0 +1,48 @@
+package test.parameters;
+
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+
+/**
+ * This class
+ *
+ * @author Cedric Beust, Jul 22, 2004
+ *
+ */
+public class ParameterTest extends BaseTest {
+
+  public static void ppp(String s) {
+    System.out.println("[ParameterTest] " + s);
+  }
+
+  @Test
+  public void stringSingle() {
+    addClass("test.parameters.ParameterSample");
+    setParameter("first-name", "Cedric");
+    run();
+    String[] passed = {
+      "testSingleString",
+      "testNonExistentParameter",
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void beforeMethodWithParameters() {
+    addClass("test.parameters.BeforeSampleTest");
+    setParameter("parameter", "parameter value");
+    run();
+    String[] passed = {
+      "testExample",
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+}
diff --git a/src/test/java/test/parameters/SampleTest.java b/src/test/java/test/parameters/SampleTest.java
new file mode 100644
index 0000000..5e57d68
--- /dev/null
+++ b/src/test/java/test/parameters/SampleTest.java
@@ -0,0 +1,11 @@
+package test.parameters;
+
+import org.testng.annotations.Test;
+
+public class SampleTest
+{
+  @Test
+  public void foo() {
+    //purposefully does nothing
+  }
+}
diff --git a/src/test/java/test/parameters/Shadow1SampleTest.java b/src/test/java/test/parameters/Shadow1SampleTest.java
new file mode 100644
index 0000000..414afb4
--- /dev/null
+++ b/src/test/java/test/parameters/Shadow1SampleTest.java
@@ -0,0 +1,14 @@
+package test.parameters;
+
+import org.testng.Assert;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+public class Shadow1SampleTest {
+  @Parameters("a")
+  @Test
+  public void test1(String a) {
+    Assert.assertEquals("First", a);
+  }
+
+}
diff --git a/src/test/java/test/parameters/Shadow2SampleTest.java b/src/test/java/test/parameters/Shadow2SampleTest.java
new file mode 100644
index 0000000..9fb5c50
--- /dev/null
+++ b/src/test/java/test/parameters/Shadow2SampleTest.java
@@ -0,0 +1,14 @@
+package test.parameters;
+
+import org.testng.Assert;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+public class Shadow2SampleTest {
+  @Parameters("a")
+  @Test
+  public void test2(String a) {
+    Assert.assertEquals("Second", a);
+  }
+
+}
diff --git a/src/test/java/test/parameters/ShadowTest.java b/src/test/java/test/parameters/ShadowTest.java
new file mode 100644
index 0000000..ddcc42a
--- /dev/null
+++ b/src/test/java/test/parameters/ShadowTest.java
@@ -0,0 +1,49 @@
+package test.parameters;
+
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlInclude;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.SimpleBaseTest;
+
+import java.util.Arrays;
+
+public class ShadowTest extends SimpleBaseTest {
+
+  @Test
+  public void parametersShouldNotBeShadowed() {
+    XmlSuite s = createXmlSuite("s");
+    XmlTest t = createXmlTest(s, "t");
+
+    {
+      XmlClass c1 = new XmlClass(Shadow1SampleTest.class.getName());
+      XmlInclude include1 = new XmlInclude("test1");
+      include1.setXmlClass(c1);
+      c1.getLocalParameters().put("a", "First");
+      c1.getIncludedMethods().add(include1);
+      t.getXmlClasses().add(c1);
+    }
+
+    {
+      XmlClass c2 = new XmlClass(Shadow2SampleTest.class.getName());
+      XmlInclude include2 = new XmlInclude("test2");
+      include2.setXmlClass(c2);
+      c2.getLocalParameters().put("a", "Second");
+      c2.getIncludedMethods().add(include2);
+      t.getXmlClasses().add(c2);
+    }
+
+    TestNG tng = create();
+    tng.setXmlSuites(Arrays.asList(s));
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+//    System.out.println(s.toXml());
+    assertTestResultsEqual(tla.getPassedTests(), Arrays.asList("test1", "test2"));
+  }
+}
diff --git a/src/test/java/test/parameters/SuiteSampleTest.java b/src/test/java/test/parameters/SuiteSampleTest.java
new file mode 100644
index 0000000..8bfab6b
--- /dev/null
+++ b/src/test/java/test/parameters/SuiteSampleTest.java
@@ -0,0 +1,17 @@
+package test.parameters;
+
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Parameters;
+
+public class SuiteSampleTest {
+
+  @Parameters({"param"})
+  @BeforeSuite
+  public void suiteParameter(String s) {
+    ppp("PARAM:" + s);
+  }
+
+  private void ppp(String s) {
+    System.out.println("[SuiteSampleTest] " + s);
+  }
+}
diff --git a/src/test/java/test/pholser/Captor.java b/src/test/java/test/pholser/Captor.java
new file mode 100644
index 0000000..deb7c7c
--- /dev/null
+++ b/src/test/java/test/pholser/Captor.java
@@ -0,0 +1,35 @@
+package test.pholser;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:pholser@thoughtworks.com">Paul Holser</a>
+ * @version $Id: Captor.java,v 1.3 2004/08/26 22:25:22 cedric Exp $
+ */
+public class Captor {
+  private static Captor instance = null;
+  private List<String> captives;
+
+  public static Captor instance() {
+    if (null == instance) {
+      instance = new Captor();
+    }
+    return instance;
+  }
+
+  public static void reset() {
+//    System.out.println("@@PHOLSER RESETTING CAPTOR");
+    instance().captives = new ArrayList<>();
+  }
+
+  public void capture( String aString ) {
+//    System.out.println("@@PHOLSER CAPTURING " + aString);
+    captives.add( aString );
+  }
+
+  public List<String> captives() {
+    return Collections.unmodifiableList( captives );
+  }
+}
diff --git a/src/test/java/test/pholser/Demo.java b/src/test/java/test/pholser/Demo.java
new file mode 100644
index 0000000..ff2cb61
--- /dev/null
+++ b/src/test/java/test/pholser/Demo.java
@@ -0,0 +1,61 @@
+package test.pholser;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:pholser@thoughtworks.com">Paul Holser</a>
+ * @version $Id: Demo.java,v 1.5 2006/06/22 13:45:01 cedric Exp $
+ */
+public class Demo {
+  @BeforeClass
+  public void setUpFixture() {
+    Captor.reset();
+    Captor.instance().capture( "Demo.setUpFixture" );
+  }
+
+  @BeforeMethod
+  public void setUp() {
+    Captor.instance().capture( "Demo.setUp" );
+  }
+
+  @AfterMethod
+  public void tearDown() {
+    Captor.instance().capture( "Demo.tearDown" );
+  }
+
+  @AfterClass
+  public void tearDownFixture() {
+    final List<String> expected = Arrays.asList(new String[] { "Demo.setUpFixture", "Demo.setUp", "Demo.tearDown" });
+    final List<String> actual = Captor.instance().captives();
+    verify(expected, actual);
+  }
+
+  @Test
+  public void go() {
+    final List<String> expected = Arrays.asList(new String[] { "Demo.setUpFixture", "Demo.setUp" } );
+    final List<String> actual = Captor.instance().captives();
+    verify(expected, actual);
+  }
+
+  private void verify(List<String> expected, List<String> actual) {
+    if (! expected.equals(actual)) {
+      throw new AssertionError("\nExpected:" + dumpList(expected) + "\n     Got:" + dumpList(actual));
+    }
+  }
+
+  private String dumpList(List<String> list) {
+    StringBuffer result = new StringBuffer();
+    for (String l : list) {
+      result.append(" " + l);
+    }
+
+    return result.toString();
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/pholser/Saboteur.java b/src/test/java/test/pholser/Saboteur.java
new file mode 100644
index 0000000..833a59e
--- /dev/null
+++ b/src/test/java/test/pholser/Saboteur.java
@@ -0,0 +1,38 @@
+package test.pholser;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ * @author <a href="mailto:pholser@thoughtworks.com">Paul Holser</a>
+ * @version $Id: Saboteur.java,v 1.4 2006/06/22 13:45:01 cedric Exp $
+ */
+public class Saboteur {
+  @BeforeClass
+  public void setUpFixture() {
+    Captor.reset();
+    Captor.instance().capture( "Saboteur.setUpFixture" );
+  }
+
+  @BeforeMethod
+  public void setUp() {
+    Captor.instance().capture( "Saboteur.setUp" );
+  }
+
+  @AfterMethod
+  public void tearDown() {
+    Captor.instance().capture( "Saboteur.tearDown" );
+  }
+
+  @AfterClass
+  public void tearDownFixture() {
+    Captor.instance().capture( "Saboteur.tearDownFixture" );
+  }
+
+  @Test
+  public void go() {
+  }
+}
diff --git a/src/test/java/test/pkg/PackageTest.java b/src/test/java/test/pkg/PackageTest.java
new file mode 100644
index 0000000..97f46ee
--- /dev/null
+++ b/src/test/java/test/pkg/PackageTest.java
@@ -0,0 +1,103 @@
+package test.pkg;
+
+import static org.testng.Assert.assertTrue;
+
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+import test.pkg2.Test2;
+/**
+ * Tests that <package> in testng.xml works.
+ *
+ * Created on Aug 2, 2005
+ * @author cbeust
+ */
+public class PackageTest extends BaseTest {
+  public static boolean NON_TEST_CONSTRUCTOR= false;
+
+  @Test
+  public void stringSingle() {
+    addPackage("test.pkg2", new String[0], new String[0]);
+    run();
+    String[] passed = {
+      "method11", "method12",
+      "method31",
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void packageWithNonTestClasses() {
+    addPackage("test.pkg2", new String[0], new String[0]);
+    run();
+    assertTrue(!NON_TEST_CONSTRUCTOR, Test2.class.getName() + " should not be considered");
+  }
+
+  @Test
+  public void packageWithRegExp1() {
+    addPackage("test.pkg2", new String[] { ".*1.*"}, new String[0]);
+    run();
+    String[] passed = {
+      "method11", "method12",
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void packageWithRegExp2() {
+    addPackage("test.pkg2", new String[0], new String[] { ".*1.*"});
+    run();
+    String[] passed = {
+      "method31",
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void packageWithRegExp3() {
+    addPackage("test.pkg2", new String[] { ".*3.*"}, new String[] { ".*1.*"});
+    run();
+    String[] passed = {
+      "method31",
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void packageWithRegExp4() {
+    addPackage("test.pkg2",  new String[] { ".*1.*"}, new String[] { ".*3.*"});
+    run();
+    String[] passed = {
+      "method11", "method12"
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void packageWithRegExp5() {
+    addPackage("test.pkg2",  new String[0], new String[] { "Test.*"});
+    run();
+    String[] passed = {
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+}
diff --git a/src/test/java/test/pkg2/Test1.java b/src/test/java/test/pkg2/Test1.java
new file mode 100644
index 0000000..fa4a89a
--- /dev/null
+++ b/src/test/java/test/pkg2/Test1.java
@@ -0,0 +1,17 @@
+package test.pkg2;
+
+import org.testng.annotations.Test;
+
+public class Test1 {
+
+  @Test
+  public void method11() {
+
+  }
+
+  @Test
+  public void method12() {
+
+  }
+
+}
diff --git a/src/test/java/test/pkg2/Test2.java b/src/test/java/test/pkg2/Test2.java
new file mode 100644
index 0000000..c11c2e2
--- /dev/null
+++ b/src/test/java/test/pkg2/Test2.java
@@ -0,0 +1,13 @@
+package test.pkg2;
+
+import test.pkg.PackageTest;
+
+
+public class Test2 {
+  private Test2(float afloat) {
+    PackageTest.NON_TEST_CONSTRUCTOR= true;
+  }
+
+  public void nonTestMethod() {
+  }
+}
diff --git a/src/test/java/test/pkg2/Test3.java b/src/test/java/test/pkg2/Test3.java
new file mode 100644
index 0000000..880b9d2
--- /dev/null
+++ b/src/test/java/test/pkg2/Test3.java
@@ -0,0 +1,13 @@
+package test.pkg2;
+
+import org.testng.annotations.Test;
+
+public class Test3 {
+
+  @Test
+  public void method31() {
+
+  }
+
+
+}
diff --git a/src/test/java/test/preserveorder/AAA.java b/src/test/java/test/preserveorder/AAA.java
new file mode 100644
index 0000000..eec3a67
--- /dev/null
+++ b/src/test/java/test/preserveorder/AAA.java
@@ -0,0 +1,23 @@
+package test.preserveorder;
+
+import org.testng.annotations.Test;
+
+import test.BaseLogTest;
+
+public class AAA extends BaseLogTest {
+
+  @Test
+  public void a1() {
+    log("AAA.a1");
+  }
+
+  @Test
+  public void a2() {
+    log("AAA.a2");
+  }
+
+  @Test
+  public void a3() {
+    log("AAA.a3");
+  }
+}
diff --git a/src/test/java/test/preserveorder/B.java b/src/test/java/test/preserveorder/B.java
new file mode 100644
index 0000000..870efcb
--- /dev/null
+++ b/src/test/java/test/preserveorder/B.java
@@ -0,0 +1,23 @@
+package test.preserveorder;
+
+import org.testng.annotations.Test;
+
+import test.BaseLogTest;
+
+public class B extends BaseLogTest {
+
+  @Test
+  public void b1() {
+    log("B.b1");
+  }
+
+  @Test
+  public void b2() {
+    log("B.b2");
+  }
+
+  @Test
+  public void b3() {
+    log("B.b3");
+  }
+}
diff --git a/src/test/java/test/preserveorder/C.java b/src/test/java/test/preserveorder/C.java
new file mode 100644
index 0000000..4ac4c77
--- /dev/null
+++ b/src/test/java/test/preserveorder/C.java
@@ -0,0 +1,23 @@
+package test.preserveorder;
+
+import org.testng.annotations.Test;
+
+import test.BaseLogTest;
+
+public class C extends BaseLogTest {
+
+  @Test
+  public void c1() {
+    log("C.c1");
+  }
+
+  @Test
+  public void c2() {
+    log("C.c2");
+  }
+
+  @Test
+  public void c3() {
+    log("C.c3");
+  }
+}
diff --git a/src/test/java/test/preserveorder/ChuckTest3.java b/src/test/java/test/preserveorder/ChuckTest3.java
new file mode 100755
index 0000000..74a9ac3
--- /dev/null
+++ b/src/test/java/test/preserveorder/ChuckTest3.java
@@ -0,0 +1,22 @@
+package test.preserveorder;
+
+import org.testng.annotations.Test;
+
+public class ChuckTest3 {
+
+    @Test(groups = {"functional"}, dependsOnMethods = {"c3TestTwo"})
+    public void c3TestThree() {
+//        System.out.println("chucktest3: test three");
+    }
+
+    @Test(groups = {"functional"})
+    public static void c3TestOne() {
+//        System.out.println("chucktest3: test one");
+    }
+
+    @Test(groups = {"functional"}, dependsOnMethods = {"c3TestOne"})
+    public static void c3TestTwo() {
+//        System.out.println("chucktest3: test two");
+    }
+
+}
diff --git a/src/test/java/test/preserveorder/ChuckTest4.java b/src/test/java/test/preserveorder/ChuckTest4.java
new file mode 100755
index 0000000..503daf8
--- /dev/null
+++ b/src/test/java/test/preserveorder/ChuckTest4.java
@@ -0,0 +1,22 @@
+package test.preserveorder;
+
+import org.testng.annotations.Test;
+
+public class ChuckTest4 {
+
+    @Test(groups = {"functional"}, dependsOnMethods = {"c4TestTwo"})
+    public void c4TestThree() {
+//        System.out.println("chucktest4: test three");
+    }
+
+    @Test(groups = {"functional"})
+    public static void c4TestOne() {
+//        System.out.println("chucktest4: test one");
+    }
+
+    @Test(groups = {"functional"}, dependsOnMethods = {"c4TestOne"})
+    public static void c4TestTwo() {
+//        System.out.println("chucktest4: test two");
+    }
+
+}
diff --git a/src/test/java/test/preserveorder/EdnTest.java b/src/test/java/test/preserveorder/EdnTest.java
new file mode 100755
index 0000000..6fbcaa6
--- /dev/null
+++ b/src/test/java/test/preserveorder/EdnTest.java
@@ -0,0 +1,16 @@
+package test.preserveorder;
+
+import org.testng.annotations.Test;
+
+@Test(singleThreaded = true)
+public class EdnTest {
+
+  @Test
+  public void edn1() {
+  }
+
+  @Test
+  public void edn2() {
+  }
+
+}
diff --git a/src/test/java/test/preserveorder/PreserveOrderTest.java b/src/test/java/test/preserveorder/PreserveOrderTest.java
new file mode 100644
index 0000000..c5a6f8e
--- /dev/null
+++ b/src/test/java/test/preserveorder/PreserveOrderTest.java
@@ -0,0 +1,119 @@
+package test.preserveorder;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.BaseLogTest;
+import test.SimpleBaseTest;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class PreserveOrderTest extends SimpleBaseTest {
+
+  @Test
+  public void preserveClassOrder() {
+    String[][] tests = new String[][] {
+      new String[] { "AAA", "B", "C" },
+      new String[] { "AAA", "C", "B" },
+      new String[] { "B", "AAA", "C" },
+      new String[] { "B", "C", "AAA" },
+      new String[] { "C", "B", "AAA" },
+      new String[] { "C", "AAA", "B" },
+    };
+    for (String[] t : tests) {
+      TestNG tng = create();
+//      tng.setVerbose(2);
+      XmlSuite s = createXmlSuite("Suite");
+      String[] fullTestNames = new String[t.length];
+      for (int i = 0; i < t.length; i++) {
+        fullTestNames[i] = "test.preserveorder." + t[i];
+      }
+      XmlTest xt = createXmlTest(s, "Test", fullTestNames);
+      xt.setPreserveOrder("true");
+//      System.out.println(s.toXml());
+      tng.setXmlSuites(Arrays.asList(s));
+      tng.run();
+
+      // 3 methods per class, 3 classes, so the log should contain 9 calls
+      List<String> log = BaseLogTest.getLog();
+      Assert.assertEquals(log.size(), 9);
+
+      // Every slice of 3 logs should belong to the same class in the same
+      // order as in the specified string:  AAA.a1, AAA.a2, AAA.a3, B.a1, etc...
+      // Since we're only testing class ordering in this test, we only match
+      // against the class name
+      for (int i = 0; i < t.length; i++) {
+        for (int j = 0; j < 3; j++) {
+          Assert.assertTrue(log.get(j + (3 * i)).startsWith(t[i] + "."));
+        }
+      }
+    }
+  }
+
+  @Test
+  public void preserveMethodOrder() {
+    String[][] methods = new String[][] {
+        new String[] { "a1", "a2", "a3" },
+        new String[] { "a1", "a3", "a2" },
+        new String[] { "a2", "a1", "a3" },
+        new String[] { "a2", "a3", "a1" },
+        new String[] { "a3", "a2", "a1" },
+        new String[] { "a3", "a1", "a2" },
+    };
+
+    for (String[] m : methods) {
+      TestNG tng = create();
+  //      tng.setVerbose(2);
+      XmlSuite s = createXmlSuite("Suite");
+      String testName = "test.preserveorder.AAA";
+      XmlTest xt = createXmlTest(s, "Test", testName);
+      addMethods(xt.getXmlClasses().get(0), m);
+      xt.setPreserveOrder("true");
+  //      System.out.println(s.toXml());
+      tng.setXmlSuites(Arrays.asList(s));
+      tng.run();
+
+      List<String> log = BaseLogTest.getLog();
+//      System.out.println(log);
+      for (int i = 0; i < log.size(); i++) {
+        if (!log.get(i).endsWith(m[i])) {
+          throw new AssertionError("Expected " + Arrays.asList(m) + " but got " + log);
+        }
+      }
+    }
+  }
+
+  @Test
+  public void orderShouldBePreservedWithDependencies() {
+    TestNG tng = create();
+    XmlSuite s = createXmlSuite("PreserveOrder");
+    XmlTest t = new XmlTest(s);
+    t.getXmlClasses().add(new XmlClass("test.preserveorder.ChuckTest4"));
+    t.getXmlClasses().add(new XmlClass("test.preserveorder.ChuckTest3"));
+    t.setPreserveOrder("true");
+    tng.setXmlSuites(Arrays.asList(s));
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+    verifyPassedTests(tla, "c4TestOne", "c4TestTwo", "c4TestThree",
+        "c3TestOne", "c3TestTwo", "c3TestThree");
+  }
+
+  @Test(description = "preserve-order on a factory doesn't cause an NPE")
+  public void factoryPreserve() {
+    TestNG tng = create();
+    XmlSuite s = createXmlSuite("FactoryPreserve");
+    XmlTest t = new XmlTest(s);
+    t.getXmlClasses().add(new XmlClass(TestClassFactory.class.getName()));
+    t.setPreserveOrder("true");
+    tng.setXmlSuites(Arrays.asList(s));
+    tng.run();
+  }
+}
diff --git a/src/test/java/test/preserveorder/PrgTest.java b/src/test/java/test/preserveorder/PrgTest.java
new file mode 100755
index 0000000..62738a4
--- /dev/null
+++ b/src/test/java/test/preserveorder/PrgTest.java
@@ -0,0 +1,16 @@
+package test.preserveorder;
+
+import org.testng.annotations.Test;
+
+@Test(singleThreaded=true)
+public class PrgTest {
+	  @Test
+	  public void prg1() {
+	  }
+
+	  @Test
+	  public void prg2() {
+	  }
+
+
+}
diff --git a/src/test/java/test/preserveorder/SibTest.java b/src/test/java/test/preserveorder/SibTest.java
new file mode 100755
index 0000000..9dbee5b
--- /dev/null
+++ b/src/test/java/test/preserveorder/SibTest.java
@@ -0,0 +1,16 @@
+package test.preserveorder;
+
+import org.testng.annotations.Test;
+
+@Test(singleThreaded = true)
+public class SibTest {
+  @Test
+  public void sib1() {
+  }
+
+  @Test(dependsOnMethods = "sib1")
+  public void sib2() {
+    // System.out.println("sib2");
+  }
+
+}
diff --git a/src/test/java/test/preserveorder/TestClass.java b/src/test/java/test/preserveorder/TestClass.java
new file mode 100644
index 0000000..362829a
--- /dev/null
+++ b/src/test/java/test/preserveorder/TestClass.java
@@ -0,0 +1,33 @@
+/*

+ * @(#) TestClass.java

+ * Created: Aug 10, 2011

+ * By: Wolfgang & Monika Baltes

+ * Copyright 2011 Wolfgang Baltes

+ * WOLFGANG & MONIKA BALTES PROPRIETARY/CONFIDENTIAL.

+ * Use is subject to license terms.

+ */

+package test.preserveorder;

+

+import org.testng.Assert;

+import org.testng.annotations.Test;

+

+

+/**

+ * @author Wolfgang Baltes

+ *

+ */

+public class TestClass {

+

+    private final int val;

+

+    public TestClass(final int val) {

+        this.val = val;

+    }

+

+    @Test

+    public void

+    checkVal() {

+        Assert.assertTrue(this.val != 0);

+    }

+

+}

diff --git a/src/test/java/test/preserveorder/TestClassFactory.java b/src/test/java/test/preserveorder/TestClassFactory.java
new file mode 100644
index 0000000..f7e30ea
--- /dev/null
+++ b/src/test/java/test/preserveorder/TestClassFactory.java
@@ -0,0 +1,15 @@
+package test.preserveorder;
+
+import org.testng.annotations.Factory;
+
+public class TestClassFactory {
+
+    @Factory
+    public Object[] f() {
+        final Object[] res = new Object[4];
+        for (int i = 1; i < 5; i++) {
+            res[i-1] = new TestClass(i);
+        }
+        return res;
+    }
+}
diff --git a/src/test/java/test/priority/BaseSample.java b/src/test/java/test/priority/BaseSample.java
new file mode 100644
index 0000000..8c372e5
--- /dev/null
+++ b/src/test/java/test/priority/BaseSample.java
@@ -0,0 +1,50 @@
+package test.priority;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+
+import java.util.List;
+
+
+public class BaseSample {
+  public static List<String> m_methods = Lists.newArrayList();
+
+  protected void add(String m) {
+    String s = m;
+//    System.out.println("BaseSample recording " + this + " " + s);
+    synchronized(m_methods) {
+      m_methods.add(s);
+    }
+  }
+
+  @BeforeClass
+  public void bc() {
+    m_methods = Lists.newArrayList();
+  }
+
+  @Test
+  public void f1() { add("f1"); }
+
+  @Test
+  public void f2() { add("f2"); }
+
+  @Test
+  public void f3() { add("f3"); }
+
+  @Test
+  public void f4() { add("f4"); }
+
+  @Test
+  public void f5() { add("f5"); }
+
+  @Test
+  public void f6() { add("f6"); }
+
+  @Test
+  public void f7() { add("f7"); }
+
+  @Test
+  public void f8() { add("f8"); }
+}
diff --git a/src/test/java/test/priority/Priority2SampleTest.java b/src/test/java/test/priority/Priority2SampleTest.java
new file mode 100644
index 0000000..96cd34b
--- /dev/null
+++ b/src/test/java/test/priority/Priority2SampleTest.java
@@ -0,0 +1,17 @@
+package test.priority;
+
+import org.testng.annotations.Test;
+
+public class Priority2SampleTest {
+  @Test(priority = 1)
+  public void cOne() {
+  }
+
+  @Test(priority = 2)
+  public void bTwo() {
+  }
+
+  @Test(priority = 3)
+  public void aThree() {
+  }
+}
diff --git a/src/test/java/test/priority/PriorityTest.java b/src/test/java/test/priority/PriorityTest.java
new file mode 100644
index 0000000..bce8088
--- /dev/null
+++ b/src/test/java/test/priority/PriorityTest.java
@@ -0,0 +1,39 @@
+package test.priority;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+
+import test.SimpleBaseTest;
+
+public class PriorityTest extends SimpleBaseTest {
+
+  private void runTest(Class<?> cls, String first, String second, boolean parallel) {
+    TestNG tng = create(cls);
+    if (parallel) tng.setParallel(XmlSuite.ParallelMode.METHODS);
+    tng.run();
+    Assert.assertEquals(BaseSample.m_methods.get(0), first);
+    Assert.assertEquals(BaseSample.m_methods.get(1), second);
+  }
+
+  @Test(enabled = false, description = "Make sure priorities work in parallel mode")
+  public void priorityInParallel1() {
+    runTest(WithPrioritySampleTest.class, "first", "second", true /* parallel */);
+  }
+
+  @Test(enabled = false, description = "Make sure priorities work in parallel mode")
+  public void priorityInParallel2() {
+    runTest(WithPrioritySample2Test.class, "second", "first", true /* parallel */);
+  }
+
+  @Test(description = "Make sure priorities work in sequential mode")
+  public void priorityInSequential1() {
+    runTest(WithPrioritySampleTest.class, "first", "second", false /* sequential */);
+  }
+
+  @Test(description = "Make sure priorities work in sequential mode")
+  public void priorityInSequential2() {
+    runTest(WithPrioritySample2Test.class, "second", "first", false /* sequential */);
+  }
+}
diff --git a/src/test/java/test/priority/WithPrioritySample2Test.java b/src/test/java/test/priority/WithPrioritySample2Test.java
new file mode 100644
index 0000000..1ed29e0
--- /dev/null
+++ b/src/test/java/test/priority/WithPrioritySample2Test.java
@@ -0,0 +1,17 @@
+package test.priority;
+
+import org.testng.annotations.Test;
+
+
+public class WithPrioritySample2Test extends BaseSample {
+  @Test(priority = -2)
+  public void first() {
+    add("first");
+  }
+
+  @Test(priority = -3)
+  public void second() {
+    add("second");
+  }
+
+}
diff --git a/src/test/java/test/priority/WithPrioritySampleTest.java b/src/test/java/test/priority/WithPrioritySampleTest.java
new file mode 100644
index 0000000..8955ca6
--- /dev/null
+++ b/src/test/java/test/priority/WithPrioritySampleTest.java
@@ -0,0 +1,17 @@
+package test.priority;
+
+import org.testng.annotations.Test;
+
+
+public class WithPrioritySampleTest extends BaseSample {
+  @Test(priority = -2)
+  public void first() {
+    add("first");
+  }
+
+  @Test(priority = -1)
+  public void second() {
+    add("second");
+  }
+
+}
diff --git a/src/test/java/test/priority/WithoutPrioritySampleTest.java b/src/test/java/test/priority/WithoutPrioritySampleTest.java
new file mode 100644
index 0000000..c6dc5bc
--- /dev/null
+++ b/src/test/java/test/priority/WithoutPrioritySampleTest.java
@@ -0,0 +1,16 @@
+package test.priority;
+
+import org.testng.annotations.Test;
+
+public class WithoutPrioritySampleTest extends BaseSample {
+
+  @Test
+  public void first() {
+    add("first");
+  }
+
+  @Test
+  public void second() {
+    add("second");
+  }
+}
diff --git a/src/test/java/test/privatemethod/PrivateMethodTest.java b/src/test/java/test/privatemethod/PrivateMethodTest.java
new file mode 100644
index 0000000..985e56d
--- /dev/null
+++ b/src/test/java/test/privatemethod/PrivateMethodTest.java
@@ -0,0 +1,24 @@
+package test.privatemethod;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+
+public class PrivateMethodTest {
+  public PrivateMethodTest(String name, int value) {
+  }
+
+  private int privateMethod() {
+    return 1;
+  }
+
+  public static class PrivateMethodInnerTest {
+    @Test
+    public void testPrivateMethod() {
+      PrivateMethodTest pmt = new PrivateMethodTest("aname", 1);
+      int returnValue = pmt.privateMethod();
+
+      Assert.assertEquals(returnValue, 1);
+    }
+  }
+}
diff --git a/src/test/java/test/regression/BeforeTestFailingTest.java b/src/test/java/test/regression/BeforeTestFailingTest.java
new file mode 100644
index 0000000..eeab4d1
--- /dev/null
+++ b/src/test/java/test/regression/BeforeTestFailingTest.java
@@ -0,0 +1,22 @@
+package test.regression;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+public class BeforeTestFailingTest extends SimpleBaseTest {
+
+  @Test
+  public void beforeTestFailingShouldCauseSkips() {
+    TestNG tng = create(MyTestngTest2.class);
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+    Assert.assertEquals(tla.getSkippedTests().size(), 1);
+    Assert.assertEquals(tla.getPassedTests().size(), 0);
+  }
+}
diff --git a/src/test/java/test/regression/MyTestngTest.java b/src/test/java/test/regression/MyTestngTest.java
new file mode 100644
index 0000000..4ba40a8
--- /dev/null
+++ b/src/test/java/test/regression/MyTestngTest.java
@@ -0,0 +1,17 @@
+package test.regression;
+
+import org.testng.ITestContext;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.BeforeTest;
+
+public class MyTestngTest {
+
+  @BeforeSuite()
+  public void beforeSuite(ITestContext tc) throws Exception {
+  }
+
+  @BeforeTest()
+  public void beforeTest(ITestContext tc) throws Exception {
+      throw new RuntimeException("barfing now");
+  }
+}
diff --git a/src/test/java/test/regression/MyTestngTest2.java b/src/test/java/test/regression/MyTestngTest2.java
new file mode 100644
index 0000000..19c1951
--- /dev/null
+++ b/src/test/java/test/regression/MyTestngTest2.java
@@ -0,0 +1,21 @@
+package test.regression;
+
+import org.testng.ITestContext;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class MyTestngTest2 extends MyTestngTest {
+
+  @BeforeClass()
+  public void beforeClass(ITestContext tc) throws Exception {
+  }
+
+  @BeforeMethod()
+  public void beforeMethod(ITestContext tc) throws Exception {
+      //throw new Exception("fail me");
+  }
+  @Test()
+  public void test(ITestContext tc) {
+  }
+}
diff --git a/src/test/java/test/regression/groupsordering/A.java b/src/test/java/test/regression/groupsordering/A.java
new file mode 100644
index 0000000..d5fbf25
--- /dev/null
+++ b/src/test/java/test/regression/groupsordering/A.java
@@ -0,0 +1,12 @@
+package test.regression.groupsordering;

+

+

+import org.testng.annotations.Test;

+

+public class A extends Base {

+

+  @Test(groups= "a")

+  public void testA() throws Exception {

+    s_childAWasRun= true;

+  }

+}

diff --git a/src/test/java/test/regression/groupsordering/B.java b/src/test/java/test/regression/groupsordering/B.java
new file mode 100644
index 0000000..9ecb928
--- /dev/null
+++ b/src/test/java/test/regression/groupsordering/B.java
@@ -0,0 +1,12 @@
+package test.regression.groupsordering;

+

+

+import org.testng.annotations.Test;

+

+public class B extends Base {

+

+  @Test(groups= "a")

+  public void testB() throws Exception {

+    Base.s_childBWasRun= true;

+  }

+}

diff --git a/src/test/java/test/regression/groupsordering/Base.java b/src/test/java/test/regression/groupsordering/Base.java
new file mode 100644
index 0000000..7344f06
--- /dev/null
+++ b/src/test/java/test/regression/groupsordering/Base.java
@@ -0,0 +1,27 @@
+package test.regression.groupsordering;

+

+

+import org.testng.Assert;

+import org.testng.annotations.AfterGroups;

+import org.testng.annotations.BeforeGroups;

+

+public abstract class Base {

+  protected static boolean s_childAWasRun;

+  protected static boolean s_childBWasRun;

+

+  @BeforeGroups(value= "a", groups= "a")

+  public void setUp() throws Exception {

+//    System.out.println("class is " + getClass().getName() + " Before group  ");

+    Assert.assertFalse(s_childAWasRun || s_childBWasRun, "Static field was not reset: @AfterGroup method not invoked");

+  }

+

+  @AfterGroups(value= "a", groups= "a")

+  public void tearDown() {

+//    System.out.println("class is " + getClass().getName() + " After group  ");

+    Assert.assertTrue(s_childAWasRun, "Child A was not run");

+    Assert.assertTrue(s_childBWasRun, "Child B was not run");

+    s_childAWasRun = false;

+    s_childBWasRun = false;

+  }

+

+}

diff --git a/src/test/java/test/regression/groupsordering/testng.xml b/src/test/java/test/regression/groupsordering/testng.xml
new file mode 100644
index 0000000..1c84d6b
--- /dev/null
+++ b/src/test/java/test/regression/groupsordering/testng.xml
@@ -0,0 +1,21 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

+<suite name="Groups ordering regression" verbose="2">
+  <test name="Class Run">

+		<classes>

+			<class name="test.regression.groupsordering.A" />

+			<class name="test.regression.groupsordering.B" />

+		</classes>

+  </test>

+	

+	<test name="Groups Run">

+		<groups>

+			<run>

+				<include name="a" />

+			</run>

+		</groups>

+		<classes>

+			<class name="test.regression.groupsordering.A" />

+			<class name="test.regression.groupsordering.B" />

+		</classes>

+	</test>

+</suite>
\ No newline at end of file
diff --git a/src/test/java/test/remote/RemoteSampleTest.java b/src/test/java/test/remote/RemoteSampleTest.java
new file mode 100644
index 0000000..e21f594
--- /dev/null
+++ b/src/test/java/test/remote/RemoteSampleTest.java
@@ -0,0 +1,18 @@
+package test.remote;
+
+import org.testng.annotations.Test;
+
+/**
+ * Used by RemoteTest to test RemoteTestNG.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ *
+ */
+public class RemoteSampleTest {
+
+  @Test
+  public void f1() {}
+
+  @Test
+  public void f2() {}
+}
diff --git a/src/test/java/test/remote/RemoteTest.java b/src/test/java/test/remote/RemoteTest.java
new file mode 100644
index 0000000..7e1554b
--- /dev/null
+++ b/src/test/java/test/remote/RemoteTest.java
@@ -0,0 +1,89 @@
+package test.remote;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+import org.testng.remote.RemoteTestNG;
+import org.testng.remote.strprotocol.IMessage;
+import org.testng.remote.strprotocol.IMessageSender;
+import org.testng.remote.strprotocol.MessageHub;
+import org.testng.remote.strprotocol.SerializedMessageSender;
+import org.testng.remote.strprotocol.StringMessageSender;
+
+import test.SimpleBaseTest;
+
+import java.net.SocketTimeoutException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A simple client that launches RemoteTestNG and then talks to it via the
+ * two supported protocols, String and Serialized.
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public class RemoteTest extends SimpleBaseTest {
+  // Note: don't use the ports used by the plug-in or the RemoteTestNG processes
+  // launched in this test will interfere with the plug-in.
+  private static final int PORT1 = 1243;
+  private static final int PORT2 = 1242;
+  private static final List<String> EXPECTED_MESSAGES = new ArrayList<String>() {{
+    add("GenericMessage"); // method and test counts
+    add("SuiteMessage");  // suite started
+    add("TestMessage");  // test started
+    add("TestResultMessage"); // status: started
+    add("TestResultMessage"); // status: success
+    add("TestResultMessage"); // status: started
+    add("TestResultMessage"); // status: success
+    add("TestMessage"); // test finished
+    add("SuiteMessage"); // suite finished
+  }};
+
+  @Test
+  public void testSerialized() {
+    runTest("-serport", PORT1, new SerializedMessageSender("localhost", PORT1));
+  }
+
+  @Test
+  public void testString() {
+    runTest("-port", PORT2, new StringMessageSender("localhost", PORT2));
+  }
+
+  private void launchRemoteTestNG(final String portArg, final int portValue) {
+    new Thread(new Runnable() {
+      @Override
+      public void run() {
+        RemoteTestNG.main(new String[] {
+            portArg, Integer.toString(portValue), "-dontexit",
+            getPathToResource("testng-remote.xml")
+          });
+        }
+      }).start();
+  }
+
+  private void runTest(String arg, int portValue, IMessageSender sms) {
+    p("Launching RemoteTestNG on port " + portValue);
+    launchRemoteTestNG(arg, portValue);
+    MessageHub mh = new MessageHub(sms);
+    List<String> received = Lists.newArrayList();
+    try {
+      mh.initReceiver();
+      IMessage message = mh.receiveMessage();
+      while (message != null) {
+        received.add(message.getClass().getSimpleName());
+        message = mh.receiveMessage();
+      }
+
+      Assert.assertEquals(received, EXPECTED_MESSAGES);
+    }
+    catch(SocketTimeoutException ex) {
+      Assert.fail("Time out");
+    }
+  }
+
+  private static void p(String s) {
+    if (false) {
+      System.out.println("[RemoteTest] " + s);
+    }
+  }
+}
diff --git a/src/test/java/test/reports/A.java b/src/test/java/test/reports/A.java
new file mode 100644
index 0000000..e63bc8b
--- /dev/null
+++ b/src/test/java/test/reports/A.java
@@ -0,0 +1,10 @@
+package test.reports;
+
+import org.testng.annotations.Test;
+
+@Test(suiteName = "SuiteA-JDK5", testName = "TestA-JDK5")
+public class A {
+
+  public void f() {}
+
+}
diff --git a/src/test/java/test/reports/B.java b/src/test/java/test/reports/B.java
new file mode 100644
index 0000000..2cd74ca
--- /dev/null
+++ b/src/test/java/test/reports/B.java
@@ -0,0 +1,10 @@
+package test.reports;
+
+import org.testng.annotations.Test;
+
+@Test(suiteName = "SuiteB-JDK5")
+public class B {
+
+  public void f() {}
+
+}
diff --git a/src/test/java/test/reports/EmailableReportDriver.java b/src/test/java/test/reports/EmailableReportDriver.java
new file mode 100644
index 0000000..cab38f9
--- /dev/null
+++ b/src/test/java/test/reports/EmailableReportDriver.java
@@ -0,0 +1,38 @@
+package test.reports;
+
+import org.testng.Assert;
+import org.testng.Reporter;
+import org.testng.annotations.Test;
+
+/**
+ * Generates multiple permutations of TestNG output to see how things look in EmailableReporter.
+ *
+ * @author Paul Mendelson
+ * @since 5.3
+ * @version $Revision$
+ */
+@Test
+public class EmailableReportDriver {
+
+  public void doFailureSansLog() {
+    Assert.fail("show failure in report");
+  }
+  public void doFailureNested() {
+    Assert.fail("show failure in report",new Exception("Real cuase"));
+  }
+  public void doFailureWithLog() {
+    Reporter.log("Preparing to fail");
+    Assert.fail("show failure in report");
+  }
+  @Test(expectedExceptions={NumberFormatException.class})
+  public void doExpectedExceptionSansLog() {
+    Reporter.log("step 1");
+    Reporter.log("step 2");
+    Integer.parseInt("BAD TEXT");
+  }
+  @Test(expectedExceptions={NumberFormatException.class})
+  public void doExpectedExceptionWithLog() {
+    Integer.parseInt("BAD TEXT");
+  }
+
+}
diff --git a/src/test/java/test/reports/FailedReporterTest.java b/src/test/java/test/reports/FailedReporterTest.java
new file mode 100644
index 0000000..e8c9bd8
--- /dev/null
+++ b/src/test/java/test/reports/FailedReporterTest.java
@@ -0,0 +1,61 @@
+package test.reports;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+import org.testng.reporters.FailedReporter;
+import org.testng.xml.Parser;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+import org.xml.sax.SAXException;
+
+import test.SimpleBaseTest;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+
+public class FailedReporterTest extends SimpleBaseTest {
+
+  private static final String XML = "<suite name=\"Suite\">\n"
+      + "<parameter name=\"n\" value=\"42\"/>\n"
+      + "<test name=\"Test\">\n"
+      + "<classes>\n"
+      + "<parameter name=\"o\" value=\"43\"/>\n"
+      + "<class name=\"test.reports.FailedSampleTest\">\n"
+      + "<parameter name=\"p\" value=\"44\"/>\n"
+      + "</class>"
+      + "</classes>"
+      + "</test></suite>";
+
+  @Test
+  public void failedFile() throws ParserConfigurationException, SAXException, IOException {
+    TestNG tng = new TestNG();
+    tng.setVerbose(0);
+    Collection<XmlSuite> suites = 
+        new Parser(new ByteArrayInputStream(XML.getBytes())).parse();
+    tng.setXmlSuites(Lists.newArrayList(suites));
+    TestListenerAdapter tla = new TestListenerAdapter();
+    File f = new File("/tmp");
+    tng.setOutputDirectory(f.getAbsolutePath());
+    tng.addListener(tla);
+    tng.run();
+
+    Collection<XmlSuite> failedSuites =
+        new Parser(new File(f, FailedReporter.TESTNG_FAILED_XML).getAbsolutePath()).parse();
+    XmlSuite failedSuite = failedSuites.iterator().next();
+    Assert.assertEquals("42", failedSuite.getParameter("n"));
+
+    XmlTest test = failedSuite.getTests().get(0);
+    Assert.assertEquals("43", test.getParameter("o"));
+
+    XmlClass c = test.getClasses().get(0);
+    Assert.assertEquals("44", c.getAllParameters().get("p"));
+  }
+}
diff --git a/src/test/java/test/reports/FailedSampleTest.java b/src/test/java/test/reports/FailedSampleTest.java
new file mode 100644
index 0000000..c999a08
--- /dev/null
+++ b/src/test/java/test/reports/FailedSampleTest.java
@@ -0,0 +1,11 @@
+package test.reports;
+
+import org.testng.annotations.Test;
+
+public class FailedSampleTest {
+
+  @Test
+  public void failed() {
+    throw new RuntimeException("Failing intentionally");
+  }
+}
diff --git a/src/test/java/test/reports/ReportTest.java b/src/test/java/test/reports/ReportTest.java
new file mode 100644
index 0000000..778dd2e
--- /dev/null
+++ b/src/test/java/test/reports/ReportTest.java
@@ -0,0 +1,142 @@
+package test.reports;
+
+import org.testng.Assert;
+import org.testng.ITestListener;
+import org.testng.ITestResult;
+import org.testng.Reporter;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.TestHelper;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+public class ReportTest {
+
+  @Test
+  public void verifyIndex() {
+    File outputDir = TestHelper.createRandomDirectory();
+
+    String suiteName = "VerifyIndexSuite";
+    String testName = "TmpTest";
+    XmlSuite suite = TestHelper.createSuite(test.simple.SimpleTest.class, suiteName, testName);
+
+    TestNG tng = TestHelper.createTestNG(suite, outputDir.getAbsolutePath());
+
+    File f = getHtmlReportFile(outputDir, suiteName, testName);
+
+    tng.run();
+
+    Assert.assertTrue(f.exists());
+  }
+
+  @Test
+  public void directoryShouldBeSuiteName() {
+    File outputDirectory = TestHelper.createRandomDirectory();
+
+    XmlSuite xmlSuite = new XmlSuite();
+    String suiteName = "ReportTestSuite1";
+    xmlSuite.setName(suiteName);
+
+    XmlTest xmlTest = new XmlTest(xmlSuite);
+    String testName = "Test1";
+    xmlTest.setName(testName);
+
+    XmlTest xmlTest2 = new XmlTest(xmlSuite);
+    String testName2 = "Test2";
+    xmlTest2.setName(testName2);
+
+    TestNG testng = new TestNG();
+    testng.setVerbose(0);
+    testng.setOutputDirectory(outputDirectory.getAbsolutePath());
+    testng.setXmlSuites(Arrays.asList(xmlSuite));
+
+    File f = getHtmlReportFile(outputDirectory, suiteName, testName);
+    File f2 = getHtmlReportFile(outputDirectory, suiteName, testName2);
+
+    testng.run();
+
+    Assert.assertTrue(f.exists(), "Expected to find file:" + f);
+    Assert.assertTrue(f2.exists(), "Expected to find file:" + f2);
+  }
+
+  @Test
+  public void oneDirectoryPerSuite() {
+    File outputDirectory = TestHelper.createRandomDirectory();
+
+    String testName = "TmpTest";
+    String suiteNameA = "ReportSuiteA";
+    XmlSuite xmlSuiteA = TestHelper.createSuite(test.reports.A.class, suiteNameA, testName);
+
+    String suiteNameB = "ReportSuiteB";
+    XmlSuite xmlSuiteB = TestHelper.createSuite(test.reports.B.class, suiteNameB, testName);
+
+    TestNG testng = TestHelper.createTestNG();
+    testng.setOutputDirectory(outputDirectory.getAbsolutePath());
+    testng.setXmlSuites(Arrays.asList(xmlSuiteA, xmlSuiteB));
+
+    File f1 = getHtmlReportFile(outputDirectory, suiteNameA, testName);
+    File f2 = getHtmlReportFile(outputDirectory, suiteNameB, testName);
+
+    testng.run();
+
+    Assert.assertTrue(f1.exists());
+    Assert.assertTrue(f2.exists());
+  }
+
+  private static File getHtmlReportFile(File outputDir, String suiteName, String testName) {
+    File f = new File(outputDir.getAbsolutePath() + File.separatorChar + suiteName
+                    + File.separatorChar + testName + ".html");
+    if (f.exists()) {
+      f.delete();
+    }
+    return f;
+  }
+
+  @Test
+  public void shouldHonorSuiteName() {
+    TestNG testng = TestHelper.createTestNG();
+    testng.setTestClasses(new Class[] { A.class, B.class });
+    String outputDir = testng.getOutputDirectory();
+
+    String dirA = outputDir + File.separatorChar + "SuiteA-JDK5";
+    File fileA = new File(dirA);
+    String dirB = outputDir + File.separatorChar + "SuiteB-JDK5";
+    File fileB = new File(dirB);
+    Assert.assertFalse(fileA.exists());
+    Assert.assertFalse(fileB.exists());
+
+    testng.run();
+
+    Assert.assertTrue(fileA.exists(), fileA + " wasn't created");
+    Assert.assertTrue(fileB.exists(), fileB + " wasn't created");
+  }
+
+  static boolean m_success = false;
+
+  @Test
+  public void reportLogShouldBeAvailableEvenWithTimeOut() {
+    m_success = false;
+    TestNG tng = new TestNG();
+    tng.setVerbose(0);
+    tng.setTestClasses(new Class[] { ReporterSampleTest.class });
+
+    ITestListener listener = new TestListenerAdapter() {
+      @Override
+      public void onTestSuccess(ITestResult tr) {
+        super.onTestSuccess(tr);
+        List<String> output = Reporter.getOutput(tr);
+        m_success = output != null && output.size() > 0;
+      }
+    };
+    tng.addListener(listener);
+    tng.run();
+
+    Assert.assertTrue(m_success);
+  }
+}
diff --git a/src/test/java/test/reports/ReporterLogSampleTest.java b/src/test/java/test/reports/ReporterLogSampleTest.java
new file mode 100644
index 0000000..04d6a7d
--- /dev/null
+++ b/src/test/java/test/reports/ReporterLogSampleTest.java
@@ -0,0 +1,25 @@
+package test.reports;
+
+import org.testng.ITestResult;
+import org.testng.Reporter;
+import org.testng.TestListenerAdapter;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+import test.reports.ReporterLogSampleTest.MyTestListener;
+
+@Listeners(MyTestListener.class)
+public class ReporterLogSampleTest {
+  public static class MyTestListener extends TestListenerAdapter {
+    @Override
+    public void onTestSuccess(ITestResult result) {
+      Reporter.log("Log from listener");
+      super.onTestSuccess(result);
+    }
+  }
+
+  @Test
+  public void test1() {
+    Reporter.log("Log from test method");
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/reports/ReporterLogTest.java b/src/test/java/test/reports/ReporterLogTest.java
new file mode 100644
index 0000000..425852c
--- /dev/null
+++ b/src/test/java/test/reports/ReporterLogTest.java
@@ -0,0 +1,32 @@
+package test.reports;
+
+import org.testng.Assert;
+import org.testng.Reporter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+import java.util.List;
+
+/**
+ * Make sure that Reporter.log() in listeners don't get discarded.
+ */
+public class ReporterLogTest extends SimpleBaseTest {
+
+  @Test
+  public void shouldLogFromListener() {
+    TestNG tng = create(ReporterLogSampleTest.class);
+    tng.run();
+    List<String> output = Reporter.getOutput();
+    boolean success = false;
+    for(String s : output) {
+      if (s.contains("Log from listener")) {
+        success = true;
+        break;
+      }
+    }
+    Assert.assertTrue(success);
+//    System.out.println(output);
+  }
+}
diff --git a/src/test/java/test/reports/ReporterSampleTest.java b/src/test/java/test/reports/ReporterSampleTest.java
new file mode 100644
index 0000000..4f2fafa
--- /dev/null
+++ b/src/test/java/test/reports/ReporterSampleTest.java
@@ -0,0 +1,29 @@
+package test.reports;

+

+import org.testng.Reporter;

+import org.testng.annotations.DataProvider;

+import org.testng.annotations.Test;

+

+/**

+ * Regression test:  if a timeOut is provided, getReporter(testResult) returns

+ * null.

+ *

+ * Created on Sep 21, 2006

+ * @author <a href="mailto:cedric@beust.com">Cedric Beust</a>

+ */

+public class ReporterSampleTest {

+

+  @DataProvider(name = "dp")

+  public Object[][] createParameters() {

+    return new Object[][] {

+        new Object[] { "param1"},

+        new Object[] {"param2"}

+    };

+  }

+

+  @Test(dataProvider = "dp", timeOut = 10000)

+  public void report(String p) {

+    Reporter.log("IN THE REPORTER: " + p);

+  }

+

+}

diff --git a/src/test/java/test/reports/ReporterTest.java b/src/test/java/test/reports/ReporterTest.java
new file mode 100644
index 0000000..4a58143
--- /dev/null
+++ b/src/test/java/test/reports/ReporterTest.java
@@ -0,0 +1,20 @@
+package test.reports;
+
+import org.testng.*;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+import java.util.Set;
+
+@Listeners(ReporterTest.class)
+public class ReporterTest extends TestListenerAdapter {
+    @Override public void onStart (ITestContext testContext) {
+        Reporter.log ("foo");
+    }
+    @Test
+    public void testMethod() {
+        Reporter.log ("bar"); // This line is required. Else the log that was triggered from onStart() would never be
+        // persisted at all.
+        Assert.assertTrue (Reporter.getOutput ().size () == 2);
+    }
+}
diff --git a/src/test/java/test/retryAnalyzer/EventualSuccess.java b/src/test/java/test/retryAnalyzer/EventualSuccess.java
new file mode 100644
index 0000000..4652c60
--- /dev/null
+++ b/src/test/java/test/retryAnalyzer/EventualSuccess.java
@@ -0,0 +1,17 @@
+package test.retryAnalyzer;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class EventualSuccess {
+  private static final AtomicBoolean ranYet = new AtomicBoolean(false);
+
+  @Test(retryAnalyzer = MyRetry.class)
+  public void test() {
+    if (!ranYet.getAndSet(true)) {
+      Assert.fail();
+    }
+  }
+}
diff --git a/src/test/java/test/retryAnalyzer/ExitCodeTest.java b/src/test/java/test/retryAnalyzer/ExitCodeTest.java
new file mode 100644
index 0000000..4e67fde
--- /dev/null
+++ b/src/test/java/test/retryAnalyzer/ExitCodeTest.java
@@ -0,0 +1,31 @@
+package test.retryAnalyzer;
+
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import test.SimpleBaseTest;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class ExitCodeTest extends SimpleBaseTest {
+  @Test
+  public void exitsWithZeroOnSuccess() {
+    TestNG tng = create(ImmediateSuccess.class);
+    tng.run();
+    assertEquals(tng.getStatus(), 0);
+  }
+
+  @Test
+  public void exitsWithNonzeroOnFailure() {
+    TestNG tng = create(PersistentFailure.class);
+    tng.run();
+    assertTrue(tng.getStatus() != 0);
+  }
+
+  @Test
+  public void exitsWithZeroAfterSuccessfulRetry() {
+    TestNG tng = create(EventualSuccess.class);
+    tng.run();
+    assertEquals(tng.getStatus(), 0);
+  }
+}
diff --git a/src/test/java/test/retryAnalyzer/FactoryTest.java b/src/test/java/test/retryAnalyzer/FactoryTest.java
new file mode 100755
index 0000000..98dc3ce
--- /dev/null
+++ b/src/test/java/test/retryAnalyzer/FactoryTest.java
@@ -0,0 +1,30 @@
+package test.retryAnalyzer;
+
+import static org.testng.Assert.fail;
+
+import org.testng.ITest;
+import org.testng.annotations.Test;
+
+public class FactoryTest implements ITest {
+    public static int m_count = 0;
+
+    private String name;
+
+    public FactoryTest(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public String getTestName() {
+        return name;
+    }
+
+    @Test(retryAnalyzer = MyRetry.class)
+    public void someTest1() {
+        System.out.println("Test Called : " + this.name);
+        if (name.contains("5")) {
+            m_count++;
+            fail();
+        }
+    }
+}
diff --git a/src/test/java/test/retryAnalyzer/ImmediateSuccess.java b/src/test/java/test/retryAnalyzer/ImmediateSuccess.java
new file mode 100644
index 0000000..dea4ed3
--- /dev/null
+++ b/src/test/java/test/retryAnalyzer/ImmediateSuccess.java
@@ -0,0 +1,9 @@
+package test.retryAnalyzer;
+
+import org.testng.annotations.Test;
+
+public class ImmediateSuccess {
+  @Test(retryAnalyzer = MyRetry.class)
+  public void test() {
+  }
+}
diff --git a/src/test/java/test/retryAnalyzer/InvocationCountTest.java b/src/test/java/test/retryAnalyzer/InvocationCountTest.java
new file mode 100644
index 0000000..0ef5baf
--- /dev/null
+++ b/src/test/java/test/retryAnalyzer/InvocationCountTest.java
@@ -0,0 +1,129 @@
+package test.retryAnalyzer;

+

+import static org.testng.Assert.assertEquals;

+import static org.testng.Assert.fail;

+

+import java.util.concurrent.atomic.AtomicInteger;

+

+import org.testng.IRetryAnalyzer;

+import org.testng.ITestResult;

+import org.testng.TestNG;

+import org.testng.annotations.DataProvider;

+import org.testng.annotations.Test;

+

+import com.google.common.collect.ConcurrentHashMultiset;

+import com.google.common.collect.Multiset;

+

+/**

+ * retryAnalyzer parameter unit tests.

+ * @author tocman@gmail.com (Jeremie Lenfant-Engelmann)

+ *

+ */

+public final class InvocationCountTest implements IRetryAnalyzer {

+  static final Multiset<String> invocations = ConcurrentHashMultiset.create();

+  private static final AtomicInteger retriesRemaining = new AtomicInteger(100);

+

+  private int r1 = 0;

+  private int r2 = 0;

+  private int r3 = 0;

+  private int r7 = 0;

+  private static int value = 42;

+

+  @Test(retryAnalyzer = InvocationCountTest.class)

+  public void testAnnotationWithNoRetries() {

+  }

+

+  @Test(retryAnalyzer = InvocationCountTest.class)

+  public void testAnnotationWithOneRetry() {

+    if (r1++ < 1) {

+      fail();

+    }

+  }

+

+  @Test(retryAnalyzer = InvocationCountTest.class)

+  public void testAnnotationWithSevenRetries() {

+    if (r7++ < 7) {

+      fail();

+    }

+  }

+

+  @Test(retryAnalyzer = ThreeRetries.class, successPercentage = 0)

+  public void failAfterThreeRetries() {

+    fail();

+  }

+

+  @Test(dependsOnMethods = {

+      "testAnnotationWithNoRetries",

+      "testAnnotationWithOneRetry",

+      "testAnnotationWithSevenRetries",

+      "failAfterThreeRetries"

+  }, alwaysRun = true)

+  public void checkInvocationCounts() {

+    assertEquals(invocations.count("testAnnotationWithNoRetries"), 0);

+    assertEquals(invocations.count("testAnnotationWithOneRetry"), 1);

+    assertEquals(invocations.count("testAnnotationWithSevenRetries"), 7);

+    assertEquals(invocations.count("failAfterThreeRetries"), 4);

+  }

+

+  @DataProvider(name="dataProvider")

+  private Object[][] dataProvider() {

+    return new Object[][] { { 1, false }, { 0, true }, { 0, true },

+        { 1, false } };

+  }

+

+  @DataProvider(name="dataProvider2")

+  private Object[][] dataProvider2() {

+    value = 42;

+

+    return new Object[][] { { true }, { true } };

+  }

+

+  @Test(retryAnalyzer = InvocationCountTest.class, dataProvider = "dataProvider")

+  public void testAnnotationWithDataProvider(int paf, boolean test) {

+    if (paf == 1 && test == false) {

+      if (r2 >= 1) {

+        r2--;

+        fail();

+      }

+    }

+  }

+

+  @Test(retryAnalyzer = InvocationCountTest.class, dataProvider = "dataProvider2")

+  public void testAnnotationWithDataProviderAndRecreateParameters(boolean dummy) {

+    if (r3 == 1) {

+      this.value = 0;

+      r3--;

+      fail();

+    } else if (r3 == 0) {

+      assertEquals(this.value, 42);

+    }

+  }

+

+  @Test

+  public void withFactory() {

+    TestNG tng = new TestNG();

+    tng.setVerbose(0);

+    tng.setTestClasses(new Class[] { MyFactory.class});

+    FactoryTest.m_count = 0;

+

+    tng.run();

+

+    assertEquals(FactoryTest.m_count, 4);

+  }

+

+  @Override

+  public boolean retry(ITestResult result) {

+    invocations.add(result.getName());

+    return retriesRemaining.getAndDecrement() >= 0;

+  }

+

+  public static class ThreeRetries implements IRetryAnalyzer {

+    private final AtomicInteger remainingRetries = new AtomicInteger(3);

+

+    @Override

+    public boolean retry(ITestResult result) {

+      invocations.add(result.getName());

+      return remainingRetries.getAndDecrement() > 0;

+    }

+  }

+}

diff --git a/src/test/java/test/retryAnalyzer/MyFactory.java b/src/test/java/test/retryAnalyzer/MyFactory.java
new file mode 100755
index 0000000..1202a54
--- /dev/null
+++ b/src/test/java/test/retryAnalyzer/MyFactory.java
@@ -0,0 +1,16 @@
+package test.retryAnalyzer;
+
+import org.testng.annotations.Factory;
+
+public class MyFactory {
+    @Factory
+    public Object[] createTests() {
+        int num = 10;
+        Object[] result = new Object[num];
+        for (int i = 0; i < num; i++) {
+            FactoryTest obj = new FactoryTest("Test" + i);
+            result[i] = obj;
+        }
+        return result;
+    }
+}
diff --git a/src/test/java/test/retryAnalyzer/MyRetry.java b/src/test/java/test/retryAnalyzer/MyRetry.java
new file mode 100755
index 0000000..edf2f16
--- /dev/null
+++ b/src/test/java/test/retryAnalyzer/MyRetry.java
@@ -0,0 +1,18 @@
+package test.retryAnalyzer;
+
+import org.testng.ITestResult;
+import org.testng.util.RetryAnalyzerCount;
+
+public class MyRetry extends  RetryAnalyzerCount {
+
+	public MyRetry(){
+		setCount(3);
+	}
+
+	@Override
+	public boolean retryMethod(ITestResult arg0) {
+		// TODO Auto-generated method stub
+		return true;
+	}
+
+}
diff --git a/src/test/java/test/retryAnalyzer/PersistentFailure.java b/src/test/java/test/retryAnalyzer/PersistentFailure.java
new file mode 100644
index 0000000..62e4fbb
--- /dev/null
+++ b/src/test/java/test/retryAnalyzer/PersistentFailure.java
@@ -0,0 +1,11 @@
+package test.retryAnalyzer;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class PersistentFailure {
+  @Test(retryAnalyzer = MyRetry.class)
+  public void test() {
+    Assert.fail();
+  }
+}
diff --git a/src/test/java/test/retryAnalyzer/RetryAnalyzerTest.java b/src/test/java/test/retryAnalyzer/RetryAnalyzerTest.java
new file mode 100644
index 0000000..ee3fca6
--- /dev/null
+++ b/src/test/java/test/retryAnalyzer/RetryAnalyzerTest.java
@@ -0,0 +1,37 @@
+package test.retryAnalyzer;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+public class RetryAnalyzerTest extends SimpleBaseTest {
+    @Test
+    public void testInvocationCounts() {
+        TestNG tng = create(InvocationCountTest.class);
+        TestListenerAdapter tla = new TestListenerAdapter();
+        tng.addListener(tla);
+
+        tng.run();
+
+        assertFalse(tng.hasFailure());
+        assertFalse(tng.hasSkip());
+
+        assertTrue(tla.getFailedTests().isEmpty());
+
+        List<ITestResult> fsp = tla.getFailedButWithinSuccessPercentageTests();
+        assertEquals(fsp.size(), 1);
+        assertEquals(fsp.get(0).getName(), "failAfterThreeRetries");
+
+        List<ITestResult> skipped = tla.getSkippedTests();
+        assertEquals(skipped.size(), InvocationCountTest.invocations.size() - fsp.size());
+    }
+}
diff --git a/src/test/java/test/sample/AfterClassCalledAtEnd.java b/src/test/java/test/sample/AfterClassCalledAtEnd.java
new file mode 100644
index 0000000..d46f89c
--- /dev/null
+++ b/src/test/java/test/sample/AfterClassCalledAtEnd.java
@@ -0,0 +1,50 @@
+package test.sample;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ * Check to see that AfterClass is called only at the end and that after methods
+ * are called in reverse order of the before methods.
+ */
+public class AfterClassCalledAtEnd extends BaseAfterClassCalledAtEnd {
+  boolean m_before1Class = false;
+  boolean m_test1 = false;
+  boolean m_test2 = false;
+  boolean m_test3 = false;
+
+  @BeforeClass(groups = { "before1Class" } )
+  public void before1Class() {
+    m_before1Class = true;
+  }
+
+  @AfterClass(groups = { "someGroup" })
+  public void afterClass() {
+    m_afterClass = true;
+    assert m_test1 && m_test2 && m_test3 :
+      "One of the test methods was not invoked: " + m_test1 + " " + m_test2 + " " + m_test3;
+  }
+
+  @Test(description = "Verify that beforeClass and afterClass are called correctly")
+  public void test1() {
+    m_test1 = true;
+    assert m_before1Class : "beforeClass configuration must be called before method";
+    assert ! m_afterClass : "afterClass configuration must not be called before test method";
+  }
+
+  @Test
+  public void test2() {
+    m_test2 = true;
+    assert m_before1Class : "beforeClass configuration must be called before method";
+    assert ! m_afterClass : "afterClass configuration must not be called before test method";
+  }
+
+  @Test
+  public void test3() {
+    m_test3 = true;
+    assert m_before1Class : "beforeClass configuration must be called before method";
+    assert ! m_afterClass : "afterClass configuration must not be called before test method";
+  }
+
+}
\ No newline at end of file
diff --git a/src/test/java/test/sample/AllJUnitTests.java b/src/test/java/test/sample/AllJUnitTests.java
new file mode 100644
index 0000000..240655f
--- /dev/null
+++ b/src/test/java/test/sample/AllJUnitTests.java
@@ -0,0 +1,19 @@
+package test.sample;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests the suite() functionality of TestNG
+ *
+ * @author Cedric Beust, May 5, 2004
+ *
+ */
+public class AllJUnitTests {
+  public static Test suite() {
+    TestSuite suite= new TestSuite();
+    suite.addTest(new JUnitSample1(JUnitSample1.EXPECTED1));
+    suite.addTest(new JUnitSample2(JUnitSample2.EXPECTED));
+    return suite;
+  }
+}
diff --git a/src/test/java/test/sample/BaseAfterClassCalledAtEnd.java b/src/test/java/test/sample/BaseAfterClassCalledAtEnd.java
new file mode 100644
index 0000000..20af675
--- /dev/null
+++ b/src/test/java/test/sample/BaseAfterClassCalledAtEnd.java
@@ -0,0 +1,12 @@
+package test.sample;
+
+import org.testng.annotations.AfterClass;
+
+public class BaseAfterClassCalledAtEnd {
+  protected boolean m_afterClass = false;
+
+  @AfterClass(dependsOnGroups = { ".*" })
+  public void baseAfterClass() {
+    assert m_afterClass : "This afterClass method should have been called last";
+  }
+}
diff --git a/src/test/java/test/sample/BaseSample1.java b/src/test/java/test/sample/BaseSample1.java
new file mode 100644
index 0000000..a1e69a4
--- /dev/null
+++ b/src/test/java/test/sample/BaseSample1.java
@@ -0,0 +1,16 @@
+package test.sample;
+
+import org.testng.annotations.Test;
+
+
+/**
+ * @author Cedric Beust, Apr 30, 2004
+ *
+ */
+public class BaseSample1 {
+
+  @Test(groups = { "odd" })
+  public void method1() {
+  }
+
+}
diff --git a/src/test/java/test/sample/BaseSampleInheritance.java b/src/test/java/test/sample/BaseSampleInheritance.java
new file mode 100644
index 0000000..a97ece9
--- /dev/null
+++ b/src/test/java/test/sample/BaseSampleInheritance.java
@@ -0,0 +1,50 @@
+package test.sample;
+
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * @author Cedric Beust, Apr 30, 2004
+ *
+ */
+public class BaseSampleInheritance {
+
+  protected List<String> m_configurations = new ArrayList<>();
+
+  protected void addConfiguration(String c) {
+    m_configurations.add(c);
+    }
+
+  protected boolean m_invokedBaseMethod = false;
+
+  @Test(groups = { "inheritedTestMethod" })
+  public void baseMethod() {
+    m_invokedBaseMethod = true;
+  }
+
+  protected boolean m_invokedBaseConfiguration = false;
+
+  @BeforeClass
+  public void baseConfiguration() {
+    m_invokedBaseConfiguration = true;
+  }
+
+  @BeforeClass(groups = { "configuration1" },
+                 dependsOnGroups = { "configuration0" })
+  public void configuration1() {
+//    System.out.println("CONFIGURATION 1");
+    addConfiguration("configuration1");
+  }
+
+  @Test(dependsOnGroups = { "inheritedTestMethod" })
+  public void testBooleans() {
+    assert m_invokedBaseMethod : "Didn't invoke test method in base class";
+    assert m_invokedBaseConfiguration : "Didn't invoke configuration method in base class";
+  }
+
+}
diff --git a/src/test/java/test/sample/Basic1.java b/src/test/java/test/sample/Basic1.java
new file mode 100644
index 0000000..c3947e3
--- /dev/null
+++ b/src/test/java/test/sample/Basic1.java
@@ -0,0 +1,30 @@
+package test.sample;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class Basic1 {
+  static private int m_count = 0;
+
+  public static void incrementCount() {
+    m_count++;
+  }
+
+  public static int getCount() {
+    return m_count;
+  }
+
+  @BeforeMethod
+  public void beforeTestMethod() {
+    incrementCount();
+  }
+
+  @Test(groups = { "basic1" } )
+  public void basic1() {
+    assert getCount() > 0 : "COUNT WAS NOT INCREMENTED";
+  }
+
+  static private void ppp(String s) {
+    System.out.println("[Basic1] " + s);
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/sample/Basic2.java b/src/test/java/test/sample/Basic2.java
new file mode 100644
index 0000000..64cca2a
--- /dev/null
+++ b/src/test/java/test/sample/Basic2.java
@@ -0,0 +1,34 @@
+package test.sample;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.Test;
+
+public class Basic2 {
+  private boolean m_basic2WasRun = false;
+  private static int m_afterClass = 0;
+
+  @Test(dependsOnGroups = { "basic1" })
+  public void basic2() {
+    m_basic2WasRun = true;
+    assert Basic1.getCount() > 0 : "COUNT WAS NOT INCREMENTED";
+  }
+
+  @AfterTest
+  public void cleanUp() {
+    m_basic2WasRun = false;
+    m_afterClass = 0;
+  }
+
+  private void ppp(String s) {
+    System.out.println("[Basic2 "
+        + Thread.currentThread().getId() + " ] " + hashCode() + " " + s);
+  }
+
+  @AfterClass
+  public void checkTestAtClassLevelWasRun() {
+    m_afterClass++;
+    assert m_basic2WasRun : "Class annotated with @Test didn't have its methods run.";
+    assert 1 == m_afterClass : "After class should have been called exactly once, not " + m_afterClass;
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/sample/ConverterSample3.java b/src/test/java/test/sample/ConverterSample3.java
new file mode 100644
index 0000000..29d6293
--- /dev/null
+++ b/src/test/java/test/sample/ConverterSample3.java
@@ -0,0 +1,94 @@
+package test.sample;

+

+/*

+ * Created on 12-Sep-2006 by micheb10

+ */

+

+/**

+ * Sample file for the Javadoc annotations to Java 5 annotations converter for a non-default package

+ * @author micheb10 12-Sep-2006

+ * @testng.test

+ */

+public class ConverterSample3 {

+	/**

+	 * This comment line should be preserved

+	 * @testng.before-suite alwaysRun = "true"

+	 */

+	public void beforeSuiteAlwaysRun() {

+		// We are just checking appropriate annotations are added so we don't care about body

+	}

+

+	/**

+	 * @testng.test

+	 */

+	public void plainTest() {

+		// Empty body

+	}

+

+	/**

+	 * @testng.test

+	 * @testng.expected-exceptions

+	 * value = "java.lang.NullPointerException java.lang.NumberFormatException"

+	 */

+	public void expectedExceptions() {

+		// Empty body

+	}

+

+	/**

+	 * @testng.test groups = "groupA groupB"

+	 */

+	public void testGroups() {

+		// Empty body

+	}

+

+	/**

+	 * @testng.after-method

+	 */

+	public void afterMethod() {

+		// Empty body

+	}

+

+	/**

+	 * This key should be preserved

+	 * @author The author is a standard tag and should not be touched

+	 * @testng.test groups = "groupA" alwaysRun=true

+	 * 		dependsOnMethods = "expectedExceptions" timeOut="3000"

+	 * @version another standard tag should not be changed

+	 * @testng.expected-exceptions

+	 * value = "java.lang.NullPointerException java.lang.NumberFormatException"

+	 * @testng.parameters value="firstParameter secondParameter thirdParameter"

+	 */

+	public void testEverything() {

+		// Lots and lots of stuff

+	}

+

+	/**

+	 * @testng.data-provider name="test1"

+	 */

+	public Object[][] dataProvider() {

+		return null;

+	}

+

+	/**

+	 * @testng.factory

+	 */

+	public Object[] factory() {

+		return null;

+	}

+

+	/**

+	 * @testng.test

+	 */

+	public class TestInnerClass {

+		public void bareInnerMethod() {

+			// Empty body

+		}

+

+		/**

+		 * @testng.test

+		 */

+		public void testInnerMethod() {

+			// empty body

+		}

+	}

+}

diff --git a/src/test/java/test/sample/InvocationCountTest.java b/src/test/java/test/sample/InvocationCountTest.java
new file mode 100644
index 0000000..9971975
--- /dev/null
+++ b/src/test/java/test/sample/InvocationCountTest.java
@@ -0,0 +1,75 @@
+package test.sample;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.Test;
+
+
+/**
+ * This class is used to test invocationCountTest
+ *
+ * @author cbeust
+ */
+public class InvocationCountTest {
+
+  //
+  // Invocation test
+  //
+  private static int m_count = 0;
+
+  @AfterSuite(groups = {"invocationOnly"})
+  public void afterSuite() {
+    m_count = 0;
+    m_count2 = 0;
+    m_count3 = 0;
+  }
+
+  @Test(groups = { "invocationOnly"}, invocationCount = 10 )
+  public void tenTimesShouldSucceed() {
+    m_count++;
+  }
+
+  //
+  // Invocation + Success percentage test
+  // This method will work the first 8 times and fail after that, but overall
+  // the test should still pass because successPercentage = 80
+  //
+  private static int m_count2 = 0;
+
+  @Test(groups = { "successPercentageThatSucceedsOnly" },
+    invocationCount = 10, successPercentage = 80)
+  public void successPercentageShouldSucceed() {
+    if (m_count2 >= 8) {
+      throw new RuntimeException("Called more than eight times : " + m_count2);
+    }
+    m_count2++;
+  }
+
+  //
+  // Invocation + Success percentage test
+  // This method will work the first 8 times and fail after that.  One of
+  // the failures will fall under the percentage tolerance but the next one
+  // will not.
+  //
+  private static int m_count3 = 0;
+
+  @Test(groups = { "successPercentageThatFailsOnly" },
+    invocationCount = 10, successPercentage = 90)
+  public void successPercentageShouldFail() {
+    if (m_count3>= 8) {
+      throw new RuntimeException("Called more than eight times : " + m_count3);
+    }
+    m_count3++;
+  }
+
+  @AfterClass(groups = { "invocationOnly"})
+  public void verify() {
+    assert 10 == m_count : "Method should have been invoked 10 times but was invoked "
+      + m_count + " times";
+  }
+
+  public static void ppp(String s) {
+    System.out.println("[InvocationCount] " + s);
+  }
+
+}
diff --git a/src/test/java/test/sample/JUnitSample1.java b/src/test/java/test/sample/JUnitSample1.java
new file mode 100644
index 0000000..35f8cdd
--- /dev/null
+++ b/src/test/java/test/sample/JUnitSample1.java
@@ -0,0 +1,52 @@
+package test.sample;
+
+import junit.framework.TestCase;
+
+/**
+ * This class
+ *
+ * @author Cedric Beust, May 5, 2004
+ *
+ */
+public class JUnitSample1 extends TestCase {
+  private String m_field = null;
+  public static final String EXPECTED2 = "testSample1_2";
+  public static final String EXPECTED1 = "testSample1_1";
+
+  public JUnitSample1() {
+    super();
+  }
+
+  public JUnitSample1(String n) {
+    super(n);
+  }
+
+ @Override
+public void setUp() {
+    m_field = "foo";
+  }
+
+  @Override
+  public void tearDown() {
+    m_field = null;
+  }
+
+  /**
+   *
+   *
+   */
+  public void testSample1_1() {
+//    ppp("Sample 1_1");
+  }
+
+  public void testSample1_2() {
+//    ppp("Sample 1_2");
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[JUnitSample1] " + s);
+  }
+
+
+
+}
diff --git a/src/test/java/test/sample/JUnitSample2.java b/src/test/java/test/sample/JUnitSample2.java
new file mode 100644
index 0000000..9c3ae1d
--- /dev/null
+++ b/src/test/java/test/sample/JUnitSample2.java
@@ -0,0 +1,32 @@
+package test.sample;
+
+import junit.framework.TestCase;
+
+/**
+ * This class
+ *
+ * @author Cedric Beust, May 5, 2004
+ *
+ */
+public class JUnitSample2 extends TestCase {
+  public static final String EXPECTED = "testSample2ThatSetUpWasRun";
+  private String m_field = null;
+
+  public JUnitSample2() {
+    super();
+  }
+
+  public JUnitSample2(String n) {
+    super(n);
+  }
+
+  @Override
+  public void setUp() {
+    m_field = "foo";
+  }
+
+  public void testSample2ThatSetUpWasRun() {
+    assert null != m_field : "setUp() wasn't run";
+  }
+
+}
diff --git a/src/test/java/test/sample/JUnitSample3.java b/src/test/java/test/sample/JUnitSample3.java
new file mode 100644
index 0000000..a1333cb
--- /dev/null
+++ b/src/test/java/test/sample/JUnitSample3.java
@@ -0,0 +1,28 @@
+package test.sample;
+
+import org.testng.Assert;
+
+import junit.framework.TestCase;
+
+/**
+ * This class verifies that a new instance is used every time
+ *
+ * @author cbeust
+ */
+public class JUnitSample3 extends TestCase {
+  private int m_count = 0;
+
+  public void test1() {
+    Assert.assertEquals( m_count, 0);
+    m_count++;
+  }
+
+  public void test2() {
+    Assert.assertEquals( m_count, 0);
+    m_count++;
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[JUnitSample3] " + s);
+  }
+}
diff --git a/src/test/java/test/sample/JUnitSample4.java b/src/test/java/test/sample/JUnitSample4.java
new file mode 100644
index 0000000..9985d8e
--- /dev/null
+++ b/src/test/java/test/sample/JUnitSample4.java
@@ -0,0 +1,38 @@
+package test.sample;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ *
+ * @author lukas
+ */
+public abstract class JUnitSample4 extends TestCase {
+
+    private int i = 0;
+
+    public JUnitSample4(String name, int i) {
+        super(name);
+        this.i = i;
+    }
+
+    public void testXY() {
+        Assert.assertEquals(1, 1);
+    }
+
+    public static TestSuite suite() {
+        TestSuite ts = new TestSuite("Sample Suite");
+        for (int i = 0; i < 3; i++) {
+            ts.addTest(new T(i));
+        }
+        return ts;
+    }
+
+    private static class T extends JUnitSample4 {
+
+        public T(int i) {
+            super("testXY", i);
+        }
+    }
+}
diff --git a/src/test/java/test/sample/PartialGroupTest.java b/src/test/java/test/sample/PartialGroupTest.java
new file mode 100644
index 0000000..c93b8f9
--- /dev/null
+++ b/src/test/java/test/sample/PartialGroupTest.java
@@ -0,0 +1,45 @@
+package test.sample;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ * This class tests groups that are partially defined at the class level
+ * and then augmented at the method level.
+ *
+ * @author cbeust
+ */
+
+@Test(groups = { "classGroup" })
+public class PartialGroupTest {
+  public static boolean m_successMethod = false;
+  public static boolean m_successClass = false;
+
+  @BeforeClass
+  public void init() {
+    m_successMethod = false;
+    m_successClass = false;
+  }
+
+  @Test(groups = { "methodGroup" })
+  public void testMethodGroup() {
+    m_successMethod = true;
+  }
+
+  @Test
+  public void testClassGroupShouldFail() {
+    Assert.assertTrue(false);
+  }
+
+  @Test(groups = { "methodGroup" })
+  public void testMethodGroupShouldFail() {
+    Assert.assertTrue(false);
+  }
+
+  @Test
+  public void testClassGroup() {
+    m_successClass = true;
+  }
+
+}
diff --git a/src/test/java/test/sample/PartialGroupVerification.java b/src/test/java/test/sample/PartialGroupVerification.java
new file mode 100644
index 0000000..ce310f6
--- /dev/null
+++ b/src/test/java/test/sample/PartialGroupVerification.java
@@ -0,0 +1,18 @@
+
+package test.sample;
+
+import org.testng.annotations.Test;
+
+/**
+ * This class verifies the PartialGroupTest
+ *
+ * @author cbeust
+ */
+
+public class PartialGroupVerification {
+  @Test
+  public void verify() {
+    assert PartialGroupTest.m_successMethod && PartialGroupTest.m_successClass
+      : "test1 and test2 should have been invoked both";
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/sample/README.txt b/src/test/java/test/sample/README.txt
new file mode 100644
index 0000000..ebe94a0
--- /dev/null
+++ b/src/test/java/test/sample/README.txt
@@ -0,0 +1,2 @@
+This directory contains "samples": TestNG tests that are not invoked directly but invoked
+programmatically by the real test classes in the main directory.
diff --git a/src/test/java/test/sample/Sample1.java b/src/test/java/test/sample/Sample1.java
new file mode 100644
index 0000000..beefb72
--- /dev/null
+++ b/src/test/java/test/sample/Sample1.java
@@ -0,0 +1,76 @@
+package test.sample;
+
+
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.ExpectedExceptions;
+import org.testng.annotations.Test;
+
+/**
+ * This class
+ *
+ * @author Cedric Beust, Apr 26, 2004
+ *
+ */
+public class Sample1 extends BaseSample1 {
+	@AfterClass
+	public static void tearDownClass1() {
+	}
+
+	@AfterClass
+	public void tearDownClass2() {
+	}
+
+	@BeforeMethod
+	public void beforeTest() {
+	}
+
+	@AfterMethod
+	public void afterTest() {
+	}
+
+	@Test(groups = {"even"})
+	public void method2() {
+	}
+
+	@Test(groups = {"odd"})
+	public void method3() {
+	}
+
+	@Test(groups = {"odd"}, enabled = false)
+	public void oddDisableMethod() {
+	}
+
+	@Test(groups = {"broken"})
+	public void broken() {
+	}
+
+	@Test(groups = {"fail"})
+	@ExpectedExceptions( {NumberFormatException.class,	ArithmeticException.class})
+	public void throwExpectedException1ShouldPass() {
+		throw new NumberFormatException();
+	}
+
+	@Test(groups = {"fail"})
+	@ExpectedExceptions( {NumberFormatException.class,	ArithmeticException.class})
+	public void throwExpectedException2ShouldPass() {
+		throw new ArithmeticException();
+	}
+
+	@Test(groups = {"fail", "bug"})
+	public void throwExceptionShouldFail() {
+		throw new NumberFormatException();
+	}
+
+	@Test(groups = {"assert"})
+	public void verifyLastNameShouldFail() {
+	  Assert.assertEquals("Beust", "", "Expected name Beust, found blah");
+	}
+
+	private static void ppp(String s) {
+		System.out.println("[Test1] " + s);
+	}
+
+}
diff --git a/src/test/java/test/sample/Sample2.java b/src/test/java/test/sample/Sample2.java
new file mode 100644
index 0000000..5b3e9ef
--- /dev/null
+++ b/src/test/java/test/sample/Sample2.java
@@ -0,0 +1,30 @@
+package test.sample;
+
+import org.testng.annotations.Test;
+
+/**
+ * This class
+ *
+ * @author Cedric Beust, Apr 26, 2004
+ *
+ */
+
+public class Sample2 {
+
+  @Test(groups = "g1")
+  public void method1() {
+//    System.out.println("@@@@@@@@@@@@@@@@@@@ METHOD1");
+  }
+
+  @Test
+  public void method2() {
+//    System.out.println("@@@@@@@@@@@@@@@@@@@ METHOD2");
+  }
+
+  @Test
+  public void method3() {
+//    System.out.println("@@@@@@@@@@@@@@@@@@@ METHOD3");
+  }
+
+
+}
diff --git a/src/test/java/test/sample/Scope.java b/src/test/java/test/sample/Scope.java
new file mode 100644
index 0000000..ac8641d
--- /dev/null
+++ b/src/test/java/test/sample/Scope.java
@@ -0,0 +1,23 @@
+package test.sample;
+
+import org.testng.annotations.Test;
+
+/**
+ * This class tests paramete scopes.
+ *
+ * @author cbeust
+ */
+public class Scope {
+
+  @Test(groups = { "outer-group" }, parameters = { "parameter" })
+  public void outerDeprecated(String s) {
+    assert "out".equals(s)
+      : "Expected out got " + s;
+  }
+
+  @Test(groups = { "inner-group" }, parameters = { "parameter" })
+  public void innerDeprecated(String s) {
+    assert "in".equals(s)
+      : "Expected in got " + s;
+  }
+}
diff --git a/src/test/java/test/sample/SetUpWithParameterTest.java b/src/test/java/test/sample/SetUpWithParameterTest.java
new file mode 100644
index 0000000..bc03060
--- /dev/null
+++ b/src/test/java/test/sample/SetUpWithParameterTest.java
@@ -0,0 +1,21 @@
+package test.sample;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ * This class fails in setUp and should result in 1 failure, 1 skip
+ *
+ * @author cbeust
+ */
+public class SetUpWithParameterTest {
+
+  @BeforeClass
+  public void setUp(String bogusParameter) {
+  }
+
+  @Test
+  public void test() {
+
+  }
+}
diff --git a/src/test/java/test/sanitycheck/CheckSuiteNamesTest.java b/src/test/java/test/sanitycheck/CheckSuiteNamesTest.java
new file mode 100644
index 0000000..f8cc1ad
--- /dev/null
+++ b/src/test/java/test/sanitycheck/CheckSuiteNamesTest.java
@@ -0,0 +1,84 @@
+package test.sanitycheck;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.Parser;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+import org.xml.sax.SAXException;
+import test.SimpleBaseTest;
+import java.io.IOException;
+import java.util.Arrays;
+import javax.xml.parsers.ParserConfigurationException;
+
+public class CheckSuiteNamesTest extends SimpleBaseTest {
+
+  /**
+   * Child suites have different names
+   */
+  @Test
+  public void checkChildSuites() {
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG tng = create();
+    String testngXmlPath = getPathToResource("sanitycheck/test-s-b.xml");
+    tng.setTestSuites(Arrays.asList(testngXmlPath));
+    tng.addListener(tla);
+    tng.run();
+    Assert.assertEquals(tla.getPassedTests().size(), 4);
+  }
+
+  /**
+   * Child suites have same names
+   */
+  @Test
+  public void checkChildSuitesFails() {
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG tng = create();
+    String testngXmlPath = getPathToResource("sanitycheck/test-s-a.xml");
+    tng.setTestSuites(Arrays.asList(testngXmlPath));
+    tng.addListener(tla);
+    tng.run();
+    Assert.assertEquals(tla.getTestContexts().get(0).getSuite().getName(), "SanityCheck suites");
+    Assert.assertEquals(tla.getTestContexts().get(1).getSuite().getName(), "SanityCheck suites");
+    Assert.assertEquals(tla.getTestContexts().get(2).getSuite().getName(), "SanityCheck suites (0)");
+    Assert.assertEquals(tla.getTestContexts().get(3).getSuite().getName(), "SanityCheck suites (0)");
+  }
+
+  /**
+   * Checks that suites created programmatically also works as expected
+   */
+  @Test
+  public void checkProgrammaticSuitesFails() {
+    XmlSuite xmlSuite1 = new XmlSuite();
+    xmlSuite1.setName("SanityCheckSuite");
+    {
+      XmlTest result = new XmlTest(xmlSuite1);
+      result.getXmlClasses().add(new XmlClass(SampleTest1.class.getCanonicalName()));
+    }
+
+    XmlSuite xmlSuite2 = new XmlSuite();
+    xmlSuite2.setName("SanityCheckSuite");
+    {
+      XmlTest result = new XmlTest(xmlSuite2);
+      result.getXmlClasses().add(new XmlClass(SampleTest2.class.getCanonicalName()));
+    }
+
+    TestNG tng = create();
+    tng.setXmlSuites(Arrays.asList(xmlSuite1, xmlSuite2));
+    tng.run();
+    Assert.assertEquals(xmlSuite1.getName(), "SanityCheckSuite");
+    Assert.assertEquals(xmlSuite2.getName(), "SanityCheckSuite (0)");
+  }
+  
+  @Test
+  public void checkXmlSuiteAddition() throws ParserConfigurationException, SAXException, IOException {
+    TestNG tng = create();
+    String testngXmlPath = getPathToResource("sanitycheck/test-s-b.xml");
+    Parser parser = new Parser(testngXmlPath);	
+    tng.setXmlSuites(parser.parseToList());
+    tng.initializeSuitesAndJarFile();		
+  }
+}
diff --git a/src/test/java/test/sanitycheck/CheckTestNamesTest.java b/src/test/java/test/sanitycheck/CheckTestNamesTest.java
new file mode 100644
index 0000000..5992724
--- /dev/null
+++ b/src/test/java/test/sanitycheck/CheckTestNamesTest.java
@@ -0,0 +1,97 @@
+package test.sanitycheck;
+
+import java.util.Arrays;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.TestNGException;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.SimpleBaseTest;
+
+public class CheckTestNamesTest extends SimpleBaseTest {
+
+  /**
+   * Child suites and same suite has two tests with same name
+   */
+  @Test
+  public void checkWithChildSuites() {
+    runSuite("sanitycheck/test-a.xml");
+  }
+
+  /**
+   * Simple suite with two tests with same name
+   */
+  @Test
+  public void checkWithoutChildSuites() {
+    runSuite("sanitycheck/test1.xml");
+  }
+
+  private void runSuite(String suitePath)
+  {
+    TestListenerAdapter tla = new TestListenerAdapter();
+    boolean exceptionRaised = false;
+    try {
+      TestNG tng = create();
+      String testngXmlPath = getPathToResource(suitePath);
+      tng.setTestSuites(Arrays.asList(testngXmlPath));
+      tng.addListener(tla);
+      tng.run();
+    } catch (TestNGException ex) {
+      exceptionRaised = true;
+      Assert.assertEquals(tla.getPassedTests().size(), 0);
+      Assert.assertEquals(tla.getFailedTests().size(), 0);
+    }
+    Assert.assertTrue(exceptionRaised);
+  }
+
+  /**
+   * Simple suite with no two tests with same name
+   */
+  @Test
+  public void checkNoError() {
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG tng = create();
+    String testngXmlPath = getPathToResource("sanitycheck/test2.xml");
+    tng.setTestSuites(Arrays.asList(testngXmlPath));
+    tng.addListener(tla);
+    tng.run();
+    Assert.assertEquals(tla.getPassedTests().size(), 2);
+  }
+
+  /**
+   * Child suites and tests within different suites have same names
+   */
+  @Test(enabled = false)
+  public void checkNoErrorWtihChildSuites() {
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG tng = create();
+    String testngXmlPath = getPathToResource("sanitycheck/test-b.xml");
+    tng.setTestSuites(Arrays.asList(testngXmlPath));
+    tng.addListener(tla);
+    tng.run();
+    Assert.assertEquals(tla.getPassedTests().size(), 4);
+  }
+
+  /**
+   * Checks that suites created programmatically also run as expected
+   */
+  @Test
+  public void checkTestNamesForProgrammaticSuites() {
+    XmlSuite xmlSuite = new XmlSuite();
+    xmlSuite.setName("SanityCheckSuite");
+    XmlTest result = new XmlTest(xmlSuite);
+    result.getXmlClasses().add(new XmlClass(SampleTest1.class.getCanonicalName()));
+    result = new XmlTest(xmlSuite);
+    result.getXmlClasses().add(new XmlClass(SampleTest2.class.getCanonicalName()));
+
+    TestNG tng = new TestNG();
+    tng.setVerbose(0);
+    tng.setXmlSuites(Arrays.asList(new XmlSuite[] { xmlSuite }));
+    tng.run();
+  }
+}
diff --git a/src/test/java/test/sanitycheck/SampleTest1.java b/src/test/java/test/sanitycheck/SampleTest1.java
new file mode 100644
index 0000000..d412f82
--- /dev/null
+++ b/src/test/java/test/sanitycheck/SampleTest1.java
@@ -0,0 +1,9 @@
+package test.sanitycheck;
+
+import org.testng.annotations.Test;
+
+public class SampleTest1
+{
+   @Test()
+   public void test1() {}
+}
diff --git a/src/test/java/test/sanitycheck/SampleTest2.java b/src/test/java/test/sanitycheck/SampleTest2.java
new file mode 100644
index 0000000..249f779
--- /dev/null
+++ b/src/test/java/test/sanitycheck/SampleTest2.java
@@ -0,0 +1,9 @@
+package test.sanitycheck;
+
+import org.testng.annotations.Test;
+
+public class SampleTest2
+{
+   @Test()
+   public void test2() {}
+}
diff --git a/src/test/java/test/sanitycheck/SampleTest3.java b/src/test/java/test/sanitycheck/SampleTest3.java
new file mode 100644
index 0000000..3abe5d0
--- /dev/null
+++ b/src/test/java/test/sanitycheck/SampleTest3.java
@@ -0,0 +1,9 @@
+package test.sanitycheck;
+
+import org.testng.annotations.Test;
+
+public class SampleTest3
+{
+   @Test()
+   public void test3() {}
+}
diff --git a/src/test/java/test/serviceloader/MyConfigurationListener.java b/src/test/java/test/serviceloader/MyConfigurationListener.java
new file mode 100644
index 0000000..f362f02
--- /dev/null
+++ b/src/test/java/test/serviceloader/MyConfigurationListener.java
@@ -0,0 +1,22 @@
+package test.serviceloader;
+
+import org.testng.IConfigurationListener;
+import org.testng.ITestResult;
+
+public class MyConfigurationListener implements IConfigurationListener {
+
+    @Override
+    public void onConfigurationSuccess(ITestResult itr) {
+
+    }
+
+    @Override
+    public void onConfigurationFailure(ITestResult itr) {
+
+    }
+
+    @Override
+    public void onConfigurationSkip(ITestResult itr) {
+
+    }
+}
diff --git a/src/test/java/test/serviceloader/ServiceLoaderSampleTest.java b/src/test/java/test/serviceloader/ServiceLoaderSampleTest.java
new file mode 100644
index 0000000..e4e907e
--- /dev/null
+++ b/src/test/java/test/serviceloader/ServiceLoaderSampleTest.java
@@ -0,0 +1,14 @@
+package test.serviceloader;
+
+import org.testng.annotations.Test;
+
+/**
+ * Dummy class to test ServiceLoader
+ *
+ * @author Cedric Beust <cedric@beust.com>
+ */
+public class ServiceLoaderSampleTest {
+
+  @Test
+  public void f() {}
+}
diff --git a/src/test/java/test/serviceloader/ServiceLoaderTest.java b/src/test/java/test/serviceloader/ServiceLoaderTest.java
new file mode 100644
index 0000000..683ec00
--- /dev/null
+++ b/src/test/java/test/serviceloader/ServiceLoaderTest.java
@@ -0,0 +1,49 @@
+package test.serviceloader;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+import test.listeners.ListenerAssert;
+
+public class ServiceLoaderTest extends SimpleBaseTest {
+
+  @Test
+  public void serviceLoaderShouldWork() throws MalformedURLException {
+    TestNG tng = create(ServiceLoaderSampleTest.class);
+    URL url = getClass().getClassLoader().getResource("serviceloader.jar");
+    URLClassLoader ucl = URLClassLoader.newInstance(new URL[] { url });
+    tng.setServiceLoaderClassLoader(ucl);
+    tng.run();
+
+    ListenerAssert.assertListenerType(tng.getServiceLoaderListeners(), TmpSuiteListener.class);
+  }
+
+  @Test
+  public void serviceLoaderWithNoClassLoader() {
+    //Here ServiceLoader is expected to rely on the current context class loader to load the service loader file
+    //Since serviceloader.jar doesn't seem to be visible to the current thread's contextual class loader
+    //resorting to pushing in a class loader into the current thread that can load the resource
+    URL url = getClass().getClassLoader().getResource("serviceloader.jar");
+    URLClassLoader ucl = URLClassLoader.newInstance(new URL[] { url });
+    Thread.currentThread().setContextClassLoader(ucl);
+    TestNG tng = create(ServiceLoaderSampleTest.class);
+    tng.run();
+
+    ListenerAssert.assertListenerType(tng.getServiceLoaderListeners(), TmpSuiteListener.class);
+  }
+
+  @Test(description = "GITHUB-491")
+  public void serviceLoaderShouldWorkWithConfigurationListener() {
+    TestNG tng = create(ServiceLoaderSampleTest.class);
+    tng.run();
+
+    Assert.assertEquals(1, tng.getServiceLoaderListeners().size());
+    ListenerAssert.assertListenerType(tng.getServiceLoaderListeners(), MyConfigurationListener.class);
+  }
+}
diff --git a/src/test/java/test/serviceloader/TmpSuiteListener.java b/src/test/java/test/serviceloader/TmpSuiteListener.java
new file mode 100644
index 0000000..0e1d306
--- /dev/null
+++ b/src/test/java/test/serviceloader/TmpSuiteListener.java
@@ -0,0 +1,12 @@
+package test.serviceloader;
+
+import org.testng.ISuite;
+import org.testng.ISuiteListener;
+
+public class TmpSuiteListener implements ISuiteListener {
+  @Override
+  public void onFinish(ISuite suite) {}
+  @Override
+  public void onStart(ISuite suite) {}
+}
+
diff --git a/src/test/java/test/simple/IncludedExcludedSampleTest.java b/src/test/java/test/simple/IncludedExcludedSampleTest.java
new file mode 100644
index 0000000..22ec7ef
--- /dev/null
+++ b/src/test/java/test/simple/IncludedExcludedSampleTest.java
@@ -0,0 +1,50 @@
+package test.simple;
+
+import org.testng.Reporter;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+public class IncludedExcludedSampleTest {
+
+  @BeforeSuite
+  public void beforeSuite() {
+    Reporter.log("beforeSuite");
+  }
+
+  @BeforeTest
+  public void beforeTest() {
+    Reporter.log("beforeTest");
+  }
+
+  @BeforeClass
+  public void beforeTestClass() {
+    Reporter.log("beforeTestClass");
+  }
+
+  @BeforeMethod
+  public void beforeTestMethod() {
+    Reporter.log("beforeTestMethod");
+  }
+
+  @Test
+  public void test1() {
+    Reporter.log("Child.test1");
+  }
+
+  @Test(enabled = false)
+  public void test2() {
+    Reporter.log("Child.test2");
+  }
+
+  @Test(groups = "a")
+  public void test3() {
+    Reporter.log("Child.test3");
+  }
+
+  private void ppp(String string) {
+    System.out.println("[TestNGBug] " + string);
+  }
+}
diff --git a/src/test/java/test/simple/IncludedExcludedTest.java b/src/test/java/test/simple/IncludedExcludedTest.java
new file mode 100644
index 0000000..6d2b70d
--- /dev/null
+++ b/src/test/java/test/simple/IncludedExcludedTest.java
@@ -0,0 +1,95 @@
+package test.simple;
+
+import org.testng.Assert;
+import org.testng.IReporter;
+import org.testng.ISuite;
+import org.testng.ITestNGMethod;
+import org.testng.TestNG;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+
+import testhelper.OutputDirectoryPatch;
+
+import java.util.Collection;
+import java.util.List;
+
+public class IncludedExcludedTest {
+
+  private TestNG m_tng;
+
+  @BeforeMethod
+  public void init() {
+    m_tng = new TestNG();
+    m_tng.setOutputDirectory(OutputDirectoryPatch.getOutputDirectory());
+    m_tng.setVerbose(0);
+    m_tng.setUseDefaultListeners(false);
+  }
+
+  @Test(description = "First test method")
+  public void verifyIncludedExcludedCount1() {
+    m_tng.setTestClasses(new Class[] {IncludedExcludedSampleTest.class});
+    m_tng.setGroups("a");
+    m_tng.addListener(
+        new MyReporter(new String[] { "test3" }, new String[] { "test1", "test2"}));
+    m_tng.run();
+  }
+
+  @Test(description = "Second test method")
+  public void verifyIncludedExcludedCount2() {
+    m_tng.setTestClasses(new Class[] {IncludedExcludedSampleTest.class});
+    m_tng.addListener(
+        new MyReporter(
+            new String[] {
+                "beforeSuite", "beforeTest", "beforeTestClass",
+                "beforeTestMethod", "test1", "beforeTestMethod", "test3"
+              },
+            new String[] { "test2"}));
+    m_tng.run();
+  }
+
+}
+
+class MyReporter implements IReporter {
+
+  private String[] m_included;
+  private String[] m_excluded;
+
+  public MyReporter(String[] included, String[] excluded) {
+    m_included = included;
+    m_excluded = excluded;
+  }
+
+  @Override
+  public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
+    Assert.assertEquals(suites.size(), 1);
+    ISuite suite = suites.get(0);
+
+    {
+      Collection<ITestNGMethod> invoked = suite.getInvokedMethods();
+      Assert.assertEquals(invoked.size(), m_included.length);
+      for (String s : m_included) {
+        Assert.assertTrue(containsMethod(invoked, s));
+      }
+    }
+
+    {
+      Collection<ITestNGMethod> excluded = suite.getExcludedMethods();
+      Assert.assertEquals(excluded.size(), m_excluded.length);
+      for (String s : m_excluded) {
+        Assert.assertTrue(containsMethod(excluded, s));
+      }
+    }
+  }
+
+  private boolean containsMethod(Collection<ITestNGMethod> invoked, String string) {
+    for (ITestNGMethod m : invoked) {
+      if (m.getMethodName().equals(string)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+}
diff --git a/src/test/java/test/simple/SimpleTest.java b/src/test/java/test/simple/SimpleTest.java
new file mode 100644
index 0000000..a718dd4
--- /dev/null
+++ b/src/test/java/test/simple/SimpleTest.java
@@ -0,0 +1,15 @@
+package test.simple;
+
+import org.testng.annotations.Test;
+
+public class SimpleTest {
+
+  @Test
+  public void f() {
+  }
+
+  private void ppp(String string) {
+    System.out.println("[SimpleTest] " + string);
+  }
+
+}
diff --git a/src/test/java/test/skipex/ConfigurationSkippedExceptionTest.java b/src/test/java/test/skipex/ConfigurationSkippedExceptionTest.java
new file mode 100644
index 0000000..dd04208
--- /dev/null
+++ b/src/test/java/test/skipex/ConfigurationSkippedExceptionTest.java
@@ -0,0 +1,20 @@
+package test.skipex;

+

+import org.testng.SkipException;

+import org.testng.annotations.BeforeMethod;

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class ConfigurationSkippedExceptionTest {

+  @BeforeMethod

+  public void configurationLevelSkipException() {

+    throw new SkipException("some skip message");

+  }

+

+  @Test

+  public void dummyTest() {

+  }

+}

diff --git a/src/test/java/test/skipex/SkipAndExpectedSampleTest.java b/src/test/java/test/skipex/SkipAndExpectedSampleTest.java
new file mode 100644
index 0000000..fe070c1
--- /dev/null
+++ b/src/test/java/test/skipex/SkipAndExpectedSampleTest.java
@@ -0,0 +1,12 @@
+package test.skipex;
+
+import org.testng.SkipException;
+import org.testng.annotations.Test;
+
+public class SkipAndExpectedSampleTest {
+  @Test(expectedExceptions = NullPointerException.class)
+  public void a2() {
+    throw new SkipException("test");
+  }
+
+}
diff --git a/src/test/java/test/skipex/SkipAndExpectedTest.java b/src/test/java/test/skipex/SkipAndExpectedTest.java
new file mode 100644
index 0000000..d96adcb
--- /dev/null
+++ b/src/test/java/test/skipex/SkipAndExpectedTest.java
@@ -0,0 +1,23 @@
+package test.skipex;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+public class SkipAndExpectedTest extends SimpleBaseTest {
+
+  @Test
+  public void shouldSkip() {
+    TestNG tng = create(SkipAndExpectedSampleTest.class);
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+    Assert.assertEquals(tla.getPassedTests().size(), 0);
+    Assert.assertEquals(tla.getSkippedTests().size(), 1);
+    Assert.assertEquals(tla.getFailedTests().size(), 0);
+  }
+}
diff --git a/src/test/java/test/skipex/SkippedExceptionTest.java b/src/test/java/test/skipex/SkippedExceptionTest.java
new file mode 100644
index 0000000..80a71e7
--- /dev/null
+++ b/src/test/java/test/skipex/SkippedExceptionTest.java
@@ -0,0 +1,51 @@
+package test.skipex;

+

+import org.testng.Assert;

+import org.testng.ITestResult;

+import org.testng.TestListenerAdapter;

+import org.testng.TestNG;

+import org.testng.annotations.Test;

+

+import java.util.List;

+

+

+/**

+ * This class/interface

+ */

+public class SkippedExceptionTest {

+  @Test

+  public void skippedExceptionInConfigurationMethods() {

+    TestListenerAdapter listener= new TestListenerAdapter();

+    TestNG test= new TestNG(false);

+    test.addListener(listener);

+    test.setVerbose(0);

+    test.setTestClasses(new Class[] {ConfigurationSkippedExceptionTest.class});

+    test.run();

+    List<ITestResult> confSkips= listener.getConfigurationSkips();

+    List<ITestResult> testSkips= listener.getSkippedTests();

+    Assert.assertEquals(testSkips.size(), 1);

+    Assert.assertEquals(testSkips.get(0).getMethod().getMethodName(), "dummyTest");

+

+    Assert.assertEquals(confSkips.size(), 1);

+    Assert.assertEquals(confSkips.get(0).getMethod().getMethodName(), "configurationLevelSkipException");

+  }

+

+

+  @Test

+  public void skippedExceptionInTestMethods() {

+    TestListenerAdapter listener= new TestListenerAdapter();

+    TestNG test= new TestNG(false);

+    test.addListener(listener);

+    test.setTestClasses(new Class[] {TestSkippedExceptionTest.class});

+    test.run();

+    List<ITestResult> skips= listener.getSkippedTests();

+    List<ITestResult> failures= listener.getFailedTests();

+    List<ITestResult> passed = listener.getPassedTests();

+    Assert.assertEquals(skips.size(), 1);

+    Assert.assertEquals(failures.size(), 1);

+    Assert.assertEquals(passed.size(), 1);

+    Assert.assertEquals(skips.get(0).getMethod().getMethodName(), "genericSkipException");

+    Assert.assertEquals(failures.get(0).getMethod().getMethodName(), "timedSkipException");

+    Assert.assertEquals(passed.get(0).getMethod().getMethodName(), "genericExpectedSkipException");

+  }

+}

diff --git a/src/test/java/test/skipex/TestSkippedExceptionTest.java b/src/test/java/test/skipex/TestSkippedExceptionTest.java
new file mode 100644
index 0000000..13df237
--- /dev/null
+++ b/src/test/java/test/skipex/TestSkippedExceptionTest.java
@@ -0,0 +1,26 @@
+package test.skipex;

+

+import org.testng.SkipException;

+import org.testng.TimeBombSkipException;

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class TestSkippedExceptionTest {

+  @Test

+  public void genericSkipException() {

+    throw new SkipException("genericSkipException is skipped for now");

+  }

+

+  @Test(expectedExceptions = SkipException.class)

+  public void genericExpectedSkipException() {

+    throw new SkipException("genericExpectedSkipException should not be skipped");

+  }

+

+  @Test

+  public void timedSkipException() {

+    throw new TimeBombSkipException("timedSkipException is time bombed", "2007/04/10");

+  }

+}

diff --git a/src/test/java/test/superclass/Base1.java b/src/test/java/test/superclass/Base1.java
new file mode 100644
index 0000000..4898e89
--- /dev/null
+++ b/src/test/java/test/superclass/Base1.java
@@ -0,0 +1,27 @@
+package test.superclass;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+public class Base1 {
+  @BeforeClass
+  public void bc() {
+    ppp("BEFORE_CLASS");
+  }
+
+  @BeforeMethod
+  public void bm() {
+    ppp("BEFORE_METHOD");
+  }
+
+  @Test
+  public void tbase() {
+    ppp("TEST IN BASE");
+  }
+
+  private static void ppp(String s) {
+    if (false) {
+      System.out.println("[Base] " + s);
+    }
+  }
+}
diff --git a/src/test/java/test/superclass/Base2.java b/src/test/java/test/superclass/Base2.java
new file mode 100644
index 0000000..8bacf6a
--- /dev/null
+++ b/src/test/java/test/superclass/Base2.java
@@ -0,0 +1,28 @@
+package test.superclass;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+@Test
+public class Base2 {
+  @BeforeClass
+  public void bc() {
+    ppp("BEFORE_CLASS");
+  }
+
+  @BeforeMethod
+  public void bm() {
+    ppp("BEFORE_METHOD");
+  }
+
+  public void tbase() {
+    ppp("TEST IN BASE");
+  }
+
+  private static void ppp(String s) {
+    if (false) {
+      System.out.println("[Base] " + s);
+    }
+  }
+}
diff --git a/src/test/java/test/superclass/BaseSampleTest3.java b/src/test/java/test/superclass/BaseSampleTest3.java
new file mode 100644
index 0000000..9a5771e
--- /dev/null
+++ b/src/test/java/test/superclass/BaseSampleTest3.java
@@ -0,0 +1,11 @@
+package test.superclass;
+
+import org.testng.annotations.Test;
+
+public class BaseSampleTest3 {
+  @Test
+  public void base() {
+      assert true;
+  }
+
+}
diff --git a/src/test/java/test/superclass/Child1Test.java b/src/test/java/test/superclass/Child1Test.java
new file mode 100644
index 0000000..5b57433
--- /dev/null
+++ b/src/test/java/test/superclass/Child1Test.java
@@ -0,0 +1,24 @@
+package test.superclass;
+
+import org.testng.annotations.Test;
+
+@Test
+public class Child1Test extends Base1 {
+  public void t1() {
+    ppp("T1");
+  }
+
+  public void t2() {
+    ppp("T2");
+  }
+
+  public void t3() {
+    ppp("T3");
+  }
+
+  private static void ppp(String s) {
+    if (false) {
+      System.out.println("[Child] " + s);
+    }
+  }
+}
diff --git a/src/test/java/test/superclass/Child2Test.java b/src/test/java/test/superclass/Child2Test.java
new file mode 100644
index 0000000..d984890
--- /dev/null
+++ b/src/test/java/test/superclass/Child2Test.java
@@ -0,0 +1,24 @@
+package test.superclass;
+
+import org.testng.annotations.Test;
+
+@Test
+public class Child2Test extends Base2 {
+  public void t1() {
+    ppp("T1");
+  }
+
+  public void t2() {
+    ppp("T2");
+  }
+
+  public void t3() {
+    ppp("T3");
+  }
+
+  private static void ppp(String s) {
+    if (false) {
+      System.out.println("[Child] " + s);
+    }
+  }
+}
diff --git a/src/test/java/test/superclass/ChildSampleTest3.java b/src/test/java/test/superclass/ChildSampleTest3.java
new file mode 100644
index 0000000..6bfd89a
--- /dev/null
+++ b/src/test/java/test/superclass/ChildSampleTest3.java
@@ -0,0 +1,16 @@
+package test.superclass;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class ChildSampleTest3 extends BaseSampleTest3 {
+  @Test
+  public void pass() {
+    Assert.assertTrue(true);
+  }
+
+  @Test
+  public void fail() {
+    Assert.assertTrue(false);
+  }
+}
diff --git a/src/test/java/test/superclass/MainTest.java b/src/test/java/test/superclass/MainTest.java
new file mode 100644
index 0000000..a07daca
--- /dev/null
+++ b/src/test/java/test/superclass/MainTest.java
@@ -0,0 +1,36 @@
+package test.superclass;
+
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+
+public class MainTest extends BaseTest {
+
+  @Test
+  public void baseMethodIsCalledWithMethodTest() {
+    addClass("test.superclass.Child1Test");
+    run();
+    String[] passed = {
+      "tbase", "t1", "t2", "t3"
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+  @Test
+  public void baseMethodIsCalledWithClassTest() {
+    addClass("test.superclass.Child2Test");
+    run();
+    String[] passed = {
+      "tbase", "t1", "t2", "t3"
+    };
+    String[] failed = {
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+
+
+}
diff --git a/src/test/java/test/superclass/Test3.java b/src/test/java/test/superclass/Test3.java
new file mode 100644
index 0000000..b9585fe
--- /dev/null
+++ b/src/test/java/test/superclass/Test3.java
@@ -0,0 +1,23 @@
+package test.superclass;
+
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+
+public class Test3 extends BaseTest {
+
+  @Test
+  public void shouldExcludeBaseMethods() {
+    addClass("test.superclass.ChildSampleTest3");
+    addExcludedMethod("test.superclass.ChildSampleTest3", "pass");
+    addExcludedMethod("test.superclass.ChildSampleTest3", "base");
+    run();
+    String[] passed = {
+    };
+    String[] failed = {
+        "fail"
+    };
+    verifyTests("Failed", failed, getFailedTests());
+    verifyTests("Passed", passed, getPassedTests());
+  }
+}
diff --git a/src/test/java/test/test111/AbstractTest.java b/src/test/java/test/test111/AbstractTest.java
new file mode 100644
index 0000000..28bdeee
--- /dev/null
+++ b/src/test/java/test/test111/AbstractTest.java
@@ -0,0 +1,19 @@
+package test.test111;
+
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.Test;
+
+public abstract class AbstractTest {
+
+	public static int R=0;
+	
+    @Test
+    public void testAbstract() {
+    }
+
+    @AfterClass
+    public void afterClass() {
+        R++;
+    }
+}
diff --git a/src/test/java/test/test111/Test1.java b/src/test/java/test/test111/Test1.java
new file mode 100644
index 0000000..0fbb0ea
--- /dev/null
+++ b/src/test/java/test/test111/Test1.java
@@ -0,0 +1,12 @@
+package test.test111;
+
+import org.testng.annotations.Test;
+
+import junit.framework.Assert;
+
+public class Test1 extends AbstractTest {
+    @Test
+    public void test() {
+    	Assert.assertEquals(0, AbstractTest.R);
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/test/testnames/TestNamesFeature.java b/src/test/java/test/testnames/TestNamesFeature.java
new file mode 100644
index 0000000..54b4c56
--- /dev/null
+++ b/src/test/java/test/testnames/TestNamesFeature.java
@@ -0,0 +1,12 @@
+package test.testnames;
+
+import org.testng.annotations.Test;
+
+public class TestNamesFeature {
+
+    @Test
+    public void sampleOutputTest1() {}
+
+    @Test
+    public void sampleOutputTest2() {}
+}
diff --git a/src/test/java/test/testnames/TestNamesTest.java b/src/test/java/test/testnames/TestNamesTest.java
new file mode 100644
index 0000000..5a7cdc4
--- /dev/null
+++ b/src/test/java/test/testnames/TestNamesTest.java
@@ -0,0 +1,25 @@
+package test.testnames;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import test.SimpleBaseTest;
+
+import java.util.Collections;
+
+public class TestNamesTest extends SimpleBaseTest {
+
+    @Test
+    public void checkWithoutChildSuites() {
+        TestListenerAdapter tla = new TestListenerAdapter();
+        TestNG tng = create();
+        tng.setTestNames(Collections.singletonList("testGroup2"));
+        tng.setTestSuites(Collections.singletonList(getPathToResource("testnames/upstream-suite.xml")));
+        tng.addListener(tla);
+        tng.run();
+        Assert.assertEquals(tla.getFailedTests().size(), 0);
+        Assert.assertEquals(tla.getPassedTests().size(), 1);
+        Assert.assertEquals(tla.getPassedTests().get(0).getMethod().getMethodName(), "sampleOutputTest2");
+    }
+}
diff --git a/src/test/java/test/testng106/FailingSuiteFixture.java b/src/test/java/test/testng106/FailingSuiteFixture.java
new file mode 100644
index 0000000..2144de0
--- /dev/null
+++ b/src/test/java/test/testng106/FailingSuiteFixture.java
@@ -0,0 +1,16 @@
+package test.testng106;

+

+import org.testng.annotations.BeforeSuite;

+

+

+/**

+ * TESTNG-106: failing @BeforeSuite doesn't skip all tests

+ */

+public class FailingSuiteFixture {

+  static int s_invocations = 0;

+

+  @BeforeSuite

+  public void failingBeforeSuite() {

+    throw new RuntimeException();

+  }

+}

diff --git a/src/test/java/test/testng106/Test1.java b/src/test/java/test/testng106/Test1.java
new file mode 100644
index 0000000..daa5cf4
--- /dev/null
+++ b/src/test/java/test/testng106/Test1.java
@@ -0,0 +1,15 @@
+package test.testng106;

+

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class Test1 {

+  @Test

+  public void method1() {

+    System.out.println("method1");

+    FailingSuiteFixture.s_invocations++;

+  }

+}

diff --git a/src/test/java/test/testng106/Test2.java b/src/test/java/test/testng106/Test2.java
new file mode 100644
index 0000000..0d5ba0f
--- /dev/null
+++ b/src/test/java/test/testng106/Test2.java
@@ -0,0 +1,21 @@
+package test.testng106;

+

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class Test2 {

+  @Test

+  public void method2() {

+    System.out.println("method2");

+    FailingSuiteFixture.s_invocations++;

+  }

+

+  @Test

+  public void method3() {

+    System.out.println("method3");

+    FailingSuiteFixture.s_invocations++;

+  }

+}

diff --git a/src/test/java/test/testng106/TestNG106.java b/src/test/java/test/testng106/TestNG106.java
new file mode 100644
index 0000000..171463a
--- /dev/null
+++ b/src/test/java/test/testng106/TestNG106.java
@@ -0,0 +1,25 @@
+package test.testng106;
+
+import java.util.Arrays;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+
+import test.SimpleBaseTest;
+
+public class TestNG106 extends SimpleBaseTest {
+  @Test
+  public void testFailingBeforeSuiteShouldSkipAllTests() throws Exception {
+    TestNG tng = create();
+    XmlSuite s = createXmlSuite("TESTNG-106");
+    createXmlTest(s, "myTest1", FailingSuiteFixture.class.getName(), Test1.class.getName());
+    createXmlTest(s, "myTest2", Test1.class.getName());
+    createXmlTest(s, "myTest3", Test2.class.getName());
+    createXmlTest(s, "myTest-last", Test2.class.getName());
+    tng.setXmlSuites(Arrays.asList(s));
+    tng.run();
+    Assert.assertEquals(FailingSuiteFixture.s_invocations, 0, "@BeforeSuite has failed. All tests should be skipped.");
+  }
+}
diff --git a/src/test/java/test/testng109/SkippedTestWithExpectedExceptionTest.java b/src/test/java/test/testng109/SkippedTestWithExpectedExceptionTest.java
new file mode 100644
index 0000000..d757a26
--- /dev/null
+++ b/src/test/java/test/testng109/SkippedTestWithExpectedExceptionTest.java
@@ -0,0 +1,27 @@
+package test.testng109;

+

+import org.testng.annotations.BeforeClass;

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class SkippedTestWithExpectedExceptionTest {

+  @BeforeClass

+  public void setup() {

+    throw new RuntimeException("test-exception");

+  }

+

+  @Test

+  public void test1()

+  {

+//   empty

+  }

+

+  @Test(expectedExceptions={OutOfMemoryError.class})

+  public void test2()

+  {

+//  empty

+  }

+}

diff --git a/src/test/java/test/testng173/ClassA.java b/src/test/java/test/testng173/ClassA.java
new file mode 100644
index 0000000..1a7091a
--- /dev/null
+++ b/src/test/java/test/testng173/ClassA.java
@@ -0,0 +1,15 @@
+package test.testng173;

+

+import org.testng.annotations.Test;

+

+public class ClassA {

+

+	@Test

+	public void test1() {

+	}

+

+	@Test(dependsOnMethods = "test1")

+	public void test2() {

+	}

+

+}

diff --git a/src/test/java/test/testng173/ClassB.java b/src/test/java/test/testng173/ClassB.java
new file mode 100644
index 0000000..7adb5ab
--- /dev/null
+++ b/src/test/java/test/testng173/ClassB.java
@@ -0,0 +1,18 @@
+package test.testng173;

+

+import org.testng.annotations.*;

+

+public class ClassB {

+	@Test

+	public void testX() {

+	}

+

+	@Test(dependsOnMethods = "testX")

+	public void test2() {

+	}

+

+	@Test(dependsOnMethods = "test2")

+	public void test1() {

+	}

+

+}
\ No newline at end of file
diff --git a/src/test/java/test/testng173/TestNG173Test.java b/src/test/java/test/testng173/TestNG173Test.java
new file mode 100644
index 0000000..b2b39b0
--- /dev/null
+++ b/src/test/java/test/testng173/TestNG173Test.java
@@ -0,0 +1,65 @@
+package test.testng173;
+
+import java.util.Arrays;
+
+import org.testng.Assert;
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.SimpleBaseTest;
+
+public class TestNG173Test extends SimpleBaseTest {
+
+	@Test
+	public void orderShouldBePreservedInMethodsWithSameNameAndInDifferentClasses() {
+		TestNG tng = create();
+		XmlSuite s = createXmlSuite("PreserveOrder");
+		XmlTest t = new XmlTest(s);
+
+		t.getXmlClasses().add(new XmlClass("test.testng173.ClassA"));
+		t.getXmlClasses().add(new XmlClass("test.testng173.ClassB"));
+
+		t.setPreserveOrder("true");
+
+		tng.setXmlSuites(Arrays.asList(s));
+
+		TestListenerAdapter tla = new TestListenerAdapter();
+		tng.addListener(tla);
+		tng.run();
+
+		// bug
+		//verifyPassedTests(tla, "test1", "test2", "testX", "test1", "test2");
+
+		// Proposed fix
+		verifyPassedTests(tla, "test1", "test2", "testX", "test2", "test1");
+	}
+
+	@Test
+	public void orderShouldBePreservedInMethodsWithSameNameAndInDifferentClassesAndDifferentPackage() {
+		TestNG tng = create();
+		XmlSuite s = createXmlSuite("PreserveOrder");
+		XmlTest t = new XmlTest(s);
+
+		t.getXmlClasses().add(new XmlClass("test.testng173.ClassA"));
+		t.getXmlClasses().add(new XmlClass("test.testng173.anotherpackage.ClassC"));
+
+		t.setPreserveOrder("true");
+
+		tng.setXmlSuites(Arrays.asList(s));
+
+		TestListenerAdapter tla = new TestListenerAdapter();
+		tng.addListener(tla);
+		tng.run();
+
+		// bug
+		//verifyPassedTests(tla, "test1", "test2", "testX", "test1", "test2");
+
+		verifyPassedTests(tla, "test1", "test2", "testX", "test2", "test1");
+	}
+
+}
diff --git a/src/test/java/test/testng173/anotherpackage/ClassC.java b/src/test/java/test/testng173/anotherpackage/ClassC.java
new file mode 100644
index 0000000..ee51d1f
--- /dev/null
+++ b/src/test/java/test/testng173/anotherpackage/ClassC.java
@@ -0,0 +1,22 @@
+package test.testng173.anotherpackage;

+

+import org.testng.annotations.Test;

+

+public class ClassC {

+

+	@Test

+	public void testX() {

+

+	}

+

+	@Test(dependsOnMethods = "testX")

+	public void test2() {

+

+	}

+

+	@Test(dependsOnMethods = "test2")

+	public void test1() {

+

+	}

+

+}
\ No newline at end of file
diff --git a/src/test/java/test/testng195/AfterMethodSampleTest.java b/src/test/java/test/testng195/AfterMethodSampleTest.java
new file mode 100644
index 0000000..59169c2
--- /dev/null
+++ b/src/test/java/test/testng195/AfterMethodSampleTest.java
@@ -0,0 +1,28 @@
+package test.testng195;
+
+import org.testng.IResultMap;
+import org.testng.ITestContext;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Method;
+
+public class AfterMethodSampleTest {
+  static boolean m_success;
+
+  @Test
+  public void pass() {
+  }
+
+  @BeforeClass
+  public void init() {
+    m_success = false;
+  }
+
+  @AfterMethod
+  public void afterMethod(ITestContext c, Method m) {
+      IResultMap map = c.getPassedTests();
+      m_success = map.size() == 1;
+  }
+}
diff --git a/src/test/java/test/testng195/AfterMethodTest.java b/src/test/java/test/testng195/AfterMethodTest.java
new file mode 100644
index 0000000..4b4648b
--- /dev/null
+++ b/src/test/java/test/testng195/AfterMethodTest.java
@@ -0,0 +1,17 @@
+package test.testng195;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+public class AfterMethodTest extends SimpleBaseTest {
+
+  @Test
+  public void testContextShouldBeInitialized() {
+    TestNG tng = create(AfterMethodSampleTest.class);
+    tng.run();
+    Assert.assertTrue(AfterMethodSampleTest.m_success);
+  }
+}
diff --git a/src/test/java/test/testng249/B.java b/src/test/java/test/testng249/B.java
new file mode 100644
index 0000000..782fa16
--- /dev/null
+++ b/src/test/java/test/testng249/B.java
@@ -0,0 +1,10 @@
+package test.testng249;
+
+import org.testng.annotations.Test;
+
+public class B extends Base {
+  @Override
+   @Test
+   public void b() {
+   }
+}
\ No newline at end of file
diff --git a/src/test/java/test/testng249/Base.java b/src/test/java/test/testng249/Base.java
new file mode 100644
index 0000000..09d59aa
--- /dev/null
+++ b/src/test/java/test/testng249/Base.java
@@ -0,0 +1,9 @@
+package test.testng249;
+
+import org.testng.annotations.Test;
+
+public class Base {
+  @Test
+  public void b() {
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/testng249/VerifyTest.java b/src/test/java/test/testng249/VerifyTest.java
new file mode 100644
index 0000000..b0b4e82
--- /dev/null
+++ b/src/test/java/test/testng249/VerifyTest.java
@@ -0,0 +1,40 @@
+package test.testng249;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlInclude;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.SimpleBaseTest;
+
+import java.util.Arrays;
+
+public class VerifyTest extends SimpleBaseTest {
+
+  @Test
+  public void verify() {
+    XmlSuite suite = new XmlSuite();
+    suite.setName("Suite");
+
+    XmlTest test = new XmlTest(suite);
+    test.setName("Test");
+    XmlClass c1 = new XmlClass(B.class);
+    c1.setIncludedMethods(Arrays.asList(new XmlInclude[] { new XmlInclude("b")}));
+    XmlClass c2 = new XmlClass(Base.class);
+    c2.setIncludedMethods(Arrays.asList(new XmlInclude[] { new XmlInclude("b")}));
+    test.setXmlClasses(Arrays.asList(new XmlClass[] { c1, c2 }));
+
+    TestNG tng = new TestNG();
+    tng.setVerbose(0);
+    tng.setXmlSuites(Arrays.asList(new XmlSuite[] { suite }));
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+    Assert.assertEquals(tla.getPassedTests().size(), 2);
+  }
+}
diff --git a/src/test/java/test/testng285/BugBase.java b/src/test/java/test/testng285/BugBase.java
new file mode 100644
index 0000000..0cdcb4b
--- /dev/null
+++ b/src/test/java/test/testng285/BugBase.java
@@ -0,0 +1,26 @@
+package test.testng285;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@Test(sequential = true)
+public class BugBase {
+  static Set<Long> m_threadIds;
+
+  @BeforeClass
+  public void setup() {
+    m_threadIds = new HashSet<>();
+  }
+
+  void log(long threadId) {
+    m_threadIds.add(threadId);
+  }
+
+  public void fbase() {
+    log(Thread.currentThread().getId());
+  }
+
+}
diff --git a/src/test/java/test/testng285/Derived.java b/src/test/java/test/testng285/Derived.java
new file mode 100644
index 0000000..10a3326
--- /dev/null
+++ b/src/test/java/test/testng285/Derived.java
@@ -0,0 +1,16 @@
+package test.testng285;
+
+import org.testng.annotations.Test;
+
+public class Derived extends BugBase {
+
+  @Test
+  public void f1() {
+    log(Thread.currentThread().getId());
+  }
+
+  @Test
+  public void f2() {
+    log(Thread.currentThread().getId());
+  }
+}
diff --git a/src/test/java/test/testng285/TestNG285Test.java b/src/test/java/test/testng285/TestNG285Test.java
new file mode 100644
index 0000000..61a6d18
--- /dev/null
+++ b/src/test/java/test/testng285/TestNG285Test.java
@@ -0,0 +1,21 @@
+package test.testng285;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+
+import test.BaseTest;
+
+public class TestNG285Test extends BaseTest {
+
+  @Test
+  public void verifyBug() {
+    addClass("test.testng285.Derived");
+    setParallel(XmlSuite.ParallelMode.METHODS);
+    setThreadCount(5);
+
+    run();
+
+    Assert.assertEquals(BugBase.m_threadIds.size(), 1);
+  }
+}
diff --git a/src/test/java/test/testng317/ClassA.java b/src/test/java/test/testng317/ClassA.java
new file mode 100755
index 0000000..c8ab64d
--- /dev/null
+++ b/src/test/java/test/testng317/ClassA.java
@@ -0,0 +1,52 @@
+package test.testng317;
+
+import org.testng.annotations.Test;
+
+
+public class ClassA {
+  @Test
+  public void sameNameA(){
+    printMethod();
+  }
+  @Test (dependsOnMethods="sameNameA")
+  public void uniqueNameB(){
+    printMethod();
+  }
+  @Test (dependsOnMethods="uniqueNameB")
+  public void uniqueNameC(){
+    printMethod();
+  }
+  @Test (dependsOnMethods="uniqueNameC")
+  public void uniqueNameD(){
+    printMethod();
+  }
+  @Test (dependsOnMethods="uniqueNameD")
+  public void sameNameE(){
+    printMethod();
+  }
+  @Test (dependsOnMethods="sameNameE")
+  public void sameNameF(){
+    printMethod();
+  }
+  @Test (dependsOnMethods="sameNameF")
+  public void sameNameG(){
+    printMethod();
+  }
+  @Test (dependsOnMethods="sameNameG")
+  public void sameNameH(){
+    printMethod();
+  }
+
+  public void nullTest(){
+    printMethod();
+  }
+  protected void printMethod() {
+    StackTraceElement[] sTrace = new Exception().getStackTrace();
+    String className = sTrace[0].getClassName();
+    String methodName = sTrace[1].getMethodName();
+
+    System.out.printf("*********** executing --- %s %s\n", className, methodName);
+
+    VerifyTest.m_methods.add(className + "." + methodName);
+  }
+}
diff --git a/src/test/java/test/testng317/ClassB.java b/src/test/java/test/testng317/ClassB.java
new file mode 100755
index 0000000..7a5e1d5
--- /dev/null
+++ b/src/test/java/test/testng317/ClassB.java
@@ -0,0 +1,39 @@
+package test.testng317;
+
+import org.testng.annotations.Test;
+
+public class ClassB {
+  @Test
+  public void sameNameAA(){
+    printMethod();
+  }
+  @Test (dependsOnMethods="sameNameAA")
+  public void uniqueNameBB(){
+    printMethod();
+  }
+  @Test (dependsOnMethods="uniqueNameBB")
+  public void uniqueNameCC(){
+    printMethod();
+  }
+  @Test (dependsOnMethods="uniqueNameCC")
+  public void uniqueNameDD(){
+    printMethod();
+  }
+  @Test (dependsOnMethods="uniqueNameDD")
+  public void sameNameE(){
+    printMethod();
+  }
+
+  public void nullTest(){
+    printMethod();
+  }
+  protected void printMethod() {
+    StackTraceElement[] sTrace = new Exception().getStackTrace();
+    String className = sTrace[0].getClassName();
+    String methodName = sTrace[1].getMethodName();
+
+    System.out.printf("*********** executing --- %s %s\n", className, methodName);
+
+    VerifyTest.m_methods.add(className + "." + methodName);
+  }
+}
diff --git a/src/test/java/test/testng317/VerifyTest.java b/src/test/java/test/testng317/VerifyTest.java
new file mode 100755
index 0000000..3b415b4
--- /dev/null
+++ b/src/test/java/test/testng317/VerifyTest.java
@@ -0,0 +1,28 @@
+package test.testng317;
+
+import org.testng.TestNG;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class VerifyTest extends SimpleBaseTest {
+  static List<String> m_methods = new ArrayList<>();
+
+  @BeforeMethod
+  public void before() {
+    m_methods = new ArrayList<>();
+  }
+
+  @Test
+  public void verify() {
+    TestNG tng = create();
+    tng.setTestClasses(new Class[] { test.testng317.ClassB.class, test.testng317.ClassA.class });
+    tng.run();
+
+    System.out.println("Methods:" + m_methods.size());
+  }
+}
diff --git a/src/test/java/test/testng37/NullParameterTest.java b/src/test/java/test/testng37/NullParameterTest.java
new file mode 100644
index 0000000..7900b87
--- /dev/null
+++ b/src/test/java/test/testng37/NullParameterTest.java
@@ -0,0 +1,18 @@
+package test.testng37;

+

+import org.testng.Assert;

+import org.testng.annotations.Parameters;

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class NullParameterTest {

+  @Test

+  @Parameters({"notnull", "nullvalue"})

+  public void nullParameter(String notNull, int mustBeNull) {

+    Assert.assertNotNull(notNull, "not null parameter expected");

+    Assert.assertNull(mustBeNull, "null parameter expected");

+  }

+}

diff --git a/src/test/java/test/testng37/testng-37.xml b/src/test/java/test/testng37/testng-37.xml
new file mode 100644
index 0000000..6c7d09d
--- /dev/null
+++ b/src/test/java/test/testng37/testng-37.xml
@@ -0,0 +1,10 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

+<suite name="TESTNG-37 Suite" verbose="2">
+  <test name="TESTNG-57">

+      <parameter name="notnull" value="null"/>

+      <parameter name="nullvalue" value="NULL"/>

+      <packages>
+          <package name="test.testng37" />
+      </packages>
+  </test>
+</suite>
\ No newline at end of file
diff --git a/src/test/java/test/testng387/FailedDPTest.java b/src/test/java/test/testng387/FailedDPTest.java
new file mode 100644
index 0000000..341df5f
--- /dev/null
+++ b/src/test/java/test/testng387/FailedDPTest.java
@@ -0,0 +1,45 @@
+package test.testng387;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * test for http://jira.opensymphony.com/browse/TESTNG-387
+ * The invocation-numbers logic in failed.xml is wrong for dataprovider and parallel=true
+ *
+ * The test will throw exception when numbers are prime, so getFailedInvocationNumbers() should be a list of prime numbers.
+ *
+ * @author freynaud
+ */
+public class FailedDPTest {
+	static final List<Integer> primes = Arrays.asList(2, 3, 5, 7);
+
+	/**
+	 * DP generating all number from 0 to 9.
+	 * */
+	@DataProvider(name = "DP", parallel = true)
+	public Iterator<Integer[]> getData() {
+		List<Integer[]> list = new ArrayList<>();
+		for (int i = 0; i < 10; i++) {
+			list.add(new Integer[] { i });
+		}
+		return list.iterator();
+	}
+
+	/**
+	 * Throws an exception for a prime number.
+	 * @throws Exception
+	 */
+	@Test(dataProvider = "DP", groups = { "DPTest" })
+	public void isNotPrime(Integer i) throws Exception {
+		if (primes.contains(i)){
+			throw new Exception(i+" is prime");
+		}
+	}
+
+}
diff --git a/src/test/java/test/testng387/TestNG387.java b/src/test/java/test/testng387/TestNG387.java
new file mode 100644
index 0000000..df68728
--- /dev/null
+++ b/src/test/java/test/testng387/TestNG387.java
@@ -0,0 +1,29 @@
+package test.testng387;
+
+import org.testng.*;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+
+import test.SimpleBaseTest;
+
+import java.util.List;
+
+import static org.testng.Assert.assertEqualsNoOrder;
+
+public class TestNG387 extends SimpleBaseTest {
+  @Test(invocationCount = 500)
+  public void testInvocationCounterIsCorrectForMethodWithDataProvider() throws Exception {
+    final TestNG tng = create(FailedDPTest.class);
+    tng.setThreadCount(1);
+    tng.setParallel(XmlSuite.ParallelMode.FALSE);
+    tng.setPreserveOrder(true);
+    final TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+    ITestNGMethod method = tla.getTestContexts().get(0).getAllTestMethods()[0];
+
+    List<Integer> failed = method.getFailedInvocationNumbers();
+    assertEqualsNoOrder(failed.toArray(), FailedDPTest.primes.toArray());
+  }
+}
diff --git a/src/test/java/test/testng56/ParallelTest.java b/src/test/java/test/testng56/ParallelTest.java
new file mode 100644
index 0000000..cba0947
--- /dev/null
+++ b/src/test/java/test/testng56/ParallelTest.java
@@ -0,0 +1,46 @@
+package test.testng56;

+

+import org.testng.annotations.AfterClass;

+import org.testng.annotations.BeforeClass;

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class ParallelTest {

+  @BeforeClass

+  public void setup() {

+    System.out.println(Thread.currentThread().getId() + ":setup");

+  }

+

+  @AfterClass

+  public void teardown() {

+    System.out.println(Thread.currentThread().getId() + ":teardown");

+  }

+

+  @Test

+  public void test1() {

+    System.out.println(Thread.currentThread().getId() + ":test1");

+  }

+

+  @Test(dependsOnMethods = {"test1"})

+  public void test2() {

+    System.out.println(Thread.currentThread().getId() + ":test2");

+  }

+

+  @Test

+  public void test3() {

+    System.out.println(Thread.currentThread().getId() + ":test3");

+  }

+

+  @Test(dependsOnMethods = {"test3"})

+  public void test4() {

+    System.out.println(Thread.currentThread().getId() + ":test4");

+  }

+

+  @Test

+  public void test5() {

+    System.out.println(Thread.currentThread().getId() + ":test5");

+  }

+}

diff --git a/src/test/java/test/testng56/testng-56.xml b/src/test/java/test/testng56/testng-56.xml
new file mode 100644
index 0000000..94cf92c
--- /dev/null
+++ b/src/test/java/test/testng56/testng-56.xml
@@ -0,0 +1,8 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

+<suite name="TESTNG-56 Suite" verbose="3" parallel="true" thread-count="3">
+  <test name="TESTNG-56">

+      <packages>
+          <package name="test.testng56" />
+      </packages>
+  </test>
+</suite>
\ No newline at end of file
diff --git a/src/test/java/test/testng59/Test1.java b/src/test/java/test/testng59/Test1.java
new file mode 100644
index 0000000..21ad93b
--- /dev/null
+++ b/src/test/java/test/testng59/Test1.java
@@ -0,0 +1,23 @@
+package test.testng59;

+

+import org.testng.Assert;

+import org.testng.annotations.AfterClass;

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class Test1 {

+  private boolean m_run= false;

+

+  @Test

+  public void test1() {

+    m_run= true;

+  }

+

+  @AfterClass

+  public void checkWasRun() {

+    Assert.assertTrue(m_run, "test1() should have been run according to testng-59.xml");

+  }

+}

diff --git a/src/test/java/test/testng59/Test2.java b/src/test/java/test/testng59/Test2.java
new file mode 100644
index 0000000..f718951
--- /dev/null
+++ b/src/test/java/test/testng59/Test2.java
@@ -0,0 +1,23 @@
+package test.testng59;

+

+import org.testng.Assert;

+import org.testng.annotations.AfterClass;

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class Test2 {

+  private boolean m_run= false;

+

+  @Test

+  public void test2() {

+    m_run= true;

+  }

+

+  @AfterClass

+  public void checkWasRun() {

+    Assert.assertTrue(m_run, "test2() should have been run according to testng-59.xml");

+  }

+}

diff --git a/src/test/java/test/testng59/testng-59.xml b/src/test/java/test/testng59/testng-59.xml
new file mode 100644
index 0000000..63185bd
--- /dev/null
+++ b/src/test/java/test/testng59/testng-59.xml
@@ -0,0 +1,17 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

+<suite name="TESTNG-59 Suite" verbose="3" >
+  <test name="TESTNG-59">
+      <classes>
+          <class name="test.testng59.Test1">
+              <methods>
+                  <include name="test1"/>
+              </methods>
+          </class>
+          <class name="test.testng59.Test2">
+              <methods>
+                  <include name="test2"/>
+              </methods>
+          </class>
+      </classes>
+  </test>
+</suite>
\ No newline at end of file
diff --git a/src/test/java/test/testng93/SingleTestTest.java b/src/test/java/test/testng93/SingleTestTest.java
new file mode 100644
index 0000000..46d03d0
--- /dev/null
+++ b/src/test/java/test/testng93/SingleTestTest.java
@@ -0,0 +1,25 @@
+package test.testng93;

+

+import org.testng.annotations.BeforeMethod;

+import org.testng.annotations.Test;

+

+

+/**

+ * This class/interface

+ */

+public class SingleTestTest {

+  @BeforeMethod(groups={"group1"})

+  public void shouldRunBefore() {

+      System.out.println("Runs before");

+  }

+

+  @Test(groups={"group1"})

+  public void theFirstActualTest() {

+      System.out.println("The first actual test");

+  }

+

+  @Test

+  public void theSecondActualTest() {

+      System.out.println("The second actual test");

+  }

+}

diff --git a/src/test/java/test/testng93/testng-93.xml b/src/test/java/test/testng93/testng-93.xml
new file mode 100644
index 0000000..13e195d
--- /dev/null
+++ b/src/test/java/test/testng93/testng-93.xml
@@ -0,0 +1,17 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

+<suite name="Bugtest" verbose="1">

+    <test name="Single test">

+        <groups>

+            <run>

+                <include name="group1"/>

+            </run>

+        </groups>

+        <classes>

+            <class name="test.testng93.SingleTestTest">

+                <methods>

+                    <include name="theFirstActualTest"/>

+                </methods>

+            </class>

+        </classes>

+    </test>

+</suite>
\ No newline at end of file
diff --git a/src/test/java/test/thread/B.java b/src/test/java/test/thread/B.java
new file mode 100644
index 0000000..d0a91be
--- /dev/null
+++ b/src/test/java/test/thread/B.java
@@ -0,0 +1,26 @@
+package test.thread;

+

+import java.util.HashMap;

+import java.util.Map;

+

+import org.testng.annotations.Test;

+import org.testng.collections.Maps;

+

+public class B {

+  public static Map<Long, Long> m_threadIds = Maps.newHashMap();

+

+  public static void setUp() {

+    m_threadIds = new HashMap<>();

+  }

+

+  @Test

+  public void f2() {

+    Long id = Thread.currentThread().getId();

+    m_threadIds.put(id, id);

+  }

+

+  private static void ppp(String s) {

+    System.out.println("[FactoryTest] " + s);

+  }

+

+}

diff --git a/src/test/java/test/thread/BaseSequentialSample.java b/src/test/java/test/thread/BaseSequentialSample.java
new file mode 100644
index 0000000..53bdb14
--- /dev/null
+++ b/src/test/java/test/thread/BaseSequentialSample.java
@@ -0,0 +1,26 @@
+package test.thread;

+

+import java.util.Map;

+

+public class BaseSequentialSample {

+

+  protected void addId(String method, long id) {

+    ppp(method + " ID:" + id);

+    getMap().put(id, id);

+  }

+

+  Map getMap() {

+    Map result = Helper.getMap(getClass().getName());

+    ppp("RETURNING MAP " + result + " THIS:" + this);

+

+    return result;

+  }

+

+  protected void ppp(String s) {

+    if (false) {

+      System.out.println("[" + getClass().getName() + " "

+          + Thread.currentThread().getId() + " "

+          + "] " + s);

+    }

+  }

+}

diff --git a/src/test/java/test/thread/BaseThreadTest.java b/src/test/java/test/thread/BaseThreadTest.java
new file mode 100644
index 0000000..0924088
--- /dev/null
+++ b/src/test/java/test/thread/BaseThreadTest.java
@@ -0,0 +1,74 @@
+package test.thread;
+
+import org.testng.Assert;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.collections.Sets;
+
+import test.SimpleBaseTest;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class BaseThreadTest extends SimpleBaseTest {
+  static private Set<Long> m_threadIds;
+  static private Map<String, Long> m_suitesMap;
+  static private List<String> m_strings;
+
+  static void initThreadLog() {
+    m_threadIds = Sets.newHashSet();
+    m_suitesMap = Maps.newHashMap();
+    m_strings = Lists.newArrayList();
+  }
+
+  protected void logString(String s) {
+    synchronized(m_strings) {
+      log("BaseThreadTest", "Logging string:" + s);
+      m_strings.add(s);
+    }
+  }
+
+  public static List<String> getStrings() {
+    return m_strings;
+  }
+
+  protected void logCurrentThread() {
+    logThread(Thread.currentThread().getId());
+  }
+
+  protected void logThread(long threadId) {
+    synchronized(m_threadIds) {
+      log("BaseThreadTest", "Logging thread:" + threadId);
+      m_threadIds.add(threadId);
+    }
+  }
+
+  protected void logSuite(String suiteName, long time) {
+    synchronized(m_suitesMap) {
+      m_suitesMap.put(suiteName, time);
+    }
+  }
+
+  static int getThreadCount() {
+    synchronized(m_threadIds) {
+      return m_threadIds.size();
+    }
+  }
+
+  static Map<String, Long> getSuitesMap() {
+    return m_suitesMap;
+  }
+
+  protected void log(String cls, String s) {
+    if (false) {
+      System.out.println("[" + cls + "] thread:" + Thread.currentThread().getId()
+          + " hash:" + hashCode() + " " + s);
+    }
+  }
+
+  protected void verifyThreads(int expected) {
+    Assert.assertEquals(getThreadCount(), expected,
+        "Ran on " + getThreadCount() + " threads instead of " + expected);
+  }
+}
diff --git a/src/test/java/test/thread/DataProviderThreadPoolSizeSampleTest.java b/src/test/java/test/thread/DataProviderThreadPoolSizeSampleTest.java
new file mode 100644
index 0000000..1b031a6
--- /dev/null
+++ b/src/test/java/test/thread/DataProviderThreadPoolSizeSampleTest.java
@@ -0,0 +1,70 @@
+package test.thread;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class DataProviderThreadPoolSizeSampleTest extends BaseThreadTest {
+  @BeforeClass(alwaysRun = true)
+  public void setUp() {
+    log(getClass().getName(), "Init log ids");
+    initThreadLog();
+  }
+
+  @DataProvider(parallel = true)
+  public Object[][] parallelDataProvider() {
+    return createArray();
+  }
+
+  @DataProvider
+  public Object[][] sequentialDataProvider() {
+    return createArray();
+  }
+
+  private Object[][] createArray() {
+    int i = 0;
+    return new Object[][] {
+        new Object[] { i++ },
+        new Object[] { i++ },
+        new Object[] { i++ },
+        new Object[] { i++ },
+        new Object[] { i++ },
+        new Object[] { i++ },
+        new Object[] { i++ },
+        new Object[] { i++ },
+        new Object[] { i++ },
+        new Object[] { i++ },
+        new Object[] { i++ },
+        new Object[] { i++ },
+        new Object[] { i++ },
+        new Object[] { i++ },
+        new Object[] { i++ },
+        new Object[] { i++ },
+        new Object[] { i++ },
+        new Object[] { i++ },
+        new Object[] { i++ },
+        new Object[] { i++ },
+    };
+  }
+
+  @Test(dataProvider = "sequentialDataProvider", groups = "sequential")
+  public void fSequential(Integer p) {
+    long n = Thread.currentThread().getId();
+    log(getClass().getName(), "Sequential");
+    logThread(n);
+  }
+
+  @Test(dataProvider = "parallelDataProvider", groups = "parallel")
+  public void fParallel(Integer p) {
+    long n = Thread.currentThread().getId();
+    log(getClass().getName(), "Parallel");
+    logThread(n);
+  }
+
+//  @Test(dependsOnMethods = {"f1"})
+//  public void verify() {
+//    int expected = 3;
+//    Assert.assertEquals(m_threadIds.size(), expected,
+//        "Should have run on " + expected + " threads but ran on " + m_threadIds.size());
+//  }
+}
diff --git a/src/test/java/test/thread/DataProviderThreadPoolSizeTest.java b/src/test/java/test/thread/DataProviderThreadPoolSizeTest.java
new file mode 100644
index 0000000..45083f6
--- /dev/null
+++ b/src/test/java/test/thread/DataProviderThreadPoolSizeTest.java
@@ -0,0 +1,35 @@
+package test.thread;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+public class DataProviderThreadPoolSizeTest extends SimpleBaseTest {
+
+  @Test
+  public void shouldUseDefaultDataProviderThreadCount() {
+    TestNG tng = create(DataProviderThreadPoolSizeSampleTest.class);
+    tng.setGroups("parallel");
+    tng.run();
+    Assert.assertEquals(DataProviderThreadPoolSizeSampleTest.getThreadCount(), 10);
+  }
+
+  @Test
+  public void shouldNotUseThreadsIfNotUsingParallel() {
+    TestNG tng = create(DataProviderThreadPoolSizeSampleTest.class);
+    tng.setGroups("sequential");
+    tng.run();
+    Assert.assertEquals(DataProviderThreadPoolSizeSampleTest.getThreadCount(), 1);
+  }
+
+  @Test
+  public void shouldUseSpecifiedDataProviderThreadCount() {
+    TestNG tng = create(DataProviderThreadPoolSizeSampleTest.class);
+    tng.setGroups("parallel");
+    tng.setDataProviderThreadCount(3);
+    tng.run();
+    Assert.assertEquals(DataProviderThreadPoolSizeSampleTest.getThreadCount(), 3);
+  }
+}
diff --git a/src/test/java/test/thread/FactorySampleTest.java b/src/test/java/test/thread/FactorySampleTest.java
new file mode 100644
index 0000000..4137781
--- /dev/null
+++ b/src/test/java/test/thread/FactorySampleTest.java
@@ -0,0 +1,18 @@
+package test.thread;
+
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+
+
+@Test
+public class FactorySampleTest {
+
+  @Factory
+  public Object[] init() {
+    return new Object[] {
+        new B(),
+        new B(),
+    };
+  }
+}
+
diff --git a/src/test/java/test/thread/FactoryTest.java b/src/test/java/test/thread/FactoryTest.java
new file mode 100644
index 0000000..fbf8f90
--- /dev/null
+++ b/src/test/java/test/thread/FactoryTest.java
@@ -0,0 +1,60 @@
+package test.thread;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+
+public class FactoryTest {
+
+  @Test
+  /**
+   * In non-parallel mode, we should only have one thread id
+   * for the two methods invoked on B.
+   */
+  public void verifyFactoryNotParallel() {
+    runTest(null, 1);
+  }
+
+  /**
+   * In parallel mode "methods", we should have as many thread id's
+   * as there are test methods on B (2).
+   */
+  @Test
+  public void verifyFactoryParallelMethods() {
+    runTest(XmlSuite.ParallelMode.METHODS, 2);
+  }
+
+  @Test
+  public void verifyFactoryParallelTests() {
+    runTest(XmlSuite.ParallelMode.TESTS, 1);
+  }
+
+  private void runTest(XmlSuite.ParallelMode parallelMode, int expectedThreadIdCount) {
+    TestNG tng = new TestNG();
+    tng.setVerbose(0);
+    tng.setTestClasses(new Class[] { FactorySampleTest.class});
+    if (parallelMode != null) {
+      tng.setParallel(parallelMode);
+    }
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+
+    B.setUp();
+    tng.run();
+
+    Assert.assertEquals(tla.getPassedTests().size(), 2);
+    Assert.assertEquals(B.m_threadIds.size(), expectedThreadIdCount);
+
+//    ppp("# TESTS RUN " + tla.getPassedTests().size()
+//        + " ID:" + B.m_threadIds.size());
+  }
+
+  private void ppp(String string) {
+    System.out.println("[FactoryTest] " + string);
+  }
+
+
+
+}
diff --git a/src/test/java/test/thread/Helper.java b/src/test/java/test/thread/Helper.java
new file mode 100644
index 0000000..aa944ac
--- /dev/null
+++ b/src/test/java/test/thread/Helper.java
@@ -0,0 +1,28 @@
+package test.thread;

+

+import java.util.HashMap;

+import java.util.Map;

+

+public class Helper {

+  private static Map<String, Map<Long, Long>> m_maps = new HashMap<>();

+

+  public static Map<Long, Long> getMap(String className) {

+    synchronized(m_maps) {

+      Map<Long, Long> result = m_maps.get(className);

+      if (result == null) {

+        // TODO a synchronizedMap will break MultiThreadedDependentSampleTest

+        // a not synchronizedMap will __sometimes__ break ParallelITestTest

+        //result = Collections.synchronizedMap(new HashMap<Long, Long>());

+        result = new HashMap<>();

+        m_maps.put(className, result);

+      }

+      return result;

+    }

+//    System.out.println("Putting class:" + className + " result:" + result);

+

+  }

+

+  public static void reset() {

+    m_maps = new HashMap<>();

+  }

+}

diff --git a/src/test/java/test/thread/MultiThreadedDependentSampleTest.java b/src/test/java/test/thread/MultiThreadedDependentSampleTest.java
new file mode 100644
index 0000000..3227751
--- /dev/null
+++ b/src/test/java/test/thread/MultiThreadedDependentSampleTest.java
@@ -0,0 +1,135 @@
+package test.thread;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+
+import java.util.List;
+
+public class MultiThreadedDependentSampleTest {
+  public static List<String> m_methods = Lists.newArrayList();
+
+  @BeforeClass
+  public void bc() {
+//    log("beforeClass");
+  }
+
+  @AfterClass
+  public void ac() {
+//    log("afterClass");
+  }
+
+  @BeforeMethod
+  public void bm() {
+//    log("beforeMethod");
+  }
+
+  @AfterMethod
+  public void am() {
+//    log("afterMethod");
+  }
+
+  @Test(groups = "1")
+  public void a1() {
+    logThread();
+    log("a1");
+  }
+
+  @Test(groups = "1")
+  public void a2() {
+    logThread();
+    log("a2");
+  }
+
+  @Test(groups = "1")
+  public void a3() {
+    logThread();
+    log("a3");
+  }
+
+  @Test(groups = "2", dependsOnGroups = "1")
+  public void b1() {
+    logThread();
+    log("b1");
+  }
+
+  @Test(groups = "2", dependsOnGroups = "1")
+  public void b2() {
+    logThread();
+    log("b2");
+  }
+
+  @Test(groups = "2", dependsOnGroups = "1")
+  public void b3() {
+    logThread();
+    log("b3");
+  }
+
+  @Test(groups = "2", dependsOnGroups = "1")
+  public void b4() {
+    logThread();
+    log("b4");
+  }
+
+  @Test(groups = "2", dependsOnGroups = "1")
+  public void b5() {
+    logThread();
+    log("b5");
+  }
+
+  @Test(dependsOnGroups = "2")
+  public void c1() {
+    logThread();
+    log("c1");
+  }
+
+  @Test(dependsOnGroups = { "1" })
+  public void d() {
+    logThread();
+    log("d");
+  }
+
+  @Test
+  public void x() {
+    log("x");
+  }
+
+  @Test
+  public void y() {
+    log("y");
+  }
+
+  @Test
+  public void z() {
+    log("z");
+  }
+
+  @Test
+  public void t() {
+    log("t");
+  }
+
+  //  @Test(groups = "mytest", dependsOnMethods = "g")
+//  public void f() {
+//  }
+//
+//
+//  @AfterMethod
+//  public void after() {
+//  }
+
+  private void logThread() {
+    long id = Thread.currentThread().getId();
+    Helper.getMap(getClass().getName()).put(id, id);
+  }
+
+  private void log(String string) {
+    synchronized(m_methods) {
+      m_methods.add(string);
+    }
+  }
+
+}
diff --git a/src/test/java/test/thread/MultiThreadedDependentTest.java b/src/test/java/test/thread/MultiThreadedDependentTest.java
new file mode 100644
index 0000000..b6cfd7a
--- /dev/null
+++ b/src/test/java/test/thread/MultiThreadedDependentTest.java
@@ -0,0 +1,82 @@
+package test.thread;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+import org.testng.xml.XmlSuite;
+
+import test.SimpleBaseTest;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Test that classes with dependent methods are still run in different threads
+ * and in the correct order.
+ */
+public class MultiThreadedDependentTest extends SimpleBaseTest {
+
+  /**
+   * Make sure that the topological order is preserved and that if
+   * the TestNG runner is configured to run n threads, the dependent
+   * methods are using these n threads.
+   */
+  private void assertOrder(List<String> methods) {
+    List<String> expectedMethods = Arrays.asList(new String[] {
+      "a1", "a2", "a3", "b1", "b2", "b3", "b4", "b5", "c1", "d", "x", "y", "z", "t"
+    });
+    int size = expectedMethods.size();
+    Assert.assertEquals(methods.size(), size);
+    for (String em : expectedMethods) {
+      Assert.assertTrue(methods.contains(em));
+    }
+    Map<String, Boolean> map = Maps.newHashMap();
+    for (String m : methods) {
+      map.put(m, Boolean.TRUE);
+      if ("b1".equals(m) || "b2".equals(m) || "b3".equals(m) || "b4".equals(m) || "b5".equals(m)) {
+        Assert.assertTrue(map.get("a1"));
+        Assert.assertTrue(map.get("a2"));
+        Assert.assertTrue(map.get("a3"));
+      }
+      if ("d".equals(m)) {
+        Assert.assertTrue(map.get("a1"));
+        Assert.assertTrue(map.get("a2"));
+      }
+      if ("c1".equals(m)) {
+        Assert.assertTrue(map.get("b1"));
+        Assert.assertTrue(map.get("b2"));
+      }
+    }
+    Assert.assertEquals(map.size(), size);
+    for (Boolean val : map.values()) {
+      Assert.assertTrue(val);
+    }
+  }
+
+  @Test
+  public void test2Threads() {
+    test(2);
+  }
+
+  @Test
+  public void test3Threads() {
+    test(3);
+  }
+
+  private void test(int threadCount) {
+    Helper.reset();
+    MultiThreadedDependentSampleTest.m_methods = Lists.newArrayList();
+    TestNG tng = create(MultiThreadedDependentSampleTest.class);
+    tng.setThreadCount(threadCount);
+    tng.setParallel(XmlSuite.ParallelMode.METHODS);
+    Map<Long, Long> map = Helper.getMap(MultiThreadedDependentSampleTest.class.getName());
+    synchronized(map) {
+      tng.run();
+      Assert.assertTrue(map.size() > 1, "Map size:" + map.size() + " expected more than 1");
+      assertOrder(MultiThreadedDependentSampleTest.m_methods);
+    }
+  }
+}
diff --git a/src/test/java/test/thread/ParallelSuiteTest.java b/src/test/java/test/thread/ParallelSuiteTest.java
new file mode 100644
index 0000000..0004a73
--- /dev/null
+++ b/src/test/java/test/thread/ParallelSuiteTest.java
@@ -0,0 +1,120 @@
+package test.thread;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+public class ParallelSuiteTest extends SimpleBaseTest {
+
+  @Test
+  public void suitesShouldRunInParallel1() {
+    runTest(5, 2, 2, null, Arrays.asList(
+        getPathToResource("suite-parallel-1.xml"),
+        getPathToResource("suite-parallel-2.xml")));
+  }
+
+  @Test
+  public void suitesShouldRunInParallel2() {
+    runTest(5, 3, 3, null, Arrays.asList(
+        getPathToResource("suite-parallel-0.xml")));
+  }
+
+  @Test(description = "Number of threads (2) is less than number of suites (3)")
+  public void suitesShouldRunInParallel3() {
+    final int SUITE_THREAD_POOL_SIZE = 2;
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG tng = create();
+    tng.setSuiteThreadPoolSize(SUITE_THREAD_POOL_SIZE);
+    tng.setTestSuites(Arrays.asList(getPathToResource("suite-parallel-0.xml")));
+    tng.addListener(tla);
+
+    BaseThreadTest.initThreadLog();
+    tng.run(); //Shouldn't not deadlock
+    Assert.assertEquals(BaseThreadTest.getThreadCount(), SUITE_THREAD_POOL_SIZE);
+  }
+
+  private void runTest(int suiteThreadPoolSize, int expectedThreadCount,
+        int expectedSuiteCount, Boolean randomizeSuites, List<String> paths) {
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG tng = create();
+    tng.setSuiteThreadPoolSize(suiteThreadPoolSize);
+    tng.setTestSuites(paths);
+    tng.addListener(tla);
+    if (null != randomizeSuites) {
+      tng.setRandomizeSuites(randomizeSuites);
+    }
+
+    BaseThreadTest.initThreadLog();
+    tng.run();
+
+    Assert.assertEquals(BaseThreadTest.getThreadCount(), expectedThreadCount,
+        "Thread count expected:" + expectedThreadCount
+        + " actual:" + BaseThreadTest.getThreadCount());
+    Assert.assertEquals(BaseThreadTest.getSuitesMap().keySet().size(), expectedSuiteCount);
+  }
+
+  @Test
+  public void suitesShouldRunInParallel4() {
+    runTest(10, 5, 5, null, Arrays.asList(
+        getPathToResource("parallel-suites/suite-parallel-1.xml"),
+        getPathToResource("parallel-suites/suite-parallel-2.xml"),
+        getPathToResource("parallel-suites/suite-parallel-2-1.xml"),
+        getPathToResource("parallel-suites/suite-parallel-2-2.xml")));
+  }
+
+  @Test
+  public void suitesShouldRunInParallel5() {
+    runTest(5, 5, 7, null, Arrays.asList(
+        getPathToResource("parallel-suites/suite-parallel-0.xml")));
+  }
+
+  @Test(description = "Number of threads (2) is less than level of suites (3)")
+  public void suitesShouldRunInParallel6() {
+    runTest(2, 2, 7, null, Arrays.asList(
+          getPathToResource("parallel-suites/suite-parallel-0.xml")));
+  }
+
+  @Test(description = "If suiteThreadPoolSize and randomizeSuites are not specified" +
+  		" suites should run in order specified in XML")
+  public void suitesShouldRunInOrder() {
+    TestListenerAdapter tla = new TestListenerAdapter();
+    TestNG tng = create();
+    tng.setTestSuites(Arrays.asList(getPathToResource("suite-parallel-0.xml")));
+    tng.addListener(tla);
+    BaseThreadTest.initThreadLog();
+    tng.run();
+
+    Map<String, Long> suitesMap = BaseThreadTest.getSuitesMap();
+    Assert.assertEquals(BaseThreadTest.getThreadCount(), 1);
+    Assert.assertEquals(suitesMap.keySet().size(), 3);
+
+    final String SUITE_NAME_PREFIX = "Suite Parallel ";
+    if (suitesMap.get(SUITE_NAME_PREFIX + 1) > suitesMap.get(SUITE_NAME_PREFIX + 2)) {
+      Assert.fail("Suite " + (SUITE_NAME_PREFIX + 1) + " should have run before "
+          + (SUITE_NAME_PREFIX + 2));
+    }
+    Assert.assertTrue(suitesMap.get(SUITE_NAME_PREFIX + 2)
+          <= suitesMap.get(SUITE_NAME_PREFIX + 0));
+
+  }
+
+  @Test(description = "Number of threads (1) is less than number of levels of suites (2)")
+  public void suitesShouldRun1() {
+    runTest(1, 1, 3, true, Arrays.asList(
+          getPathToResource("suite-parallel-0.xml")));
+
+//    runTest(1, 1, 7, true, Arrays.asList(
+//          getPathToResource("parallel-suites/suite-parallel-0.xml")));
+//
+//    runTest(2, 2, 7, true, Arrays.asList(
+//          getPathToResource("parallel-suites/suite-parallel-0.xml")));
+  }
+
+}
diff --git a/src/test/java/test/thread/ParallelTestTest.java b/src/test/java/test/thread/ParallelTestTest.java
new file mode 100644
index 0000000..9cbc67d
--- /dev/null
+++ b/src/test/java/test/thread/ParallelTestTest.java
@@ -0,0 +1,100 @@
+package test.thread;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.BaseTest;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ParallelTestTest extends BaseTest {
+
+  @Test
+  public void verifyParallelNone() {
+    verifyExpected(XmlSuite.ParallelMode.NONE, 1);
+  }
+
+  @Test
+  public void verifyParallelTests() {
+    verifyExpected(XmlSuite.ParallelMode.TESTS, 2);
+  }
+
+  @Test
+  public void verifyParallelMethods() {
+    verifyExpected(XmlSuite.ParallelMode.METHODS, 4);
+  }
+
+  @Test
+  public void verifyParallelClasses() {
+    verifyExpected(XmlSuite.ParallelMode.CLASSES, 2);
+  }
+
+  @Test
+  public void verifyParallelClassesWithFactory() {
+    verifyExpected(XmlSuite.ParallelMode.INSTANCES, 2, ParallelWithFactorySampleTest.class.getName());
+  }
+
+  @Test
+  public void verifyNonParallelClassesWithFactory() {
+    verifyExpected(XmlSuite.ParallelMode.NONE, 1, ParallelWithFactorySampleTest.class.getName());
+  }
+
+  public static final String CLASS1 = "test.thread.Test1Test";
+  public static final String CLASS2 = "test.thread.Test2Test";
+
+  private void createTest(XmlSuite xmlSuite, String className) {
+    XmlTest result = new XmlTest(xmlSuite);
+    List<XmlClass> classes = result.getXmlClasses();
+    XmlClass xmlClass = new XmlClass(className);
+    classes.add(xmlClass);
+  }
+
+  private void verifyExpected(XmlSuite.ParallelMode parallelMode, int expectedThreadCount) {
+    verifyExpected(parallelMode, expectedThreadCount, CLASS1, CLASS2);
+  }
+
+  private void verifyExpected(XmlSuite.ParallelMode parallelMode, int expectedThreadCount,
+      String... classNames) {
+    XmlSuite xmlSuite = new XmlSuite();
+    xmlSuite.setName("ParallelTestTest");
+    xmlSuite.setParallel(parallelMode);
+    for (String cn : classNames) {
+      createTest(xmlSuite, cn);
+    }
+
+    TestNG tng = new TestNG();
+    tng.setVerbose(0);
+    tng.setXmlSuites(Arrays.asList(new XmlSuite[] { xmlSuite }));
+
+    Helper.reset();
+
+    tng.run();
+
+    List<Map<Long, Long>> maps = Lists.newArrayList();
+    for (String c : classNames) {
+      maps.add(Helper.getMap(c));
+    };
+
+    Map<Long, Long> mergedMap = new HashMap<>();
+    for (Map<Long, Long>m : maps) {
+      mergedMap.putAll(m);
+    }
+
+    Assert.assertEquals(mergedMap.size(), expectedThreadCount);
+  }
+
+  private static void ppp(String s) {
+    if (false) {
+      System.out.println("[SequentialTest] " + s);
+    }
+  }
+
+}
diff --git a/src/test/java/test/thread/ParallelWithFactorySampleTest.java b/src/test/java/test/thread/ParallelWithFactorySampleTest.java
new file mode 100644
index 0000000..ee59e6e
--- /dev/null
+++ b/src/test/java/test/thread/ParallelWithFactorySampleTest.java
@@ -0,0 +1,38 @@
+package test.thread;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+
+public class ParallelWithFactorySampleTest extends BaseSequentialSample {
+  private int m_n;
+
+  @DataProvider
+  public static Object[][] dp() {
+    return new Object[][] {
+        new Object[] { 42 },
+        new Object[] { 43 }
+    };
+  }
+
+  @Factory(dataProvider = "dp")
+  public ParallelWithFactorySampleTest(int n) {
+    m_n = n;
+  }
+
+
+  protected int getN() {
+    return m_n;
+  }
+
+
+  @Test
+  public void f1() {
+    addId("f1 " + getN(), Thread.currentThread().getId());
+  }
+
+  @Test
+  public void f2() {
+    addId("f2", Thread.currentThread().getId());
+  }
+}
diff --git a/src/test/java/test/thread/Sample1.java b/src/test/java/test/thread/Sample1.java
new file mode 100644
index 0000000..3af97b3
--- /dev/null
+++ b/src/test/java/test/thread/Sample1.java
@@ -0,0 +1,11 @@
+package test.thread;
+
+import org.testng.annotations.Test;
+
+public class Sample1 extends BaseThreadTest {
+
+  @Test
+  public void s1() {
+    logThread(Thread.currentThread().getId());
+  }
+}
diff --git a/src/test/java/test/thread/Sample2.java b/src/test/java/test/thread/Sample2.java
new file mode 100644
index 0000000..82ce13d
--- /dev/null
+++ b/src/test/java/test/thread/Sample2.java
@@ -0,0 +1,18 @@
+package test.thread;
+
+import org.testng.ITestContext;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class Sample2 extends BaseThreadTest {
+
+  @BeforeMethod
+  public void before(ITestContext ctx) {
+    logSuite(ctx.getSuite().getName(), System.currentTimeMillis());
+  }
+
+  @Test
+  public void s1() {
+    logThread(Thread.currentThread().getId());
+  }
+}
diff --git a/src/test/java/test/thread/SequentialSample2Test.java b/src/test/java/test/thread/SequentialSample2Test.java
new file mode 100644
index 0000000..014c5ee
--- /dev/null
+++ b/src/test/java/test/thread/SequentialSample2Test.java
@@ -0,0 +1,20 @@
+package test.thread;

+

+import org.testng.annotations.Test;

+

+@Test(sequential = true)

+public class SequentialSample2Test extends BaseSequentialSample {

+

+  public void f1() {

+    addId("SequentialSample2Test.f1()", Thread.currentThread().getId());

+  }

+

+  public void f2() {

+    addId("SequentialSample2Test.f2()", Thread.currentThread().getId());

+  }

+

+  public void f3() {

+    addId("SequentialSample2Test.f3()", Thread.currentThread().getId());

+  }

+

+}

diff --git a/src/test/java/test/thread/SequentialSample3Test.java b/src/test/java/test/thread/SequentialSample3Test.java
new file mode 100644
index 0000000..79844fd
--- /dev/null
+++ b/src/test/java/test/thread/SequentialSample3Test.java
@@ -0,0 +1,20 @@
+package test.thread;

+

+import org.testng.annotations.Test;

+

+@Test(sequential = true)

+public class SequentialSample3Test extends BaseSequentialSample {

+

+  public void f1() {

+    addId("SequentialSample3Test.f1()", Thread.currentThread().getId());

+  }

+

+  public void f2() {

+    addId("SequentialSample3Test.f2()", Thread.currentThread().getId());

+  }

+

+  public void f3() {

+    addId("SequentialSample3Test.f3()", Thread.currentThread().getId());

+  }

+

+}

diff --git a/src/test/java/test/thread/SequentialSampleTest.java b/src/test/java/test/thread/SequentialSampleTest.java
new file mode 100644
index 0000000..9a2f424
--- /dev/null
+++ b/src/test/java/test/thread/SequentialSampleTest.java
@@ -0,0 +1,20 @@
+package test.thread;

+

+import org.testng.annotations.Test;

+

+@Test(sequential = true)

+public class SequentialSampleTest extends BaseSequentialSample {

+

+  public void f1() {

+    addId("SequentialSampleTest.f1()", Thread.currentThread().getId());

+  }

+

+  public void f2() {

+    addId("SequentialSampleTest.f2()", Thread.currentThread().getId());

+  }

+

+  public void f3() {

+    addId("SequentialSampleTest.f3()", Thread.currentThread().getId());

+  }

+

+}

diff --git a/src/test/java/test/thread/SequentialTest.java b/src/test/java/test/thread/SequentialTest.java
new file mode 100644
index 0000000..52e59a2
--- /dev/null
+++ b/src/test/java/test/thread/SequentialTest.java
@@ -0,0 +1,101 @@
+package test.thread;

+

+import org.testng.Assert;

+import org.testng.annotations.Test;

+import org.testng.xml.XmlSuite;

+

+import test.BaseTest;

+

+import java.util.HashMap;

+import java.util.Map;

+

+public class SequentialTest extends BaseTest {

+

+  @Test

+  public void verifySequential1() {

+    verifySequential(1);

+  }

+

+  @Test

+  public void verifySequential2() {

+    verifySequential(2);

+  }

+

+  @Test

+  public void verifySequential3() {

+    verifySequential(3);

+  }

+

+  @Test

+  public void verifySingleThreaded1() {

+    verifySingleThreaded(1);

+  }

+

+  @Test

+  public void verifySingleThreaded2() {

+    verifySingleThreaded(2);

+  }

+

+  @Test

+  public void verifySingleThreaded3() {

+    verifySingleThreaded(3);

+  }

+

+  public void verifySequential(int threadCount) {

+    runTest(threadCount,

+        SequentialSampleTest.class.getName(),

+        SequentialSample2Test.class.getName(),

+        SequentialSample3Test.class.getName());

+  }

+

+  public void verifySingleThreaded(int threadCount) {

+    runTest(threadCount,

+        SingleThreadedSampleTest.class.getName(),

+        SingleThreadedSample2Test.class.getName(),

+        SingleThreadedSample3Test.class.getName());

+  }

+

+  private void runTest(int threadCount, String... classes) {

+    Helper.reset();

+

+    for (String c : classes) {

+      addClass(c);

+    }

+    setParallel(XmlSuite.ParallelMode.METHODS);

+    setThreadCount(threadCount);

+

+    run();

+

+    Map<Long, Long>[] maps = new Map[] {

+        Helper.getMap(classes[0]),

+        Helper.getMap(classes[1]),

+        Helper.getMap(classes[2]),

+    };

+

+    for(Map m : maps) {

+      Assert.assertEquals(m.size(), 1);

+    }

+

+    long[] ids = new long[] {

+            maps[0].keySet().iterator().next(),

+            maps[1].keySet().iterator().next(),

+            maps[2].keySet().iterator().next(),

+    };

+    Map<Long, Long> verifyMap = new HashMap<>();

+

+    for (long id : ids) {

+      verifyMap.put(id, id);

+    }

+

+    Assert.assertEquals(verifyMap.size(), threadCount);

+

+    ppp("COUNT:" + threadCount  + " THREAD ID'S:" + ids[0] + " " + ids[1] + " " + ids[2]);

+  }

+

+  private static void ppp(String s) {

+    if (false) {

+      System.out.println("[SequentialTest] " + s);

+    }

+  }

+

+}

diff --git a/src/test/java/test/thread/SingleThreadedSample2Test.java b/src/test/java/test/thread/SingleThreadedSample2Test.java
new file mode 100644
index 0000000..37931b2
--- /dev/null
+++ b/src/test/java/test/thread/SingleThreadedSample2Test.java
@@ -0,0 +1,23 @@
+package test.thread;

+

+import org.testng.annotations.Test;

+

+@Test(singleThreaded = true)

+public class SingleThreadedSample2Test extends BaseSequentialSample {

+

+  @Test

+  public void f1() {

+    addId("SingleThreadedSample2Test.f1()", Thread.currentThread().getId());

+  }

+

+  @Test

+  public void f2() {

+    addId("SingleThreadedSample2Test.f2()", Thread.currentThread().getId());

+  }

+

+  @Test

+  public void f3() {

+    addId("SingleThreadedSample2Test.f3()", Thread.currentThread().getId());

+  }

+

+}

diff --git a/src/test/java/test/thread/SingleThreadedSample3Test.java b/src/test/java/test/thread/SingleThreadedSample3Test.java
new file mode 100644
index 0000000..a2aa50f
--- /dev/null
+++ b/src/test/java/test/thread/SingleThreadedSample3Test.java
@@ -0,0 +1,23 @@
+package test.thread;

+

+import org.testng.annotations.Test;

+

+@Test(singleThreaded = true)

+public class SingleThreadedSample3Test extends BaseSequentialSample {

+

+  @Test

+  public void f1() {

+    addId("SingleThreadedSample3Test.f1()", Thread.currentThread().getId());

+  }

+

+  @Test // (dependsOnMethods = "f1")

+  public void f2() {

+    addId("SingleThreadedSample3Test.f2()", Thread.currentThread().getId());

+  }

+

+  @Test // (dependsOnMethods = "f2")

+  public void f3() {

+    addId("SingleThreadedSample3Test.f3()", Thread.currentThread().getId());

+  }

+

+}

diff --git a/src/test/java/test/thread/SingleThreadedSampleTest.java b/src/test/java/test/thread/SingleThreadedSampleTest.java
new file mode 100644
index 0000000..440da44
--- /dev/null
+++ b/src/test/java/test/thread/SingleThreadedSampleTest.java
@@ -0,0 +1,23 @@
+package test.thread;

+

+import org.testng.annotations.Test;

+

+@Test(singleThreaded = true)

+public class SingleThreadedSampleTest extends BaseSequentialSample {

+

+  @Test

+  public void f1() {

+    addId("SingleThreadedSampleTest.f1()", Thread.currentThread().getId());

+  }

+

+  @Test

+  public void f2() {

+    addId("SingleThreadedSampleTest.f2()", Thread.currentThread().getId());

+  }

+

+  @Test

+  public void f3() {

+    addId("SingleThreadedSampleTest.f3()", Thread.currentThread().getId());

+  }

+

+}

diff --git a/src/test/java/test/thread/SuiteThreadCountTest.java b/src/test/java/test/thread/SuiteThreadCountTest.java
new file mode 100644
index 0000000..416bfc0
--- /dev/null
+++ b/src/test/java/test/thread/SuiteThreadCountTest.java
@@ -0,0 +1,34 @@
+package test.thread;

+

+import org.testng.Assert;

+import org.testng.annotations.AfterClass;

+import org.testng.annotations.Test;

+import org.testng.internal.thread.ThreadUtil;

+

+import java.util.Collections;

+import java.util.HashSet;

+import java.util.Set;

+

+/**

+ * Test for test level thread-count.

+ *

+ * @author <a href="mailto:the.mindstorm@gmail.com">Alex Popescu</a>

+ */

+public class SuiteThreadCountTest {

+  private Set<String> m_threads= Collections.synchronizedSet(new HashSet<String>());

+

+  @Test

+  public void test1() {

+    m_threads.add(ThreadUtil.currentThreadInfo());

+  }

+

+  @Test

+  public void test2() {

+    m_threads.add(ThreadUtil.currentThreadInfo());

+  }

+

+  @AfterClass

+  public void checkThreading() {

+    Assert.assertEquals(m_threads.size(), 2, "Test should use 2 threads (suite level)");

+  }

+}

diff --git a/src/test/java/test/thread/Test1Test.java b/src/test/java/test/thread/Test1Test.java
new file mode 100644
index 0000000..08340df
--- /dev/null
+++ b/src/test/java/test/thread/Test1Test.java
@@ -0,0 +1,20 @@
+package test.thread;
+
+import org.testng.annotations.Test;
+
+public class Test1Test extends BaseSequentialSample {
+
+  @Test
+  public void f11() {
+    ppp("f11");
+    addId("Test1Test.f11()", Thread.currentThread().getId());
+  }
+
+  @Test
+  public void f12() {
+    ppp("f12");
+    addId("Test1Test.f12()", Thread.currentThread().getId());
+  }
+
+
+}
diff --git a/src/test/java/test/thread/Test2Test.java b/src/test/java/test/thread/Test2Test.java
new file mode 100644
index 0000000..b8b92e7
--- /dev/null
+++ b/src/test/java/test/thread/Test2Test.java
@@ -0,0 +1,20 @@
+package test.thread;
+
+import org.testng.annotations.Test;
+
+public class Test2Test extends BaseSequentialSample {
+
+  @Test
+  public void f21() {
+    ppp("f21");
+    addId("Test2Test.f21()", Thread.currentThread().getId());
+  }
+
+  @Test
+  public void f22() {
+    ppp("f22");
+    addId("Test2Test.f22()", Thread.currentThread().getId());
+  }
+
+
+}
diff --git a/src/test/java/test/thread/TestThreadCountTest.java b/src/test/java/test/thread/TestThreadCountTest.java
new file mode 100644
index 0000000..3f64c99
--- /dev/null
+++ b/src/test/java/test/thread/TestThreadCountTest.java
@@ -0,0 +1,39 @@
+package test.thread;

+

+import org.testng.Assert;

+import org.testng.annotations.AfterClass;

+import org.testng.annotations.Test;

+import org.testng.internal.thread.ThreadUtil;

+

+import java.util.Collections;

+import java.util.HashSet;

+import java.util.Set;

+

+/**

+ * Test for test level thread-count.

+ *

+ * @author <a href="mailto:the.mindstorm@gmail.com">Alex Popescu</a>

+ */

+public class TestThreadCountTest {

+  private Set<String> m_threads= Collections.synchronizedSet(new HashSet<String>());

+

+  @Test

+  public void test1() {

+    m_threads.add(ThreadUtil.currentThreadInfo());

+  }

+

+  @Test

+  public void test2() {

+    m_threads.add(ThreadUtil.currentThreadInfo());

+  }

+

+  @Test

+  public void test3() {

+    m_threads.add(ThreadUtil.currentThreadInfo());

+  }

+

+  @AfterClass

+  public void checkThreading() {

+    Assert.assertEquals(m_threads.size(), 3, "Test should use 3 threads");

+  }

+}

diff --git a/src/test/java/test/thread/ThreadPoolSampleBugTest.java b/src/test/java/test/thread/ThreadPoolSampleBugTest.java
new file mode 100644
index 0000000..3e79307
--- /dev/null
+++ b/src/test/java/test/thread/ThreadPoolSampleBugTest.java
@@ -0,0 +1,27 @@
+package test.thread;
+
+import org.testng.annotations.Test;
+
+public class ThreadPoolSampleBugTest {
+  private static final long TIMEOUT = 500;
+
+  @Test(invocationCount = 1, threadPoolSize = 5)
+  public void shouldPass1() throws InterruptedException {
+    Thread.sleep(TIMEOUT);
+  }
+
+  @Test(invocationCount = 2, threadPoolSize = 5)
+  public void shouldPass2() throws InterruptedException {
+    Thread.sleep(TIMEOUT);
+  }
+
+  @Test(timeOut = 10, invocationCount = 1, threadPoolSize = 5)
+  public void shouldFail1() throws InterruptedException {
+    Thread.sleep(TIMEOUT);
+  }
+
+  @Test(timeOut = 10, invocationCount = 2, threadPoolSize = 5)
+  public void shouldFail2() throws InterruptedException {
+    Thread.sleep(TIMEOUT);
+  }
+}
diff --git a/src/test/java/test/thread/ThreadPoolSizeBase.java b/src/test/java/test/thread/ThreadPoolSizeBase.java
new file mode 100644
index 0000000..0822fdb
--- /dev/null
+++ b/src/test/java/test/thread/ThreadPoolSizeBase.java
@@ -0,0 +1,18 @@
+package test.thread;
+
+import org.testng.annotations.BeforeClass;
+
+public class ThreadPoolSizeBase extends BaseThreadTest {
+  @BeforeClass
+  public void setUp() {
+    log(getClass().getName(), "Init log ids");
+    initThreadLog();
+  }
+
+  protected void logThread() {
+    long n = Thread.currentThread().getId();
+    log(getClass().getName(), "");
+    logThread(n);
+  }
+
+}
diff --git a/src/test/java/test/thread/ThreadPoolSizeSampleTest.java b/src/test/java/test/thread/ThreadPoolSizeSampleTest.java
new file mode 100644
index 0000000..8066544
--- /dev/null
+++ b/src/test/java/test/thread/ThreadPoolSizeSampleTest.java
@@ -0,0 +1,11 @@
+package test.thread;
+
+import org.testng.annotations.Test;
+
+public class ThreadPoolSizeSampleTest {
+  @Test(threadPoolSize=2, timeOut=100)
+  public void willPassBug() throws InterruptedException{
+      Thread.sleep(500);
+  }
+
+}
diff --git a/src/test/java/test/thread/ThreadPoolSizeTest.java b/src/test/java/test/thread/ThreadPoolSizeTest.java
new file mode 100644
index 0000000..332c734
--- /dev/null
+++ b/src/test/java/test/thread/ThreadPoolSizeTest.java
@@ -0,0 +1,16 @@
+package test.thread;
+
+import org.testng.annotations.Test;
+
+public class ThreadPoolSizeTest extends ThreadPoolSizeBase {
+  @Test(invocationCount = 5, threadPoolSize = 3)
+  public void f1() {
+    logThread();
+  }
+
+  @Test(dependsOnMethods = {"f1"})
+  public void verify() {
+    verifyThreads(3);
+  }
+
+}
diff --git a/src/test/java/test/thread/ThreadPoolSizeWithTimeOutTest.java b/src/test/java/test/thread/ThreadPoolSizeWithTimeOutTest.java
new file mode 100644
index 0000000..b9480e7
--- /dev/null
+++ b/src/test/java/test/thread/ThreadPoolSizeWithTimeOutTest.java
@@ -0,0 +1,31 @@
+package test.thread;
+
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import junit.framework.Assert;
+
+public class ThreadPoolSizeWithTimeOutTest extends ThreadPoolSizeBase {
+
+  @Test(invocationCount = 5, threadPoolSize = 3, timeOut = 1000)
+  public void f1() {
+    logThread();
+  }
+
+  @Test(dependsOnMethods = {"f1"})
+  public void verify() {
+    verifyThreads(3);
+  }
+
+  @Test
+  public void threadPoolAndTimeOutShouldFail() {
+    TestNG tng = create(ThreadPoolSizeSampleTest.class);
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+    Assert.assertEquals(0, tla.getPassedTests().size());
+    Assert.assertEquals(1, tla.getFailedTests().size());
+  }
+}
diff --git a/src/test/java/test/thread/ThreadTest.java b/src/test/java/test/thread/ThreadTest.java
new file mode 100644
index 0000000..4ee87a8
--- /dev/null
+++ b/src/test/java/test/thread/ThreadTest.java
@@ -0,0 +1,23 @@
+package test.thread;
+
+import org.testng.annotations.Test;
+
+import test.BaseTest;
+
+public class ThreadTest extends BaseTest {
+
+  @Test(groups = "broken",
+      description = "This can be fixed by using sets instead of lists in DynamicGraph, but more failures happen then")
+  public void timeoutAndInvocationCountShouldFail() {
+    addClass(ThreadPoolSampleBugTest.class.getName());
+    run();
+    String[] passed = {
+        "shouldPass1", "shouldPass2"
+    };
+    String[] failed = {
+        "shouldFail1", "shouldFail2"
+    };
+    verifyTests("Passed", passed, getPassedTests());
+    verifyTests("Failed", failed, getFailedTests());
+  }
+}
diff --git a/src/test/java/test/thread/TrueParallelSampleTest.java b/src/test/java/test/thread/TrueParallelSampleTest.java
new file mode 100644
index 0000000..838cea8
--- /dev/null
+++ b/src/test/java/test/thread/TrueParallelSampleTest.java
@@ -0,0 +1,41 @@
+package test.thread;
+
+import org.testng.annotations.Test;
+
+import java.util.Random;
+
+@Test
+public class TrueParallelSampleTest extends BaseThreadTest {
+  static Random random = new Random(System.currentTimeMillis());
+
+  private void log(String s) {
+    logString(s);
+    try {
+      Thread.sleep(random.nextInt(10));
+    } catch (InterruptedException ex) {
+      Thread.yield();
+    }
+    logString(s);
+    logCurrentThread();
+  }
+
+  public void m1() {
+    log("m1");
+  }
+
+  public void m2() {
+    log("m2");
+  }
+
+  public void m3() {
+    log("m3");
+  }
+
+  public void m4() {
+    log("m4");
+  }
+
+  public void m5() {
+    log("m5");
+  }
+}
diff --git a/src/test/java/test/thread/TrueParallelTest.java b/src/test/java/test/thread/TrueParallelTest.java
new file mode 100644
index 0000000..864c915
--- /dev/null
+++ b/src/test/java/test/thread/TrueParallelTest.java
@@ -0,0 +1,51 @@
+package test.thread;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+
+import test.SimpleBaseTest;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Attempt to make sure that we are truly running methods in parallel. The best I can think
+ * of right now is to run the tests a few times in a row and verify the ordering is never
+ * the same.
+ */
+public class TrueParallelTest extends SimpleBaseTest {
+
+  @Test
+  public void shouldRunInParallel() {
+    boolean success = false;
+    for (int i = 0, count = Runtime.getRuntime().availableProcessors() * 4; i < count; i++) {
+      XmlSuite s = createXmlSuite("TrueParallel");
+      createXmlTest(s, "Test", TrueParallelSampleTest.class.getName());
+      TestNG tng = create();
+      s.setParallel(XmlSuite.ParallelMode.METHODS);
+      tng.setXmlSuites(Arrays.asList(s));
+      BaseThreadTest.initThreadLog();
+      tng.run();
+
+      // A sequential result will look like "m1 m1 m3 m3 m2 m2 m4 m4 m5 m5". A properly
+      // multithreaded result will have at least one non-consecutive different pair:
+      // "m1 m1 m3 m2 m4 m4 m2 m3 m5 m5"
+      List<String> strings = TrueParallelSampleTest.getStrings();
+      boolean ii = isInterleaved(strings);
+      success = success || ii;
+//      System.out.println(strings + " -> " + ii);
+    }
+    Assert.assertTrue(success, "Couldn't find any interleaved test method run");
+  }
+
+  private boolean isInterleaved(List<String> strings) {
+    for (int i = 0; i < strings.size(); i += 2) {
+      if (! strings.get(i).equals(strings.get(i + 1))) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
diff --git a/src/test/java/test/thread/testng.xml b/src/test/java/test/thread/testng.xml
new file mode 100644
index 0000000..1c19df7
--- /dev/null
+++ b/src/test/java/test/thread/testng.xml
@@ -0,0 +1,13 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

+<suite name="Thread Counters" verbose="2" thread-count="2">

+  <test name="Test level thread-count" thread-count="3" parallel="methods">

+    <classes>

+      <class name="test.thread.TestThreadCountTest" />

+    </classes>

+  </test>

+  <test name="Suite level thread-count" parallel="methods">

+    <classes>

+      <class name="test.thread.SuiteThreadCountTest" />

+    </classes>

+  </test>

+</suite>
\ No newline at end of file
diff --git a/src/test/java/test/timeout/InvocationTimeOutSampleTest.java b/src/test/java/test/timeout/InvocationTimeOutSampleTest.java
new file mode 100644
index 0000000..75c8f24
--- /dev/null
+++ b/src/test/java/test/timeout/InvocationTimeOutSampleTest.java
@@ -0,0 +1,24 @@
+package test.timeout;
+
+import org.testng.annotations.Test;
+
+public class InvocationTimeOutSampleTest {
+
+  @Test(invocationCount = 5, invocationTimeOut = 2_000)
+  public void shouldPass() {
+    try {
+      Thread.sleep(250);
+    } catch (InterruptedException handled) {
+      Thread.currentThread().interrupt();
+    }
+  }
+
+  @Test(invocationCount = 5, invocationTimeOut = 1_000)
+  public void shouldFail() {
+    try {
+      Thread.sleep(250);
+    } catch (InterruptedException handled) {
+      Thread.currentThread().interrupt();
+    }
+  }
+}
diff --git a/src/test/java/test/timeout/TestTimeOutSampleTest.java b/src/test/java/test/timeout/TestTimeOutSampleTest.java
new file mode 100644
index 0000000..c92a510
--- /dev/null
+++ b/src/test/java/test/timeout/TestTimeOutSampleTest.java
@@ -0,0 +1,15 @@
+package test.timeout;
+
+import org.testng.annotations.Test;
+
+public class TestTimeOutSampleTest {
+
+    @Test
+    public void timeoutTest() {
+        try {
+            Thread.sleep(2_000);
+        } catch (InterruptedException handled) {
+            Thread.currentThread().interrupt();
+        }
+    }
+}
diff --git a/src/test/java/test/timeout/TimeOutFromXmlTest.java b/src/test/java/test/timeout/TimeOutFromXmlTest.java
new file mode 100644
index 0000000..7b373e6
--- /dev/null
+++ b/src/test/java/test/timeout/TimeOutFromXmlTest.java
@@ -0,0 +1,93 @@
+package test.timeout;
+
+import org.testng.annotations.Test;
+import org.testng.xml.SuiteXmlParser;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlInclude;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.BaseTest;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class TimeOutFromXmlTest extends BaseTest {
+
+    private void timeOutTest(boolean onSuite) {
+        addClass(TestTimeOutSampleTest.class);
+        if (onSuite) {
+            setSuiteTimeOut(1_000);
+        } else {
+            setTestTimeOut(1_000);
+        }
+        run();
+
+        verifyPassedTests();
+        verifyFailedTests("timeoutTest");
+    }
+
+    @Test
+    public void timeOutOnSuiteTag() {
+        timeOutTest(true /* on suite */);
+    }
+
+    @Test
+    public void timeOutOnTestTag() {
+        timeOutTest(false /* on test */);
+    }
+
+    @Test
+    public void noTimeOut() {
+      addClass(TestTimeOutSampleTest.class);
+      run();
+
+      verifyPassedTests("timeoutTest");
+      verifyFailedTests();
+    }
+
+    @Test
+    public void twoDifferentTests() {
+      XmlSuite result = new XmlSuite();
+      result.setName("Suite");
+
+      createXmlTest(result, "WithoutTimeOut");
+      createXmlTest(result, "WithTimeOut").setTimeOut(1_000);
+
+      setSuite(result);
+      run();
+
+      verifyPassedTests("timeoutTest");
+      verifyFailedTests("timeoutTest");
+    }
+
+    private XmlTest createXmlTest(XmlSuite suite, String name) {
+        XmlTest result = new XmlTest(suite);
+        result.setName(name);
+        List<XmlClass> classes = new ArrayList<>();
+        XmlClass cls = new XmlClass(TestTimeOutSampleTest.class);
+        cls.setIncludedMethods(
+            Collections.singletonList(new XmlInclude("timeoutTest")));
+        classes.add(cls);
+        result.setXmlClasses(classes);
+
+        return result;
+    }
+
+    @Test
+    public void timeOutInParallelTestsFromXml() throws IOException {
+      String file = "src/test/java/test/timeout/issue575.xml";
+      try (FileInputStream stream = new FileInputStream(file)) {
+        SuiteXmlParser suiteParser = new SuiteXmlParser();
+        XmlSuite suite = suiteParser.parse(file, stream, true);
+        setSuite(suite);
+        run();
+
+        verifyPassedTests("timeoutShouldPass");
+        verifyFailedTests("timeoutShouldFailByException", "timeoutShouldFailByTimeOut");
+      }
+    }
+}
diff --git a/src/test/java/test/timeout/TimeOutIntegrationTest.java b/src/test/java/test/timeout/TimeOutIntegrationTest.java
new file mode 100644
index 0000000..01f62a6
--- /dev/null
+++ b/src/test/java/test/timeout/TimeOutIntegrationTest.java
@@ -0,0 +1,26 @@
+package test.timeout;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+
+public class TimeOutIntegrationTest {
+
+    @Test(description = "https://github.com/cbeust/testng/issues/811")
+    public void testTimeOutWhenParallelIsTest() {
+        TestNG tng = new TestNG();
+        tng.setParallel(XmlSuite.ParallelMode.TESTS);
+        tng.setTestClasses(new Class[]{TimeOutWithParallelSample.class});
+
+        TestListenerAdapter tla = new TestListenerAdapter();
+        tng.addListener(tla);
+
+        tng.run();
+
+        Assert.assertEquals(tla.getFailedTests().size(), 1);
+        Assert.assertEquals(tla.getSkippedTests().size(), 0);
+        Assert.assertEquals(tla.getPassedTests().size(), 0);
+    }
+}
diff --git a/src/test/java/test/timeout/TimeOutSample2Test.java b/src/test/java/test/timeout/TimeOutSample2Test.java
new file mode 100644
index 0000000..b5ecded
--- /dev/null
+++ b/src/test/java/test/timeout/TimeOutSample2Test.java
@@ -0,0 +1,15 @@
+package test.timeout;
+
+import org.testng.annotations.Test;
+
+/**
+ * Tests timeouts set from testng.xml
+ * @author cbeust
+ */
+public class TimeOutSample2Test {
+
+  @Test(timeOut = 1_500)
+  public void timeoutShouldFailByTimeOut() throws InterruptedException {
+      Thread.sleep(10_000);
+  }
+}
diff --git a/src/test/java/test/timeout/TimeOutSampleTest.java b/src/test/java/test/timeout/TimeOutSampleTest.java
new file mode 100644
index 0000000..3e3a037
--- /dev/null
+++ b/src/test/java/test/timeout/TimeOutSampleTest.java
@@ -0,0 +1,25 @@
+package test.timeout;
+
+import org.testng.annotations.Test;
+
+/**
+ * This class tests timeouts
+ *
+ * @author cbeust
+ */
+public class TimeOutSampleTest {
+
+  @Test(timeOut = 5_000 /* 5 seconds */)
+  public void timeoutShouldPass() {
+  }
+
+  @Test(timeOut = 5_000 /* 5 seconds */)
+  public void timeoutShouldFailByException() {
+    throw new RuntimeException("EXCEPTION SHOULD MAKE THIS METHOD FAIL");
+  }
+
+  @Test(timeOut = 1_000 /* 1 second */)
+  public void timeoutShouldFailByTimeOut() throws InterruptedException {
+      Thread.sleep(10_000 /* 10 seconds */);
+  }
+}
diff --git a/src/test/java/test/timeout/TimeOutTest.java b/src/test/java/test/timeout/TimeOutTest.java
new file mode 100644
index 0000000..a8d5b55
--- /dev/null
+++ b/src/test/java/test/timeout/TimeOutTest.java
@@ -0,0 +1,74 @@
+package test.timeout;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlSuite;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+import test.BaseTest;
+
+public class TimeOutTest extends BaseTest {
+  private final long m_id;
+
+  public TimeOutTest() {
+    m_id = System.currentTimeMillis();
+  }
+
+  private void privateTimeOutTest(XmlSuite.ParallelMode parallel) {
+    addClass(TimeOutSampleTest.class);
+    if (parallel != null) {
+      setParallel(parallel);
+    }
+    run();
+
+    verifyPassedTests("timeoutShouldPass");
+    verifyFailedTests("timeoutShouldFailByException", "timeoutShouldFailByTimeOut");
+  }
+
+  @DataProvider(name = "parallelModes")
+  public Iterator<Object[]> createData() {
+    final Iterator<XmlSuite.ParallelMode> parallelModes = Arrays.asList(XmlSuite.ParallelMode.values()).iterator();
+    return new Iterator<Object[]>() {
+      @Override
+      public boolean hasNext() {
+        return parallelModes.hasNext();
+      }
+
+      @Override
+      public Object[] next() {
+        return new Object[]{ parallelModes.next() };
+      }
+
+      @Override
+      public void remove() {
+        throw new UnsupportedOperationException("remove");
+      }
+    };
+  }
+
+
+  @Test(dataProvider = "parallelModes")
+  public void timeOutInParallel(XmlSuite.ParallelMode parallelMode) {
+    privateTimeOutTest(parallelMode);
+  }
+
+  @Test
+  public void timeOutInNonParallel() {
+    privateTimeOutTest(null);
+  }
+
+  @Test
+  public void verifyInvocationTimeOut() {
+    addClass(InvocationTimeOutSampleTest.class);
+    run();
+    verifyPassedTests("shouldPass");
+    verifyFailedTests("shouldFail");
+  }
+
+  @Override
+  public Long getId() {
+    return m_id;
+  }
+}
diff --git a/src/test/java/test/timeout/TimeOutWithParallelSample.java b/src/test/java/test/timeout/TimeOutWithParallelSample.java
new file mode 100644
index 0000000..59ea188
--- /dev/null
+++ b/src/test/java/test/timeout/TimeOutWithParallelSample.java
@@ -0,0 +1,11 @@
+package test.timeout;
+
+import org.testng.annotations.Test;
+
+public class TimeOutWithParallelSample {
+
+    @Test(timeOut = 1_000)
+    public void myTestMethod() throws InterruptedException {
+        Thread.sleep(1_500);
+    }
+}
diff --git a/src/test/java/test/timeout/issue575.xml b/src/test/java/test/timeout/issue575.xml
new file mode 100644
index 0000000..7b91fcf
--- /dev/null
+++ b/src/test/java/test/timeout/issue575.xml
@@ -0,0 +1,9 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+
+<suite name="TestNG test" parallel="tests">
+    <test name="Test TestNG" >
+        <classes>
+            <class name="test.timeout.TimeOutSampleTest" />
+        </classes>
+    </test>
+</suite>
\ No newline at end of file
diff --git a/src/test/java/test/tmp/A.java b/src/test/java/test/tmp/A.java
new file mode 100644
index 0000000..f382eb1
--- /dev/null
+++ b/src/test/java/test/tmp/A.java
@@ -0,0 +1,33 @@
+package test.tmp;
+
+import org.testng.annotations.Test;
+import org.testng.asserts.SoftAssert;
+
+@Test
+public class A {
+//  private FlexibleAssert m_assert = new FlexibleAssert();
+  private SoftAssert m_assert = new SoftAssert();
+//  private LoggingAssert m_assert = new LoggingAssert();
+
+  public void test1() {
+    m_assert.assertTrue(true, "test1()");
+  }
+
+  public void test2() {
+    m_assert.assertTrue(true, "test2()");
+  }
+
+//  @AfterClass
+//  public void ac() {
+//    System.out.println("Tests run in this class:" + m_assert.getMessages());
+//  }
+
+  public void multiple() {
+    m_assert.assertTrue(true, "Success 1");
+    m_assert.assertTrue(true, "Success 2");
+    m_assert.assertTrue(false, "Failure 1");
+    m_assert.assertTrue(true, "Success 3");
+    m_assert.assertTrue(false, "Failure 2");
+    m_assert.assertAll();
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/tmp/AA.java b/src/test/java/test/tmp/AA.java
new file mode 100644
index 0000000..492ab13
--- /dev/null
+++ b/src/test/java/test/tmp/AA.java
@@ -0,0 +1,70 @@
+package test.tmp;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+
+@Test(sequential = true)
+public class AA {
+  private int m_n;
+
+  public AA() {}
+
+  public AA(int n) {
+    m_n = n;
+  }
+
+  private void log(String s) {
+    System.out.println(" [AA(" + m_n + ") thread:" + Thread.currentThread().getId() + "] " + s);
+  }
+
+  @DataProvider
+  public Object[][] dp() {
+    return new Object[][] {
+      new Object[] { 42 },
+    };
+  }
+
+//  @BeforeClass
+//  public void bc() {
+//    log("beforeClass");
+//  }
+//
+//  @AfterClass
+//  public void ac() {
+//    log("afterClass");
+//  }
+
+  @Factory
+  public Object[] create() {
+    return new Object[] { new A(), new AA() };
+  }
+
+  @Test
+  public void aatest1() {
+    log("aatest1");
+  }
+
+  @Test(dependsOnMethods = "aatest1")
+  public void aatest2() {
+    log("aatest2");
+  }
+
+//  @Test(priority = 3)
+  public void atest3() {
+  }
+
+  public String getTestName() {
+    return "This is test A";
+  }
+
+//  @Test(groups = "mytest", dependsOnMethods = "g")
+//  public void f() {
+//  }
+//
+//
+//  @AfterMethod
+//  public void after() {
+//  }
+
+}
diff --git a/src/test/java/test/tmp/AFactory.java b/src/test/java/test/tmp/AFactory.java
new file mode 100644
index 0000000..b52a5aa
--- /dev/null
+++ b/src/test/java/test/tmp/AFactory.java
@@ -0,0 +1,11 @@
+package test.tmp;
+
+import org.testng.annotations.Factory;
+
+public class AFactory {
+  @Factory
+  public Object[] create() {
+    return new Object[] { new A(), new AA() };
+  }
+
+}
diff --git a/src/test/java/test/tmp/AnnotationTransformer.java b/src/test/java/test/tmp/AnnotationTransformer.java
new file mode 100644
index 0000000..c245e1a
--- /dev/null
+++ b/src/test/java/test/tmp/AnnotationTransformer.java
@@ -0,0 +1,64 @@
+package test.tmp;
+
+import org.testng.IAnnotationTransformer;
+import org.testng.ITestContext;
+import org.testng.ITestListener;
+import org.testng.ITestResult;
+import org.testng.annotations.ITestAnnotation;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+public class AnnotationTransformer
+  implements IAnnotationTransformer, ITestListener
+{
+
+  @Override
+  public void transform(ITestAnnotation annotation, Class testClass,
+      Constructor testConstructor, Method testMethod)
+  {
+  }
+
+  @Override
+  public void onFinish(ITestContext context) {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void onStart(ITestContext context) {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void onTestFailedButWithinSuccessPercentage(ITestResult result) {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void onTestFailure(ITestResult result) {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void onTestSkipped(ITestResult result) {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void onTestStart(ITestResult result) {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void onTestSuccess(ITestResult result) {
+    // TODO Auto-generated method stub
+
+  }
+
+}
diff --git a/src/test/java/test/tmp/AssertEqualsTest.java b/src/test/java/test/tmp/AssertEqualsTest.java
new file mode 100644
index 0000000..bcabe2a
--- /dev/null
+++ b/src/test/java/test/tmp/AssertEqualsTest.java
@@ -0,0 +1,68 @@
+package test.tmp;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Random;
+import java.util.Set;
+
+public class AssertEqualsTest {
+
+  private void log(String s) {
+    System.out.println("[" + Thread.currentThread().getId() + "] " + s);
+  }
+
+  @Test(threadPoolSize = 3, invocationCount = 6)
+  public void f1() {
+    log("start");
+    try {
+      int sleepTime = new Random().nextInt(500);
+      Thread.sleep(sleepTime);
+    }
+    catch (Exception e) {
+      log("  *** INTERRUPTED");
+    }
+    log("end");
+  }
+
+  @Test(threadPoolSize = 10, invocationCount = 10000)
+  public void verifyMethodIsThreadSafe() {
+//    foo();
+  }
+
+  @Test(dependsOnMethods = "verifyMethodIsThreadSafe")
+  public void verify() {
+    // make sure that nothing was broken
+  }
+
+  public static void main(String[] args) {
+    Set set1 = new LinkedHashSet();
+    Set set2 = new HashSet();
+
+    set1.add(5);
+    set2.add(5);
+
+    set1.add(6);
+    set2.add(6);
+
+    set1.add(1);
+    set2.add(1);
+
+    set1.add(9);
+    set2.add(9);
+
+    System.out.println("set1 is:" + set1.toString());
+    System.out.println("set2 is:" + set2.toString());
+
+    System.out.println("is set1 equals set2 :" + set1.equals(set2));
+
+    try {
+      Assert.assertEquals(set1, set2, "set1 must equals with set2");
+    }
+    catch (Exception ex) {
+      ex.printStackTrace();
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/tmp/B.java b/src/test/java/test/tmp/B.java
new file mode 100644
index 0000000..b7ee19e
--- /dev/null
+++ b/src/test/java/test/tmp/B.java
@@ -0,0 +1,20 @@
+package test.tmp;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class B extends C {
+  public static final String G = "group";
+
+
+  @BeforeMethod
+  public void bm() {
+    System.out.println("B.bm");
+  }
+
+  @Test
+  public void btest1() {
+    System.out.println("B.btest1");
+  }
+}
+
diff --git a/src/test/java/test/tmp/BListener.java b/src/test/java/test/tmp/BListener.java
new file mode 100644
index 0000000..bc04687
--- /dev/null
+++ b/src/test/java/test/tmp/BListener.java
@@ -0,0 +1,23 @@
+package test.tmp;
+
+import org.testng.ITestResult;
+import org.testng.TestListenerAdapter;
+
+public class BListener extends TestListenerAdapter {
+  public BListener() {
+    System.out.println("BListener created");
+  }
+
+  @Override
+  public void onTestSuccess(ITestResult tr) {
+    System.out.println("Success");
+    super.onTestSuccess(tr);
+  }
+
+  @Override
+  public void onTestFailure(ITestResult tr) {
+    System.out.println("Failure");
+    super.onTestFailure(tr);
+  }
+
+}
diff --git a/src/test/java/test/tmp/Base.java b/src/test/java/test/tmp/Base.java
new file mode 100644
index 0000000..f31cddd
--- /dev/null
+++ b/src/test/java/test/tmp/Base.java
@@ -0,0 +1,10 @@
+package test.tmp;
+
+import org.testng.annotations.BeforeMethod;
+
+public class Base {
+  @BeforeMethod
+  public void bm() {
+      System.out.println("Thread" + Thread.currentThread().getId());
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/tmp/BeforeGroupTest.java b/src/test/java/test/tmp/BeforeGroupTest.java
new file mode 100644
index 0000000..18da2a6
--- /dev/null
+++ b/src/test/java/test/tmp/BeforeGroupTest.java
@@ -0,0 +1,17 @@
+package test.tmp;
+
+import org.testng.ITestContext;
+import org.testng.annotations.BeforeGroups;
+
+public class BeforeGroupTest {
+
+//  Logger l = LoggerFactory.getLogger(this.getClass());
+
+  @BeforeGroups(groups = { "NewUser" }, value = { "NewUser" })
+  public void preNewUser(ITestContext itc) {
+    System.out.println("BEFOREGROUPS perfroming pre groups init");
+//    m_inj = Guice.createInjector(new JUnitModule(), new RequestModule(),
+//        new GenericModule(), new SecurityModule());
+//    itc.setAttribute("injector", m_inj);
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/tmp/C.java b/src/test/java/test/tmp/C.java
new file mode 100644
index 0000000..3e4d344
--- /dev/null
+++ b/src/test/java/test/tmp/C.java
@@ -0,0 +1,25 @@
+package test.tmp;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class C {
+  @BeforeMethod
+  public void cm() {
+//    System.out.println("C.cm");
+  }
+
+  @Test(dataProvider = "data")
+  public void c(String s) {
+    System.out.println("c(" + s + ")");
+  }
+
+  @DataProvider(name = "data")
+  static public Object[][] data() {
+    return new Object[][] {
+        new Object[] { "Foo" },
+        new Object[] { "Bar" },
+    };
+  }
+}
diff --git a/src/test/java/test/tmp/ChildTest.java b/src/test/java/test/tmp/ChildTest.java
new file mode 100644
index 0000000..2b2a705
--- /dev/null
+++ b/src/test/java/test/tmp/ChildTest.java
@@ -0,0 +1,30 @@
+package test.tmp;
+
+import org.testng.annotations.Configuration;
+import org.testng.annotations.Test;
+
+public class ChildTest extends ParentTest {
+
+  @Configuration(beforeTestMethod = true)
+  public void btm2() {
+    ppp("CHILD BEFORE TEST");
+  }
+
+  @Configuration(afterTestMethod = true)
+  public void atm2() {
+    ppp("CHILD AFTER TEST");
+  }
+
+  @Override
+  @Test
+  public void t1() {
+    ppp("TEST CHILD");
+  }
+
+  private void ppp(String string) {
+    System.out.println("[Parent] " + string);
+  }
+
+
+
+}
diff --git a/src/test/java/test/tmp/ConcreteTest.java b/src/test/java/test/tmp/ConcreteTest.java
new file mode 100644
index 0000000..a1b8f1b
--- /dev/null
+++ b/src/test/java/test/tmp/ConcreteTest.java
@@ -0,0 +1,21 @@
+package test.tmp;
+
+import org.testng.annotations.Configuration;
+import org.testng.annotations.Test;
+
+public class ConcreteTest extends Fixturable {
+  @Configuration(beforeTest=true, afterGroups="fixture")
+  public void beforeFixture() {
+    ppp("BEFORE");
+  }
+
+  @Test(groups = "fixture")
+  public void test() {
+    ppp("TEST");
+  }
+
+
+  private static void ppp(String s) {
+    System.out.println("[ConcreteTest] " + s);
+  }
+}
diff --git a/src/test/java/test/tmp/DataDrivenTest.java b/src/test/java/test/tmp/DataDrivenTest.java
new file mode 100644
index 0000000..41ddaaa
--- /dev/null
+++ b/src/test/java/test/tmp/DataDrivenTest.java
@@ -0,0 +1,41 @@
+package test.tmp;
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Properties;
+
+
+public class DataDrivenTest {
+
+  @DataProvider(name = "provider")
+  public Object[][] createData() throws FileNotFoundException, IOException {
+    Properties p = new Properties();
+    List<Object> vResult = new ArrayList<>();
+    p.load(new FileInputStream(new File("c:/t/data.properties")));
+    for (Enumeration e = p.keys(); e.hasMoreElements(); ) {
+      vResult.add(e.nextElement());
+    }
+
+    Object[][] result = new Object[vResult.size()][1];
+    for (int i = 0; i < result.length; i++) {
+      result[i] = new Object[] { vResult.get(i) };
+    }
+
+    return result;
+  }
+
+  @Test(dataProvider = "provider")
+  public void foo(int n) {
+    Assert.assertTrue(n > 0);
+  }
+
+}
diff --git a/src/test/java/test/tmp/ExponentTest.java b/src/test/java/test/tmp/ExponentTest.java
new file mode 100644
index 0000000..d882d7c
--- /dev/null
+++ b/src/test/java/test/tmp/ExponentTest.java
@@ -0,0 +1,41 @@
+package test.tmp;
+
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+@Test(suiteName = "Exponent suite", testName = "Exponent test")
+public class ExponentTest {
+
+    @DataProvider(name = "random")
+    public Object[][] generateRandomExps() {
+      // This array should be generated with random numbers
+      return new Object[][] {
+        new Object[] { 0.0, Math.exp(0) },
+        new Object[] { 1.0, Math.exp(1) },
+        new Object[] { 2.0, Math.exp(2) },
+      };
+    }
+
+    @BeforeMethod
+    public void setUp() {
+      ppp("BEFORE METHOD");
+    }
+
+    @Test(dataProvider = "random")
+    public void testExponent(double exponent, double expected) {
+      ppp("COMPARING " + myExpFunction(exponent) + " AND " + expected);
+      assertEquals(myExpFunction(exponent), expected);
+    }
+
+    private static void ppp(String s) {
+      System.out.println("[ExponentTest] " + s);
+    }
+
+    private double myExpFunction(double exponent) {
+      return Math.exp(exponent);
+    }
+
+}
diff --git a/src/test/java/test/tmp/Fixturable.java b/src/test/java/test/tmp/Fixturable.java
new file mode 100644
index 0000000..a8fcdaa
--- /dev/null
+++ b/src/test/java/test/tmp/Fixturable.java
@@ -0,0 +1,14 @@
+package test.tmp;
+
+import org.testng.annotations.Configuration;
+
+public class Fixturable {
+  @Configuration(beforeTest=true, groups="fixture")
+  public void setupFixture() {
+    ppp("SETUP");
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[Fixturable] " + s);
+  }
+}
diff --git a/src/test/java/test/tmp/IgnoreUntil.java b/src/test/java/test/tmp/IgnoreUntil.java
new file mode 100644
index 0000000..74b34f6
--- /dev/null
+++ b/src/test/java/test/tmp/IgnoreUntil.java
@@ -0,0 +1,18 @@
+package test.tmp;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target({METHOD, TYPE, CONSTRUCTOR})
+public @interface IgnoreUntil {
+
+  /**
+   * Format:  hhmm
+   */
+  public String time();
+}
diff --git a/src/test/java/test/tmp/ParamTest.java b/src/test/java/test/tmp/ParamTest.java
new file mode 100644
index 0000000..c7c8e1d
--- /dev/null
+++ b/src/test/java/test/tmp/ParamTest.java
@@ -0,0 +1,28 @@
+package test.tmp;
+
+import org.testng.Reporter;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class ParamTest {
+
+  @Test(dataProvider="dp")
+  public void f(String s, int n) {
+//    if ("Alois".equals(s)) assert false;
+    Reporter.log("CALL " + n);
+    ppp("TEST : " + s);
+  }
+
+  @DataProvider(name="dp")
+  public Object[][] create() {
+    return new Object[][] {
+        new Object[] { "Cedric", 36},
+        new Object[] {"Alois", 35},
+    };
+  }
+
+  private void ppp(String string) {
+    System.err.println("[ParamTest] " + string);
+  }
+
+}
diff --git a/src/test/java/test/tmp/ParentTest.java b/src/test/java/test/tmp/ParentTest.java
new file mode 100644
index 0000000..68f0047
--- /dev/null
+++ b/src/test/java/test/tmp/ParentTest.java
@@ -0,0 +1,29 @@
+package test.tmp;
+
+import org.testng.annotations.Configuration;
+import org.testng.annotations.Test;
+
+public class ParentTest {
+
+  @Configuration(beforeTestMethod = true)
+  public void btm1() {
+    ppp("PARENT BEFORE TEST");
+  }
+
+  @Configuration(afterTestMethod = true)
+  public void atm1() {
+    ppp("PARENT AFTER TEST");
+  }
+
+  @Test
+  public void t1() {
+    ppp("TEST PARENT");
+  }
+
+  private void ppp(String string) {
+    System.out.println("[Parent] " + string);
+  }
+
+
+
+}
diff --git a/src/test/java/test/tmp/RegisterCommandTest.java b/src/test/java/test/tmp/RegisterCommandTest.java
new file mode 100644
index 0000000..36e3fae
--- /dev/null
+++ b/src/test/java/test/tmp/RegisterCommandTest.java
@@ -0,0 +1,24 @@
+package test.tmp;
+
+import org.testng.ITestContext;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+@Test(groups = { "NewUser" })
+public class RegisterCommandTest {
+
+//  Logger l = LoggerFactory.getLogger(this.getClass());
+
+  @BeforeClass
+  public void beforeClass(ITestContext itc) {
+    System.out.println("BEFORECLASS getting injector from context");
+//    m_inj = (Injector) itc.getAttribute("injector");
+//    m_inj.injectMembers(this);
+
+  }
+
+  public void testExecute(ITestContext itc) throws Exception {
+    System.out.println("TESTEXECUTE do somthing");
+  }
+}
+
diff --git a/src/test/java/test/tmp/RetryAnalyzer.java b/src/test/java/test/tmp/RetryAnalyzer.java
new file mode 100644
index 0000000..fd84101
--- /dev/null
+++ b/src/test/java/test/tmp/RetryAnalyzer.java
@@ -0,0 +1,14 @@
+package test.tmp;
+
+import org.testng.IRetryAnalyzer;
+import org.testng.ITestResult;
+
+public class RetryAnalyzer implements IRetryAnalyzer {
+
+  @Override
+  public boolean retry(ITestResult result) {
+    System.out.println("retry()");
+    return true;
+  }
+
+}
diff --git a/src/test/java/test/tmp/StaticInitializerTest.java b/src/test/java/test/tmp/StaticInitializerTest.java
new file mode 100644
index 0000000..085c98d
--- /dev/null
+++ b/src/test/java/test/tmp/StaticInitializerTest.java
@@ -0,0 +1,19 @@
+package test.tmp;
+
+import org.testng.annotations.Test;
+
+@Test
+public class StaticInitializerTest {
+
+    static {
+      foo();
+    }
+
+    public void testMe() {
+        System.err.println("**** testMe ****");
+    }
+
+    private static void foo() {
+      throw new RuntimeException("FAILING");
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/test/tmp/Sub.java b/src/test/java/test/tmp/Sub.java
new file mode 100644
index 0000000..18566c6
--- /dev/null
+++ b/src/test/java/test/tmp/Sub.java
@@ -0,0 +1,31 @@
+package test.tmp;
+
+import org.testng.Assert;
+import org.testng.annotations.Configuration;
+import org.testng.annotations.Test;
+
+@Test(groups = {"sub"})
+public class Sub extends Base {
+  boolean m_beforeTest;
+  boolean m_afterTest;
+
+    @Configuration(beforeTestClass = true)
+    public void subSetup() {
+        System.out.println("sub before class");
+    }
+
+    @Configuration(afterTestClass = true)
+    public void subTeardown() {
+        System.out.println("sub after class");
+    }
+
+    public void subTest() {
+        System.out.println ("sub test");
+    }
+
+    @Configuration(afterSuite = true)
+    public void verify() {
+      Assert.assertTrue(m_beforeTest);
+      Assert.assertTrue(m_afterTest);
+    }
+}
diff --git a/src/test/java/test/tmp/Test0.java b/src/test/java/test/tmp/Test0.java
new file mode 100644
index 0000000..f60649c
--- /dev/null
+++ b/src/test/java/test/tmp/Test0.java
@@ -0,0 +1,23 @@
+package test.tmp;
+
+import org.testng.annotations.Configuration;
+import org.testng.annotations.Test;
+
+public class Test0 {
+
+  @Configuration(beforeTest = true)
+  public void setup() {
+          System.out.println("setup");
+  }
+
+  @Test(groups = { "G0" })
+  public void test() {
+          System.out.println("Test0.test");
+  }
+
+  @Configuration(afterTest = true)
+  public void tearDown() {
+          System.out.println("tearDown");
+  }
+
+}
\ No newline at end of file
diff --git a/src/test/java/test/tmp/Test1.java b/src/test/java/test/tmp/Test1.java
new file mode 100644
index 0000000..22ecd68
--- /dev/null
+++ b/src/test/java/test/tmp/Test1.java
@@ -0,0 +1,12 @@
+package test.tmp;
+
+import org.testng.annotations.Test;
+
+@Test(dependsOnGroups = { "G0" })
+public class Test1 {
+
+       public void test() {
+               System.out.println("Test1.test");
+       }
+
+}
\ No newline at end of file
diff --git a/src/test/java/test/tmp/TestA.java b/src/test/java/test/tmp/TestA.java
new file mode 100644
index 0000000..0818a2f
--- /dev/null
+++ b/src/test/java/test/tmp/TestA.java
@@ -0,0 +1,37 @@
+package test.tmp;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class TestA {
+    @BeforeClass
+    public void insertA() {
+        // Inserts an entity 'A'. This is an expensive operation.
+        // After this, the entity 'A' is the first row on the table.
+    }
+
+    @Test
+    public void testGetA() {
+        // Checks that 'A' is the first row on the table, and loads it.
+    }
+
+    @Test(dependsOnMethods = "testGetA")
+    public void testViewA_Details1() {
+        // Loads the first row (assumed A) and checks some details
+    }
+
+    @Test(dependsOnMethods = "testGetA")
+    public void testViewA_Details2() {
+        // Loads the first row (assumed A) and checks some details
+    }
+
+    @Test(dependsOnMethods = "testGetA")
+    public void testViewA_Details3() {
+        // Loads the first row (assumed A) and checks some details
+    }
+
+    @Test(dependsOnMethods = "testGetA")
+    public void testViewA_Details4() {
+        // Loads the first row (assumed A) and checks some details
+    }
+}
diff --git a/src/test/java/test/tmp/TestB.java b/src/test/java/test/tmp/TestB.java
new file mode 100644
index 0000000..dfedb3a
--- /dev/null
+++ b/src/test/java/test/tmp/TestB.java
@@ -0,0 +1,37 @@
+package test.tmp;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class TestB {
+    @BeforeClass
+    public void insertB() {
+        // Inserts an entity 'B'. This is an expensive operation.
+        // After this, the entity 'B' is the first row on the table.
+    }
+
+    @Test
+    public void testGetB() {
+        // Checks that 'B' is the first row on the table, and loads it.
+    }
+
+    @Test(dependsOnMethods = "testGetB")
+    public void testViewB_Details1() {
+        // Loads the first row (assumed B) and checks some details
+    }
+
+    @Test(dependsOnMethods = "testGetB")
+    public void testViewB_Details2() {
+        // Loads the first row (assumed B) and checks some details
+    }
+
+    @Test(dependsOnMethods = "testGetB")
+    public void testViewB_Details3() {
+        // Loads the first row (assumed B) and checks some details
+    }
+
+    @Test(dependsOnMethods = "testGetB")
+    public void testViewB_Details4() {
+    }
+
+}
diff --git a/src/test/java/test/tmp/TestCaseFactory.java b/src/test/java/test/tmp/TestCaseFactory.java
new file mode 100644
index 0000000..64864e3
--- /dev/null
+++ b/src/test/java/test/tmp/TestCaseFactory.java
@@ -0,0 +1,20 @@
+package test.tmp;
+
+import org.testng.annotations.Factory;
+import org.testng.annotations.Test;
+
+public class TestCaseFactory {
+  class MyTestClass {
+    @Test
+    public void testAll() {
+    }
+  }
+
+  @Factory
+  public Object[] createTestCases() {
+    Object[] testCases = new Object[1];
+    testCases[0] = new MyTestClass() {
+    };
+    return testCases;
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/tmp/TestFixture.java b/src/test/java/test/tmp/TestFixture.java
new file mode 100644
index 0000000..4492fd9
--- /dev/null
+++ b/src/test/java/test/tmp/TestFixture.java
@@ -0,0 +1,26 @@
+package test.tmp;
+
+import org.testng.annotations.Configuration;
+
+public class TestFixture {
+
+  public static int globalCallSequence = 0;
+
+  public static int printGlobalCallSequence(String methodName) {
+    globalCallSequence++;
+    System.err.println("*** " + methodName + ": globalCallSequence=" + globalCallSequence);
+    return globalCallSequence;
+  }
+
+  public int fixtureBeforeTestCallSequence;
+
+  @Configuration(beforeTest=true, groups="fixture")
+  public void beforeTest() {
+    fixtureBeforeTestCallSequence = printGlobalCallSequence("TestFixture.beforeTest");
+  }
+
+  public int getSomeRandomValue() {
+    return 20;
+  }
+
+}
diff --git a/src/test/java/test/tmp/TestNGBug.java b/src/test/java/test/tmp/TestNGBug.java
new file mode 100644
index 0000000..d0260e9
--- /dev/null
+++ b/src/test/java/test/tmp/TestNGBug.java
@@ -0,0 +1,30 @@
+package test.tmp;
+
+import org.testng.Reporter;
+import org.testng.annotations.Test;
+
+public class TestNGBug {
+//  @Configuration(beforeTestMethod = true)
+  public void init() {
+    ppp("Base.init()");
+  }
+
+  @Test
+  public void test1() {
+    Reporter.log("Child.test1");
+  }
+
+  @Test(enabled = false)
+  public void test2() {
+    Reporter.log("Child.test2");
+  }
+
+  @Test(groups = "a")
+  public void test3() {
+    Reporter.log("Child.test3");
+  }
+
+  private void ppp(String string) {
+    System.out.println("[TestNGBug] " + string);
+  }
+}
diff --git a/src/test/java/test/tmp/Test_TestListenerAppender.java b/src/test/java/test/tmp/Test_TestListenerAppender.java
new file mode 100644
index 0000000..5bbfaba
--- /dev/null
+++ b/src/test/java/test/tmp/Test_TestListenerAppender.java
@@ -0,0 +1,18 @@
+package test.tmp;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class Test_TestListenerAppender {
+
+  @DataProvider(name = "test1")
+  public Object[][] createData1() {
+//      throw new RuntimeException("Intentionally thrown exception");
+       return new Object[][] { { "Cedric", 36}, {"Anne", 37}, };
+  }
+
+  @Test(dataProvider = "test1")
+  public void verifyData1(String n1, Integer n2) {
+      System.out.println(n1 + " " + n2);
+  }
+}
diff --git a/src/test/java/test/tmp/TimeBombTest.java b/src/test/java/test/tmp/TimeBombTest.java
new file mode 100644
index 0000000..0cdd275
--- /dev/null
+++ b/src/test/java/test/tmp/TimeBombTest.java
@@ -0,0 +1,36 @@
+package test.tmp;
+
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Method;
+import java.util.Calendar;
+
+public class TimeBombTest {
+
+  @IgnoreUntil(time = "1022")
+  @Test
+  public void timeBomb() throws SecurityException, NoSuchMethodException {
+    Method m = TimeBombTest.class.getMethod("timeBomb", new Class[0]);
+    IgnoreUntil t = m.getAnnotation(IgnoreUntil.class);
+    long now = Calendar.getInstance().getTimeInMillis();
+    long l = parseTime(t.time());
+    ppp("IGNORE:" + (now < l));
+  }
+
+  private long parseTime(String string) {
+    int hour = Integer.parseInt(string.substring(0, 2));
+    int minute = Integer.parseInt(string.substring(2));
+    Calendar result = Calendar.getInstance();
+    result.set(Calendar.HOUR_OF_DAY, hour);
+    result.set(Calendar.MINUTE, minute);
+
+    return result.getTimeInMillis();
+  }
+
+  private void ppp(String string) {
+    System.out.println("[TimeBombTest] " + string);
+  }
+
+}
+
+
diff --git a/src/test/java/test/tmp/Tmp.java b/src/test/java/test/tmp/Tmp.java
new file mode 100644
index 0000000..847751e
--- /dev/null
+++ b/src/test/java/test/tmp/Tmp.java
@@ -0,0 +1,27 @@
+package test.tmp;
+
+import org.testng.annotations.Test;
+
+import java.util.Random;
+
+public class Tmp {
+
+  @Test(invocationCount = 10, threadPoolSize = 5)
+  public void f() {
+    ppp("START " + Thread.currentThread().getId());
+    try {
+      Random r = new Random();
+      Thread.sleep(Math.abs(r.nextInt() % 300));
+    }
+    catch (InterruptedException handled) {
+      Thread.currentThread().interrupt();
+      handled.printStackTrace();
+    }
+    ppp("END " + Thread.currentThread().getId());
+  }
+
+  private void ppp(String string) {
+    System.out.println("[Tmp] " + string);
+  }
+
+}
diff --git a/src/test/java/test/tmp/Tn.java b/src/test/java/test/tmp/Tn.java
new file mode 100644
index 0000000..f1130b3
--- /dev/null
+++ b/src/test/java/test/tmp/Tn.java
@@ -0,0 +1,24 @@
+package test.tmp;
+
+import org.testng.Reporter;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+public class Tn {
+  @Test (groups = {"g1"})
+  public void m1() {
+  System.out.println("1");
+ }
+
+ @Test (groups = {"g1"}, dependsOnMethods="m1")
+  public void m2() {
+  System.out.println("2");
+ }
+
+ @Parameters(value = "param")
+ @Test (groups = {"g2"})
+  public void m3(String param) {
+  System.out.println("3");
+  Reporter.log("M3 WAS CALLED");
+ }
+}
diff --git a/src/test/java/test/tmp/b/TmpB.java b/src/test/java/test/tmp/b/TmpB.java
new file mode 100644
index 0000000..fdd4ce5
--- /dev/null
+++ b/src/test/java/test/tmp/b/TmpB.java
@@ -0,0 +1,10 @@
+package test.tmp.b;
+
+import org.testng.annotations.Test;
+
+public class TmpB {
+  @Test
+  public void tmpb1() {
+    System.out.println("tmpb1");
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/test/tmp/p1/ContainerTest.java b/src/test/java/test/tmp/p1/ContainerTest.java
new file mode 100644
index 0000000..46cff50
--- /dev/null
+++ b/src/test/java/test/tmp/p1/ContainerTest.java
@@ -0,0 +1,19 @@
+package test.tmp.p1;
+
+import static org.testng.AssertJUnit.assertNotNull;
+
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.BeforeSuite;
+
+public class ContainerTest {
+
+    @BeforeSuite
+    public void startup() {
+        assertNotNull(null);
+    }
+
+    @AfterSuite
+    public void shutdown() {
+        assertNotNull(null);
+    }
+}
diff --git a/src/test/java/test/tmp/p2/ServiceTest.java b/src/test/java/test/tmp/p2/ServiceTest.java
new file mode 100644
index 0000000..3422cbd
--- /dev/null
+++ b/src/test/java/test/tmp/p2/ServiceTest.java
@@ -0,0 +1,12 @@
+package test.tmp.p2;
+
+import static org.testng.AssertJUnit.assertNotNull;
+
+import org.testng.annotations.Test;
+
+public class ServiceTest {
+  @Test(groups = {"group1"})
+    public void service() {
+        assertNotNull(null);
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/test/tmp/verify/Verify.java b/src/test/java/test/tmp/verify/Verify.java
new file mode 100644
index 0000000..f5ad0c1
--- /dev/null
+++ b/src/test/java/test/tmp/verify/Verify.java
@@ -0,0 +1,9 @@
+package test.tmp.verify;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target(java.lang.annotation.ElementType.METHOD)
+public @interface Verify {
+}
diff --git a/src/test/java/test/tmp/verify/VerifyInterceptor.java b/src/test/java/test/tmp/verify/VerifyInterceptor.java
new file mode 100644
index 0000000..9f15cb0
--- /dev/null
+++ b/src/test/java/test/tmp/verify/VerifyInterceptor.java
@@ -0,0 +1,86 @@
+package test.tmp.verify;
+
+import org.testng.IMethodInstance;
+import org.testng.IMethodInterceptor;
+import org.testng.ITestContext;
+import org.testng.ITestNGMethod;
+import org.testng.TestNGUtils;
+import org.testng.collections.Maps;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class VerifyInterceptor implements IMethodInterceptor {
+
+  /**
+   * @return the list of methods received in parameters with all methods
+   * annotated with @Verify inserted after each of these test methods.
+   *
+   * This happens in two steps:
+   * - Find all the methods annotated with @Verify in the classes that contain test methods
+   * - Insert these verify methods after each method passed in parameter
+   * These @Verify methods are stored in a map keyed by the class in order to avoid looking them
+   * up more than once on the same class.
+   */
+  @Override
+  public List<IMethodInstance> intercept(List<IMethodInstance> methods,
+      ITestContext context) {
+
+    List<IMethodInstance> result = new ArrayList<>();
+    Map<Class<?>, List<IMethodInstance>> verifyMethods = Maps.newHashMap();
+    for (IMethodInstance mi : methods) {
+      ITestNGMethod tm = mi.getMethod();
+      List<IMethodInstance> verify = verifyMethods.get(tm.getRealClass());
+      if (verify == null) {
+        verify = findVerifyMethods(tm.getRealClass(), tm);
+      }
+      result.add(mi);
+      result.addAll(verify);
+    }
+
+    return result;
+  }
+
+  /**
+   * @return all the @Verify methods found on @code{realClass}
+   */
+  private List<IMethodInstance> findVerifyMethods(Class realClass, final ITestNGMethod tm) {
+    List<IMethodInstance> result = new ArrayList<>();
+    for (final Method m : realClass.getDeclaredMethods()) {
+      Annotation a = m.getAnnotation(Verify.class);
+      if (a != null) {
+        final ITestNGMethod vm = TestNGUtils.createITestNGMethod(tm, m);
+        result.add(new IMethodInstance() {
+
+          @Override
+          public Object[] getInstances() {
+            return tm.getInstances();
+          }
+
+          @Override
+          public ITestNGMethod getMethod() {
+            return vm;
+          }
+
+          public int compareTo(IMethodInstance o) {
+            if (getInstances()[0] == o.getInstances()[0]) {
+              return 0;
+            } else {
+              return -1;
+            }
+          }
+
+          @Override
+          public Object getInstance() {
+            return tm.getInstance();
+          }
+        });
+      }
+    }
+
+    return result;
+  }
+}
diff --git a/src/test/java/test/tmp/verify/VerifyTest.java b/src/test/java/test/tmp/verify/VerifyTest.java
new file mode 100644
index 0000000..dc10eeb
--- /dev/null
+++ b/src/test/java/test/tmp/verify/VerifyTest.java
@@ -0,0 +1,22 @@
+package test.tmp.verify;
+
+import org.testng.annotations.Test;
+
+public class VerifyTest {
+
+  @Test
+  public void f2() {
+    System.out.println("f2()");
+  }
+
+  @Test
+  public void f1() {
+    System.out.println("f1()");
+  }
+
+  @Verify
+  public void verify() {
+//    throw new RuntimeException();
+//    System.out.println("verify()");
+  }
+}
diff --git a/src/test/java/test/triangle/Base.java b/src/test/java/test/triangle/Base.java
new file mode 100644
index 0000000..b90cb59
--- /dev/null
+++ b/src/test/java/test/triangle/Base.java
@@ -0,0 +1,33 @@
+package test.triangle;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeSuite;
+
+/**
+ * This class
+ *
+ * @author cbeust
+ */
+public class Base {
+  protected boolean m_isInitialized = false;
+
+  @BeforeSuite
+  public void beforeSuite() {
+    CountCalls.numCalls = 0;
+  }
+
+  @BeforeClass
+  public void initBeforeTestClass() {
+    m_isInitialized = true;
+  }
+
+  @AfterClass
+  public void postAfterTestClass() {
+    CountCalls.incr();
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[Base] " + s);
+  }
+}
diff --git a/src/test/java/test/triangle/CheckCount.java b/src/test/java/test/triangle/CheckCount.java
new file mode 100644
index 0000000..40c4aa8
--- /dev/null
+++ b/src/test/java/test/triangle/CheckCount.java
@@ -0,0 +1,41 @@
+package test.triangle;
+
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+/**
+ * this test checks the CountCalls's static counter to see if we got the
+ * expected number of calls
+ */
+public class CheckCount {
+
+  @Test(parameters = { "expected-calls" })
+  public void testCheckCountDeprecated(String expectedCalls){
+    try {
+
+      //      System.out.println("\n\ntestCheckCount time = " + System.currentTimeMillis());
+      int i = Integer.valueOf(expectedCalls);
+      int numCalls =  CountCalls.getNumCalls();
+      assert (numCalls == i)  : "Count calls expected " + i + " but got " + numCalls;
+    }
+    catch (NumberFormatException nfe) {
+      assert false : "CountCalls needs an expected-calls numeric parameter";
+    }
+  }
+
+  @Parameters({ "expected-calls" })
+  @Test
+  public void testCheckCount(String expectedCalls){
+    try {
+
+      //      System.out.println("\n\ntestCheckCount time = " + System.currentTimeMillis());
+      int i = Integer.valueOf(expectedCalls);
+      int numCalls =  CountCalls.getNumCalls();
+      assert (numCalls == i)  : "Count calls expected " + i + " but got " + numCalls;
+    }
+    catch (NumberFormatException nfe) {
+      assert false : "CountCalls needs an expected-calls numeric parameter";
+    }
+  }
+
+}
\ No newline at end of file
diff --git a/src/test/java/test/triangle/Child1.java b/src/test/java/test/triangle/Child1.java
new file mode 100644
index 0000000..c4114ca
--- /dev/null
+++ b/src/test/java/test/triangle/Child1.java
@@ -0,0 +1,22 @@
+package test.triangle;
+
+import org.testng.annotations.Test;
+
+/**
+ * This class
+ *
+ * @author cbeust
+ */
+public class Child1 extends Base {
+  @Test
+  public void child1() {
+    assert m_isInitialized : "Wasn't initialized correctly " + hashCode() + " " + getClass();
+
+  }
+
+  @Test
+  public void child1a() {
+    assert m_isInitialized : "Wasn't initialized correctly " + hashCode() + " " + getClass();
+  }
+
+}
diff --git a/src/test/java/test/triangle/Child2.java b/src/test/java/test/triangle/Child2.java
new file mode 100644
index 0000000..73eb1bc
--- /dev/null
+++ b/src/test/java/test/triangle/Child2.java
@@ -0,0 +1,16 @@
+package test.triangle;
+
+import org.testng.annotations.Test;
+
+/**
+ * This class
+ *
+ * @author cbeust
+ */
+public class Child2 extends Base {
+
+  @Test
+  public void child2() {
+    assert m_isInitialized : "Wasn't initialized correctly";
+  }
+}
diff --git a/src/test/java/test/triangle/CountCalls.java b/src/test/java/test/triangle/CountCalls.java
new file mode 100644
index 0000000..b843f7d
--- /dev/null
+++ b/src/test/java/test/triangle/CountCalls.java
@@ -0,0 +1,18 @@
+package test.triangle;
+
+
+/**
+ * count the number of times something is called
+ * used to check whether certain methods have been called
+ */
+public class CountCalls {
+  static int numCalls = 0;
+
+  public static void incr () {
+    numCalls++;
+  }
+
+  public static int getNumCalls() {
+    return numCalls;
+  }
+}
diff --git a/src/test/java/test/uniquesuite/BaseAfter.java b/src/test/java/test/uniquesuite/BaseAfter.java
new file mode 100644
index 0000000..3a550b0
--- /dev/null
+++ b/src/test/java/test/uniquesuite/BaseAfter.java
@@ -0,0 +1,22 @@
+package test.uniquesuite;
+
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.BeforeSuite;
+
+public class BaseAfter {
+  public static int m_afterCount = 0;
+
+  @BeforeSuite
+  public void beforeSuite() {
+    m_afterCount = 0;
+  }
+
+  @AfterSuite
+  public void incrementAfter() {
+    m_afterCount++;
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[BaseAfter] " + s);
+  }
+}
diff --git a/src/test/java/test/uniquesuite/BaseBefore.java b/src/test/java/test/uniquesuite/BaseBefore.java
new file mode 100644
index 0000000..3ca243a
--- /dev/null
+++ b/src/test/java/test/uniquesuite/BaseBefore.java
@@ -0,0 +1,23 @@
+package test.uniquesuite;
+
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.BeforeSuite;
+
+public class BaseBefore {
+  public static int m_beforeCount = 0;
+  public static int m_afterCount = 0;
+
+  @BeforeSuite
+  public void incrementBefore() {
+    m_beforeCount++;
+  }
+
+  @AfterSuite
+  public void incrementAfter() {
+    m_afterCount++;
+  }
+
+  private static void ppp(String s) {
+    System.out.println("[Base] " + s);
+  }
+}
diff --git a/src/test/java/test/uniquesuite/TestAfter.java b/src/test/java/test/uniquesuite/TestAfter.java
new file mode 100644
index 0000000..cc82763
--- /dev/null
+++ b/src/test/java/test/uniquesuite/TestAfter.java
@@ -0,0 +1,31 @@
+package test.uniquesuite;
+
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.Test;
+
+import testhelper.OutputDirectoryPatch;
+
+public class TestAfter {
+
+  @Test
+  public void testAfter() {
+    TestNG tng = new TestNG();
+    tng.setOutputDirectory(OutputDirectoryPatch.getOutputDirectory());
+    tng.setTestClasses(new Class[] { TestAfter1.class, TestAfter2.class });
+    tng.setVerbose(0);
+    tng.run();
+    Assert.assertEquals(BaseAfter.m_afterCount, 1);
+  }
+
+  @AfterTest
+  public void afterTest() {
+    BaseAfter.m_afterCount = 0;
+    BaseBefore.m_beforeCount = 0;
+    BaseBefore.m_afterCount = 0;
+  }
+}
+
+/////
+
diff --git a/src/test/java/test/uniquesuite/TestAfter1.java b/src/test/java/test/uniquesuite/TestAfter1.java
new file mode 100644
index 0000000..2ffa8a6
--- /dev/null
+++ b/src/test/java/test/uniquesuite/TestAfter1.java
@@ -0,0 +1,10 @@
+package test.uniquesuite;
+
+import org.testng.annotations.Test;
+
+public class TestAfter1 extends BaseAfter {
+
+  @Test
+  public void verify() {
+  }
+}
diff --git a/src/test/java/test/uniquesuite/TestAfter2.java b/src/test/java/test/uniquesuite/TestAfter2.java
new file mode 100644
index 0000000..2273c75
--- /dev/null
+++ b/src/test/java/test/uniquesuite/TestAfter2.java
@@ -0,0 +1,10 @@
+package test.uniquesuite;
+
+import org.testng.annotations.Test;
+
+public class TestAfter2 extends BaseAfter {
+
+  @Test
+  public void verify() {
+  }
+}
diff --git a/src/test/java/test/uniquesuite/TestBefore1.java b/src/test/java/test/uniquesuite/TestBefore1.java
new file mode 100644
index 0000000..635531f
--- /dev/null
+++ b/src/test/java/test/uniquesuite/TestBefore1.java
@@ -0,0 +1,12 @@
+package test.uniquesuite;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestBefore1 extends BaseBefore {
+
+  @Test
+  public void verify() {
+    Assert.assertEquals(m_beforeCount, 1);
+  }
+}
diff --git a/src/test/java/test/uniquesuite/TestBefore2.java b/src/test/java/test/uniquesuite/TestBefore2.java
new file mode 100644
index 0000000..aa3454b
--- /dev/null
+++ b/src/test/java/test/uniquesuite/TestBefore2.java
@@ -0,0 +1,12 @@
+package test.uniquesuite;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestBefore2 extends BaseBefore {
+
+  @Test
+  public void verify() {
+    Assert.assertEquals(m_beforeCount, 1);
+  }
+}
diff --git a/src/test/java/test/v6/A.java b/src/test/java/test/v6/A.java
new file mode 100644
index 0000000..cf54acd
--- /dev/null
+++ b/src/test/java/test/v6/A.java
@@ -0,0 +1,47 @@
+package test.v6;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterGroups;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeGroups;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+public class A {
+
+  @Test(dependsOnMethods = "fa1")
+  public void fa2() {
+  }
+
+  @Test(groups = "1")
+  public void fa1() {}
+
+  @Test public void fa3() {}
+
+  @BeforeGroups("1")
+  public void beforeGroups() {}
+
+  @AfterGroups("1")
+  public void afterGroups() {}
+
+  @BeforeMethod
+  public void beforeMethod() {}
+
+  @AfterMethod
+  public void afterMethod() {}
+
+  @BeforeSuite
+  public void beforeSuite() {}
+
+  @AfterSuite
+  public void afterSuite() {}
+
+  @BeforeClass
+  public void beforeClass() {}
+
+  @AfterClass
+  public void afterClass() {}
+}
diff --git a/src/test/java/test/v6/B.java b/src/test/java/test/v6/B.java
new file mode 100644
index 0000000..ca736eb
--- /dev/null
+++ b/src/test/java/test/v6/B.java
@@ -0,0 +1,35 @@
+package test.v6;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+public class B {
+
+  @Test(dependsOnMethods = "fb1")
+  public void fb2() {
+  }
+
+  @Test(groups = "1")
+  public void fb1() {}
+
+  @Test public void fb3() {}
+
+  @BeforeMethod
+  public void beforeMethod() {}
+
+  @AfterMethod(groups = "1")
+  public void afterMethod() {}
+
+  @BeforeSuite
+  public void beforeSuite() {}
+
+  @BeforeClass
+  public void beforeClass() {}
+
+  @AfterClass
+  public void afterClass() {}
+}
diff --git a/src/test/java/test/v6/C.java b/src/test/java/test/v6/C.java
new file mode 100644
index 0000000..efcc10a
--- /dev/null
+++ b/src/test/java/test/v6/C.java
@@ -0,0 +1,26 @@
+package test.v6;
+
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+public class C {
+
+  @BeforeTest
+  public void beforeTest() {}
+
+  @AfterTest
+  public void afterTest() {}
+
+  @Test
+  public void fc1() {}
+
+  @BeforeSuite
+  public void beforeSuite() {}
+
+  @AfterSuite
+  public void afterSuite() {}
+
+}
diff --git a/src/test/java/test/verify/Verifier.java b/src/test/java/test/verify/Verifier.java
new file mode 100644
index 0000000..a04efca
--- /dev/null
+++ b/src/test/java/test/verify/Verifier.java
@@ -0,0 +1,12 @@
+package test.verify;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target({METHOD, TYPE})
+public @interface Verifier {
+}
diff --git a/src/test/java/test/verify/Verify.java b/src/test/java/test/verify/Verify.java
new file mode 100644
index 0000000..982a66f
--- /dev/null
+++ b/src/test/java/test/verify/Verify.java
@@ -0,0 +1,12 @@
+package test.verify;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target({METHOD, TYPE})
+public @interface Verify {
+}
diff --git a/src/test/java/test/verify/Verify2SampleTest.java b/src/test/java/test/verify/Verify2SampleTest.java
new file mode 100644
index 0000000..dc16a88
--- /dev/null
+++ b/src/test/java/test/verify/Verify2SampleTest.java
@@ -0,0 +1,49 @@
+package test.verify;
+
+import org.testng.ITestNGListener;
+import org.testng.ITestNGListenerFactory;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+/**
+ * Illustrate the implementation of a @Verify/@Verifier test.
+ *
+ * One method should be annotated with @Verifier and then each test method
+ * annotated with @Verify will be followed with a call to the @Verifier
+ * method.
+ */
+@Listeners(VerifyTestListener.class)
+public class Verify2SampleTest implements ITestNGListenerFactory {
+
+  public Verify2SampleTest() {}
+
+  @Verify
+  @Test
+  public void f1() {
+    log("f1");
+  }
+
+  @Verify
+  @Test
+  public void f2() {
+    log("f2");
+  }
+
+  @Verifier
+  @Test
+  public void verify() {
+    log("Verifying");
+  }
+
+  private void log(String string) {
+    if (false) {
+      System.out.println(hashCode() + " " + string);
+    }
+  }
+
+  @Override
+  public ITestNGListener createListener(Class<? extends ITestNGListener> listenerClass) {
+    log("Creating a listener of type " + listenerClass);
+    return null;
+  }
+}
diff --git a/src/test/java/test/verify/Verify3Base.java b/src/test/java/test/verify/Verify3Base.java
new file mode 100644
index 0000000..6d9bc0f
--- /dev/null
+++ b/src/test/java/test/verify/Verify3Base.java
@@ -0,0 +1,7 @@
+package test.verify;
+
+import org.testng.annotations.Listeners;
+
+@Listeners(VerifyTestListener.class)
+public class Verify3Base {
+}
diff --git a/src/test/java/test/verify/Verify3SampleTest.java b/src/test/java/test/verify/Verify3SampleTest.java
new file mode 100644
index 0000000..632adbe
--- /dev/null
+++ b/src/test/java/test/verify/Verify3SampleTest.java
@@ -0,0 +1,13 @@
+package test.verify;
+
+import org.testng.annotations.BeforeClass;
+
+/**
+ * Make sure that @Listeners annotations can come from superclasses
+ */
+public class Verify3SampleTest extends Verify3Base {
+  @BeforeClass
+  public void bc() {
+    VerifyTestListener.m_count = 0;
+  }
+}
diff --git a/src/test/java/test/verify/VerifyMethodInterceptor.java b/src/test/java/test/verify/VerifyMethodInterceptor.java
new file mode 100644
index 0000000..4a005c3
--- /dev/null
+++ b/src/test/java/test/verify/VerifyMethodInterceptor.java
@@ -0,0 +1,42 @@
+package test.verify;
+
+import org.testng.IMethodInstance;
+import org.testng.IMethodInterceptor;
+import org.testng.ITestContext;
+import org.testng.collections.Lists;
+
+import java.util.List;
+
+public class VerifyMethodInterceptor implements IMethodInterceptor {
+  @Override
+  public List<IMethodInstance> intercept(List<IMethodInstance> methods,
+      ITestContext context) {
+    List<IMethodInstance> result = Lists.newArrayList();
+    IMethodInstance verifier = null;
+
+    // Doing a naive approach here: we run through the list of methods
+    // twice, once to find the verifier and once more to actually create
+    // the result. Obviously, this can be done with just one loop
+    for (IMethodInstance m : methods) {
+      if (m.getMethod().getMethod().getAnnotation(Verifier.class) != null) {
+        verifier = m;
+        break;
+      }
+    }
+
+    // Create the result with each @Verify method followed by a call
+    // to the @Verifier method
+    for (IMethodInstance m : methods) {
+      if (m != verifier) {
+        result.add(m);
+      }
+
+      if (m.getMethod().getMethod().getAnnotation(Verify.class) != null) {
+        result.add(verifier);
+      }
+    }
+
+    return result;
+  }
+
+}
\ No newline at end of file
diff --git a/src/test/java/test/verify/VerifyNoListenersSampleTest.java b/src/test/java/test/verify/VerifyNoListenersSampleTest.java
new file mode 100644
index 0000000..013cbc4
--- /dev/null
+++ b/src/test/java/test/verify/VerifyNoListenersSampleTest.java
@@ -0,0 +1,33 @@
+package test.verify;
+
+import org.testng.annotations.Test;
+
+/**
+ * Same class as VerifySampleTest but without the @Listeners annotation.
+ */
+public class VerifyNoListenersSampleTest {
+
+  @Verify
+  @Test
+  public void f1() {
+    log("f1");
+  }
+
+  @Verify
+  @Test
+  public void f2() {
+    log("f2");
+  }
+
+  @Verifier
+  @Test
+  public void verify() {
+    log("Verifying");
+  }
+
+  private void log(String string) {
+    if (false) {
+      System.out.println(string);
+    }
+  }
+}
diff --git a/src/test/java/test/verify/VerifySampleTest.java b/src/test/java/test/verify/VerifySampleTest.java
new file mode 100644
index 0000000..d660d8e
--- /dev/null
+++ b/src/test/java/test/verify/VerifySampleTest.java
@@ -0,0 +1,39 @@
+package test.verify;
+
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+/**
+ * Illustrate the implementation of a @Verify/@Verifier test.
+ *
+ * One method should be annotated with @Verifier and then each test method
+ * annotated with @Verify will be followed with a call to the @Verifier
+ * method.
+ */
+@Listeners(VerifyMethodInterceptor.class)
+public class VerifySampleTest {
+
+  @Verify
+  @Test
+  public void f1() {
+    log("f1");
+  }
+
+  @Verify
+  @Test
+  public void f2() {
+    log("f2");
+  }
+
+  @Verifier
+  @Test
+  public void verify() {
+    log("Verifying");
+  }
+
+  private void log(String string) {
+    if (false) {
+      System.out.println(string);
+    }
+  }
+}
diff --git a/src/test/java/test/verify/VerifyTest.java b/src/test/java/test/verify/VerifyTest.java
new file mode 100644
index 0000000..e91841e
--- /dev/null
+++ b/src/test/java/test/verify/VerifyTest.java
@@ -0,0 +1,47 @@
+package test.verify;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+import test.SimpleBaseTest;
+
+public class VerifyTest extends SimpleBaseTest {
+
+  private void runTest(Class<?> cls, int expected) {
+    TestNG tng = create(cls);
+    TestListenerAdapter tla = new TestListenerAdapter();
+    tng.addListener(tla);
+    tng.run();
+
+    Assert.assertEquals(tla.getPassedTests().size(), expected);
+  }
+
+  @Test
+  public void verifyWithAnnotation() {
+    runTest(VerifySampleTest.class, 4);
+  }
+
+  @Test
+  public void verifyWithoutAnnotation() {
+    runTest(VerifyNoListenersSampleTest.class, 3);
+  }
+
+  @Test
+  public void verifyTestListener() {
+    TestNG tng = create(Verify2SampleTest.class);
+    VerifyTestListener.m_count = 0;
+    tng.run();
+    Assert.assertEquals(VerifyTestListener.m_count, 1);
+  }
+
+  @Test
+  public void verifyBaseClassTestListener() {
+    TestNG tng = create(Verify3SampleTest.class);
+    VerifyTestListener.m_count = 0;
+    tng.run();
+    Assert.assertEquals(VerifyTestListener.m_count, 1);
+  }
+
+}
diff --git a/src/test/java/test/verify/VerifyTestListener.java b/src/test/java/test/verify/VerifyTestListener.java
new file mode 100644
index 0000000..bcd8648
--- /dev/null
+++ b/src/test/java/test/verify/VerifyTestListener.java
@@ -0,0 +1,39 @@
+package test.verify;
+
+import org.testng.ITestContext;
+import org.testng.ITestListener;
+import org.testng.ITestResult;
+
+public class VerifyTestListener implements ITestListener {
+  public static int m_count = 0;
+
+  @Override
+  public void onFinish(ITestContext context) {
+  }
+
+  @Override
+  public void onStart(ITestContext context) {
+    m_count++;
+  }
+
+  @Override
+  public void onTestFailedButWithinSuccessPercentage(ITestResult result) {
+  }
+
+  @Override
+  public void onTestFailure(ITestResult result) {
+  }
+
+  @Override
+  public void onTestSkipped(ITestResult result) {
+  }
+
+  @Override
+  public void onTestStart(ITestResult result) {
+  }
+
+  @Override
+  public void onTestSuccess(ITestResult result) {
+  }
+
+}
diff --git a/src/test/java/test/xml/XmlVerifyTest.java b/src/test/java/test/xml/XmlVerifyTest.java
new file mode 100644
index 0000000..c1f4db4
--- /dev/null
+++ b/src/test/java/test/xml/XmlVerifyTest.java
@@ -0,0 +1,59 @@
+package test.xml;
+
+import org.testng.Assert;
+import org.testng.TestListenerAdapter;
+import org.testng.TestNG;
+import org.testng.TestNGException;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import test.SimpleBaseTest;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Arrays;
+
+public class XmlVerifyTest extends SimpleBaseTest {
+
+//  private String getFinalPath(String file) {
+//    File currentDir = new File(".");
+//    String path = currentDir.getAbsolutePath();
+//    char s = File.separatorChar;
+//    String testDir = System.getProperty("test.dir");
+//    System.out.println("[XmlVerifyTest] test.dir:" + testDir);
+//    Assert.assertNotNull(testDir);
+//    path = path + s + testDir + s;
+//    return path + file;
+//  }
+
+  @Test
+  public void simple() {
+    XmlSuite suite = new XmlSuite();
+    XmlTest test = new XmlTest(suite);
+    XmlClass xClass = new XmlClass(XmlVerifyTest.class);
+    test.getXmlClasses().add(xClass);
+    test.getExcludedGroups().add("fast");
+    test.setVerbose(5);
+
+    suite.toXml();
+  }
+
+  @Test(description="Ensure that TestNG stops without running any tests if some class" +
+      " included in suite is missing")
+  public void handleInvalidSuites() {
+     TestListenerAdapter tla = new TestListenerAdapter();
+     try {
+        TestNG tng = create();
+        String testngXmlPath = getPathToResource("suite1.xml");
+        tng.setTestSuites(Arrays.asList(testngXmlPath));
+        tng.addListener(tla);
+        tng.run();
+     } catch (TestNGException ex) {
+        Assert.assertEquals(tla.getPassedTests().size(), 0);
+     }
+  }
+}
diff --git a/src/test/java/test/yaml/YamlTest.java b/src/test/java/test/yaml/YamlTest.java
new file mode 100644
index 0000000..990788f
--- /dev/null
+++ b/src/test/java/test/yaml/YamlTest.java
@@ -0,0 +1,40 @@
+package test.yaml;
+
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import org.testng.xml.Parser;
+import org.testng.xml.XmlSuite;
+import org.xml.sax.SAXException;
+
+import test.SimpleBaseTest;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+
+public class YamlTest extends SimpleBaseTest {
+
+  @DataProvider
+  public Object[][] dp() {
+    return new Object[][] {
+      new Object[] { "a1" },
+      new Object[] { "a2" },
+      new Object[] { "a3" },
+      new Object[] { "a4" },
+    };
+  }
+
+  @Test(dataProvider = "dp")
+  public void compareFiles(String name)
+      throws ParserConfigurationException, SAXException, IOException {
+    Collection<XmlSuite> s1 =
+        new Parser(getPathToResource("yaml" + File.separator + name + ".yaml")).parse();
+    Collection<XmlSuite> s2 =
+        new Parser(getPathToResource("yaml" + File.separator + name + ".xml")).parse();
+
+    Assert.assertEquals(s1, s2);
+  }
+}
diff --git a/src/test/java/testhelper/OutputDirectoryPatch.java b/src/test/java/testhelper/OutputDirectoryPatch.java
new file mode 100644
index 0000000..733ae53
--- /dev/null
+++ b/src/test/java/testhelper/OutputDirectoryPatch.java
@@ -0,0 +1,47 @@
+package testhelper;
+
+/**
+ * <code>OutputDirectoryPatch</code> is a helper class to provide an output directory
+ * for TestNG tests that explicitly create an instance of TestNG and do not know the
+ * output directory specified for the test.
+ *
+ * @author cquezel
+ * @since 4.8
+ */
+public final class OutputDirectoryPatch {
+
+  /** The default output directory name if none was specified. We should use something
+   * different than "test-output" to make it clear that the output directory
+   * has not been set. */
+  private static final String DEFAULT_OUTPUT_DIRECTORY = "test-output";
+
+  /** The name of the System property used to store the output directory. */
+  private static final String OUTPUT_DIRECTORY_PROPERTY_NAME = "testng.outputDir";
+
+  /**
+   * Private constructor to disable instantiation.
+   *
+   * @since 4.8
+   */
+  private OutputDirectoryPatch() {
+    // Hide constructor
+  }
+
+  /**
+   * Returns the output directory as specified for the current test.
+   *
+   * @return the output directory as specified for the current test.
+   * @since 4.8
+   */
+  public static String getOutputDirectory() {
+    String tmp = System.getProperty(OUTPUT_DIRECTORY_PROPERTY_NAME);
+    if (tmp != null) {
+      return tmp;
+    }
+//    System.err.println("System property: " + OUTPUT_DIRECTORY_PROPERTY_NAME
+//        + " has not been set. Using default path: " + DEFAULT_OUTPUT_DIRECTORY);
+
+//    new Throwable("Stack is only to help locate the problem. No excpetion thrown.").printStackTrace(System.err);
+    return DEFAULT_OUTPUT_DIRECTORY;
+  }
+}
diff --git a/src/test/resources/META-INF/services/org.testng.ITestNGListener b/src/test/resources/META-INF/services/org.testng.ITestNGListener
new file mode 100644
index 0000000..9f76d9b
--- /dev/null
+++ b/src/test/resources/META-INF/services/org.testng.ITestNGListener
@@ -0,0 +1 @@
+test.serviceloader.MyConfigurationListener
\ No newline at end of file
diff --git a/src/test/resources/a.xml b/src/test/resources/a.xml
new file mode 100644
index 0000000..11b9efe
--- /dev/null
+++ b/src/test/resources/a.xml
@@ -0,0 +1,12 @@
+<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
+  
+<suite name="A" verbose="1" >
+
+  <test name="Test1">
+    <classes>
+      <class name="test.tmp.A" />
+    </classes>
+  </test>
+
+</suite>
+
diff --git a/src/test/resources/b.xml b/src/test/resources/b.xml
new file mode 100644
index 0000000..22036b8
--- /dev/null
+++ b/src/test/resources/b.xml
@@ -0,0 +1,12 @@
+<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
+  
+<suite name="B" verbose="1" >
+
+  <test name="Test1">
+    <classes>
+      <class name="test.tmp.B" />
+    </classes>
+  </test>
+
+</suite>
+
diff --git a/src/test/resources/checksuitesinitialization/child-suite1.xml b/src/test/resources/checksuitesinitialization/child-suite1.xml
new file mode 100644
index 0000000..0ad2aa6
--- /dev/null
+++ b/src/test/resources/checksuitesinitialization/child-suite1.xml
@@ -0,0 +1,7 @@
+<suite name="Child Suite 1">
+  <test name="Test">
+    <classes>
+      <class name="test.parameters.SampleTest"/>
+    </classes>
+  </test>
+</suite>
diff --git a/src/test/resources/checksuitesinitialization/child-suite2.xml b/src/test/resources/checksuitesinitialization/child-suite2.xml
new file mode 100644
index 0000000..460378b
--- /dev/null
+++ b/src/test/resources/checksuitesinitialization/child-suite2.xml
@@ -0,0 +1,7 @@
+<suite name="Child Suite 2">
+  <test name="Test">
+    <classes>
+      <class name="test.parameters.SampleTest"/>
+    </classes>
+  </test>
+</suite>
diff --git a/src/test/resources/checksuitesinitialization/children/child-suite-3.xml b/src/test/resources/checksuitesinitialization/children/child-suite-3.xml
new file mode 100644
index 0000000..91a4d83
--- /dev/null
+++ b/src/test/resources/checksuitesinitialization/children/child-suite-3.xml
@@ -0,0 +1,8 @@
+<suite name="Child Suite 3">
+  <parameter name="foo" value="bar"/>
+  <suite-files>
+    <suite-file path="../child-suite2.xml" />
+    <suite-file path="child-suite-4.xml" />
+    <suite-file path="morechildren/child-suite-5.xml" />
+  </suite-files>
+</suite>
\ No newline at end of file
diff --git a/src/test/resources/checksuitesinitialization/children/child-suite-4.xml b/src/test/resources/checksuitesinitialization/children/child-suite-4.xml
new file mode 100644
index 0000000..ac511c1
--- /dev/null
+++ b/src/test/resources/checksuitesinitialization/children/child-suite-4.xml
@@ -0,0 +1,7 @@
+<suite name="Child Suite 4">
+  <test name="Test">
+    <classes>
+      <class name="test.parameters.SampleTest"/>
+    </classes>
+  </test>
+</suite>
\ No newline at end of file
diff --git a/src/test/resources/checksuitesinitialization/children/morechildren/child-suite-5.xml b/src/test/resources/checksuitesinitialization/children/morechildren/child-suite-5.xml
new file mode 100644
index 0000000..04ab130
--- /dev/null
+++ b/src/test/resources/checksuitesinitialization/children/morechildren/child-suite-5.xml
@@ -0,0 +1,7 @@
+<suite name="Child Suite 5">
+  <test name="Test">
+    <classes>
+      <class name="test.parameters.SampleTest"/>
+    </classes>
+  </test>
+</suite>
\ No newline at end of file
diff --git a/src/test/resources/checksuitesinitialization/parent-suite.xml b/src/test/resources/checksuitesinitialization/parent-suite.xml
new file mode 100644
index 0000000..7239e8c
--- /dev/null
+++ b/src/test/resources/checksuitesinitialization/parent-suite.xml
@@ -0,0 +1,7 @@
+<suite name="Parent Suite">
+  <parameter name="foo" value="bar"/>
+  <suite-files>
+    <suite-file path="./child-suite1.xml" />
+    <suite-file path="children/child-suite-3.xml" />
+  </suite-files>
+</suite>
\ No newline at end of file
diff --git a/src/test/resources/create-serviceloader-jar.sh b/src/test/resources/create-serviceloader-jar.sh
new file mode 100644
index 0000000..850e9f9
--- /dev/null
+++ b/src/test/resources/create-serviceloader-jar.sh
@@ -0,0 +1,12 @@
+# This script will update serviceloader.jar with the latest version of TmpSuiteListener.class,
+# which is used by test.serviceloader.ServiceLoaderTest.
+# Run this script after building TestNG and its tests with ant
+
+j=${PWD}
+rm -rf /tmp/sl
+mkdir /tmp/sl
+cd /tmp/sl
+jar xvf ${j}/serviceloader.jar
+echo "test.serviceloader.TmpSuiteListener" >META-INF/services/org.testng.ITestNGListener
+cp ${j}/../../../target/test-classes/test/serviceloader/TmpSuiteListener.class test/tmp
+jar cvf ${j}/serviceloader.jar .
diff --git a/src/test/resources/hosts.properties b/src/test/resources/hosts.properties
new file mode 100644
index 0000000..cb18885
--- /dev/null
+++ b/src/test/resources/hosts.properties
@@ -0,0 +1,2 @@
+testng.hosts=localhost:5150
+testng.strategy=test
diff --git a/src/test/resources/junit-suite.xml b/src/test/resources/junit-suite.xml
new file mode 100644
index 0000000..455dd43
--- /dev/null
+++ b/src/test/resources/junit-suite.xml
@@ -0,0 +1,11 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+
+<suite name="TestNG Running Junit Tests" verbose="0">
+
+  <test name="Junit via TestNG XML" junit="true">
+    <classes>
+      <class name="test.junit.SetNameTest" />
+    </classes>
+  </test>
+
+</suite>
diff --git a/src/test/resources/listener-in-xml.xml b/src/test/resources/listener-in-xml.xml
new file mode 100644
index 0000000..e8e18df
--- /dev/null
+++ b/src/test/resources/listener-in-xml.xml
@@ -0,0 +1,16 @@
+<suite name="SingleSuite" verbose="1" parallel="false" thread-count="4"
+    data-provider-thread-count="3">
+
+  <listeners>
+    <listener class-name="test.listeners.LListener" />
+  </listeners>
+
+  <test name="Regression2" preserve-order="true">
+
+    <classes>
+      <class name="test.listeners.LSampleTest" />
+    </classes>
+  </test>
+
+</suite>
+
diff --git a/src/test/resources/methodinterceptors/multipleinterceptors/multiple-interceptors.xml b/src/test/resources/methodinterceptors/multipleinterceptors/multiple-interceptors.xml
new file mode 100644
index 0000000..1d13348
--- /dev/null
+++ b/src/test/resources/methodinterceptors/multipleinterceptors/multiple-interceptors.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<suite name="Multiple interceptors">
+  
+    <listeners>
+        <listener class-name="test.methodinterceptors.multipleinterceptors.FirstInterceptor"/>
+        <listener class-name="test.methodinterceptors.multipleinterceptors.SecondInterceptor"/>
+        <listener class-name="test.methodinterceptors.multipleinterceptors.ThirdInterceptor"/>
+    </listeners>
+  
+    <test name="MultipleInterceptors:Tests" preserve-order="true">
+        <classes>
+            <class name="test.methodinterceptors.multipleinterceptors.FooTest"/>
+        </classes>
+    </test>
+</suite>
diff --git a/src/test/resources/methodselector-in-xml.xml b/src/test/resources/methodselector-in-xml.xml
new file mode 100644
index 0000000..2000413
--- /dev/null
+++ b/src/test/resources/methodselector-in-xml.xml
@@ -0,0 +1,13 @@
+<suite name="SingleSuite" verbose="0">
+  <method-selectors>
+    <method-selector>
+      <selector-class name="test.methodselectors.Test2MethodSelector" priority="-1" />
+    </method-selector>
+  </method-selectors>
+  
+  <test name="Regression2" preserve-order="true">
+    <classes>
+      <class name="test.methodselectors.SampleTest" />
+    </classes>
+  </test>
+</suite>
\ No newline at end of file
diff --git a/src/test/resources/package.xml b/src/test/resources/package.xml
new file mode 100644
index 0000000..bbd7112
--- /dev/null
+++ b/src/test/resources/package.xml
@@ -0,0 +1,31 @@
+<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
+  
+<suite name="Package" verbose="1" parallel="false" thread-count="4"
+    data-provider-thread-count="3">
+
+  <listeners>
+    <listener class-name="test.tmp.BListener" />
+  </listeners>
+
+  <test name="A">
+    <groups>
+      <run>
+        <include name="group-a" />
+      </run>
+    </groups>
+    <packages>
+      <package name="test.tmp.a" />
+      <package name="test.tmp.b" />
+    </packages>
+  </test>
+
+<!-- 
+  <test name="B">
+    <packages>
+      <package name="test.tmp.b" />
+    </packages>
+  </test>
+ -->
+ 
+</suite>
+
diff --git a/src/test/resources/parallel-suites/suite-parallel-0.xml b/src/test/resources/parallel-suites/suite-parallel-0.xml
new file mode 100644
index 0000000..41d41dd
--- /dev/null
+++ b/src/test/resources/parallel-suites/suite-parallel-0.xml
@@ -0,0 +1,14 @@
+<suite name="Suite Parallel 0">
+
+  <suite-files>
+    <suite-file path="./suite-parallel-3.xml" />
+    <suite-file path="./suite-parallel-2.xml" />
+    <suite-file path="./suite-parallel-1.xml" />
+  </suite-files>
+
+  <test name="Test0">
+    <classes>
+      <class name="test.thread.Sample2"/>
+    </classes>
+  </test>
+</suite>
\ No newline at end of file
diff --git a/src/test/resources/parallel-suites/suite-parallel-1.xml b/src/test/resources/parallel-suites/suite-parallel-1.xml
new file mode 100644
index 0000000..ec27961
--- /dev/null
+++ b/src/test/resources/parallel-suites/suite-parallel-1.xml
@@ -0,0 +1,8 @@
+<suite name="Suite Parallel 3">
+
+  <test name="Test3">
+    <classes>
+      <class name="test.thread.Sample2"/>
+    </classes>
+  </test>
+</suite>
diff --git a/src/test/resources/parallel-suites/suite-parallel-2-1.xml b/src/test/resources/parallel-suites/suite-parallel-2-1.xml
new file mode 100644
index 0000000..3d4b2b7
--- /dev/null
+++ b/src/test/resources/parallel-suites/suite-parallel-2-1.xml
@@ -0,0 +1,8 @@
+<suite name="Suite Parallel 2-1">
+
+  <test name="Test2-1">
+    <classes>
+      <class name="test.thread.Sample2"/>
+    </classes>
+  </test>
+</suite>
diff --git a/src/test/resources/parallel-suites/suite-parallel-2-2-1.xml b/src/test/resources/parallel-suites/suite-parallel-2-2-1.xml
new file mode 100644
index 0000000..e13fa9e
--- /dev/null
+++ b/src/test/resources/parallel-suites/suite-parallel-2-2-1.xml
@@ -0,0 +1,8 @@
+<suite name="Suite Parallel 2-2-1">
+
+  <test name="Test2-2-1">
+    <classes>
+      <class name="test.thread.Sample2"/>
+    </classes>
+  </test>
+</suite>
diff --git a/src/test/resources/parallel-suites/suite-parallel-2-2.xml b/src/test/resources/parallel-suites/suite-parallel-2-2.xml
new file mode 100644
index 0000000..dba7395
--- /dev/null
+++ b/src/test/resources/parallel-suites/suite-parallel-2-2.xml
@@ -0,0 +1,12 @@
+<suite name="Suite Parallel 2-2">
+
+  <suite-files>
+    <suite-file path="./suite-parallel-2-2-1.xml" />
+  </suite-files>
+
+  <test name="Test2-2">
+    <classes>
+      <class name="test.thread.Sample2"/>
+    </classes>
+  </test>
+</suite>
diff --git a/src/test/resources/parallel-suites/suite-parallel-2.xml b/src/test/resources/parallel-suites/suite-parallel-2.xml
new file mode 100644
index 0000000..f49ffdf
--- /dev/null
+++ b/src/test/resources/parallel-suites/suite-parallel-2.xml
@@ -0,0 +1,13 @@
+<suite name="Suite Parallel 2">
+
+  <test name="Test2">
+    <classes>
+      <class name="test.thread.Sample2"/>
+    </classes>
+  </test>
+
+  <suite-files>
+    <suite-file path="./suite-parallel-2-1.xml" />
+    <suite-file path="./suite-parallel-2-2.xml" />
+  </suite-files>
+</suite>
diff --git a/src/test/resources/parallel-suites/suite-parallel-3.xml b/src/test/resources/parallel-suites/suite-parallel-3.xml
new file mode 100644
index 0000000..3cf729f
--- /dev/null
+++ b/src/test/resources/parallel-suites/suite-parallel-3.xml
@@ -0,0 +1,8 @@
+<suite name="Suite Parallel 1">
+
+  <test name="Test1">
+    <classes>
+      <class name="test.thread.Sample2"/>
+    </classes>
+  </test>
+</suite>
diff --git a/src/test/resources/param-inheritance/child-suite.xml b/src/test/resources/param-inheritance/child-suite.xml
new file mode 100644
index 0000000..5d60cd9
--- /dev/null
+++ b/src/test/resources/param-inheritance/child-suite.xml
@@ -0,0 +1,7 @@
+<suite name="Child Suite">
+  <test name="Test">
+    <classes>
+      <class name="test.parameters.SampleTest"/>
+    </classes>
+  </test>
+</suite>
diff --git a/src/test/resources/param-inheritance/parent-suite.xml b/src/test/resources/param-inheritance/parent-suite.xml
new file mode 100644
index 0000000..40b5ad1
--- /dev/null
+++ b/src/test/resources/param-inheritance/parent-suite.xml
@@ -0,0 +1,6 @@
+<suite name="Parent Suite">
+  <parameter name="foo" value="bar"/>
+  <suite-files>
+    <suite-file path="./child-suite.xml" />
+  </suite-files>
+</suite>
\ No newline at end of file
diff --git a/src/test/resources/parametertest/child1-suite.xml b/src/test/resources/parametertest/child1-suite.xml
new file mode 100644
index 0000000..f8f5de3
--- /dev/null
+++ b/src/test/resources/parametertest/child1-suite.xml
@@ -0,0 +1,11 @@
+<suite name="InheritedParametersSuite Child1">
+
+  <parameter name="parameter2" value="c1p2"/>
+  <parameter name="parameter4" value="c1p4"/>
+  <test name="InheritedParametersTest Child1">
+      <classes>
+          <class name="test.parameters.InheritFromSuiteChild1" />
+      </classes>
+  </test>
+
+</suite>
\ No newline at end of file
diff --git a/src/test/resources/parametertest/child2-suite.xml b/src/test/resources/parametertest/child2-suite.xml
new file mode 100644
index 0000000..8d555a0
--- /dev/null
+++ b/src/test/resources/parametertest/child2-suite.xml
@@ -0,0 +1,15 @@
+<suite name="InheritedParametersSuite Child2">
+
+  <parameter name="parameter2" value="c2p2"/>
+  <parameter name="parameter3" value="c2p3"/>
+  <test name="InheritedParametersTest Child2">
+      <classes>
+          <class name="test.parameters.InheritFromSuiteChild2" />
+      </classes>
+  </test>
+  
+  <suite-files>
+    <suite-file path="./child3-suite.xml"/>
+  </suite-files>
+
+</suite>
\ No newline at end of file
diff --git a/src/test/resources/parametertest/child3-suite.xml b/src/test/resources/parametertest/child3-suite.xml
new file mode 100644
index 0000000..94b7d64
--- /dev/null
+++ b/src/test/resources/parametertest/child3-suite.xml
@@ -0,0 +1,11 @@
+<suite name="InheritedParametersSuite Child3">
+
+  <parameter name="parameter2" value="c3p2"/>
+  <parameter name="parameter4" value="c3p4"/>
+  <test name="InheritedParametersTest Child3">
+      <classes>
+          <class name="test.parameters.InheritFromSuiteChild3" />
+      </classes>
+  </test>
+
+</suite>
\ No newline at end of file
diff --git a/src/test/resources/parametertest/parent-suite.xml b/src/test/resources/parametertest/parent-suite.xml
new file mode 100644
index 0000000..4ee67e1
--- /dev/null
+++ b/src/test/resources/parametertest/parent-suite.xml
@@ -0,0 +1,9 @@
+<suite name="Parameter Inheritance Tests">
+
+  <parameter name="parameter1" value="p1"/>
+  <parameter name="parameter2" value="p2"/>
+  <suite-files>
+    <suite-file path="./child1-suite.xml"/>
+    <suite-file path="./child2-suite.xml"/>
+  </suite-files>
+</suite>
\ No newline at end of file
diff --git a/src/test/resources/parent-module-suite.xml b/src/test/resources/parent-module-suite.xml
new file mode 100644
index 0000000..b3ea096
--- /dev/null
+++ b/src/test/resources/parent-module-suite.xml
@@ -0,0 +1,9 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+
+<suite name="parent-module-suite" parent-module="test.guice.GuiceParentModule">
+  <test name="Guice">
+    <classes>
+      <class name="test.guice.GuiceParentModuleTest" />
+    </classes>
+  </test>
+</suite>
\ No newline at end of file
diff --git a/src/test/resources/sanitycheck/test-a.xml b/src/test/resources/sanitycheck/test-a.xml
new file mode 100644
index 0000000..e2a9731
--- /dev/null
+++ b/src/test/resources/sanitycheck/test-a.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<suite name="SanityCheck tests">
+   <suite-files>
+      <suite-file path="./test1.xml"/>
+      <suite-file path="./test2.xml"/>
+   </suite-files>
+</suite>
diff --git a/src/test/resources/sanitycheck/test-b.xml b/src/test/resources/sanitycheck/test-b.xml
new file mode 100644
index 0000000..33d8ab0
--- /dev/null
+++ b/src/test/resources/sanitycheck/test-b.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<suite name="SanityCheck tests">
+   <suite-files>
+      <suite-file path="./test2.xml"/>
+      <suite-file path="./test3.xml"/>
+   </suite-files>
+</suite>
diff --git a/src/test/resources/sanitycheck/test-s-1.xml b/src/test/resources/sanitycheck/test-s-1.xml
new file mode 100644
index 0000000..f9fa2c0
--- /dev/null
+++ b/src/test/resources/sanitycheck/test-s-1.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<suite name="SanityCheck suites">
+  <test name="SanityCheck:Tests" preserve-order="true">
+    <classes>
+      <class name="test.sanitycheck.SampleTest1"/>
+    </classes>
+  </test>
+  <test name="SanityCheck:Tests:Another" preserve-order="true">
+    <classes>
+      <class name="test.sanitycheck.SampleTest3"/>
+    </classes>
+  </test>
+</suite>
diff --git a/src/test/resources/sanitycheck/test-s-2.xml b/src/test/resources/sanitycheck/test-s-2.xml
new file mode 100644
index 0000000..f9fa2c0
--- /dev/null
+++ b/src/test/resources/sanitycheck/test-s-2.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<suite name="SanityCheck suites">
+  <test name="SanityCheck:Tests" preserve-order="true">
+    <classes>
+      <class name="test.sanitycheck.SampleTest1"/>
+    </classes>
+  </test>
+  <test name="SanityCheck:Tests:Another" preserve-order="true">
+    <classes>
+      <class name="test.sanitycheck.SampleTest3"/>
+    </classes>
+  </test>
+</suite>
diff --git a/src/test/resources/sanitycheck/test-s-3.xml b/src/test/resources/sanitycheck/test-s-3.xml
new file mode 100644
index 0000000..6255414
--- /dev/null
+++ b/src/test/resources/sanitycheck/test-s-3.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<suite name="SanityCheck suites 3">
+  <test name="SanityCheck:Tests" preserve-order="true">
+    <classes>
+      <class name="test.sanitycheck.SampleTest1"/>
+    </classes>
+  </test>
+  <test name="SanityCheck:Tests:Another" preserve-order="true">
+    <classes>
+      <class name="test.sanitycheck.SampleTest3"/>
+    </classes>
+  </test>
+</suite>
diff --git a/src/test/resources/sanitycheck/test-s-a.xml b/src/test/resources/sanitycheck/test-s-a.xml
new file mode 100644
index 0000000..65d9799
--- /dev/null
+++ b/src/test/resources/sanitycheck/test-s-a.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<suite name="SanityCheck suites root">
+  <suite-files>
+    <suite-file path="./test-s-1.xml"/>
+    <suite-file path="./test-s-2.xml"/>
+  </suite-files>
+</suite>
diff --git a/src/test/resources/sanitycheck/test-s-b.xml b/src/test/resources/sanitycheck/test-s-b.xml
new file mode 100644
index 0000000..6788947
--- /dev/null
+++ b/src/test/resources/sanitycheck/test-s-b.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<suite name="SanityCheck suites root">
+  <suite-files>
+    <suite-file path="./test-s-2.xml"/>
+    <suite-file path="./test-s-3.xml"/>
+  </suite-files>
+</suite>
diff --git a/src/test/resources/sanitycheck/test1.xml b/src/test/resources/sanitycheck/test1.xml
new file mode 100644
index 0000000..7b00b9e
--- /dev/null
+++ b/src/test/resources/sanitycheck/test1.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<suite name="SanityCheck tests 1">
+  <test name="SanityCheck:Tests" preserve-order="true">
+    <classes>
+      <class name="test.sanitycheck.SampleTest1"/>
+      <class name="test.sanitycheck.SampleTest3"/>
+    </classes>
+  </test>
+  <test name="SanityCheck:Tests:Another" preserve-order="true">
+    <classes>
+      <class name="test.sanitycheck.SampleTest2"/>
+    </classes>
+  </test>
+  <test name="SanityCheck:Tests">
+    <parameter name="foo" value="bar"/>
+    <classes>
+      <class name="test.sanitycheck.SampleTest3"/>
+    </classes>
+  </test>
+</suite>
diff --git a/src/test/resources/sanitycheck/test2.xml b/src/test/resources/sanitycheck/test2.xml
new file mode 100644
index 0000000..df39aee
--- /dev/null
+++ b/src/test/resources/sanitycheck/test2.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<suite name="SanityCheck tests 2">
+  <test name="SanityCheck:Tests" preserve-order="true">
+    <classes>
+      <class name="test.sanitycheck.SampleTest1"/>
+    </classes>
+  </test>
+  <test name="SanityCheck:Tests:Another" preserve-order="true">
+    <classes>
+      <class name="test.sanitycheck.SampleTest3"/>
+    </classes>
+  </test>
+</suite>
diff --git a/src/test/resources/sanitycheck/test3.xml b/src/test/resources/sanitycheck/test3.xml
new file mode 100644
index 0000000..262be71
--- /dev/null
+++ b/src/test/resources/sanitycheck/test3.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<suite name="SanityCheck tests">
+  <test name="SanityCheck:Tests" preserve-order="true">
+    <classes>
+      <class name="test.sanitycheck.SampleTest1"/>
+    </classes>
+  </test>
+  <test name="SanityCheck:Tests:Another" preserve-order="true">
+    <classes>
+      <class name="test.sanitycheck.SampleTest3"/>
+    </classes>
+  </test>
+</suite>
diff --git a/src/test/resources/serviceloader.jar b/src/test/resources/serviceloader.jar
new file mode 100644
index 0000000..6b5fcf5
--- /dev/null
+++ b/src/test/resources/serviceloader.jar
Binary files differ
diff --git a/src/test/resources/suite-parallel-0.xml b/src/test/resources/suite-parallel-0.xml
new file mode 100644
index 0000000..1e44ef3
--- /dev/null
+++ b/src/test/resources/suite-parallel-0.xml
@@ -0,0 +1,13 @@
+<suite name="Suite Parallel 0">
+
+  <suite-files>
+    <suite-file path="./suite-parallel-1.xml" />
+    <suite-file path="./suite-parallel-2.xml" />
+  </suite-files>
+
+  <test name="Test0">
+    <classes>
+      <class name="test.thread.Sample2"/>
+    </classes>
+  </test>
+</suite>
\ No newline at end of file
diff --git a/src/test/resources/suite-parallel-1.xml b/src/test/resources/suite-parallel-1.xml
new file mode 100644
index 0000000..3cf729f
--- /dev/null
+++ b/src/test/resources/suite-parallel-1.xml
@@ -0,0 +1,8 @@
+<suite name="Suite Parallel 1">
+
+  <test name="Test1">
+    <classes>
+      <class name="test.thread.Sample2"/>
+    </classes>
+  </test>
+</suite>
diff --git a/src/test/resources/suite-parallel-2.xml b/src/test/resources/suite-parallel-2.xml
new file mode 100644
index 0000000..2026443
--- /dev/null
+++ b/src/test/resources/suite-parallel-2.xml
@@ -0,0 +1,8 @@
+<suite name="Suite Parallel 2">
+
+  <test name="Test2">
+    <classes>
+      <class name="test.thread.Sample2"/>
+    </classes>
+  </test>
+</suite>
diff --git a/src/test/resources/suite1-1.xml b/src/test/resources/suite1-1.xml
new file mode 100644
index 0000000..78261f3
--- /dev/null
+++ b/src/test/resources/suite1-1.xml
@@ -0,0 +1,11 @@
+<suite name="Suite 1-1">
+
+  <test name="Suite 1-1 Tests">
+    <classes>
+<!-- 
+      <class name="foo.bar.NonExistentClass"/>
+-->
+      <class name="test.sample.Sample1"/>
+    </classes>
+  </test>
+</suite>
diff --git a/src/test/resources/suite1-2.xml b/src/test/resources/suite1-2.xml
new file mode 100644
index 0000000..7cc5cae
--- /dev/null
+++ b/src/test/resources/suite1-2.xml
@@ -0,0 +1,8 @@
+<suite name="Suite 1-2">
+
+  <test name="Suite 1-2 Tests">
+    <classes>
+      <class name="test.asserttests.AssertTest"/>
+    </classes>
+  </test>
+</suite>
\ No newline at end of file
diff --git a/src/test/resources/suite1.xml b/src/test/resources/suite1.xml
new file mode 100644
index 0000000..5a8d3fa
--- /dev/null
+++ b/src/test/resources/suite1.xml
@@ -0,0 +1,16 @@
+<suite name="Suite 1">
+
+  <test name="Suite 1 Tests">
+    <classes>
+      <class name="test.xml.XmlVerifyTest">
+         <methods>
+            <include name="simple"/>
+         </methods>
+      </class>
+    </classes>
+  </test>
+  <suite-files>
+   <suite-file path="./suite1-1.xml" />
+   <suite-file path="./suite1-2.xml" />
+  </suite-files>
+</suite>
\ No newline at end of file
diff --git a/src/test/resources/testnames/main-suite.xml b/src/test/resources/testnames/main-suite.xml
new file mode 100644
index 0000000..52d43f6
--- /dev/null
+++ b/src/test/resources/testnames/main-suite.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
+<suite name="Surefire debug suite">
+    <test name="testGroup1">
+        <classes>
+            <class name="test.testnames.TestNamesFeature">
+                <methods>
+                    <include name="sampleOutputTest1"/>
+                </methods>
+            </class>
+        </classes>
+    </test>
+    <test name="testGroup2">
+        <classes>
+            <class name="test.testnames.TestNamesFeature">
+                <methods>
+                    <include name="sampleOutputTest2"/>
+                </methods>
+            </class>
+        </classes>
+    </test>
+</suite>
\ No newline at end of file
diff --git a/src/test/resources/testnames/upstream-suite.xml b/src/test/resources/testnames/upstream-suite.xml
new file mode 100644
index 0000000..7a72d94
--- /dev/null
+++ b/src/test/resources/testnames/upstream-suite.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+<suite name="ParentSuite">
+    <suite-files>
+        <suite-file path="main-suite.xml"/>
+    </suite-files>
+</suite>
\ No newline at end of file
diff --git a/src/test/resources/testng-all.xml b/src/test/resources/testng-all.xml
new file mode 100644
index 0000000..9a9e08c
--- /dev/null
+++ b/src/test/resources/testng-all.xml
@@ -0,0 +1,99 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+
+<suite name="TestNG" verbose="1" parallel="false" thread-count="2"
+    data-provider-thread-count="3">
+
+  <method-selectors>
+    <method-selector>
+      <script language="javascript">foo()</script>
+    </method-selector>
+    <method-selector>
+      <selector-class name="SelectorClass" priority="3" />
+    </method-selector>
+  </method-selectors>
+
+  <parameter name="first-name" value="Cedric" />
+  <parameter name="suiteParameter" value="suiteParameterValue" />
+
+  <suite-files>
+    <suite-file path="./junit-suite.xml" />
+  </suite-files>
+
+  <groups>
+    <define name="bigSuite">
+      <include name="suite1" />
+      <include name="suite2" />
+    </define>
+    <run>
+      <exclude name="excludeThisGroup" />
+      <include name="includeThisGroup" />
+    </run>
+<!-- 
+    <dependencies>
+      <group name="a" depends-on="b" />
+      <group name="c" depends-on="d" />
+    </dependencies>
+ -->
+   </groups>
+
+  <listeners>
+    <listener class-name="com.beust.Listener1" />
+    <listener class-name="com.beust.Listener2" />
+  </listeners>
+
+  <packages>
+    <package name="com.example1" />
+    <package name="com.example2" />
+  </packages>
+
+  <test name="Nopackage" allow-return-values="true" thread-count="42">
+    <groups>
+      <define name="evenodd">
+        <include name="even" />
+        <include name="odd" />
+      </define>
+      <run>
+        <include name="nopackage"/>
+      </run>
+	    <dependencies>
+	      <group name="e" depends-on="f" />
+	      <group name="g" depends-on="h" />
+	    </dependencies>
+    </groups>
+    <classes>
+      <class name="NoPackageTest" />
+    </classes>
+  </test>
+
+  <test name="Regression1" >
+    <groups>
+      <define name="evenodd">
+        <include name="even" />
+        <include name="odd" />
+      </define>
+      <run>
+        <exclude name="excludeThisGroup" />
+      </run>
+    </groups>
+
+    <classes>
+      <class name="test.parameters.ParameterSample" />
+      <class name="test.parameters.ParameterTest" />
+      <class name="test.Test1" />
+      <class name="test.Test2" />
+      <class name="test.CtorCalledOnce" />
+      <class name="test.expectedexceptions.ExpectedExceptionsTest" />
+      <class name="test.access.PrivateAccessConfigurationMethods" />
+      <class name="test.expectedexceptions.WrappedExpectedExceptionTest" />
+      <class name="test.parameters.OptionalParameterTest"/>
+      <class name="test.parameters.ParamInheritanceTest"/>
+    </classes>
+  </test>
+
+  <test name="Bug173">
+    <classes>
+      <class name="test.testng173.TestNG173Test" />
+    </classes>
+  </test>
+
+</suite>
diff --git a/src/test/resources/testng-annconv.xml b/src/test/resources/testng-annconv.xml
new file mode 100644
index 0000000..10b509a
--- /dev/null
+++ b/src/test/resources/testng-annconv.xml
@@ -0,0 +1,14 @@
+<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
+  
+<suite name="Converter" verbose="3" parallel="false">
+  
+    <test name="AnnotationConverter">
+    <parameter name="source-directory" value="src/test/sample" />
+    <classes>
+    <class name="test.converter.AnnotationConverterTest" />
+    </classes>
+    </test>
+
+
+</suite>
+
diff --git a/src/test/resources/testng-ant.xml b/src/test/resources/testng-ant.xml
new file mode 100644
index 0000000..36a388e
--- /dev/null
+++ b/src/test/resources/testng-ant.xml
@@ -0,0 +1,10 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

+  

+<suite name="Suitename from xml" verbose="1" parallel="false" thread-count="2">

+  

+  <test name="TestName">

+    <classes>

+      <class name="NoPackageTest" />

+    </classes>

+  </test>

+</suite>

diff --git a/src/test/resources/testng-configfailure.xml b/src/test/resources/testng-configfailure.xml
new file mode 100644
index 0000000..8346a60
--- /dev/null
+++ b/src/test/resources/testng-configfailure.xml
@@ -0,0 +1,9 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+
+<suite name="TestNG" verbose="0">
+    <test name="Config Failure Policy">
+        <classes>
+            <class name="test.configurationfailurepolicy.ClassWithFailedBeforeMethodAndMultipleTests" />
+        </classes>
+    </test>
+</suite>
diff --git a/src/test/resources/testng-methodselectors.xml b/src/test/resources/testng-methodselectors.xml
new file mode 100644
index 0000000..754d789
--- /dev/null
+++ b/src/test/resources/testng-methodselectors.xml
@@ -0,0 +1,8 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
+<suite name="Sample Suite">
+  <test verbose="1" name="Sample">
+    <classes>
+      <class name="test.methodselectors.SampleTest" />
+    </classes>
+  </test>
+</suite>
diff --git a/src/test/resources/testng-override.xml b/src/test/resources/testng-override.xml
new file mode 100644
index 0000000..828d2cb
--- /dev/null
+++ b/src/test/resources/testng-override.xml
@@ -0,0 +1,13 @@
+<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
+  
+<suite name="JarTestSuite" verbose="0">
+
+
+  <test name="JarTest">
+    <classes>
+      <class name="test.jar.A" />
+      <class name="test.jar.B" />
+    </classes>
+  </test>
+</suite>
+
diff --git a/src/test/resources/testng-package.xml b/src/test/resources/testng-package.xml
new file mode 100644
index 0000000..3fcee84
--- /dev/null
+++ b/src/test/resources/testng-package.xml
@@ -0,0 +1,19 @@
+<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
+  
+<suite name="SingleSuite" verbose="2" parallel="false" thread-count="4"
+    data-provider-thread-count="3">
+
+  <test name="Nested" >
+    <groups>
+      <run>
+        <include name="unittest" />
+      </run>
+    </groups>
+
+    <packages>
+      <package name="test.nested.*"/>
+    </packages>
+  </test>
+
+</suite>
+
diff --git a/src/test/resources/testng-remote.xml b/src/test/resources/testng-remote.xml
new file mode 100644
index 0000000..8b50317
--- /dev/null
+++ b/src/test/resources/testng-remote.xml
@@ -0,0 +1,12 @@
+<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
+  
+<suite name="Remote" verbose="0">
+
+  <test name="RemoteTest">
+    <classes>
+      <class name="test.remote.RemoteSampleTest" />
+    </classes>
+  </test>
+
+</suite>
+
diff --git a/src/test/resources/testng-single.xml b/src/test/resources/testng-single.xml
new file mode 100644
index 0000000..d144918
--- /dev/null
+++ b/src/test/resources/testng-single.xml
@@ -0,0 +1,20 @@
+<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
+  
+<suite name="SingleSuite" verbose="2" parallel="false" thread-count="4"
+    data-provider-thread-count="3">
+
+  <parameter name="n" value="42" />
+
+  <test name="Regression2" preserve-order="true">
+    <parameter name="string" value="s"/>
+
+    <groups>
+    </groups>
+
+    <classes>
+      <class name="test.mannotation.MAnnotationSampleTest" />
+    </classes>
+  </test>
+
+</suite>
+
diff --git a/src/test/resources/testng-single.yaml b/src/test/resources/testng-single.yaml
new file mode 100644
index 0000000..0cbe61c
--- /dev/null
+++ b/src/test/resources/testng-single.yaml
@@ -0,0 +1,12 @@
+name: SingleSuite
+verbose: 2
+
+tests:
+  - name: Test1
+    excludedGroups: [ excludeThisGroup ]
+    classes:
+      - name: test.dependent.xml.GroupDependencyTest
+#        includedMethods: [ a ]
+#      - test.Test2
+#      - name: test.CommandLineTest
+#        includedMethods: [ junitParsing ]
diff --git a/src/test/resources/testng-single2.xml b/src/test/resources/testng-single2.xml
new file mode 100644
index 0000000..0238dc3
--- /dev/null
+++ b/src/test/resources/testng-single2.xml
@@ -0,0 +1,23 @@
+<suite name="Timeout Tests" verbose="2">
+
+  <test name="TimeoutTest" time-out="1000">
+      <classes>
+          <class name="test.timeout.TestTimeOutSampleTest">
+              <methods>
+                <include name="timeoutTest"/>
+              </methods>
+          </class>
+      </classes>
+  </test>
+
+  <test name="NoTimeoutTest">
+      <classes>
+          <class name="test.timeout.TestTimeOutSampleTest">
+              <methods>
+                <include name="timeoutTest"/>
+              </methods>
+          </class>
+      </classes>
+  </test>
+    
+</suite>
diff --git a/src/test/resources/testng-single2.yaml b/src/test/resources/testng-single2.yaml
new file mode 100644
index 0000000..b2d58f1
--- /dev/null
+++ b/src/test/resources/testng-single2.yaml
@@ -0,0 +1,48 @@
+name: SingleSuite
+verbose: 2
+parallel: false
+threadCount: 4
+dataProviderThreadCount: 3
+
+packages:
+  - name: test.testng355.org.apache.tapestry5
+    include: [Foo, Foo2]
+    exclude: [Bar]
+  - name: test.testng355.org.apache.tapestry5.dom
+
+listeners:
+  - test.invokedmethodlistener.MyListener
+
+parameters: { n: 42, p: 10, s: "a b c", t: "a,b" }
+
+methodSelectors:
+  - className: org.testng.internal.XmlMethodSelector
+    priority: 1
+  - expression: groups.containsKey("test1")
+    language: beanshell
+    
+tests:
+  - name: Test3
+    includedGroups: [A, B]
+    excludedGroups: [C, D]
+    metaGroups: { a: [ b, d] }
+    packages: [ com.example1, com.example2 ]
+    parameters: { n: 43, z: foo }
+    methodSelectors:
+      - className: org.testng.internal.XmlMethodSelector
+        priority: 1
+      - expression: groups.containsKey("test1")
+        language: beanshell
+
+  - name: Test1
+    classes:
+      - name: test.tmp.A
+        includedMethods: [test1, test2]
+        excludedMethods: [test3]
+      - test.tmp.B
+
+  - name: Test2
+    classes:
+      - test.tmp.B
+
+
diff --git a/src/test/resources/testng.xml b/src/test/resources/testng.xml
new file mode 100644
index 0000000..351ee62
--- /dev/null
+++ b/src/test/resources/testng.xml
@@ -0,0 +1,766 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+
+<suite name="TestNG" verbose="1" parallel="false" thread-count="2"
+    data-provider-thread-count="3">
+
+  <suite-files>
+    <suite-file path="./junit-suite.xml" />
+    <suite-file path="./parent-module-suite.xml" />
+  </suite-files>
+
+  <test name="Nopackage">
+    <groups>
+      <run>
+        <include name="nopackage"/>
+      </run>
+    </groups>
+    <classes>
+      <class name="NoPackageTest" />
+    </classes>
+  </test>
+
+  <parameter name="first-name" value="Cedric" />
+
+  <test name="Regression1" >
+    <groups>
+      <define name="evenodd">
+        <include name="even" />
+        <include name="odd" />
+      </define>
+      <run>
+        <exclude name="excludeThisGroup" />
+      </run>
+    </groups>
+
+    <classes>
+      <class name="test.parameters.ParameterSample" />
+      <class name="test.parameters.ParameterTest" />
+      <class name="test.Test1" />
+      <class name="test.Test2" />
+      <class name="test.CtorCalledOnce" />
+      <class name="test.expectedexceptions.ExpectedExceptionsTest" />
+      <class name="test.access.PrivateAccessConfigurationMethods" />
+      <class name="test.expectedexceptions.WrappedExpectedExceptionTest" />
+      <class name="test.parameters.OptionalParameterTest"/>
+      <class name="test.parameters.ParamInheritanceTest"/>
+    </classes>
+  </test>
+
+  <test name="Regression2">
+    <groups>
+      <run>
+        <exclude name="broken" />
+      </run>
+    </groups>
+    <classes>
+      <class name="test.Test1" />
+      <class name="test.MethodTest" />
+      <class name="test.sample.AfterClassCalledAtEnd"/>
+      <class name="test.GraphTest"/>
+      <class name="test.DynamicGraphTest"/>
+      <class name="test.privatemethod.PrivateMethodTest$PrivateMethodInnerTest"/>
+      <class name="test.multiple.TestMultiple"/>
+      <class name="test.ClassConfigurations"/>
+      <class name="test.alwaysrun.AlwaysRunTest"/>
+      <class name="test.conffailure.ConfigurationFailure" />
+      <class name="test.superclass.MainTest" />
+      <class name="test.superclass.Test3" />
+      <class name="test.failures.FailuresTest" />
+      <class name="test.mannotation.MAnnotationSampleTest" />
+      <class name="test.mannotation.MAnnotation2SampleTest" />
+      <class name="test.thread.FactoryTest" />
+      <class name="test.thread.SequentialTest" />
+      <class name="test.thread.ParallelTestTest" />
+      <class name="test.thread.DataProviderThreadPoolSizeTest" />
+      <class name="test.thread.MultiThreadedDependentTest" />
+      <class name="test.thread.TrueParallelTest" />
+<!--
+      <class name="test.thread.ParallelSuiteTest"/>
+-->
+      <class name="test.simple.IncludedExcludedTest" />
+      <class name="test.reports.ReportTest" />
+      <class name="test.annotationtransformer.AnnotationTransformerTest" />
+<!-- 
+      <class name="test.jar.JarTest" />
+ -->
+      <class name="test.xml.XmlVerifyTest" />
+      <class name="test.invokedmethodlistener.InvokedMethodListenerTest" />
+      <class name="test.testng249.VerifyTest"/>
+      <class name="test.testng195.AfterMethodTest" />
+      <class name="test.regression.BeforeTestFailingTest"/>
+      <class name="test.testng285.TestNG285Test" />
+      <class name="test.failedreporter.FailedReporterTest" />
+      <class name="test.attributes.AttributeTest"/>
+      <class name="test.verify.VerifyTest"/>
+      <class name="test.abstractconfmethod.C" />
+      <class name="test.preserveorder.PreserveOrderTest" />
+      <class name="test.SerializationTest" />
+      <class name="test.CountTest" />
+      <class name="test.remote.RemoteTest" />
+      <class name="test.EclipseTest" />
+      <class name="test.ReporterApiTest" />
+      <class name="test.abstractmethods.AbstractTest" />
+      <class name="test.override.OverrideTest" />
+      <class name="test.priority.PriorityTest" />
+      <class name="test.NestedStaticTest" />
+      <class name="test.configuration.ConfigurationListenerTest" />
+      <class name="test.groupinvocation.GroupSuiteTest" />
+      <class name="test.StaticTest" />
+      <class name="test.serviceloader.ServiceLoaderTest" />
+      <class name="test.commandline.CommandLineOverridesXml" />
+      <class name="test.jason.MainTest" />
+      <class name="test.bug90.Bug90Test" />
+      <class name="test.bug92.Bug92Test" />
+      <class name="test.ReturnValueTest" />
+      <class name="test.groupbug.GroupBugTest" />
+      <class name="test.parameters.ShadowTest" />
+      <class name="test.parameters.ParameterOverrideTest" />
+      <class name="test.reports.FailedReporterTest" />
+      <class name="test.reports.ReporterLogTest" />
+      <class name="test.testng387.TestNG387"/>
+    </classes>
+  </test>
+
+  <test name="Threads">
+    <groups>
+      <run>
+        <exclude name="broken" />
+      </run>
+    </groups>
+    <classes>
+      <class name="test.thread.ThreadPoolSizeTest" />
+      <class name="test.thread.ThreadPoolSizeWithTimeOutTest" />
+      <class name="test.thread.ThreadTest" />
+    </classes>
+  </test>
+
+  <test name="Listeners">
+    <classes>
+      <class name="test.listeners.EndMillisShouldNotBeZeroTest" />
+      <class name="test.listeners.ResultEndMillisTest" />
+      <class name="test.listeners.ListenerTest"/>
+      <class name="test.listeners.SuiteAndInvokedMethodListenerTest" />
+      <class name="test.listeners.SuiteAndConfigurationListenerTest" />
+      <class name="test.listeners.ListenerInXmlTest" />
+      <class name="test.listeners.ExecutionListenerTest" />
+      <class name="test.listeners.ConfigurationListenerTest" />
+      <class name="test.multiplelisteners.TestMaker" />
+    </classes>
+  </test>
+
+  <test name="Injection">
+    <classes>
+      <class name="test.inject.InjectTestContextTest"/>
+      <class name="test.inject.InjectBeforeMethodTest"/>
+      <class name="test.inject.InjectTestResultTest" />
+      <class name="test.inject.InjectDataProviderTest"/>
+      <class name="test.inject.NoInjectionTest" />
+    </classes>
+  </test>
+
+  <!--  Keep this in its own <test> tag -->
+  <test name="BeforeMethod">
+    <classes>
+      <class name="test.configuration.BeforeMethodTest" />
+    </classes>
+  </test>
+
+  <test name="Basic" >
+    <classes>
+      <class name="test.sample.Basic2" />
+      <class name="test.sample.Basic1" />
+      <class name="test.Misc" />
+    </classes>
+  </test>
+
+  <test name="Exclusion">
+    <groups>
+      <run>
+        <exclude name="excludeThisGroup" />
+      </run>
+    </groups>
+    <classes>
+      <class name="test.Test2" />
+    </classes>
+  </test>
+
+  <test name="Dependents">
+    <parameter name="foo" value="Cedric" />
+    <classes>
+<!--
+      <class name="test.dependent.MissingGroupTest" />
+      <class name="test.dependent.MissingMethodTest" />
+-->
+      <class name="test.dependent.OrderMethodTest" />
+      <class name="test.dependent.DependentTest" />
+      <class name="test.dependent.SampleDependentTest" />
+      <class name="test.dependent.SampleDependentMethods" />
+      <class name="test.dependent.SampleDependentMethods3" />
+      <class name="test.dependent.SampleDependentConfigurationMethods" />
+      <class name="test.dependent.ClassDependsOnGroups"/>
+      <class name="test.dependent.DependentAlwaysRunTest" />
+      <class name="test.dependent.MultiGroupTest" />
+      <class name="test.dependent.ImplicitGroupInclusionTest" />
+      <class name="test.dependent.ClassWide1Test" />
+      <class name="test.dependent.ClassWide2Test" />
+      <class name="test.dependent.DepBugSampleTest" />
+      <class name="test.dependent.DepBugVerifyTest" />
+      <class name="test.dependent.DependsOnProtectedMethodTest" />
+      <class name="test.dependsongroup.DependsOnGroupsTest" />
+      <class name="test.dependent.GroupByInstancesTest" />
+      <class name="test.dependent.xml.GroupDependencyTest" />
+      <class name="test.dependent.DependencyFixTest"/>
+    </classes>
+  </test>
+
+  <test name="Inheritance">
+    <classes>
+      <class name="test.SampleInheritance" />
+      <class name="test.inheritance.ClassScopeTest" />
+      <class name="test.inheritance.testng739.TestNG739"/>
+      <class name="test.inheritance.testng234.PolymorphicFailureTest" />
+      <class name="test.inheritance.testng471.TestNG471" />
+    </classes>
+  </test>
+
+  <!-- Test scopes -->
+
+  <parameter name="parameter" value="out" />
+
+  <test name="Test outer scope">
+    <groups>
+      <run>
+        <include name="outer-group" />
+      </run>
+    </groups>
+    <classes>
+      <class name="test.sample.Scope" />
+    </classes>
+  </test>
+
+  <test name="Test inner scope">
+    <parameter name="parameter" value="in" />
+    <groups>
+      <run>
+        <include name="inner-group" />
+      </run>
+    </groups>
+    <classes>
+      <class name="test.sample.Scope" />
+    </classes>
+  </test>
+
+  <test name="AfterClassCalledAtEnd">
+    <classes>
+      <class name="test.sample.AfterClassCalledAtEnd"/>
+    </classes>
+  </test>
+
+  <test name="Triangle">
+    <classes>
+      <class name="test.triangle.Child1" />
+      <class name="test.triangle.Child2" />
+    </classes>
+  </test>
+
+  <!-- note that CheckTriangePost must be run sequentially after Triangle-->
+  <test name="CheckTrianglePost">
+    <parameter name="expected-calls" value="2" />
+    <classes>
+      <class name="test.triangle.CheckCount"/>
+    </classes>
+  </test>
+
+  <!-- Test that we can declare groups and dependents on classes -->
+
+  <test name="Test class groups 1" >
+    <classes>
+      <class name="test.classgroup.Second" />
+      <class name="test.classgroup.First" />
+    </classes>
+  </test>
+
+    <test name="Test class groups 2" >
+    <classes>
+      <class name="test.classgroup.First" />
+      <class name="test.classgroup.Second" />
+    </classes>
+  </test>
+
+  <parameter name="factory-param" value="FactoryParam" />
+  <test name="Factory">
+    <classes>
+      <class name="test.factory.FactoryTest" />
+      <class name="test.factory.VerifyFactoryTest" />
+      <class name="test.factory.FactoryInSeparateClass" />
+      <class name="test.factory.Factory2Test" />
+
+      <class name="test.factory.FactoryWithInstanceInfoTest" />
+      <class name="test.factory.VerifyFactoryWithInstanceInfoTest" />
+      <class name="test.factory.TestClassAnnotationTest" />
+
+      <class name="test.factory.FactoryWithDataProviderTest" />
+      <class name="test.factory.FactoryOrderMainTest" />
+      <class name="test.factory.FactoryFailureTest" />
+
+      <class name="test.factory.FactoryInSuperClassTest" />
+
+      <class name="test.factory.classconf.XClassOrderWithFactoryTest" />
+      <class name="test.factory.FactoryInterleavingTest"/>
+      <class name="test.factory.FactoryDataProviderTest"/>
+
+      <class name="test.factory.DisabledFactoryTest"/>
+      <class name="test.factory.FactoryAndTestMethodTest" />
+
+      <class name="test.factory.FactoryIntegrationTest" />
+    </classes>
+  </test>
+
+  <test name="TimeOut">
+    <classes>
+      <class name="test.timeout.TimeOutTest" />
+      <class name="test.timeout.TimeOutFromXmlTest"/>
+      <class name="test.timeout.TimeOutIntegrationTest"/>
+<!--
+      <class name="test.timeout.TimeOutThreadLocalSampleTest"/>
+-->
+    </classes>
+  </test>
+
+  <test name="InvocationCount">
+    <parameter name="count" value="10" />
+    <classes>
+      <class name="test.InvocationAndSuccessPercentageTest" />
+      <class name="test.invocationcount.FailedInvocationCountTest" />
+      <class name="test.invocationcount.FirstAndLastTimeTest" />
+    </classes>
+  </test>
+
+  <!-- Tests for included and excluded methods -->
+  <test name="Method1">
+    <classes>
+        <class name="test.methods.SampleMethod1">
+          <methods>
+            <include name="shouldRun1" />
+            <include name="shouldRun2" />
+          </methods>
+        </class>
+          <class name="test.methods.VerifyMethod1" />
+    </classes>
+  </test>
+
+  <test name="Method2">
+    <classes>
+        <class name="test.methods.SampleMethod1">
+          <methods>
+            <exclude name="shouldNotRun1" />
+            <exclude name="shouldNotRun2" />
+          </methods>
+        </class>
+        <class name="test.methods.VerifyMethod1" />
+    </classes>
+  </test>
+
+  <test name="Method3">
+    <classes>
+        <class name="test.methods.SampleMethod1">
+          <methods>
+            <exclude name=".*Not.*" />
+          </methods>
+        </class>
+        <class name="test.methods.VerifyMethod1" />
+    </classes>
+  </test>
+
+  <test name="Object factory">
+    <classes>
+      <class name="test.objectfactory.CustomFactoryTest" />
+      <class name="test.objectfactory.CombinedTestAndObjectFactoryTest" />
+      <class name="test.objectfactory.ObjectFactory2Test"/>
+    </classes>
+  </test>
+
+  <!-- Test parameters for constructors -->
+  <parameter name="string" value="Cedric" />
+  <parameter name="int" value="42" />
+  <parameter name="boolean" value="true" />
+  <parameter name="byte" value="43" />
+  <parameter name="char" value="c" />
+  <parameter name="double" value="44.0" />
+  <parameter name="float" value="45.0" />
+  <parameter name="long" value="46" />
+  <parameter name="short" value="47" />
+
+  <test name="Parameters for constructors">
+    <classes>
+      <class name="test.ParameterConstructorTest" />
+    </classes>
+  </test>
+
+  <test name="Excluded methods" >
+    <groups>
+      <run>
+        <include name="group1" />
+        <include name="group2" />
+      </run>
+    </groups>
+    <classes>
+      <class name="test.Exclude">
+    <methods>
+          <exclude name="excluded1" />
+          <exclude name="excluded2" />
+    </methods>
+      </class>
+    </classes>
+  </test>
+
+  <test name="Parameters in init 1">
+    <parameter name="param" value="value1"/>
+    <classes>
+      <class name="test.configuration.ConfigurationWithParameters"/>
+    </classes>
+  </test>
+
+  <test name="Parameters in init 2">
+  <parameter name="param" value="value2"/>
+    <classes>
+      <class name="test.configuration.ConfigurationWithParameters"/>
+    </classes>
+  </test>
+
+  <test name="Individual method">
+    <classes>
+      <class name="test.IndividualMethodsTest">
+        <methods>
+          <include name="testMethod"/>
+        </methods>
+      </class>
+    </classes>
+  </test>
+
+  <test name="Method inheritance">
+    <classes>
+      <class name="test.inheritance.DChild_2" />
+      <class name="test.inheritance.VerifyTest" />
+    </classes>
+  </test>
+
+  <test name="Method selectors">
+    <classes>
+      <class name="test.methodselectors.MethodSelectorTest" />
+      <class name="test.methodselectors.BeanShellTest" />
+      <class name="test.methodselectors.CommandLineTest" />
+      <class name="test.methodselectors.MethodSelectorInSuiteTest" />
+    </classes>
+  </test>
+
+  <test name="Test order invocation">
+    <classes>
+      <class name="test.interleavedorder.InterleavedInvocationTest" />
+    </classes>
+  </test>
+
+  <test name="DataProvider">
+    <classes>
+      <class name="test.dataprovider.Sample1Test" />
+      <class name="test.dataprovider.IterableTest" />
+      <class name="test.dataprovider.ConfigurationAndDataProvidersTest" />
+      <class name="test.dataprovider.BooleanTest" />
+      <class name="test.dataprovider.ExplicitDataProviderNameTest" />
+      <class name="test.dataprovider.MethodTest"/>
+      <class name="test.dataprovider.StaticDataProviderSampleTest" />
+      <class name="test.dataprovider.UnnamedDataProviderTest" />
+      <class name="test.dataprovider.TestContextTest" />
+      <class name="test.dataprovider.FailingDataProviderTest" />
+      <class name="test.dataprovider.DataProviderAsTest" />
+      <class name="test.dataprovider.TestInstanceFactory" />
+      <class name="test.dataprovider.FailedDataProviderTest" />
+      <class name="test.dataprovider.InstanceDataProviderTest" />
+      <class name="test.dataprovider.FailingIterableDataProviderTest" />
+      <class name="test.dataprovider.ClassTest" />
+      <class name="test.dataprovider.InheritedDataProviderTest" />
+      <class name="test.dataprovider.TestNG411Test" />
+      <class name="test.dataprovider.VarArgsDataProviderTest" />
+      <class name="test.dataprovider.IndicesTest" />
+    </classes>
+  </test>
+
+  <test name="DP">
+    <classes>
+      <class name="test.dataprovider.ParallelDataProviderTest" />
+    </classes>
+  </test>
+  
+  <test name="bug111">
+    <classes>
+      <class name="test.test111.Test1" />
+    </classes>
+  </test>
+
+  <test name="UniqueSuite">
+    <classes>
+      <class name="test.uniquesuite.TestBefore1" />
+      <class name="test.uniquesuite.TestBefore2" />
+      <class name="test.uniquesuite.TestAfter" />
+    </classes>
+  </test>
+
+  <test name="InheritGroups">
+    <groups>
+      <run>
+        <include name="group1" />
+      </run>
+    </groups>
+    <classes>
+      <class name="test.configuration.ConfigurationInheritGroupsSampleTest" />
+    </classes>
+  </test>
+
+<!--
+  <test name="Distributed">
+    <classes>
+      <class name="test.distributed.DistributedTest" />
+    </classes>
+  </test>
+-->
+
+  <test name="Nested" >
+    <groups>
+      <run>
+        <include name="unittest" />
+      </run>
+    </groups>
+
+     <packages>
+       <package name="test.nested.*"/>
+     </packages>
+  </test>
+
+  <test name="Hookable" >
+    <classes>
+      <class name="test.hook.HookableTest"/>
+    </classes>
+  </test>
+
+  <test name="BeforeGroups-AfterGroups-1" >
+    <classes>
+      <class name="test.configuration.ConfigurationGroups1SampleTest" />
+      <class name="test.configuration.ConfigurationGroups2SampleTest" />
+      <class name="test.configuration.ConfigurationGroups3SampleTest" />
+      <class name="test.configuration.ConfigurationGroups4SampleTest" />
+      <class name="test.configuration.ConfigurationGroups5SampleTest" />
+      <class name="test.configuration.ConfigurationGroups6SampleTest" />
+      <class name="test.configuration.ConfigurationGroups7SampleTest" />
+    </classes>
+  </test>
+
+  <test name="BeforeGroups-AfterGroups-2" >
+    <groups>
+      <run>
+        <include name="A" />
+        <include name="B" />
+      </run>
+    </groups>
+    <classes>
+      <class name="test.configuration.ConfigurationGroups8SampleTest" />
+    </classes>
+  </test>
+
+  <test name="JUnit">
+    <classes>
+      <class name="test.JUnitTest1" />
+      <class name="test.CommandLineTest"/>
+      <class name="test.JUnit4Test" />
+    </classes>
+  </test>
+
+  <!-- TestNG ant task related tests -->
+  <test name="Ant-ClassFileResolution" >
+    <classes>
+        <class name="test.ant.TestCommandLineArgs" />
+    </classes>
+  </test>
+
+  <!-- Group ordering 1 -->
+  <test name="Class Run">
+    <classes>
+      <class name="test.regression.groupsordering.A" />
+      <class name="test.regression.groupsordering.B" />
+    </classes>
+  </test>
+
+  <!-- Group ordering 2 -->
+  <test name="Groups Run">
+    <groups>
+      <run>
+        <include name="a" />
+      </run>
+    </groups>
+    <classes>
+      <class name="test.regression.groupsordering.A" />
+      <class name="test.regression.groupsordering.B" />
+    </classes>
+  </test>
+
+  <test name="External group invocation">
+    <groups>
+      <run>
+        <include name="a" />
+      </run>
+    </groups>
+    <classes>
+      <class name="test.groupinvocation.GroupConfiguration" />
+      <class name="test.groupinvocation.DummyTest" />
+    </classes>
+  </test>
+
+  <test name="SkipExceptions">
+    <classes>
+      <class name="test.skipex.SkippedExceptionTest" />
+      <class name="test.skipex.SkipAndExpectedTest" />
+    </classes>
+  </test>
+
+  <test name="RetryAnalyzer">
+    <classes>
+      <class name="test.retryAnalyzer.RetryAnalyzerTest" />
+      <class name="test.retryAnalyzer.ExitCodeTest" />
+    </classes>
+  </test>
+
+  <test name="MethodInterceptor">
+    <classes>
+      <class name="test.methodinterceptors.MethodInterceptorTest" />
+      <class name="test.methodinterceptors.Issue392Test" />
+      <class name="test.methodinterceptors.multipleinterceptors.MultipleInterceptorsTest" />
+    </classes>
+  </test>
+
+  <test name="Asserts">
+    <classes>
+      <class name="org.testng.AssertTest" />
+      <class name="test.asserttests.AssertTest" />
+      <class name="test.asserttests.ArrayEqualityAssertTest" />
+    </classes>
+  </test>
+
+  <test name="Utils">
+    <classes>
+      <class name="org.testng.internal.UtilsTest" />
+    </classes>
+  </test>
+
+  <test name="ConfigFailurePolicy">
+    <classes>
+      <class name="test.configurationfailurepolicy.FailurePolicyTest" />
+    </classes>
+  </test>
+
+  <test name="Nested2">
+    <packages>
+      <package name="test.nested2" />
+    </packages>
+  </test>
+
+  <test name="Guice">
+    <parameter name="inject" value="guice" />
+    <classes>
+      <class name="test.guice.GuiceTest" />
+      <class name="test.guice.GuiceInheritanceTest" />
+      <class name="test.guice.GuiceModuleFactoryTest" />
+    </classes>
+  </test>
+
+  <test name="Listener invokers">
+    <packages>
+      <package name="org.testng.internal.invokers"/>
+    </packages>
+  </test>
+
+  <test name="YAML">
+    <classes>
+      <class name="test.yaml.YamlTest" />
+    </classes>
+  </test>
+  <test name="XML">
+    <classes>
+      <class name="org.testng.xml.SuiteXmlParserTest" />
+    </classes>
+  </test>
+
+  <test name="Sanity Check">
+    <classes>
+      <class name="test.sanitycheck.CheckTestNamesTest" />
+      <class name="test.sanitycheck.CheckSuiteNamesTest" />
+    </classes>
+  </test>
+
+  <test name="Configuration">
+    <classes>
+      <class name="test.configuration.BaseGroupsTest" />
+      <class name="test.configuration.BeforeClassThreadTest" />
+      <class name="test.configuration.BeforeTestOrderingTest" />
+      <class name="test.configuration.ConfigurationTest"/>
+      <class name="test.configuration.ExternalConfigurationClass"/>
+      <class name="test.configuration.GroupsTest"/>
+      <class name="test.configuration.MethodCallOrderTest"/>
+      <class name="test.configuration.MultipleBeforeGroupTest" />
+      <class name="test.configuration.ReflectMethodParametrizedConfigurationMethodTest" />
+      <class name="test.configuration.SuiteFactoryOnceTest" />
+      <class name="test.configuration.SuiteTest" />
+      <class name="test.configuration.VerifySuiteTest" />
+      <class name="test.configuration.SingleConfigurationTest" />
+      <class name="test.configuration.BeforeClassWithDisabledTest" />
+    </classes>
+  </test>
+  
+  <test name="Bug173">
+    <classes>
+      <class name="test.testng173.TestNG173Test" />
+    </classes>
+  </test>
+
+  <test name="Mustache">
+    <classes>
+      <class name="test.mustache.MustacheTest" />
+    </classes>
+  </test>
+
+  <test name="Mixed">
+    <classes>
+      <class name="test.mixed.MixedTest" />
+    </classes>
+  </test>
+
+  <test name="Issue 107">
+    <classes>
+      <class name="test.issue107.Issue107Test"/>
+    </classes>
+  </test>
+
+  <test name="Assertion">
+    <classes>
+      <class name="test.assertion.AssertionTest"/>
+      <class name="test.assertion.SoftAssertTest"/>
+    </classes>
+  </test>
+
+  <test name="TESTNG-106">
+    <classes>
+      <class name="test.testng106.TestNG106"/>
+    </classes>
+  </test>
+
+  <test name="@Test(enable)">
+    <classes>
+      <class name="test.enable.EnableTest"/>
+    </classes>
+  </test>
+
+  <test name="Name">
+    <classes>
+      <class name="test.name.NameTest" />
+    </classes>
+  </test>
+</suite>
+
diff --git a/src/test/resources/testng.yaml b/src/test/resources/testng.yaml
new file mode 100644
index 0000000..b16c8dd
--- /dev/null
+++ b/src/test/resources/testng.yaml
@@ -0,0 +1,367 @@
+name: TestNG
+threadCount: 2
+parameters: { short: 47, parameter: out, char: c, byte: 43, first-name: Cedric, int: 42, string: Cedric, factory-param: FactoryParam, boolean: true, long: 46, double: 44.0, float: 45.0 }
+tests:
+  - name: Nopackage
+    includedGroups: [ nopackage ]
+    classes:
+      - NoPackageTest
+
+  - name: Regression1
+    excludedGroups: [ excludeThisGroup ]
+    metaGroups: { evenodd: [ even, odd ]  }
+    classes:
+      - test.parameters.ParameterSample
+      - test.parameters.ParameterTest
+      - test.Test1
+      - test.Test2
+      - test.CtorCalledOnce
+      - test.expectedexceptions.ExpectedExceptionsTest
+      - test.access.PrivateAccessConfigurationMethods
+      - test.expectedexceptions.WrappedExpectedException
+      - test.parameters.OptionalParameterTest
+
+  - name: Regression2
+    excludedGroups: [ broken ]
+    classes:
+      - test.Test1
+      - test.MethodTest
+      - test.sample.AfterClassCalledAtEnd
+      - test.GraphTest
+      - test.DynamicGraphTest
+      - test.configuration.BaseGroupsTest
+      - test.configuration.BeforeClassThreadTest
+      - test.configuration.BeforeTestOrderingTest
+      - test.configuration.ConfigurationTest
+      - test.configuration.ExternalConfigurationClass
+      - test.configuration.GroupsTest
+      - test.configuration.MethodCallOrderTest
+      - test.configuration.MultipleBeforeGroupTest
+      - test.configuration.ReflectMethodParametrizedConfigurationMethodTest
+      - test.configuration.SuiteFactoryOnceTest
+      - test.configuration.SuiteTest
+      - test.configuration.VerifySuiteTest
+      - test.privatemethod.PrivateMethodTest$PrivateMethodInnerTest
+      - test.multiple.TestMultiple
+      - test.ClassConfigurations
+      - test.alwaysrun.AlwaysRunTest
+      - test.conffailure.ConfigurationFailure
+      - test.superclass.MainTest
+      - test.superclass.Test3
+      - test.failures.FailuresTest
+      - test.mannotation.MAnnotationSampleTest
+      - test.mannotation.MAnnotation2SampleTest
+      - test.thread.ThreadPoolSizeTest
+      - test.thread.SequentialTest
+      - test.thread.ParallelTestTest
+      - test.thread.FactoryTest
+      - test.thread.DataProviderThreadPoolSizeTest
+      - test.thread.MultiThreadedDependentTest
+      - test.simple.IncludedExcludedTest
+      - test.reports.ReportTest
+      - test.annotationtransformer.AnnotationTransformerTest
+      - test.jar.JarTest
+      - test.xml.XmlVerifyTest
+      - test.invokedmethodlistener.InvokedMethodListenerTest
+      - test.testng249.VerifyTest
+      - test.testng195.AfterMethodTest
+      - test.regression.BeforeTestFailingTest
+      - test.testng285.TestNG285Test
+      - test.failedreporter.FailedReporterTest
+      - test.attributes.AttributeTest
+      - test.verify.VerifyTest
+      - test.abstractconfmethod.C
+      - test.issue78.NonPublicClassTest
+      - test.listeners.ListenerTest
+      - test.preserveorder.PreserveOrderTest
+      - test.listeners.ResultEndMillisTest
+
+  - name: Injection
+    classes:
+      - test.inject.InjectTestContextTest
+      - test.inject.InjectBeforeMethodTest
+      - test.inject.InjectTestResultTest
+      - test.inject.InjectDataProviderTest
+      - test.inject.NoInjectionTest
+
+  - name: BeforeMethod
+    classes:
+      - test.configuration.BeforeMethodTest
+
+  - name: Factory tests
+    classes:
+      - test.factory.classconf.XClassOrderWithFactoryTest
+      - test.factory.FactoryInterleavingTest
+
+  - name: Basic
+    classes:
+      - test.sample.Basic2
+      - test.sample.Basic1
+      - test.Misc
+
+  - name: Exclusion
+    excludedGroups: [ excludeThisGroup ]
+    classes:
+      - test.Test2
+
+  - name: Dependents
+    parameters: { foo: Cedric }
+    classes:
+      - test.dependent.MissingGroupTest
+      - test.dependent.MissingMethodTest
+      - test.dependent.OrderMethodTest
+      - test.dependent.DependentTest
+      - test.dependent.SampleDependentMethods
+      - test.dependent.SampleDependentMethods2
+      - test.dependent.SampleDependentMethods3
+      - test.dependent.SampleDependentConfigurationMethods
+      - test.dependent.ClassDependsOnGroups
+      - test.dependent.DependentAlwaysRunTest
+      - test.dependent.MultiGroupTest
+      - test.dependent.ImplicitGroupInclusionTest
+      - test.dependent.ClassWide1Test
+      - test.dependent.ClassWide2Test
+      - test.dependent.DepBugSampleTest
+      - test.dependent.DepBugVerifyTest
+      - test.dependent.DependsOnProtectedMethodTest
+      - test.dependsongroup.DependsOnGroupsTest
+
+  - name: Inheritance
+    classes:
+      - test.SampleInheritance
+      - test.inheritance.ClassScopeTest
+
+  - name: Test outer scope
+    includedGroups: [ outer-group ]
+    classes:
+      - test.sample.Scope
+
+  - name: Test inner scope
+    parameters: { parameter: in }
+    includedGroups: [ inner-group ]
+    classes:
+      - test.sample.Scope
+
+  - name: AfterClassCalledAtEnd
+    classes:
+      - test.sample.AfterClassCalledAtEnd
+
+  - name: Triangle
+    classes:
+      - test.triangle.Child1
+      - test.triangle.Child2
+
+  - name: CheckTrianglePost
+    parameters: { expected-calls: 2 }
+    classes:
+      - test.triangle.CheckCount
+
+  - name: Test class groups 1
+    classes:
+      - test.classgroup.Second
+      - test.classgroup.First
+
+  - name: Test class groups 2
+    classes:
+      - test.classgroup.First
+      - test.classgroup.Second
+
+  - name: Factory
+    classes:
+      - test.factory.FactoryTest
+      - test.factory.VerifyFactoryTest
+      - test.factory.FactoryInSeparateClass
+      - test.factory.Factory2Test
+      - test.factory.FactoryWithInstanceInfoTest
+      - test.factory.VerifyFactoryWithInstanceInfoTest
+      - test.factory.TestClassAnnotationTest
+      - test.factory.FactoryWithDataProviderTest
+      - test.factory.FactoryOrderMainTest
+      - test.factory.FactoryFailureTest
+      - test.factory.FactoryInSuperClassTest
+
+  - name: TimeOut
+    classes:
+      - test.timeout.TimeOutTest
+      - test.timeout.TimeOutFromXmlTest
+
+  - name: InvocationCount
+    parameters: { count: 10 }
+    classes:
+      - test.InvocationAndSuccessPercentageTest
+      - test.invocationcount.FailedInvocationCountTest
+      - test.invocationcount.FirstAndLastTimeTest
+
+  - name: Method1
+    classes:
+      - name: test.methods.SampleMethod1
+        includedMethods:
+          - shouldRun1
+          - shouldRun2
+      - test.methods.VerifyMethod1
+
+  - name: Method2
+    classes:
+      - name: test.methods.SampleMethod1
+        excludedMethods:
+          - shouldNotRun1
+          - shouldNotRun2
+      - test.methods.VerifyMethod1
+
+  - name: Method3
+    classes:
+      - name: test.methods.SampleMethod1
+        excludedMethods:
+          - .*Not.*
+      - test.methods.VerifyMethod1
+
+  - name: Object factory
+    classes:
+      - test.objectfactory.CustomFactoryTest
+      - test.objectfactory.CombinedTestAndObjectFactoryTest
+
+  - name: Parameters for constructors
+    classes:
+      - test.ParameterConstructorTest
+
+  - name: Excluded methods
+    includedGroups: [ group1, group2 ]
+    classes:
+      - name: test.Exclude
+        excludedMethods:
+          - excluded1
+          - excluded2
+
+  - name: Parameters in init 1
+    parameters: { param: value1 }
+    classes:
+      - test.configuration.ConfigurationWithParameters
+
+  - name: Parameters in init 2
+    parameters: { param: value2 }
+    classes:
+      - test.configuration.ConfigurationWithParameters
+
+  - name: Individual method
+    classes:
+      - name: test.IndividualMethodsTest
+        includedMethods:
+          - testMethod
+
+  - name: Method inheritance
+    classes:
+      - test.inheritance.DChild_2
+      - test.inheritance.VerifyTest
+
+  - name: Method selectors
+    classes:
+      - test.methodselectors.MethodSelectorTest
+      - test.methodselectors.BeanShellTest
+      - test.methodselectors.CommandLineTest
+
+  - name: Test order invocation
+    classes:
+      - test.interleavedorder.InterleavedInvocationTest
+
+  - name: DataProvider
+    classes:
+      - test.dataprovider.Sample1Test
+      - test.dataprovider.IterableTest
+      - test.dataprovider.ConfigurationAndDataProvidersTest
+      - test.dataprovider.BooleanTest
+      - test.dataprovider.MethodTest
+      - test.dataprovider.StaticDataProviderSampleTest
+      - test.dataprovider.UnnamedDataProviderTest
+      - test.dataprovider.TestContextTest
+      - test.dataprovider.FailingDataProviderTest
+      - test.dataprovider.DataProviderAsTest
+      - test.dataprovider.TestInstanceFactory
+      - test.dataprovider.FailedDataProviderTest
+      - test.dataprovider.InstanceDataProviderTest
+      - test.dataprovider.FailingIterableDataProviderTest
+      - test.dataprovider.ClassTest
+      - test.dataprovider.InheritedDataProviderTest
+
+  - name: DP
+    classes:
+      - test.dataprovider.ParallelDataProviderTest
+
+  - name: UniqueSuite
+    classes:
+      - test.uniquesuite.TestBefore1
+      - test.uniquesuite.TestBefore2
+      - test.uniquesuite.TestAfter
+
+  - name: InheritGroups
+    includedGroups: [ group1 ]
+    classes:
+      - test.configuration.ConfigurationInheritGroupsSampleTest
+
+  - name: Nested
+    includedGroups: [ unittest ]
+    xmlPackages:
+      - name: test.nested.*
+
+  - name: Hookable
+    classes:
+      - test.hook.HookSuccessTest
+      - test.hook.HookFailureTest
+
+  - name: BeforeGroups-AfterGroups-1
+    classes:
+      - test.configuration.ConfigurationGroups1SampleTest
+      - test.configuration.ConfigurationGroups2SampleTest
+      - test.configuration.ConfigurationGroups3SampleTest
+      - test.configuration.ConfigurationGroups4SampleTest
+      - test.configuration.ConfigurationGroups5SampleTest
+      - test.configuration.ConfigurationGroups6SampleTest
+      - test.configuration.ConfigurationGroups7SampleTest
+
+  - name: BeforeGroups-AfterGroups-2
+    includedGroups: [ A, B ]
+    classes:
+      - test.configuration.ConfigurationGroups8SampleTest
+
+  - name: JUnit
+    classes:
+      - test.JUnitTest1
+      - test.CommandLineTest
+
+  - name: Ant-ClassFileResolution
+    classes:
+      - test.ant.TestCommandLineArgs
+
+  - name: Class Run
+    classes:
+      - test.regression.groupsordering.A
+      - test.regression.groupsordering.B
+
+  - name: Groups Run
+    includedGroups: [ a ]
+    classes:
+      - test.regression.groupsordering.A
+      - test.regression.groupsordering.B
+
+  - name: External group invocation
+    includedGroups: [ a ]
+    classes:
+      - test.groupinvocation.GroupConfiguration
+      - test.groupinvocation.DummyTest
+
+  - name: SkipExceptions
+    classes:
+      - test.skipex.SkippedExceptionTest
+
+  - name: MethodInterceptor
+    classes:
+      - test.methodinterceptors.MethodInterceptorTest
+
+  - name: Asserts
+    classes:
+      - test.asserttests.AssertTest
+
+  - name: ConfigFailurePolicy
+    classes:
+      - test.configurationfailurepolicy.FailurePolicyTest
+
+
diff --git a/src/test/resources/testng_convert.xml b/src/test/resources/testng_convert.xml
new file mode 100644
index 0000000..7eaf84c
--- /dev/null
+++ b/src/test/resources/testng_convert.xml
@@ -0,0 +1,10 @@
+<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
+
+<suite name="JUnit Convert suite" verbose="1" parallel="false" thread-count="2" annotations="javadoc">
+   <parameter name="inputDir" value="./test/src/test/convert"/>
+   <test name="JUnit Convert">
+     <classes>
+       <class name="test.convert.JUnitConverterTest"/>
+     </classes>
+   </test>
+</suite>
diff --git a/src/test/resources/with-different-name-testng-xml.jar b/src/test/resources/with-different-name-testng-xml.jar
new file mode 100644
index 0000000..0b7d697
--- /dev/null
+++ b/src/test/resources/with-different-name-testng-xml.jar
Binary files differ
diff --git a/src/test/resources/withouttestngxml.jar b/src/test/resources/withouttestngxml.jar
new file mode 100644
index 0000000..fd203eb
--- /dev/null
+++ b/src/test/resources/withouttestngxml.jar
Binary files differ
diff --git a/src/test/resources/withtestngxml.jar b/src/test/resources/withtestngxml.jar
new file mode 100644
index 0000000..3bc2d2b
--- /dev/null
+++ b/src/test/resources/withtestngxml.jar
Binary files differ
diff --git a/src/test/resources/xml/badWithDoctype.xml b/src/test/resources/xml/badWithDoctype.xml
new file mode 100644
index 0000000..1dd12de
--- /dev/null
+++ b/src/test/resources/xml/badWithDoctype.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+<Suite name="GitHub809" verbose="3">
+    <test name="Demo 1" preserve-order="true" />
+</Suite>
\ No newline at end of file
diff --git a/src/test/resources/xml/badWithoutDoctype.xml b/src/test/resources/xml/badWithoutDoctype.xml
new file mode 100644
index 0000000..61d1c2b
--- /dev/null
+++ b/src/test/resources/xml/badWithoutDoctype.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Suite name="GitHub809" verbose="3">
+    <test name="Demo 1" preserve-order="true" />
+</Suite>
\ No newline at end of file
diff --git a/src/test/resources/xml/goodWithDoctype.xml b/src/test/resources/xml/goodWithDoctype.xml
new file mode 100644
index 0000000..16fd66a
--- /dev/null
+++ b/src/test/resources/xml/goodWithDoctype.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+<suite name="GitHub809" verbose="3">
+    <test name="Demo 1" preserve-order="true" />
+</suite>
\ No newline at end of file
diff --git a/src/test/resources/xml/goodWithoutDoctype.xml b/src/test/resources/xml/goodWithoutDoctype.xml
new file mode 100644
index 0000000..93d1042
--- /dev/null
+++ b/src/test/resources/xml/goodWithoutDoctype.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<suite name="GitHub809" verbose="3">
+    <test name="Demo 1" preserve-order="true" />
+</suite>
\ No newline at end of file
diff --git a/src/test/resources/yaml/a1.xml b/src/test/resources/yaml/a1.xml
new file mode 100644
index 0000000..dc0f74b
--- /dev/null
+++ b/src/test/resources/yaml/a1.xml
@@ -0,0 +1,56 @@
+<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
+  
+<suite name="SingleSuite" verbose="2" parallel="false" thread-count="4"
+    data-provider-thread-count="3">
+
+  <parameter name="n" value="42" />
+
+  <listeners>
+    <listener class-name="test.methodinterceptors.FastTestsFirstInterceptor" />
+    <listener class-name="test.invokedmethodlistener.MyListener" />
+  </listeners>
+
+  <packages>
+    <package name="test.testng355.org.apache.tapestry5">
+      <include name="Foo"/>
+      <include name="Foo2"/>
+      <exclude name="Bar"/>
+    </package>
+    <package name="test.testng355.org.apache.tapestry5.dom" />
+  </packages>
+
+  <test name="Regression2" verbose="2">
+    <parameter name="count" value="10"/>
+    <groups>
+      <run>
+        <exclude name="broken" />
+      </run>
+    </groups>
+    <classes>
+      <class name="test.listeners.ResultEndMillisTest">
+<!-- 
+        <methods>
+          <include name="withFactory" />
+        </methods>
+ -->
+       </class>
+<!-- 
+      <class name="test.methodselectors.CommandLineTest">
+       <class name="test.inject.NoInjectionTest" />
+      <class name="test.tmp.TestFactory" />
+      <class name="test.thread.MultiThreadedDependentTest" />
+      <class name="test.tmp.B" />
+      <class name="test.thread.MultiThreadedDependentTest" />
+      <class name="test.thread.FactoryTest" />
+      <class name="test.tmp.B" />
+      <class name="test.failedreporter.FailedReporterTest" />
+        <methods>
+          <include name="verifyIPAddress" invocationNumbers="0 2" />
+        </methods>
+      </class>
+ -->
+    </classes>
+  </test>
+
+</suite>
+
diff --git a/src/test/resources/yaml/a1.yaml b/src/test/resources/yaml/a1.yaml
new file mode 100644
index 0000000..9719d76
--- /dev/null
+++ b/src/test/resources/yaml/a1.yaml
@@ -0,0 +1,24 @@
+name: SingleSuite
+verbose: 2
+threadCount: 4
+parameters: { n: 42 }
+dataProviderThreadCount: 3
+parallel: false
+
+listeners:
+  - test.methodinterceptors.FastTestsFirstInterceptor
+  - test.invokedmethodlistener.MyListener
+
+packages:
+  - name: test.testng355.org.apache.tapestry5
+    include: [Foo, Foo2]
+    exclude: [Bar]
+  - name: test.testng355.org.apache.tapestry5.dom
+
+tests:
+  - name: Regression2
+    verbose: 2
+    parameters: { count: 10 }
+    excludedGroups: [ broken ]
+    classes:
+      - test.listeners.ResultEndMillisTest
diff --git a/src/test/resources/yaml/a2.xml b/src/test/resources/yaml/a2.xml
new file mode 100644
index 0000000..5f3024f
--- /dev/null
+++ b/src/test/resources/yaml/a2.xml
@@ -0,0 +1,79 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
+<suite thread-count="4" configfailurepolicy="skip" verbose="2" name="SingleSuite" skipfailedinvocationcounts="false" parallel="false" annotations="JDK" data-provider-thread-count="3">
+  <parameter name="n" value="42"/>
+  <parameter name="p" value="10"/>
+  <parameter name="s" value="a b c"/>
+  <parameter name="t" value="a,b"/>
+  <listeners>
+    <listener class-name="test.invokedmethodlistener.MyListener"/>
+  </listeners>
+  <packages>
+    <package name="test.testng355.org.apache.tapestry5">
+      <include name="Foo"/>
+      <include name="Foo2"/>
+      <exclude name="Bar"/>
+    </package>
+    <package name="test.testng355.org.apache.tapestry5.dom">
+    </package>
+  </packages>
+  <method-selectors>
+	  <method-selector>
+	    <selector-class name="org.testng.internal.XmlMethodSelector" priority="1"/>
+	  </method-selector>
+	  <method-selector>
+	    <script language="beanshell">
+	      <![CDATA[groups.containsKey("test1")]]>
+	    </script>
+	  </method-selector>
+  </method-selectors>
+  <test verbose="1" name="Test3" junit="false" preserve-order="false">
+    <method-selectors>
+      <method-selector>
+        <selector-class name="org.testng.internal.XmlMethodSelector" priority="1"/>
+      </method-selector>
+      <method-selector>
+        <script language="beanshell">
+          <![CDATA[groups.containsKey("test1")]]>
+        </script>
+      </method-selector>
+    </method-selectors>
+    <parameter name="n" value="43"/>
+    <parameter name="z" value="foo"/>
+    <groups>
+      <define name="a">
+        <include name="b"/>
+        <include name="d"/>
+      </define>
+      <run>
+        <include name="A"/>
+        <include name="B"/>
+        <exclude name="C"/>
+        <exclude name="D"/>
+      </run>
+    </groups>
+    <packages>
+      <package name="com.example1">
+      </package>
+      <package name="com.example2">
+      </package>
+    </packages>
+  </test>
+  <test verbose="1" name="Test1" junit="false" preserve-order="false">
+    <classes>
+      <class name="test.tmp.A">
+        <methods>
+          <include name="test1"/>
+          <include name="test2"/>
+          <exclude name="test3"/>
+        </methods>
+      </class>
+      <class name="test.tmp.B"/>
+    </classes>
+  </test>
+  <test verbose="1" name="Test2" junit="false" preserve-order="false">
+    <classes>
+      <class name="test.tmp.B"/>
+    </classes>
+  </test>
+</suite>
+
diff --git a/src/test/resources/yaml/a2.yaml b/src/test/resources/yaml/a2.yaml
new file mode 100644
index 0000000..d665fea
--- /dev/null
+++ b/src/test/resources/yaml/a2.yaml
@@ -0,0 +1,49 @@
+name: SingleSuite
+verbose: 2
+parallel: false
+threadCount: 4
+dataProviderThreadCount: 3
+
+packages:
+  - name: test.testng355.org.apache.tapestry5
+    include: [Foo, Foo2]
+    exclude: [Bar]
+  - name: test.testng355.org.apache.tapestry5.dom
+
+listeners:
+  - test.invokedmethodlistener.MyListener
+
+parameters: { n: 42, p: 10, s: "a b c", t: "a,b" }
+
+tests:
+  - name: Test3
+    preserveOrder: false
+    includedGroups: [A, B]
+    excludedGroups: [C, D]
+    metaGroups: { a: [ b, d] }
+    packages: [ com.example1, com.example2 ]
+    parameters: { n: 43, z: foo }
+    methodSelectors:
+      - className: org.testng.internal.XmlMethodSelector
+        priority: 1
+      - expression: groups.containsKey("test1")
+        language: beanshell
+
+  - name: Test1
+    preserveOrder: false
+    classes:
+      - name: test.tmp.A
+        includedMethods: [test1, test2]
+        excludedMethods: [test3]
+      - test.tmp.B
+
+  - name: Test2
+    preserveOrder: false
+    classes:
+      - test.tmp.B
+
+methodSelectors:
+  - className: org.testng.internal.XmlMethodSelector
+    priority: 1
+  - expression: groups.containsKey("test1")
+    language: beanshell
diff --git a/src/test/resources/yaml/a3-a.xml b/src/test/resources/yaml/a3-a.xml
new file mode 100644
index 0000000..d15af01
--- /dev/null
+++ b/src/test/resources/yaml/a3-a.xml
@@ -0,0 +1,9 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
+<suite thread-count="5" configfailurepolicy="skip" name="a3-a" skipfailedinvocationcounts="false" annotations="JDK" data-provider-thread-count="10">
+  <test verbose="1" name="Test-a3-a" junit="false" preserve-order="false">
+    <classes>
+      <class name="test.listeners.ResultEndMillisTest"/>
+    </classes>
+  </test>
+</suite>
+
diff --git a/src/test/resources/yaml/a3-a.yaml b/src/test/resources/yaml/a3-a.yaml
new file mode 100644
index 0000000..566fea7
--- /dev/null
+++ b/src/test/resources/yaml/a3-a.yaml
@@ -0,0 +1,6 @@
+name: a3-a
+
+tests:
+  - name: Test-a3-a
+    classes:
+      - test.listeners.ResultEndMillisTest
diff --git a/src/test/resources/yaml/a3-b.xml b/src/test/resources/yaml/a3-b.xml
new file mode 100644
index 0000000..25c684a
--- /dev/null
+++ b/src/test/resources/yaml/a3-b.xml
@@ -0,0 +1,9 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
+<suite thread-count="5" configfailurepolicy="skip" name="a3-b" skipfailedinvocationcounts="false" annotations="JDK" data-provider-thread-count="10">
+  <test verbose="1" name="Test-a3-b" junit="false" preserve-order="false">
+    <classes>
+      <class name="test.listeners.ResultEndMillisTest"/>
+    </classes>
+  </test>
+</suite>
+
diff --git a/src/test/resources/yaml/a3-b.yaml b/src/test/resources/yaml/a3-b.yaml
new file mode 100644
index 0000000..d81c16f
--- /dev/null
+++ b/src/test/resources/yaml/a3-b.yaml
@@ -0,0 +1,6 @@
+name: a3-b
+
+tests:
+  - name: Test-a3-b
+    classes:
+      - test.listeners.ResultEndMillisTest
diff --git a/src/test/resources/yaml/a3.xml b/src/test/resources/yaml/a3.xml
new file mode 100644
index 0000000..c44a163
--- /dev/null
+++ b/src/test/resources/yaml/a3.xml
@@ -0,0 +1,10 @@
+<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
+  
+<suite name="a3" verbose="2">
+
+	<suite-files>
+	  <suite-file path="a3-a.xml" />
+	  <suite-file path="a3-b.xml" />
+	</suite-files>
+
+</suite>
diff --git a/src/test/resources/yaml/a3.yaml b/src/test/resources/yaml/a3.yaml
new file mode 100644
index 0000000..50b07bf
--- /dev/null
+++ b/src/test/resources/yaml/a3.yaml
@@ -0,0 +1,4 @@
+name: a3
+verbose: 2
+
+suiteFiles: [ a3-a.xml, a3-b.xml ]
diff --git a/src/test/resources/yaml/a4-a.yaml b/src/test/resources/yaml/a4-a.yaml
new file mode 100644
index 0000000..f3a8145
--- /dev/null
+++ b/src/test/resources/yaml/a4-a.yaml
@@ -0,0 +1,3 @@
+name: a4-a
+verbose: 2
+
diff --git a/src/test/resources/yaml/a4-b.yaml b/src/test/resources/yaml/a4-b.yaml
new file mode 100644
index 0000000..d819424
--- /dev/null
+++ b/src/test/resources/yaml/a4-b.yaml
@@ -0,0 +1,3 @@
+name: a4-b
+verbose: 2
+
diff --git a/src/test/resources/yaml/a4.xml b/src/test/resources/yaml/a4.xml
new file mode 100644
index 0000000..94d1356
--- /dev/null
+++ b/src/test/resources/yaml/a4.xml
@@ -0,0 +1,10 @@
+<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
+  
+<suite name="SingleSuite" verbose="2">
+
+	<suite-files>
+	  <suite-file path="a4-a.yaml" />
+	  <suite-file path="a4-b.yaml" />
+	</suite-files>
+
+</suite>
diff --git a/src/test/resources/yaml/a4.yaml b/src/test/resources/yaml/a4.yaml
new file mode 100644
index 0000000..1ba3237
--- /dev/null
+++ b/src/test/resources/yaml/a4.yaml
@@ -0,0 +1,4 @@
+name: SingleSuite
+verbose: 2
+
+suiteFiles: [ a4-a.yaml, a4-b.yaml ]
diff --git a/testng-1.0.dtd.html b/testng-1.0.dtd.html
new file mode 100755
index 0000000..fa04592
--- /dev/null
+++ b/testng-1.0.dtd.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html>
+  <head>
+    <META HTTP-EQUIV=REFRESH CONTENT="0; URL=http://testng.org/testng-1.0.dtd.php">.
+  </head>
+
+<body />
diff --git a/testng-1.0.dtd.php b/testng-1.0.dtd.php
new file mode 100644
index 0000000..f30525e
--- /dev/null
+++ b/testng-1.0.dtd.php
@@ -0,0 +1,46 @@
+<?
+
+print '<html><head><title>TestNG DTD</title>
+
+        <link rel="stylesheet" href="testng.css" type="text/css" />
+        <link type="text/css" rel="stylesheet" href="http://beust.com/beust.css"  />
+        <script type="text/javascript" src="http://beust.com/prettify.js"></script>
+        <script type="text/javascript" src="banner.js"></script>
+
+      <script type="text/javascript" src="http://beust.com/scripts/shCore.js"></script>
+      <script type="text/javascript" src="http://beust.com/scripts/shBrushJava.js"></script>
+      <script type="text/javascript" src="http://beust.com/scripts/shBrushXml.js"></script>
+      <script type="text/javascript" src="http://beust.com/scripts/shBrushBash.js"></script>
+      <script type="text/javascript" src="http://beust.com/scripts/shBrushPlain.js"></script>
+      <link type="text/css" rel="stylesheet" href="http://beust.com/styles/shCore.css"/>
+      <link type="text/css" rel="stylesheet" href="http://beust.com/styles/shThemeCedric.css"/>
+      <script type="text/javascript">
+        SyntaxHighlighter.config.clipboardSwf = "scripts/clipboard.swf";
+        SyntaxHighlighter.defaults["gutter"] = false;
+        SyntaxHighlighter.all();
+      </script>
+
+        <style type="text/css">
+            /* Set the command-line table option column width. */
+            #command-line colgroup.option {
+                 width: 7em;
+            }
+        </style>
+</head>
+<body>
+
+<h1>
+<p align="center">
+DTD for TestNG
+</p>
+</h1>
+
+<pre class="brush: xml">
+'
+;
+
+print(htmlentities(file_get_contents("testng-1.0.dtd")));
+
+print "</pre>";
+
+?>
diff --git a/travis.sh b/travis.sh
new file mode 100755
index 0000000..5891325
--- /dev/null
+++ b/travis.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
+  if [ -n "$SONAR_GITHUB_OAUTH" ]; then
+    ./gradlew sonarqube \
+      -Dsonar.github.pullRequest=$TRAVIS_PULL_REQUEST \
+      -Dsonar.github.oauth=$SONAR_GITHUB_OAUTH \
+  else
+    echo "No oauth token available"
+  fi
+fi
\ No newline at end of file
diff --git a/update-beust.com b/update-beust.com
new file mode 100755
index 0000000..7725ee8
--- /dev/null
+++ b/update-beust.com
@@ -0,0 +1,26 @@
+v=6.5.2
+ev=5.13.0.3
+DEST=testng
+TARGET=target
+
+set -x
+
+if [ -z "${U}" ]
+then
+  echo Variable "$U" is not set
+  exit -1
+fi
+
+RSYNC="rsync --verbose --progress --stats"
+
+${RSYNC} ${TARGET}/testng-${v}.zip ${U}@beust.com:${DEST}
+${RSYNC} -r src/main/resources/testng-1.0.dtd testng-1.0.dtd.html ${U}@beust.com:${DEST}/doc
+
+#${RSYNC} testng-${v}.jar testng-${v}-bundle.jar testng-${v}.zip testng-eclipse-${ev}.zip ${U}@beust.com:${DEST}
+#scp -r javadocs doc/*.html doc/*.css src/main/resources/testng-1.0.dtd testng-1.0.dtd.html ${U}@beust.com:${DEST}
+#scp dtd/* ${U}@beust.com:w/dtd
+#(cd ~/java/beust.com; scp -r . ${U}@beust.com:w/eclipse)
+
+
+# scp testng-eclipse-${ev}.zip doc/download.html ${U}@beust.com:${DEST}
+
diff --git a/upload-beta b/upload-beta
new file mode 100755
index 0000000..17a6bce
--- /dev/null
+++ b/upload-beta
@@ -0,0 +1,4 @@
+#scp `ls -tr *beta.zip|tail -1` cedric@beust.com:w/testng
+scp `ls -tr target/*beta.zip|tail -1` beust:domains/testng.org/html
+ssh beust cat testng/beta/index.html
+
diff --git a/verify-release b/verify-release
new file mode 100755
index 0000000..d4b6353
--- /dev/null
+++ b/verify-release
@@ -0,0 +1,45 @@
+if [ $# -eq 0 ]
+then
+  echo "Specify the release number (e.g. 5.14.2)"
+  exit -1
+fi
+
+rel=testng-$1
+
+#
+# Make sure that pom.xml and pom-test.xml test the same and latest version
+#
+
+v1=`perl -ne 'BEGIN{undef $/}; print "$1" if m/(TestNG.*\n.*version)/mg' pom.xml | awk -F ">" '{ print $2 }' | awk -F "<" '{ print $1 }'`
+
+v2=`perl -ne 'BEGIN{undef $/}; print "$1" if m/(artifactId.testng.*\n.*version)/mg' pom-test.xml | grep version | awk -F ">" '{print $2}' | awk -F "<" '{print $1}'`
+
+if [ $v1 != $v2 ]
+then
+  echo "Versions of pom.xml and pom-test.xml do not match: $v1 $v2"
+#  exit -1
+fi
+
+#
+# Maven
+#
+
+./build-with-maven
+
+#cd $HOME/t
+#rm -rf surefire
+#svn co http://svn.apache.org/repos/asf/maven/surefire/trunk surefire
+#cd surefire
+#mvn clean install -Dtestng.version=$1
+
+#
+# Distribution
+#
+
+cd $HOME/t
+rm -f *zip
+rm -rf $rel
+curl http://testng.org/$rel.zip >$rel.zip
+unzip $rel.zip
+cd $HOME/java/testng
+java -Dtest.resources.dir=$HOME/java/testng/src/test/resources -classpath $HOME/t/$rel/$rel.jar:target/test-classes:lib/guice-2.0.jar org.testng.TestNG $HOME/java/testng/src/test/resources/testng.xml
diff --git a/www/index.html b/www/index.html
new file mode 100644
index 0000000..40bd357
--- /dev/null
+++ b/www/index.html
@@ -0,0 +1,8 @@
+<html>
+  <head>
+    <title>Default Project Content</title>
+  </head>
+  <body>
+    This is the default project content.
+  </body>
+</html>