Java tutorial
/* * Copyright (c) 2007 Pyxis Technologies inc. * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, * or see the FSF site: http://www.fsf.org. */ package com.greenpepper.maven.plugin; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.greenpepper.Statistics; import com.greenpepper.document.GreenPepperInterpreterSelector; import com.greenpepper.maven.plugin.runner.Runner; import com.greenpepper.maven.plugin.runner.RunnerTask; import com.greenpepper.maven.plugin.utils.NullStatisticsListener; import com.greenpepper.maven.plugin.utils.ProjectsIndex; import com.greenpepper.maven.plugin.utils.RepositoryIndex; import com.greenpepper.maven.plugin.utils.TestResultsIndex; import com.greenpepper.repository.DocumentRepository; import com.greenpepper.repository.FileSystemRepository; import com.greenpepper.runner.CompositeSpecificationRunnerMonitor; import com.greenpepper.runner.RecorderMonitor; import com.greenpepper.runner.RunnerStatistics; import com.greenpepper.runner.SpecificationRunnerMonitor; import com.greenpepper.runner.repository.DocumentNeverImplementedException; import com.greenpepper.server.domain.*; import com.greenpepper.util.IOUtil; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.project.MavenProject; import org.jsoup.Jsoup; import org.jsoup.select.Elements; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.util.*; import java.util.concurrent.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; import static com.greenpepper.maven.plugin.runner.Runner.DEFAULT_RUNNER_NAME; import static com.greenpepper.util.URIUtil.escapeFileSystemForbiddenCharacters; import static java.lang.String.format; import static java.util.Arrays.asList; import static org.apache.commons.io.FileUtils.*; import static org.apache.commons.lang3.StringUtils.*; /** * Run the specification testing. * * @goal run * @phase integration-test * @requiresDependencyResolution test * @description Runs GreenPepper specifications * @author oaouattara * @version $Id: $Id */ @SuppressWarnings("JavaDoc") public class SpecificationRunnerMojo extends SpecificationNavigatorMojo { private static final Logger LOGGER = LoggerFactory.getLogger(SpecificationRunnerMojo.class); private static final String HTML_EXTENSION = ".html"; private class RunnerResult { private final Future<?> future; private final RunnerTask runnerTask; RunnerResult(RunnerTask runnerTask, Future<?> future) { this.runnerTask = runnerTask; this.future = future; } } /** * Set this to 'true' to bypass greenpepper tests entirely. * Its use is NOT RECOMMENDED, but quite convenient on occasion. * * @parameter property="maven.greenpepper.test.skip" default-value="false" */ @SuppressWarnings("unused") private boolean skip; /** * Project fixture classpath. * @parameter property="project.runtimeClasspathElements" * @required * @readonly */ List<String> classpathElements; /** * The directory where compiled fixture classes go. * * @parameter default-value="${project.build.directory}/fixture-test-classes" * @required */ @SuppressWarnings("unused") private File fixtureOutputDirectory; /** * The SystemUnderDevelopment class to use * @parameter default-value="com.greenpepper.systemunderdevelopment.DefaultSystemUnderDevelopment" * @required */ String systemUnderDevelopment; /** * The {@link com.greenpepper.systemunderdevelopment.SystemUnderDevelopment} constructor args. * This parameter is optionnal and can be achieved by appending them to the systemUnderDevelopment parameter. * @parameter */ String systemUnderDevelopmentArgs; /** * @parameter property="plugin.artifacts" * @required * @readonly */ List<Artifact> pluginDependencies; /** * Set this to 'true' to stop the execution on a failure. * @parameter property="maven.greenpepper.test.stop" default-value="false" */ @SuppressWarnings("unused") private boolean stopOnFirstFailure; /** * Set the locale for the execution. * @parameter property="maven.greenpepper.locale" */ String locale; /** * Set the Selector class. * @parameter property="maven.greenpepper.selector" * default-value="com.greenpepper.document.GreenPepperInterpreterSelector" */ String selector; /** * Set the Debug mode. * * @parameter property="maven.greenpepper.debug" default-value="false" */ boolean debug; /** * Set this to true to ignore a failure during testing. * Its use is NOT RECOMMENDED, but quite convenient on occasion. * * @parameter property="maven.greenpepper.test.failure.ignore" default-value="false" */ @SuppressWarnings("unused") private boolean testFailureIgnore; /** * Set this to true to output the logs only in the log file and not in the console. * * @parameter property="maven.greenpepper.redirect.output" default-value="false" */ @SuppressWarnings("unused") private boolean redirectOutputToFile; /** * Set this property to true to launch only new specifications + failed ones. * * @parameter property="maven.greenpepper.resume" default-value="false" */ boolean resume; /** * Set this to a Specification name to run only this test. * The test is searched inside the default repository. * * @parameter property="gp.test" */ String testSpecification; /** * Set this to a Specification name to run only this test. * The test is searched inside the default repository. * * @parameter property="gp.testOutput" */ String testSpecificationOutput; /** * Set this to a Repository name defined in the pom.xml. * This option is only used in case <code>-Dgp.test</code> is used. * * @parameter property="gp.repo" */ String selectedRepository; /** * Launch the test in the Maven process if false. Or fork a java process if true. * * @parameter property="maven.greenpepper.fork" default-value="false" */ boolean fork; /** * The maximum number of default runner processes that needs to be spawn; * * @parameter property="maven.greenpepper.forkcount" default=1 */ Integer forkCount; /** * The Java Virtual Machine path to use for the default runner in fork mode. * * @parameter property="maven.greenpepper.jvm" default-value="java" */ @SuppressWarnings("FieldCanBeLocal") private String jvm = "java"; /** * Additionnal JAVA Options to be added to the java command in fork mode. * * <strong>This is only used in FORK mode and for the default runner.</strong> * * @parameter property="maven.greenpepper.javaoptions" */ String javaOptions; /** * When launching the tests in a fork, we create a default runner. You can exclude this default runner from the * testing process if you want to configure your owns. * * @parameter property="maven.greenpepper.excludedefaultrunner" default-value=false */ boolean excludeDefaultRunner; /** * The list of runners that can be associated to repositories for testing. * * @parameter */ List<Runner> runners; /** * @component */ protected MavenProject project; private HashMap<String, ExecutorService> executorMap = new HashMap<String, ExecutorService>(); HashMap<String, Runner> runnerMap = new HashMap<String, Runner>(); private LinkedHashSet<RunnerResult> runnerResults = new LinkedHashSet<RunnerResult>(); RunnerStatistics runnerStatistics; private boolean testFailed; private boolean exceptionOccured; HashMap<String, TestResultsIndex> testResultsIndexes = new HashMap<String, TestResultsIndex>(); /** * <p>Constructor for SpecificationRunnerMojo.</p> */ public SpecificationRunnerMojo() { this.runnerStatistics = new RunnerStatistics(); } /** * <p>execute.</p> * * @throws org.apache.maven.plugin.MojoExecutionException if any. * @throws org.apache.maven.plugin.MojoFailureException if any. */ public void execute() throws MojoExecutionException, MojoFailureException { if (skip) { getLog().info("Not executing specifications."); } else { prepareReportsDir(); prepareRunnerExecutors(); printBanner(); try { runAllTests(); } finally { printFooter(); for (TestResultsIndex testResultsIndex : testResultsIndexes.values()) { testResultsIndex.dump(); } } checkTestsResults(); } } private void prepareRunnerExecutors() { if (runners == null || runners.isEmpty()) { runners = new ArrayList<Runner>(); if (!excludeDefaultRunner) { runners.add(getDefaultRunner()); } } for (Runner runner : runners) { ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat(runner.getName() + "-%d") .build(); ExecutorService executorService = Executors.newFixedThreadPool(runner.getForkCount(), threadFactory); executorMap.put(runner.getName(), executorService); runnerMap.put(runner.getName(), runner); if (fork) { runner.setRedirectOutputToFile(true); } } } private void checkTestsResults() throws MojoExecutionException, MojoFailureException { if (exceptionOccured) notifyExceptionsOccured(); if (testFailed) notifyTestsFailed(); } private void notifyExceptionsOccured() throws MojoExecutionException { if (testFailureIgnore) { getLog().error("Some greenpepper tests did not run\n"); } else { throw new MojoExecutionException("Some greenpepper tests did not run"); } } private void notifyTestsFailed() throws MojoFailureException { if (!testResultsIndexes.isEmpty()) { System.out.println("List of failed tests"); for (Map.Entry<String, TestResultsIndex> indexEntry : testResultsIndexes.entrySet()) { String repoName = indexEntry.getKey(); TestResultsIndex testResultsIndex = indexEntry.getValue(); for (Map.Entry<String, TestResultsIndex.TestResults> entry : testResultsIndex.getNameToInfo() .entrySet()) { Statistics statistics = entry.getValue().getStatistics(); if (statistics.hasFailed()) { System.out.println( format("\t %s (Repository: %s) (%s)", entry.getKey(), repoName, statistics)); } } } System.out.println(); System.out.println( "You can run the failed tests using the resume option '-Dmaven.greenpepper.resume=true'"); System.out.println(); } if (testFailureIgnore) { getLog().error("There were greenpepper tests failures\n"); } else { throw new MojoFailureException("There were greenpepper tests failures"); } } private void printBanner() { System.out.println(); System.out.println("-----------------------------------------------------"); System.out.println(" G R E E N P E P P E R S P E C I F I C A T I O N S "); System.out.println("-----------------------------------------------------"); System.out.println(); } private void runAllTests() throws MojoExecutionException, MojoFailureException { if (StringUtils.isNotEmpty(testSpecification)) { // Locate default repository Repository defaultRepository = null; if (repositories.size() == 1) { defaultRepository = repositories.get(0); } else { boolean repositorySelected = StringUtils.isNotEmpty(selectedRepository); if (repositorySelected) { defaultRepository = extractSelectedRepository(); } else { for (Repository repository : repositories) { if (repository.isDefault()) { defaultRepository = repository; break; } } } } if (defaultRepository == null) { throw new MojoExecutionException( "A default repository should be set when using '-Dgp.test='. Use '-Dgp.repo=' or specify it in the pom.xml"); } // Run the test runSingleTest(defaultRepository, testSpecification); checkAsynchTasks(); } else { boolean repositorySelected = StringUtils.isNotEmpty(selectedRepository); if (repositorySelected) { runAllIn(extractSelectedRepository()); } else { for (Repository repository : repositories) { if (shouldStop()) { break; } runAllIn(repository); } } } } private Repository extractSelectedRepository() throws MojoExecutionException { for (Repository repository : repositories) { if (StringUtils.equalsIgnoreCase(selectedRepository, repository.getName())) { return repository; } } throw new MojoExecutionException( format("Repository '%s' not found in the list of repository.", selectedRepository)); } private void checkAsynchTasks() { for (RunnerResult runnerResult : runnerResults) { try { // This will wait for this Task completion runnerResult.future.get(); RecorderMonitor recorder = runnerResult.runnerTask.getRecorder(); exceptionOccured |= recorder.hasException(); testFailed |= recorder.hasTestFailures(); registerStatistics(recorder); } catch (InterruptedException e) { getLog().error(format("The task [%s] has been interrupted", runnerResult.runnerTask.getSpecification().getName())); } catch (ExecutionException e) { getLog().error( format("The task [%s] has failed", runnerResult.runnerTask.getSpecification().getName()), e); } } runnerResults.clear(); } private void runAllIn(Repository repository) throws MojoExecutionException, MojoFailureException { List<String> repositorySpecifications; try { repositorySpecifications = listRepositorySpecifications(repository); } catch (Exception e) { throw new MojoExecutionException( format("Couldn't list repository '%s' specifications", repository.getName()), e); } try { extractHtmlReportSummary(); prepareProjectIndex(repository); prepareTestResultsIndex(repository); } catch (URISyntaxException e) { throw new MojoExecutionException( format("Couldn't prepare the report for repository '%s'", repository.getName()), e); } catch (IOException e) { throw new MojoExecutionException( format("Couldn't prepare the report for repository '%s'", repository.getName()), e); } repository.getTests().clear(); repository.getTests().addAll(repositorySpecifications); runTestsIn(repository); checkAsynchTasks(); System.out.println(); System.out.println(format("See the report at %s", getFile(reportsDirectory, "index" + HTML_EXTENSION).getAbsolutePath())); System.out.println(); } private void prepareProjectIndex(Repository repository) throws IOException { File storage = new File(reportsDirectory, "index.json"); ProjectsIndex projectsIndex = new ProjectsIndex(storage); try { projectsIndex.load(); } catch (IOException e) { getLog().warn(format("index.json is corrupted. Start a new one. Cause: %s", e.getMessage())); FileUtils.moveFile(storage, new File(reportsDirectory, "index.json.orig")); } ProjectsIndex.ProjectInfo projectInfo = projectsIndex.getNameToInfo().get(repository.getName()); if (projectInfo == null) { projectInfo = new ProjectsIndex.ProjectInfo(); projectInfo.projectName = repository.getProjectName(); projectInfo.repoName = repository.getName(); projectInfo.repoId = getRepositoryMetaName(repository); projectInfo.systemUnderTest = repository.getSystemUnderTest(); projectInfo.startDate = ProjectsIndex.ProjectInfo.SIMPLE_DATE_FORMAT.format(new Date()); projectsIndex.getNameToInfo().put(repository.getName(), projectInfo); } else { projectInfo.startDate = ProjectsIndex.ProjectInfo.SIMPLE_DATE_FORMAT.format(new Date()); } projectsIndex.dump(); } private void prepareTestResultsIndex(Repository repository) throws IOException { File storage = getResultsIndexFile(repository); TestResultsIndex testResultsIndex = TestResultsIndex.newInstance(storage); testResultsIndexes.put(repository.getName(), testResultsIndex); } private void extractHtmlReportSummary() throws IOException, URISyntaxException { final String path = "html-summary-report"; final File jarFile = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath()); forceMkdir(reportsDirectory); if (jarFile.isFile()) { // Run with JAR file JarFile jar = new JarFile(jarFile); Enumeration<JarEntry> entries = jar.entries(); //gives ALL entries in jar while (entries.hasMoreElements()) { JarEntry jarEntry = entries.nextElement(); String name = jarEntry.getName(); if (name.startsWith(path)) { //filter according to the path File file = getFile(reportsDirectory, substringAfter(name, path)); if (jarEntry.isDirectory()) { forceMkdir(file); } else { forceMkdir(file.getParentFile()); if (!file.exists()) { copyInputStreamToFile(jar.getInputStream(jarEntry), file); } } } } jar.close(); } else { // Run with IDE URL url = getClass().getResource("/" + path); if (url != null) { File apps = FileUtils.toFile(url); if (apps.isDirectory()) { copyDirectory(apps, reportsDirectory); } else { throw new IllegalStateException( format("Internal resource '%s' should be a directory.", apps.getAbsolutePath())); } } else { throw new IllegalStateException(format("Internal resource '/%s' should be here.", path)); } } } File getResultsIndexFile(Repository repository) throws UnsupportedEncodingException { return new File(reportsDirectory, getRepositoryMetaName(repository) + ".results"); } private void runTestsIn(Repository repository) throws MojoExecutionException, MojoFailureException { if (!resume) { TestResultsIndex testResultsIndex = testResultsIndexes.get(repository.getName()); if (testResultsIndex != null) { testResultsIndex.getNameToInfo().clear(); } } for (String test : repository.getTests()) { if (shouldStop()) { break; } runSingleTest(repository, test); } } private void runSingleTest(Repository repository, String test) throws MojoExecutionException, MojoFailureException { if (resume) { // Resume set, we will run the test only if it's new or failing; TestResultsIndex testResultsIndex = testResultsIndexes.get(repository.getName()); if (testResultsIndex != null) { Map<String, TestResultsIndex.TestResults> results = testResultsIndex.getNameToInfo(); if (results.containsKey(test) && !results.get(test).getStatistics().hasFailed()) { getLog().info(format("Skipping %s due to 'resume' option set", test)); return; } } } String repoCmdOption; boolean managingFileSystem; try { DocumentRepository documentRepository = repository.getDocumentRepository(); managingFileSystem = FileSystemRepository.class.isAssignableFrom(documentRepository.getClass()); } catch (Exception e) { throw new MojoFailureException("Unable to get the document repository", e); } if (managingFileSystem) { File projectBasedir = project.getBasedir(); repoCmdOption = repository.getType() + ";"; if (repository.getRoot() != null) { File relativeRoot = new File(repository.getRoot()); File absoluteDir; if (relativeRoot.getAbsoluteFile().compareTo(relativeRoot) == 0) { absoluteDir = relativeRoot; } else { absoluteDir = new File(projectBasedir, repository.getRoot()); } repoCmdOption += absoluteDir.getAbsolutePath(); } else { repoCmdOption += projectBasedir.getAbsolutePath(); } } else { repoCmdOption = repository.getType() + (repository.getRoot() != null ? ";" + repository.getRoot() : ""); } File repositoryReportsFolder = new File(reportsDirectory, repository.getName()); runnerStatistics.addToTotal(1); if (fork || isNotBlank(repository.getRunnerName())) { // If the repository specified a runner runInForkedRunner(repository, test, repositoryReportsFolder); } else { runInEmbeddedRunner(repository, test, repoCmdOption, repositoryReportsFolder); } } private void runInForkedRunner(Repository repository, String test, File repositoryReportsFolder) throws MojoExecutionException { File outputFile = new File(repositoryReportsFolder, test); SystemUnderTest systemUnderTest = new SystemUnderTest(); systemUnderTest.setName(repository.getSystemUnderTest()); systemUnderTest.setProject(Project.newInstance(repository.getProjectName())); Specification specification = Specification.newInstance(test); com.greenpepper.server.domain.Repository repositoryRunner = com.greenpepper.server.domain.Repository .newInstance(repository.getName()); RepositoryType repositoryType = RepositoryType.newInstance("FILE"); repositoryType.setRepositoryClass(repository.getType()); EnvironmentType java = EnvironmentType.newInstance("JAVA"); repositoryType.registerClassForEnvironment(repository.getType(), java); repositoryRunner.setBaseTestUrl(repository.getRoot()); repositoryRunner.setType(repositoryType); specification.setRepository(repositoryRunner); systemUnderTest.setFixtureFactory(systemUnderDevelopment); systemUnderTest.setFixtureFactoryArgs(systemUnderDevelopmentArgs); specification.setDialectClass(repository.getDialect()); String runnerName = repository.getRunnerName(); if (runnerName == null) { if (runnerMap.size() == 1) { runnerName = runnerMap.keySet().iterator().next(); } else { runnerName = DEFAULT_RUNNER_NAME; } } Runner defaultRunner = runnerMap.get(runnerName); ExecutorService executorService = executorMap.get(runnerName); if (defaultRunner != null && executorService != null) { if (defaultRunner.getForkCount() == 1) { defaultRunner.setRedirectOutputToFile(redirectOutputToFile); } // We will try to get the external-link after the test (for version of Greepepper < 4.1 ) defaultRunner.setRepositoryIndex(this.repositoryIndexes.get(repository.getName())); TestResultsIndex testResultsIndex = testResultsIndexes.get(repository.getName()); RunnerTask runnerTask = new RunnerTask(specification, systemUnderTest, outputFile.getAbsolutePath(), defaultRunner, getLog(), testResultsIndex); if (defaultRunner.isIncludeProjectClasspath()) { TreeSet<String> classpath = new TreeSet<String>(); for (URL url : createClasspath()) { classpath.add(FileUtils.toFile(url).getAbsolutePath()); } systemUnderTest.setSutClasspaths(classpath); } Future<?> future = executorService.submit(runnerTask); RunnerResult runnerResult = new RunnerResult(runnerTask, future); runnerResults.add(runnerResult); } else { getLog().warn(format( "No runner found for executing %s in repository %s. Runner '%s' was specified (or falled back to)", specification.getName(), repository.getName(), runnerName)); } } private Runner getDefaultRunner() { List<String> optionsList = new ArrayList<String>(); appendOptionsList(optionsList); Runner defaultRunner = Runner.createDefault(jvm, javaOptions, optionsList); if (forkCount != null) { defaultRunner.setForkCount(forkCount); } return defaultRunner; } private void runInEmbeddedRunner(Repository repository, String test, String repoCmdOption, File repositoryReportsFolder) throws MojoExecutionException, MojoFailureException { String outputDir = repositoryReportsFolder.getAbsolutePath(); String systemUnderDevelopmentWithArgs = getFixtureFactoryWithArgs(); List<String> args = new ArrayList<String>(); args.addAll(asList("-f", systemUnderDevelopmentWithArgs, "-r", repoCmdOption, "-o", outputDir)); if (isNotBlank(repository.getDialect())) { List<String> dialectOption = asList("-d", repository.getDialect()); args.addAll(dialectOption); } appendOptionsList(args); args.add(test); // Add the target output (which might have the same name as the test String output = null; if (StringUtils.isNoneEmpty(testSpecification, testSpecificationOutput)) { output = testSpecificationOutput; } else if (endsWithIgnoreCase(test, HTML_EXTENSION)) { output = test; } if (isNotBlank(output)) { args.add(output); } // try to define the output file File outputFile; if (isNotBlank(output)) { outputFile = getFile(outputDir, escapeFileSystemForbiddenCharacters(output)); } else { if (!endsWithIgnoreCase(test, HTML_EXTENSION)) { outputFile = getFile(outputDir, escapeFileSystemForbiddenCharacters(test) + HTML_EXTENSION); } else { outputFile = getFile(outputDir, escapeFileSystemForbiddenCharacters(test)); } } File outLogFile = new File(outputFile.getAbsolutePath() + "-output.log"); File errLogFile = new File(outputFile.getAbsolutePath() + "-err.log"); LogWriterMonitor logWriterMonitor = new LogWriterMonitor(outLogFile, errLogFile); TestMonitor testMonitor = new TestMonitor(getLog(), new NullStatisticsListener()); RecorderMonitor recorderMonitor = run(args, logWriterMonitor, testMonitor); TestResultsIndex testResultsIndex = testResultsIndexes.get(repository.getName()); if (testResultsIndex != null) { testResultsIndex.notify(test, recorderMonitor.getStatistics(), testMonitor.duration); } RepositoryIndex repositoryIndex = repositoryIndexes.get(repository.getName()); if (repositoryIndex != null && repositoryIndex.getNameToInfo().containsKey(test) && isBlank(repositoryIndex.getNameToInfo().get(test).getLink())) { if (outputFile.isFile() && outputFile.canRead()) { try { String outputHTML = readFileToString(outputFile); recoverLinkInResult(test, outputHTML, repositoryIndex); } catch (Exception e) { getLog().debug(format("Could not read the output file '%s'. Cause : %s", outputFile.getAbsolutePath(), e.getMessage())); LOGGER.trace("full trace: ", e); } } } } private String getFixtureFactoryWithArgs() { String systemUnderDevelopmentWithArgs = systemUnderDevelopment; if (isNotBlank(systemUnderDevelopmentArgs)) { systemUnderDevelopmentWithArgs = systemUnderDevelopment + ";" + systemUnderDevelopmentArgs; } return systemUnderDevelopmentWithArgs; } public static void recoverLinkInResult(String specification, String htmlString, RepositoryIndex repositoryIndex) throws IOException { RepositoryIndex.SpecificationInfo specificationInfo = repositoryIndex.getNameToInfo().get(specification); if (isBlank(specificationInfo.getLink()) && isNotBlank(htmlString)) { LOGGER.trace("got new missing link in index for '{}'. trying to find it in the result output", specification); org.jsoup.nodes.Document resultOutput = Jsoup.parse(htmlString); Elements metaTags = resultOutput.head().getElementsByTag("meta"); String link = metaTags.select("[name=\"external-link\"]").attr("content"); if (isNotBlank(link)) { LOGGER.trace("Found {}", link); specificationInfo.setLink(link); repositoryIndex.dump(); } } } private RecorderMonitor run(List<String> args, SpecificationRunnerMonitor... testMonitors) throws MojoExecutionException, MojoFailureException { DynamicCoreInvoker runner = new DynamicCoreInvoker(createClassLoader()); CompositeSpecificationRunnerMonitor monitors = new CompositeSpecificationRunnerMonitor(); for (SpecificationRunnerMonitor specificationRunnerMonitor : testMonitors) { monitors.add(specificationRunnerMonitor); } RecorderMonitor recorder = new RecorderMonitor(); monitors.add(recorder); runner.setMonitor(monitors); try { runner.run(toArray(args)); } catch (DocumentNeverImplementedException e) { LOGGER.info(DocumentRepository.THIS_SPECIFICATION_WAS_NEVER_SET_AS_IMPLEMENTED); } catch (Exception e) { exceptionOccured = true; throw new MojoExecutionException("Unable to run tests", e); } exceptionOccured |= recorder.hasException(); testFailed |= recorder.hasTestFailures(); registerStatistics(recorder); return recorder; } private void registerStatistics(RecorderMonitor recorder) { runnerStatistics.tally(recorder.getStatistics()); } private void printFooter() { System.out.println(); System.out.println(runnerStatistics); System.out.println(); } private ClassLoader createClassLoader() throws MojoExecutionException { URL[] classpath = createClasspath(); return new URLClassLoader(classpath, ClassLoader.getSystemClassLoader()); } private URL[] createClasspath() throws MojoExecutionException { List<URL> urls = new ArrayList<URL>(); if (classpathElements != null) { for (String classpathElement : classpathElements) { urls.add(toURL(new File(classpathElement))); } } urls.add(toURL(fixtureOutputDirectory)); if (!containsGreenPepperCore(urls)) { urls.add(getDependencyURL("greenpepper-core")); } urls.add(getDependencyURL("greenpepper-extensions-java")); urls.add(getDependencyURL("slf4j-api")); urls.add(getDependencyURL("jcl-over-slf4j")); return urls.toArray(new URL[urls.size()]); } private URL getDependencyURL(String name) throws MojoExecutionException { if (pluginDependencies != null && !pluginDependencies.isEmpty()) { for (Artifact artifact : pluginDependencies) { if (artifact.getArtifactId().equals(name) && artifact.getType().equals("jar")) return toURL(artifact.getFile()); } } throw new MojoExecutionException("Dependency not found: " + name); } private URL toURL(File f) throws MojoExecutionException { try { return f.toURI().toURL(); } catch (MalformedURLException e) { throw new MojoExecutionException("Invalid dependency: " + f.getAbsolutePath(), e); } } private boolean containsGreenPepperCore(List<URL> urls) { for (URL url : urls) { if (url.getFile().contains("greenpepper-core") && url.getFile().endsWith(".jar")) { return true; } } return false; } private void prepareReportsDir() throws MojoExecutionException { if (StringUtils.isAnyEmpty(testSpecification, testSpecificationOutput)) { try { IOUtil.createDirectoryTree(reportsDirectory); } catch (IOException e) { throw new MojoExecutionException( "Could not create reports directory: " + reportsDirectory.getAbsolutePath()); } } } private boolean shouldStop() { return stopOnFirstFailure && runnerStatistics.hasFailure(); } private void appendOptionsList(List<String> arguments) { if (!StringUtils.isEmpty(locale)) { arguments.add("--locale"); arguments.add(locale); } if (!StringUtils.isEmpty(selector) && !GreenPepperInterpreterSelector.class.getName().equals(selector)) { arguments.add("--selector"); arguments.add(selector); } if (stopOnFirstFailure) { arguments.add("--stop"); } if (debug) { arguments.add("--debug"); } } private String[] toArray(List<String> args) { String[] arguments = new String[args.size()]; args.toArray(arguments); return arguments; } private class LogWriterMonitor implements SpecificationRunnerMonitor { private final File outLogFile; private final File errLogFile; private PrintStream sysout; private PrintStream syserr; LogWriterMonitor(File outLogFile, File errLogFile) { this.outLogFile = outLogFile; this.errLogFile = errLogFile; } @Override public void testRunning(String location) { getLog().debug(format("Creating Log files (by redirecting sysout and stderr) for: %s", location)); sysout = System.out; syserr = System.err; syserr.flush(); sysout.flush(); try { FileOutputStream output = openOutputStream(outLogFile); FileOutputStream err = openOutputStream(errLogFile); if (redirectOutputToFile) { System.setOut(new PrintStream(output)); System.setErr(new PrintStream(err)); } else { TeePrintStream teeoutput = new TeePrintStream(output, sysout); System.setOut(teeoutput); TeePrintStream teeErr = new TeePrintStream(err, syserr); System.setErr(teeErr); } } catch (IOException e) { System.setOut(sysout); System.setErr(syserr); getLog().warn(format("Could not create the log files. Cause: %s", e.getMessage())); getLog().debug("Could not create the log files.", e); } } @Override public void testDone(int rightCount, int wrongCount, int exceptionCount, int ignoreCount) { getLog().debug("Restoring stdout and stderr"); System.setOut(sysout); System.setErr(syserr); } @Override public void exceptionOccured(Throwable t) { } } private class TeePrintStream extends PrintStream { private final PrintStream second; TeePrintStream(OutputStream main, PrintStream second) { super(main); this.second = second; } /** * Closes the main stream. * The second stream is just flushed but <b>not</b> closed. * @see java.io.PrintStream#close() */ @Override public void close() { // just for documentation super.close(); } @Override public void flush() { super.flush(); second.flush(); } @SuppressWarnings("NullableProblems") @Override public void write(byte[] buf, int off, int len) { super.write(buf, off, len); second.write(buf, off, len); } @Override public void write(int b) { super.write(b); second.write(b); } @SuppressWarnings("NullableProblems") @Override public void write(byte[] b) throws IOException { super.write(b); second.write(b); } } }