View Javadoc

1   /*
2    * Copyright 2011 SOFTEC sa. All rights reserved.
3    *
4    * This source code is licensed under the Creative Commons
5    * Attribution-NonCommercial-NoDerivs 3.0 Luxembourg
6    * License.
7    *
8    * To view a copy of this license, visit
9    * http://creativecommons.org/licenses/by-nc-nd/3.0/lu/
10   * or send a letter to Creative Commons, 171 Second Street,
11   * Suite 300, San Francisco, California, 94105, USA.
12   */
13  
14  package org.codehaus.mojo.javascript;
15  
16  import org.apache.maven.artifact.Artifact;
17  import org.apache.maven.artifact.DefaultArtifact;
18  import org.apache.maven.plugin.AbstractMojo;
19  import org.apache.maven.plugin.MojoExecutionException;
20  import org.apache.maven.plugin.MojoFailureException;
21  import org.apache.maven.project.MavenProject;
22  import org.apache.maven.surefire.shade.org.codehaus.plexus.util.FileUtils;
23  import org.codehaus.mojo.javascript.archive.JavascriptArtifactManager;
24  import org.codehaus.plexus.archiver.ArchiverException;
25  import org.codehaus.plexus.util.DirectoryScanner;
26  import org.codehaus.plexus.util.IOUtil;
27  import searls.jasmine.io.IOUtilsWrapper;
28  import searls.jasmine.runner.SpecRunnerTitaniumGenerator;
29  import org.antlr.stringtemplate.StringTemplate;
30  import org.antlr.stringtemplate.language.DefaultTemplateLexer;
31  
32  import java.io.*;
33  import java.util.*;
34  
35  /**
36   * This Mojo create the titanium project structure
37   * required to execute the jasmine tests.
38   *
39   * @goal prepare-titanium-jasmine-tests
40   * @phase test-compile
41   * @requiresDependencyResolution test
42   */
43  public class PrepareTitaniumJasmineTestMojo extends AbstractMojo {
44  
45      /** default includes pattern */
46      protected static final String[] DEFAULT_INCLUDES = { "**/*.js" };
47      private static final String JAVASCRIPT_TYPE = "js";
48  
49      /**
50  	 * JavaScript sources (typically vendor/lib dependencies) that need to be loaded
51  	 * before other sources (and specs) in a particular order, these are relative to the ${sourceDirectory}
52  	 * directory! Therefore, if jquery.js is in `${sourceDirectory}/vendor`, you would configure:
53  	 * <code>
54  	 *  	&lt;preloadSources&gt;
55  	 *			&lt;source&gt;vendor/z.js&lt;/source&gt;
56  	 *		&lt;/preloadSources&gt;
57  	 * </code>
58  	 * And z.js would load before all the other sources and specs.
59  	 *
60  	 * @parameter
61  	 */
62  	protected List<String> preloadSources;
63  
64      /**
65       * The folder for javascripts dependencies
66       *
67       * @parameter expression="${scripts}" default-value="lib"
68       */
69      protected String libsDirectory;
70  
71      /**
72       * The output folder for jasmine spec files.
73       * @parameter expression="${specs}" default-value="specs"
74       */
75      protected String specsDirectory;
76  
77      /**
78       * The location of the javascript test files
79       * @parameter default-value="${project.basedir}${file.separator}src${file.separator}test${file.separator}javascript" expression="${jsunitTestSourceDirectory}"
80       */
81      protected File jasmineTestSourceDirectory;
82  
83      /**
84       * <p>The platform for which the code should be packaged.</p>
85       * <p>Supported platforms are:</p>
86       * <dl>
87       *     <dt>android</dt>
88       *     <dd>Package for the android platform.</dd>
89       *     <dt>iphone</dt>
90       *     <dd>Package for the iPhone platform.</dd>
91       *     <dt>ipad</dt>
92       *     <dd>Package for the iPad platform.</dd>
93       *     <dt>universal</dt>
94       *     <dd>Package for iPhone and iPad.</dd>
95       * </dl>
96       *
97       * @parameter expression="${platform}"
98       * @required
99       */
100     protected String platform;
101 
102     /**
103      * Location of the source files.
104      *
105      * @parameter default-value="${basedir}/src/main/javascript"
106      */
107     protected File sourceDirectory;
108 
109     /**
110      * The output directory of the assembled js file.
111      *
112      * @parameter default-value="${project.build.outputDirectory}"
113      */
114     protected File outputDirectory;
115 
116     /**
117      * The output directory of the test files.
118      * @parameter default-value="${project.build.testOutputDirectory}"
119      */
120     protected File testOutputDirectory;
121 
122     /**
123 	 * @parameter default-value="${plugin.artifacts}"
124 	 */
125 	protected List<Artifact> pluginArtifacts;
126 
127     /**
128      * Set this to 'true' to bypass unit tests entirely. Its use is NOT
129      * RECOMMENDED, but quite convenient on occasion.
130      *
131      * @parameter expression="${maven.test.skip}"
132      */
133     protected boolean skipTests;
134 
135     /**
136      * Exclusion pattern.
137      * <p>Allow to specify which jasmine spec files should be excluded.</p>
138      *
139      * @parameter
140      */
141     protected String[] specExcludes;
142 
143     protected File getPlatformTestSourceDirectory() {
144         return new File(jasmineTestSourceDirectory, platform);
145     }
146 
147     protected File getPlatformTestOutputDirectory() {
148         return new File(testOutputDirectory, platform);
149     }
150 
151     protected File getTiProjectDirectory() {
152         return new File(outputDirectory, platform);
153     }
154 
155     protected File getTiProjectResourceDirectory() {
156         return new File(getTiProjectDirectory(), "Resources");
157     }
158 
159     /**
160      * Use the artifactId as folder
161      *
162      * @parameter
163      */
164     protected boolean useArtifactId;
165 
166     /**
167      * The maven project.
168      *
169      * @parameter expression="${project}"
170      * @required
171      * @readonly
172      */
173     private MavenProject project;
174 
175     /**
176      * @component
177      */
178     private JavascriptArtifactManager javascriptArtifactManager;
179 
180     public void execute() throws MojoExecutionException, MojoFailureException {
181         if(skipTests || !jasmineTestSourceDirectory.exists()) {
182             getLog().info("No Jasmine tests, skipping Jasmine tests preparation.");
183             return;
184         }
185 
186         getLog().info("Processing source folder: " + outputDirectory.getAbsolutePath());
187         getPlatformTestOutputDirectory().mkdirs();
188         File depsDirectory = new File(getPlatformTestOutputDirectory(), "Resources" + File.separator + libsDirectory);
189         depsDirectory.mkdirs();
190 
191         try
192         {
193             javascriptArtifactManager.unpack( project, DefaultArtifact.SCOPE_TEST, depsDirectory, useArtifactId );
194         }
195         catch ( ArchiverException e )
196         {
197             throw new MojoExecutionException( "Failed to unpack javascript dependencies", e );
198         }
199 
200 
201         // 1. Copy source files
202         List<String> sourceFiles = new ArrayList<String>();
203         try {
204             File appDestDirectory = new File(getPlatformTestOutputDirectory(), "Resources");
205             if (getTiProjectResourceDirectory().exists()) {
206                 appDestDirectory.mkdirs();
207                 FileUtils.copyDirectoryStructure(getTiProjectResourceDirectory(), appDestDirectory);
208 
209                 DirectoryScanner scanner = new DirectoryScanner();
210                 scanner.setBasedir(getTiProjectResourceDirectory());
211                 scanner.setIncludes(DEFAULT_INCLUDES);
212                 scanner.setExcludes(new String[] { "**/app.js" });
213                 scanner.addDefaultExcludes();
214                 scanner.scan();
215                 String[] foundFiles = scanner.getIncludedFiles();
216                 if (foundFiles != null) {
217                     for (String fFile : foundFiles) {
218                         sourceFiles.add(fFile);
219                     }
220                 }
221             } else {
222                 getLog().info("Titanium resources folder doesn't exist.");
223                 if (getTiProjectDirectory().exists()) {
224                     getLog().info("Trying to copy source script from: " + getTiProjectDirectory().getAbsolutePath());
225                     appDestDirectory.mkdirs();
226                     FileUtils.copyDirectoryStructure(getTiProjectDirectory(), appDestDirectory);
227 
228                     DirectoryScanner scanner = new DirectoryScanner();
229                     scanner.setBasedir(getTiProjectDirectory());
230                     scanner.setIncludes(DEFAULT_INCLUDES);
231                     scanner.setExcludes(new String[] { "**/app.js" });
232                     scanner.addDefaultExcludes();
233                     scanner.scan();
234                     String[] foundFiles = scanner.getIncludedFiles();
235                     if (foundFiles != null) {
236                         for (String fFile : foundFiles) {
237                             sourceFiles.add(fFile);
238                         }
239                     }
240                 } else if (outputDirectory.exists()) {
241                     getLog().info("Processing output directory: " + outputDirectory.getAbsolutePath());
242                     appDestDirectory.mkdirs();
243                     FileUtils.copyDirectoryStructure(outputDirectory, appDestDirectory);
244 
245                     DirectoryScanner scanner = new DirectoryScanner();
246                     scanner.setBasedir(outputDirectory);
247                     scanner.setIncludes(DEFAULT_INCLUDES);
248                     scanner.setExcludes(new String[] { "**/app.js" });
249                     scanner.addDefaultExcludes();
250                     scanner.scan();
251                     String[] foundFiles = scanner.getIncludedFiles();
252                     if (foundFiles != null) {
253                         for (String fFile : foundFiles) {
254                             sourceFiles.add(fFile);
255                         }
256                     }
257                 }
258             }
259         } catch (IOException ioe) {
260             getLog().error("Failed to copy titanium project files to test directory", ioe);
261             return;
262         }
263 
264         // 2. Assemble test scripts
265         copySpecFiles();
266 
267         // 3. generate app.js file
268         createTestAppJs(sourceFiles);
269 
270         // 4. Ensure tiapp.xml presence
271         try {
272             ensureTiApp();
273         } catch (Throwable t) {
274             throw new MojoExecutionException("Unable to create test application tiapp.xml file");
275         }
276     }
277 
278     protected void ensureTiApp() throws IOException {
279         File tiapp = new File(getPlatformTestOutputDirectory(), "tiapp.xml");
280         if (!tiapp.exists()) {
281             getLog().info("Generating custom tiapp.xml file.");
282             IOUtilsWrapper wrapper = new IOUtilsWrapper();
283             String content = wrapper.toString("/titanium/tiapp.xml");
284             StringTemplate template = new StringTemplate(content, DefaultTemplateLexer.class);
285             template.setAttribute("id", project.getGroupId() + "." + project.getArtifactId() + ".test");
286             template.setAttribute("name", project.getName() + " Test");
287             template.setAttribute("version", project.getVersion());
288             template.setAttribute("guid", UUID.randomUUID().toString());
289             if (project.getDescription() != null) {
290                 template.setAttribute("description", project.getDescription());
291             } else {
292                 template.setAttribute("description", "not specified");
293             }
294 
295             FileWriter writer = new FileWriter(tiapp);
296             try {
297                 IOUtil.copy(template.toString(), writer);
298             } finally {
299                 IOUtil.close(writer);
300             }
301         }
302     }
303 
304     protected void copySpecFiles() throws MojoExecutionException {
305         DirectoryScanner scanner = new DirectoryScanner();
306         scanner.setBasedir(jasmineTestSourceDirectory);
307         scanner.setIncludes(DEFAULT_INCLUDES);
308         scanner.setExcludes(specExcludes);
309         scanner.addDefaultExcludes();
310 
311         scanner.scan();
312         String[] files = scanner.getIncludedFiles();
313 
314         File testFolder = new File(getPlatformTestOutputDirectory(), "Resources" + File.separator + specsDirectory);
315         testFolder.mkdirs();
316 
317         try {
318             for (String file : files) {
319                 File srcFile = new File(jasmineTestSourceDirectory, file);
320                 File destFile = new File(testFolder, file);
321                 destFile.getParentFile().mkdirs();
322                 FileUtils.copyFile(srcFile, destFile);
323             }
324         } catch (IOException ioe) {
325             throw new MojoExecutionException("Error while creating jasmine test file script", ioe);
326         }
327     }
328 
329     protected void createTestAppJs(List<String> sourceFiles)
330     throws MojoExecutionException {
331         File appFile = new File(getPlatformTestOutputDirectory(), "Resources" + File.separatorChar + "app.js");
332         PrintWriter writer = null;
333 
334         try {
335             writer = new PrintWriter(appFile);
336             SpecRunnerTitaniumGenerator generator = new SpecRunnerTitaniumGenerator(preloadSources,
337                     sourceFiles,
338                     new File(getPlatformTestOutputDirectory(), "Resources"),
339                     libsDirectory,
340                     specsDirectory);
341             writer.write(generator.generate(SpecRunnerTitaniumGenerator.ReporterType.TITANIUM));
342         } catch (IOException ioe) {
343             throw new MojoExecutionException("Error while generating jasmine test application file", ioe);
344         }   finally {
345             IOUtil.close(writer);
346         }
347     }
348 }