View Javadoc
1   package com.github.searls.jasmine.mojo;
2   
3   import com.github.searls.jasmine.config.JasmineConfiguration;
4   import com.github.searls.jasmine.exception.StringifiesStackTraces;
5   import com.github.searls.jasmine.io.ScansDirectory;
6   import com.github.searls.jasmine.model.FileSystemReporter;
7   import com.github.searls.jasmine.model.Reporter;
8   import com.github.searls.jasmine.model.ScriptSearch;
9   import com.github.searls.jasmine.runner.SpecRunnerTemplate;
10  import com.github.searls.jasmine.thirdpartylibs.ProjectClassLoaderFactory;
11  import org.apache.maven.plugin.AbstractMojo;
12  import org.apache.maven.plugin.MojoExecutionException;
13  import org.apache.maven.plugin.MojoFailureException;
14  import org.apache.maven.plugins.annotations.Component;
15  import org.apache.maven.plugins.annotations.Parameter;
16  import org.apache.maven.project.MavenProject;
17  import org.codehaus.plexus.resource.ResourceManager;
18  import org.eclipse.jetty.server.Connector;
19  
20  import java.io.File;
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.List;
24  
25  public abstract class AbstractJasmineMojo extends AbstractMojo implements JasmineConfiguration {
26  
27    // Properties in order of most-to-least interesting for client projects to override
28  
29    /**
30     * Directory storing your JavaScript.
31     *
32     * @since 1.1.0
33     */
34    @Parameter(
35      property = "jsSrcDir",
36      defaultValue = "${project.basedir}${file.separator}src${file.separator}main${file.separator}javascript")
37    private File jsSrcDir;
38  
39    /**
40     * Directory storing your Jasmine Specs.
41     *
42     * @since 1.1.0
43     */
44    @Parameter(
45      property = "jsTestSrcDir",
46      defaultValue = "${project.basedir}${file.separator}src${file.separator}test${file.separator}javascript")
47    private File jsTestSrcDir;
48  
49    /**
50     * <p>JavaScript sources (typically vendor/lib dependencies) that need to be loaded
51     * before other sources (and specs) in a particular order. Each source will first be
52     * searched for relative to <code>${jsSrcDir}</code>, then <code>${jsTestSrcDir}</code>,
53     * then (if it's not found in either) it will be included exactly as it appears in your POM.</p>
54     * <br>
55     * <p>Therefore, if jquery.js is in <code>${jsSrcDir}/vendor</code>, you would configure:</p>
56     * <pre>
57     * &lt;preloadSources&gt;
58     *   &lt;source&gt;vendor/jquery.js&lt;/source&gt;
59     * &lt;/preloadSources&gt;
60     * </pre>
61     * <br>
62     * <p>And jquery.js would load before all the other sources and specs.</p>
63     *
64     * @since 1.1.0
65     */
66    @Parameter
67    protected List<String> preloadSources;
68  
69    /**
70     * <p>It may be the case that the jasmine-maven-plugin doesn't currently suit all of your needs,
71     * and as a result the generated SpecRunner HTML files are set up in a way that you can't run
72     * your specs. Have no fear! Simply specify a custom spec runner template in the plugin configuration
73     * and make the changes you need.</p>
74     * <br>
75     * <p>Potential values are a filesystem path, a URL, or a classpath resource. The default template is
76     * stored in <code>src/main/resources/jasmine-templates/SpecRunner.htmltemplate</code>, and the
77     * required template strings are tokenized in "$*$" patterns.</p>
78     * <br>
79     * <p>Example usage:</p>
80     * <pre>
81     * &lt;customRunnerTemplate&gt;${project.basedir}/src/test/resources/myCustomRunner.template&lt;/customRunnerTemplate&gt;
82     * </pre>
83     *
84     * @since 1.1.0
85     */
86    @Parameter
87    protected String customRunnerTemplate;
88  
89    /**
90     * <p>Sometimes you want to have full control over how scriptloaders are configured. In order to
91     * interpolate custom configuration into the generated runnerTemplate, specify a file containing
92     * the additional config. Potential values are a filesystem path, a URL, or a classpath resource.</p>
93     * <br>
94     * <p>Example usage:</p>
95     * <pre>
96     * &lt;customRunnerConfiguration&gt;${project.basedir}/src/test/resources/myCustomConfig.txt&lt;/customRunnerConfiguration&gt;
97     * </pre>
98     *
99     * @since 1.1.0
100    */
101   @Parameter
102   protected String customRunnerConfiguration;
103 
104   /**
105    * <p> Specify a custom reporter to be used to print the test report.</p>
106    * <p>Example usage:</p>
107    * <pre>
108    * &lt;reporters&gt;
109    *   &lt;reporter&gt;
110    *     &lt;reporterName&gt;${project.basedir}/src/test/resources/myCustomReporter.js&lt;/reporterName&gt;
111    *   &lt;/reporter&gt;
112    *   &lt;reporter&gt;
113    *     &lt;reporterName&gt;STANDARD&lt;/reporterName&gt;
114    *   &lt;/reporter&gt;
115    * &lt;/reporters&gt;
116    * </pre>
117    */
118   @Parameter
119   protected List<Reporter> reporters = new ArrayList<Reporter>();
120 
121   /**
122    * <p> Specify a custom file system reporter to be used to store the test report.</p>
123    * <p>Example usage:</p>
124    * <pre>
125    * &lt;fileSystemReporters&gt;
126    *   &lt;reporter&gt;
127    *     &lt;fileName&gt;MyFile.log&lt;/fileName&gt;
128    *     &lt;reporterName&gt;${project.basedir}/src/test/resources/myCustomReporter.js&lt;/reporterName&gt;
129    *   &lt;/reporter&gt;
130    *   &lt;reporter&gt;
131    *     &lt;fileName&gt;Test-jasmine.xml&lt;/fileName&gt;
132    *     &lt;reporterName&gt;JUNIT_XML&lt;/reporterName&gt;
133    *   &lt;/reporter&gt;
134    * &lt;/fileSystemReporters&gt;
135    * </pre>
136    */
137   @Parameter
138   protected List<FileSystemReporter> fileSystemReporters = new ArrayList<FileSystemReporter>();
139 
140   /**
141    * Target directory for files created by the plugin.
142    *
143    * @since 1.1.0
144    */
145   @Parameter(defaultValue = "${project.build.directory}${file.separator}jasmine")
146   protected File jasmineTargetDir;
147 
148   /**
149    * Skip execution of tests.
150    *
151    * @see <a href="http://maven.apache.org/general.html#skip-test">http://maven.apache.org/general.html#skip-test</a>
152    * @since 1.1.0
153    */
154   @Parameter(property = "skipTests")
155   protected boolean skipTests;
156 
157   /**
158    * Skip compilation and execution of tests.
159    *
160    * @see <a href="http://maven.apache.org/general.html#skip-test">http://maven.apache.org/general.html#skip-test</a>
161    * @since 1.3.1.3
162    */
163   @Parameter(property = "maven.test.skip")
164   protected boolean mvnTestSkip;
165 
166   /**
167    * Skip only jasmine tests
168    *
169    * @since 1.3.1.3
170    */
171   @Parameter(property = "skipJasmineTests")
172   protected boolean skipJasmineTests;
173 
174   /**
175    * Halt the build on test failure.
176    *
177    * @since 1.1.0
178    */
179   @Parameter(property = "haltOnFailure", defaultValue = "true")
180   protected boolean haltOnFailure;
181 
182   /**
183    * Timeout for spec execution in seconds.
184    *
185    * @since 1.1.0
186    */
187   @Parameter(defaultValue = "300")
188   protected int timeout;
189 
190   /**
191    * True to increase HtmlUnit output and attempt reporting on specs even if a timeout occurred.
192    *
193    * @since 1.1.0
194    */
195   @Parameter(defaultValue = "false")
196   protected boolean debug;
197 
198   /**
199    * The name of the Spec Runner file.
200    *
201    * @since 1.1.0
202    */
203   @Parameter(defaultValue = "SpecRunner.html")
204   protected String specRunnerHtmlFileName;
205 
206   /**
207    * The name of the Manual Spec Runner.
208    *
209    * @since 1.1.0
210    */
211   @Parameter(defaultValue = "ManualSpecRunner.html")
212   protected String manualSpecRunnerHtmlFileName;
213 
214   /**
215    * The name of the directory the specs will be deployed to on the server.
216    *
217    * @since 1.1.0
218    */
219   @Parameter(defaultValue = "spec")
220   protected String specDirectoryName;
221 
222   /**
223    * The name of the directory the sources will be deployed to on the server.
224    *
225    * @since 1.1.0
226    */
227   @Parameter(defaultValue = "src")
228   protected String srcDirectoryName;
229 
230   /**
231    * The source encoding.
232    *
233    * @since 1.1.0
234    */
235   @Parameter(defaultValue = "${project.build.sourceEncoding}")
236   protected String sourceEncoding;
237 
238   /**
239    * <p>Allows specifying which source files should be included and in what order.</p>
240    * <pre>
241    * &lt;sourceIncludes&gt;
242    *   &lt;include&gt;vendor/&#42;&#42;/&#42;.js&lt;/include&gt;
243    *   &lt;include&gt;myBootstrapFile.js&lt;/include&gt;
244    *   &lt;include&gt;&#42;&#42;/&#42;.js&lt;/include&gt;
245    *   &lt;include&gt;&#42;&#42;/&#42;.coffee&lt;/include&gt;
246    * &lt;/sourceIncludes&gt;
247    * </pre>
248    * <br>
249    * <p>Default <code>sourceIncludes</code>:</p>
250    * <pre>
251    * &lt;sourceIncludes&gt;
252    *   &lt;include&gt;&#42;&#42;/&#42;.js&lt;/include&gt;
253    *   &lt;include&gt;&#42;&#42;/&#42;.coffee&lt;/include&gt;
254    * &lt;/sourceIncludes&gt;
255    * </pre>
256    *
257    * @since 1.1.0
258    */
259   @Parameter
260   private final List<String> sourceIncludes = ScansDirectory.DEFAULT_INCLUDES;
261 
262   /**
263    * <p>Just like <code>sourceIncludes</code>, but will exclude anything matching the provided patterns.</p>
264    * <p>There are no <code>sourceExcludes</code> by default.</p>
265    *
266    * @since 1.1.0
267    */
268   @Parameter
269   private final List<String> sourceExcludes = Collections.emptyList();
270 
271   /**
272    * <p>I often find myself needing control of the spec include order
273    * when I have some global spec helpers or spec-scoped dependencies, like:</p>
274    * <pre>
275    * &lt;specIncludes&gt;
276    *   &lt;include&gt;jasmine-jquery.js&lt;/include&gt;
277    *   &lt;include&gt;spec-helper.js&lt;/include&gt;
278    *   &lt;include&gt;&#42;&#42;/&#42;.js&lt;/include&gt;
279    *   &lt;include&gt;&#42;&#42;/&#42;.coffee&lt;/include&gt;
280    * &lt;/specIncludes&gt;
281    * </pre>
282    * <br>
283    * <p>Default <code>specIncludes</code>:</p>
284    * <pre>
285    * &lt;specIncludes&gt;
286    *   &lt;include&gt;&#42;&#42;/&#42;.js&lt;/include&gt;
287    *   &lt;include&gt;&#42;&#42;/&#42;.coffee&lt;/include&gt;
288    * &lt;/specIncludes&gt;
289    * </pre>
290    *
291    * @since 1.1.0
292    */
293   @Parameter
294   private final List<String> specIncludes = ScansDirectory.DEFAULT_INCLUDES;
295 
296   /**
297    * <p>Just like <code>specIncludes</code>, but will exclude anything matching the provided patterns.</p>
298    * <p>There are no <code>specExcludes</code> by default.</p>
299    *
300    * @since 1.1.0
301    */
302   @Parameter
303   private final List<String> specExcludes = Collections.emptyList();
304 
305   /**
306    * <p>Used by the <code>jasmine:bdd</code> goal to specify port to run the server under.</p>
307    * <br>
308    * <p>The <code>jasmine:test</code> goal always uses a random available port so this property is ignored.</p>
309    *
310    * @since 1.1.0
311    */
312   @Parameter(property = "jasmine.serverPort", defaultValue = "8234")
313   protected int serverPort;
314 
315   /**
316    * <p>Specify the URI scheme in which to access the SpecRunner.</p>
317    *
318    * @since 1.3.1.4
319    */
320   @Parameter(property = "jasmine.uriScheme", defaultValue = "http")
321   protected String uriScheme;
322 
323   /**
324    * <p>Not used by the <code>jasmine:bdd</code> goal.</p>
325    * <br>
326    * <p>The <code>jasmine:test</code> goal to specify hostname where the server is running.  Useful when using
327    * the RemoteWebDriver.</p>
328    *
329    * @since 1.3.1.4
330    */
331   @Parameter(property = "jasmine.serverHostname", defaultValue = "localhost")
332   protected String serverHostname;
333 
334   /**
335    * <p>Determines the strategy to use when generation the JasmineSpecRunner. This feature allows for custom
336    * implementation of the runner generator. Typically this is used when using different script runners.</p>
337    * <br>
338    * <p>Some valid examples: DEFAULT, REQUIRE_JS</p>
339    *
340    * @since 1.1.0
341    */
342   @Parameter(property = "jasmine.specRunnerTemplate", defaultValue = "DEFAULT")
343   protected SpecRunnerTemplate specRunnerTemplate;
344 
345   /**
346    * <p>Automatically refresh the test runner at the given interval (specified in seconds) when using the <code>jasmine:bdd</code> goal.</p>
347    * <p>A value of <code>0</code> disables the automatic refresh (which is the default).</p>
348    *
349    * @since 1.3.1.1
350    */
351   @Parameter(property = "jasmine.autoRefreshInterval", defaultValue = "0")
352   protected int autoRefreshInterval;
353 
354   /**
355    * <p>Control the Coffee Script compilation.  e.g. When using RequireJS the compilation
356    * happens within the Coffee Script AMD loader plugin; we therefore need to disable the
357    * compilation here.</p>
358    *
359    * @since 1.3.1.4
360    */
361   @Parameter(property = "coffeeScriptCompilationEnabled", defaultValue = "true")
362   protected boolean coffeeScriptCompilationEnabled;
363 
364   /**
365    * <p>Type of {@link org.eclipse.jetty.server.Connector} to use on the jetty server.</p>
366    * <br>
367    * <p>Most users won't need to change this from the default value. It should only be used
368    * by advanced users.</p>
369    *
370    * @since 1.3.1.4
371    */
372   @Parameter(
373     property = "jasmine.connectorClass",
374     defaultValue = "org.eclipse.jetty.server.nio.SelectChannelConnector"
375   )
376   protected String connectorClass;
377 
378   /**
379    * <p>Specify additional contexts to make available.</p>
380    * <pre>
381    * &lt;additionalContexts&gt;
382    *   &lt;context&gt;
383    *     &lt;contextRoot&gt;lib&lt;/contextRoot&gt;
384    *     &lt;directory&gt;${project.basedir}/src/main/lib&lt;/directory&gt;
385    *   &lt;/context&gt;
386    *   &lt;context&gt;
387    *     &lt;contextRoot&gt;test/lib&lt;/contextRoot&gt;
388    *     &lt;directory&gt;${project.basedir}/src/test/lib&lt;/directory&gt;
389    *   &lt;/context&gt;
390    * &lt;/additionalContexts&gt;
391    * </pre>
392    *
393    * @since 1.3.1.5
394    */
395   @Parameter
396   private List<Context> additionalContexts = Collections.emptyList();
397 
398   @Parameter(defaultValue = "${project}", readonly = true)
399   protected MavenProject mavenProject;
400 
401   @Component
402   ResourceManager resourceManager;
403 
404   protected ResourceRetriever resourceRetriever;
405   protected ReporterRetriever reporterRetriever;
406 
407   protected StringifiesStackTraces stringifiesStackTraces = new StringifiesStackTraces();
408 
409   protected ScriptSearch sources;
410   protected ScriptSearch specs;
411 
412   private File customRunnerTemplateFile;
413   private File customRunnerConfigurationFile;
414 
415   @Override
416   public void execute() throws MojoExecutionException, MojoFailureException {
417     this.loadResources();
418 
419     this.sources = new ScriptSearch(this.jsSrcDir, this.sourceIncludes, this.sourceExcludes);
420     this.specs = new ScriptSearch(this.jsTestSrcDir, this.specIncludes, this.specExcludes);
421 
422     try {
423       this.run();
424     } catch (MojoFailureException e) {
425       throw e;
426     } catch (Exception e) {
427       throw new MojoExecutionException("The jasmine-maven-plugin encountered an exception: \n" + this.stringifiesStackTraces.stringify(e), e);
428     }
429   }
430 
431   public abstract void run() throws Exception;
432 
433   @Override
434   public String getSourceEncoding() {
435     return this.sourceEncoding;
436   }
437 
438   @Override
439   public File getCustomRunnerTemplate() {
440     return this.customRunnerTemplateFile;
441   }
442 
443   @Override
444   public SpecRunnerTemplate getSpecRunnerTemplate() {
445     return this.specRunnerTemplate;
446   }
447 
448   @Override
449   public File getJasmineTargetDir() {
450     return this.jasmineTargetDir;
451   }
452 
453   @Override
454   public String getSrcDirectoryName() {
455     return this.srcDirectoryName;
456   }
457 
458   @Override
459   public ScriptSearch getSources() {
460     return this.sources;
461   }
462 
463   @Override
464   public ScriptSearch getSpecs() {
465     return this.specs;
466   }
467 
468   @Override
469   public String getSpecDirectoryName() {
470     return this.specDirectoryName;
471   }
472 
473   @Override
474   public List<String> getPreloadSources() {
475     return this.preloadSources;
476   }
477 
478   @Override
479   public int getAutoRefreshInterval() {
480     return this.autoRefreshInterval;
481   }
482 
483   @Override
484   public boolean isCoffeeScriptCompilationEnabled() {
485     return this.coffeeScriptCompilationEnabled;
486   }
487 
488   public MavenProject getMavenProject() {
489     return this.mavenProject;
490   }
491 
492   @Override
493   public File getCustomRunnerConfiguration() {
494     return this.customRunnerConfigurationFile;
495   }
496 
497   @Override
498   public List<Reporter> getReporters() {
499     return this.reporters;
500   }
501 
502   @Override
503   public List<FileSystemReporter> getFileSystemReporters() {
504     return this.fileSystemReporters;
505   }
506 
507   @Override
508   public File getBasedir() {
509     return this.mavenProject.getBasedir();
510   }
511 
512   @Override
513   public ClassLoader getProjectClassLoader() {
514     return new ProjectClassLoaderFactory(mavenProject.getArtifacts()).create();
515   }
516 
517   @Override
518   public List<Context> getContexts() {
519     List<Context> contexts = new ArrayList<Context>();
520     contexts.add(new Context(this.srcDirectoryName, this.jsSrcDir));
521     contexts.add(new Context(this.specDirectoryName, this.jsTestSrcDir));
522     contexts.addAll(additionalContexts);
523     return contexts;
524   }
525 
526   protected Connector getConnector() throws MojoExecutionException {
527     try {
528       @SuppressWarnings("unchecked")
529       Class<? extends Connector> c = (Class<? extends Connector>) Class.forName(connectorClass);
530       return c.newInstance();
531     } catch (InstantiationException e) {
532       throw new MojoExecutionException("Unable to instantiate.", e);
533     } catch (IllegalAccessException e) {
534       throw new MojoExecutionException("Unable to instantiate.", e);
535     } catch (ClassNotFoundException e) {
536       throw new MojoExecutionException("Unable to instantiate.", e);
537     }
538   }
539 
540   protected boolean isSkipTests() {
541     return this.skipTests || this.mvnTestSkip || this.skipJasmineTests;
542   }
543 
544   private void loadResources() throws MojoExecutionException {
545     this.customRunnerTemplateFile = getResourceRetriever().getResourceAsFile("customRunnerTemplate", this.customRunnerTemplate, this.mavenProject);
546     this.customRunnerConfigurationFile = getResourceRetriever().getResourceAsFile("customRunnerConfiguration", this.customRunnerConfiguration, this.mavenProject);
547     this.reporters = getReporterRetriever().retrieveReporters(this.reporters, this.mavenProject);
548     this.fileSystemReporters = getReporterRetriever().retrieveFileSystemReporters(this.fileSystemReporters, this.getJasmineTargetDir(), this.mavenProject);
549   }
550 
551   private ResourceRetriever getResourceRetriever() {
552     if (resourceRetriever == null) {
553       resourceRetriever = new ResourceRetriever(resourceManager);
554     }
555     return resourceRetriever;
556   }
557 
558   private ReporterRetriever getReporterRetriever() {
559     if (reporterRetriever == null) {
560       reporterRetriever = new ReporterRetriever(getResourceRetriever());
561     }
562     return reporterRetriever;
563   }
564 }