/*- * #%L * jasmine-maven-plugin * %% * Copyright (C) 2010 - 2017 Justin Searls * %% * 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. * #L% */ package com.github.searls.jasmine.mojo; import com.github.searls.jasmine.config.JasmineConfiguration; import com.github.searls.jasmine.exception.StringifiesStackTraces; import com.github.searls.jasmine.io.ScansDirectory; import com.github.searls.jasmine.model.FileSystemReporter; import com.github.searls.jasmine.model.Reporter; import com.github.searls.jasmine.model.ScriptSearch; import com.github.searls.jasmine.runner.SpecRunnerTemplate; import com.github.searls.jasmine.thirdpartylibs.ProjectClassLoaderFactory; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.resource.ResourceManager; import org.eclipse.jetty.server.Connector; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; public abstract class AbstractJasmineMojo extends AbstractMojo implements JasmineConfiguration { // Properties in order of most-to-least interesting for client projects to override /** * Directory storing your JavaScript. * * @since 1.1.0 */ @Parameter( property = "jsSrcDir", defaultValue = "${project.basedir}${file.separator}src${file.separator}main${file.separator}javascript") private File jsSrcDir; /** * Directory storing your Jasmine Specs. * * @since 1.1.0 */ @Parameter( property = "jsTestSrcDir", defaultValue = "${project.basedir}${file.separator}src${file.separator}test${file.separator}javascript") private File jsTestSrcDir; /** * <p>JavaScript sources (typically vendor/lib dependencies) that need to be loaded * before other sources (and specs) in a particular order. Each source will first be * searched for relative to <code>${jsSrcDir}</code>, then <code>${jsTestSrcDir}</code>, * then (if it's not found in either) it will be included exactly as it appears in your POM.</p> * <br> * <p>Therefore, if jquery.js is in <code>${jsSrcDir}/vendor</code>, you would configure:</p> * <pre> * <preloadSources> * <source>vendor/jquery.js</source> * </preloadSources> * </pre> * <br> * <p>And jquery.js would load before all the other sources and specs.</p> * * @since 1.1.0 */ @Parameter protected List<String> preloadSources; /** * <p>It may be the case that the jasmine-maven-plugin doesn't currently suit all of your needs, * and as a result the generated SpecRunner HTML files are set up in a way that you can't run * your specs. Have no fear! Simply specify a custom spec runner template in the plugin configuration * and make the changes you need.</p> * <br> * <p>Potential values are a filesystem path, a URL, or a classpath resource. The default template is * stored in <code>src/main/resources/jasmine-templates/SpecRunner.htmltemplate</code>, and the * required template strings are tokenized in "$*$" patterns.</p> * <br> * <p>Example usage:</p> * <pre> * <customRunnerTemplate>${project.basedir}/src/test/resources/myCustomRunner.template</customRunnerTemplate> * </pre> * * @since 1.1.0 */ @Parameter protected String customRunnerTemplate; /** * <p>Sometimes you want to have full control over how scriptloaders are configured. In order to * interpolate custom configuration into the generated runnerTemplate, specify a file containing * the additional config. Potential values are a filesystem path, a URL, or a classpath resource.</p> * <br> * <p>Example usage:</p> * <pre> * <customRunnerConfiguration>${project.basedir}/src/test/resources/myCustomConfig.txt</customRunnerConfiguration> * </pre> * * @since 1.1.0 */ @Parameter protected String customRunnerConfiguration; /** * <p> Specify a custom reporter to be used to print the test report.</p> * <p>Example usage:</p> * <pre> * <reporters> * <reporter> * <reporterName>${project.basedir}/src/test/resources/myCustomReporter.js</reporterName> * </reporter> * <reporter> * <reporterName>STANDARD</reporterName> * </reporter> * </reporters> * </pre> */ @Parameter protected List<Reporter> reporters = new ArrayList<Reporter>(); /** * <p> Specify a custom file system reporter to be used to store the test report.</p> * <p>Example usage:</p> * <pre> * <fileSystemReporters> * <reporter> * <fileName>MyFile.log</fileName> * <reporterName>${project.basedir}/src/test/resources/myCustomReporter.js</reporterName> * </reporter> * <reporter> * <fileName>Test-jasmine.xml</fileName> * <reporterName>JUNIT_XML</reporterName> * </reporter> * </fileSystemReporters> * </pre> */ @Parameter protected List<FileSystemReporter> fileSystemReporters = new ArrayList<FileSystemReporter>(); /** * Target directory for files created by the plugin. * * @since 1.1.0 */ @Parameter(defaultValue = "${project.build.directory}${file.separator}jasmine") protected File jasmineTargetDir; /** * Skip execution of tests. * * @see <a href="http://maven.apache.org/general.html#skip-test">http://maven.apache.org/general.html#skip-test</a> * @since 1.1.0 */ @Parameter(property = "skipTests") protected boolean skipTests; /** * Skip compilation and execution of tests. * * @see <a href="http://maven.apache.org/general.html#skip-test">http://maven.apache.org/general.html#skip-test</a> * @since 1.3.1.3 */ @Parameter(property = "maven.test.skip") protected boolean mvnTestSkip; /** * Skip only jasmine tests * * @since 1.3.1.3 */ @Parameter(property = "skipJasmineTests") protected boolean skipJasmineTests; /** * Halt the build on test failure. * * @since 1.1.0 */ @Parameter(property = "haltOnFailure", defaultValue = "true") protected boolean haltOnFailure; /** * Timeout for spec execution in seconds. * * @since 1.1.0 */ @Parameter(defaultValue = "300") protected int timeout; /** * True to increase HtmlUnit output and attempt reporting on specs even if a timeout occurred. * * @since 1.1.0 */ @Parameter(defaultValue = "false") protected boolean debug; /** * The name of the Spec Runner file. * * @since 1.1.0 */ @Parameter(defaultValue = "SpecRunner.html") protected String specRunnerHtmlFileName; /** * The name of the Manual Spec Runner. * * @since 1.1.0 */ @Parameter(defaultValue = "ManualSpecRunner.html") protected String manualSpecRunnerHtmlFileName; /** * The name of the directory the specs will be deployed to on the server. * * @since 1.1.0 */ @Parameter(defaultValue = "spec") protected String specDirectoryName; /** * The name of the directory the sources will be deployed to on the server. * * @since 1.1.0 */ @Parameter(defaultValue = "src") protected String srcDirectoryName; /** * The source encoding. * * @since 1.1.0 */ @Parameter(defaultValue = "${project.build.sourceEncoding}") protected String sourceEncoding; /** * <p>Allows specifying which source files should be included and in what order.</p> * <pre> * <sourceIncludes> * <include>vendor/**/*.js</include> * <include>myBootstrapFile.js</include> * <include>**/*.js</include> * <include>**/*.coffee</include> * </sourceIncludes> * </pre> * <br> * <p>Default <code>sourceIncludes</code>:</p> * <pre> * <sourceIncludes> * <include>**/*.js</include> * <include>**/*.coffee</include> * </sourceIncludes> * </pre> * * @since 1.1.0 */ @Parameter private final List<String> sourceIncludes = ScansDirectory.DEFAULT_INCLUDES; /** * <p>Just like <code>sourceIncludes</code>, but will exclude anything matching the provided patterns.</p> * <p>There are no <code>sourceExcludes</code> by default.</p> * * @since 1.1.0 */ @Parameter private final List<String> sourceExcludes = Collections.emptyList(); /** * <p>I often find myself needing control of the spec include order * when I have some global spec helpers or spec-scoped dependencies, like:</p> * <pre> * <specIncludes> * <include>jasmine-jquery.js</include> * <include>spec-helper.js</include> * <include>**/*.js</include> * <include>**/*.coffee</include> * </specIncludes> * </pre> * <br> * <p>Default <code>specIncludes</code>:</p> * <pre> * <specIncludes> * <include>**/*.js</include> * <include>**/*.coffee</include> * </specIncludes> * </pre> * * @since 1.1.0 */ @Parameter private final List<String> specIncludes = ScansDirectory.DEFAULT_INCLUDES; /** * <p>Just like <code>specIncludes</code>, but will exclude anything matching the provided patterns.</p> * <p>There are no <code>specExcludes</code> by default.</p> * * @since 1.1.0 */ @Parameter private final List<String> specExcludes = Collections.emptyList(); /** * <p>Used by the <code>jasmine:bdd</code> goal to specify port to run the server under.</p> * <br> * <p>The <code>jasmine:test</code> goal always uses a random available port so this property is ignored.</p> * * @since 1.1.0 */ @Parameter(property = "jasmine.serverPort", defaultValue = "8234") protected int serverPort; /** * <p>Specify the URI scheme in which to access the SpecRunner.</p> * * @since 1.3.1.4 */ @Parameter(property = "jasmine.uriScheme", defaultValue = "http") protected String uriScheme; /** * <p>Not used by the <code>jasmine:bdd</code> goal.</p> * <br> * <p>The <code>jasmine:test</code> goal to specify hostname where the server is running. Useful when using * the RemoteWebDriver.</p> * * @since 1.3.1.4 */ @Parameter(property = "jasmine.serverHostname", defaultValue = "localhost") protected String serverHostname; /** * <p>Determines the strategy to use when generation the JasmineSpecRunner. This feature allows for custom * implementation of the runner generator. Typically this is used when using different script runners.</p> * <br> * <p>Some valid examples: DEFAULT, REQUIRE_JS</p> * * @since 1.1.0 */ @Parameter(property = "jasmine.specRunnerTemplate", defaultValue = "DEFAULT") protected SpecRunnerTemplate specRunnerTemplate; /** * <p>Automatically refresh the test runner at the given interval (specified in seconds) when using the <code>jasmine:bdd</code> goal.</p> * <p>A value of <code>0</code> disables the automatic refresh (which is the default).</p> * * @since 1.3.1.1 */ @Parameter(property = "jasmine.autoRefreshInterval", defaultValue = "0") protected int autoRefreshInterval; /** * <p>Control the Coffee Script compilation. e.g. When using RequireJS the compilation * happens within the Coffee Script AMD loader plugin; we therefore need to disable the * compilation here.</p> * * @since 1.3.1.4 */ @Parameter(property = "coffeeScriptCompilationEnabled", defaultValue = "true") protected boolean coffeeScriptCompilationEnabled; /** * <p>Type of {@link org.eclipse.jetty.server.Connector} to use on the jetty server.</p> * <br> * <p>Most users won't need to change this from the default value. It should only be used * by advanced users.</p> * * @since 1.3.1.4 */ @Parameter( property = "jasmine.connectorClass", defaultValue = "org.eclipse.jetty.server.nio.SelectChannelConnector" ) protected String connectorClass; /** * <p>Specify additional contexts to make available.</p> * <pre> * <additionalContexts> * <context> * <contextRoot>lib</contextRoot> * <directory>${project.basedir}/src/main/lib</directory> * </context> * <context> * <contextRoot>test/lib</contextRoot> * <directory>${project.basedir}/src/test/lib</directory> * </context> * </additionalContexts> * </pre> * * @since 1.3.1.5 */ @Parameter private List<Context> additionalContexts = Collections.emptyList(); @Parameter(defaultValue = "${project}", readonly = true) protected MavenProject mavenProject; @Component ResourceManager resourceManager; protected ResourceRetriever resourceRetriever; protected ReporterRetriever reporterRetriever; protected StringifiesStackTraces stringifiesStackTraces = new StringifiesStackTraces(); protected ScriptSearch sources; protected ScriptSearch specs; private File customRunnerTemplateFile; private File customRunnerConfigurationFile; @Override public void execute() throws MojoExecutionException, MojoFailureException { this.loadResources(); this.sources = new ScriptSearch(this.jsSrcDir, this.sourceIncludes, this.sourceExcludes); this.specs = new ScriptSearch(this.jsTestSrcDir, this.specIncludes, this.specExcludes); try { this.run(); } catch (MojoFailureException e) { throw e; } catch (Exception e) { throw new MojoExecutionException("The jasmine-maven-plugin encountered an exception: \n" + this.stringifiesStackTraces.stringify(e), e); } } public abstract void run() throws Exception; @Override public String getSourceEncoding() { return this.sourceEncoding; } @Override public File getCustomRunnerTemplate() { return this.customRunnerTemplateFile; } @Override public SpecRunnerTemplate getSpecRunnerTemplate() { return this.specRunnerTemplate; } @Override public File getJasmineTargetDir() { return this.jasmineTargetDir; } @Override public String getSrcDirectoryName() { return this.srcDirectoryName; } @Override public ScriptSearch getSources() { return this.sources; } @Override public ScriptSearch getSpecs() { return this.specs; } @Override public String getSpecDirectoryName() { return this.specDirectoryName; } @Override public List<String> getPreloadSources() { return this.preloadSources; } @Override public int getAutoRefreshInterval() { return this.autoRefreshInterval; } @Override public boolean isCoffeeScriptCompilationEnabled() { return this.coffeeScriptCompilationEnabled; } public MavenProject getMavenProject() { return this.mavenProject; } @Override public File getCustomRunnerConfiguration() { return this.customRunnerConfigurationFile; } @Override public List<Reporter> getReporters() { return this.reporters; } @Override public List<FileSystemReporter> getFileSystemReporters() { return this.fileSystemReporters; } @Override public File getBasedir() { return this.mavenProject.getBasedir(); } @Override public ClassLoader getProjectClassLoader() { return new ProjectClassLoaderFactory(mavenProject.getArtifacts()).create(); } @Override public List<Context> getContexts() { List<Context> contexts = new ArrayList<Context>(); contexts.add(new Context(this.srcDirectoryName, this.jsSrcDir)); contexts.add(new Context(this.specDirectoryName, this.jsTestSrcDir)); contexts.addAll(additionalContexts); return contexts; } protected Connector getConnector() throws MojoExecutionException { try { @SuppressWarnings("unchecked") Class<? extends Connector> c = (Class<? extends Connector>) Class.forName(connectorClass); return c.newInstance(); } catch (InstantiationException e) { throw new MojoExecutionException("Unable to instantiate.", e); } catch (IllegalAccessException e) { throw new MojoExecutionException("Unable to instantiate.", e); } catch (ClassNotFoundException e) { throw new MojoExecutionException("Unable to instantiate.", e); } } protected boolean isSkipTests() { return this.skipTests || this.mvnTestSkip || this.skipJasmineTests; } private void loadResources() throws MojoExecutionException { this.customRunnerTemplateFile = getResourceRetriever().getResourceAsFile("customRunnerTemplate", this.customRunnerTemplate, this.mavenProject); this.customRunnerConfigurationFile = getResourceRetriever().getResourceAsFile("customRunnerConfiguration", this.customRunnerConfiguration, this.mavenProject); this.reporters = getReporterRetriever().retrieveReporters(this.reporters, this.mavenProject); this.fileSystemReporters = getReporterRetriever().retrieveFileSystemReporters(this.fileSystemReporters, this.getJasmineTargetDir(), this.mavenProject); } private ResourceRetriever getResourceRetriever() { if (resourceRetriever == null) { resourceRetriever = new ResourceRetriever(resourceManager); } return resourceRetriever; } private ReporterRetriever getReporterRetriever() { if (reporterRetriever == null) { reporterRetriever = new ReporterRetriever(getResourceRetriever()); } return reporterRetriever; } }