1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugin.eclipse;
20  
21  import java.io.BufferedReader;
22  import java.io.File;
23  import java.io.FileFilter;
24  import java.io.FileInputStream;
25  import java.io.IOException;
26  import java.io.InputStreamReader;
27  import java.net.MalformedURLException;
28  import java.util.ArrayList;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Properties;
32  
33  import junit.framework.AssertionFailedError;
34  
35  import org.apache.maven.plugin.eclipse.writers.workspace.EclipseWorkspaceWriter;
36  import org.apache.maven.plugin.ide.IdeUtils;
37  import org.apache.maven.project.MavenProject;
38  import org.apache.maven.shared.invoker.InvocationRequest;
39  import org.apache.maven.shared.invoker.InvocationResult;
40  import org.apache.maven.shared.test.plugin.BuildTool;
41  import org.apache.maven.shared.test.plugin.PluginTestTool;
42  import org.apache.maven.shared.test.plugin.ProjectTool;
43  import org.apache.maven.shared.test.plugin.TestToolsException;
44  import org.codehaus.classworlds.ClassRealm;
45  import org.codehaus.plexus.PlexusContainer;
46  import org.codehaus.plexus.PlexusTestCase;
47  import org.codehaus.plexus.util.IOUtil;
48  import org.codehaus.plexus.util.StringUtils;
49  
50  /**
51   * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
52   * @author <a href="mailto:fgiust@apache.org">Fabrizio Giustina</a>
53   * @version $Id: AbstractEclipsePluginTestCase.java 640523 2008-03-24 19:00:52Z bentmann $
54   */
55  public abstract class AbstractEclipsePluginTestCase
56      extends PlexusTestCase
57  {
58  
59      private BuildTool buildTool;
60  
61      private ProjectTool projectTool;
62  
63      /**
64       * Test repository directory.
65       */
66      protected static File localRepositoryDirectory = getTestFile( "target/test-classes/m2repo" );
67  
68      /**
69       * Pom File
70       */
71      protected static File PomFile = new File( getBasedir(), "pom.xml" );
72  
73      /**
74       * Group-Id for running test builds.
75       */
76      protected static final String GROUP_ID = "org.apache.maven.plugins";
77  
78      /**
79       * Artifact-Id for running test builds.
80       */
81      protected static final String ARTIFACT_ID = "maven-eclipse-plugin";
82  
83      /**
84       * Version under which the plugin was installed to the test-time local repository for running test builds.
85       */
86      protected static final String VERSION = "test";
87  
88      private static final String BUILD_OUTPUT_DIRECTORY = "target/surefire-reports/build-output";
89  
90      private static boolean installed = false;
91  
92      /**
93       * @see org.codehaus.plexus.PlexusTestCase#setUp()
94       */
95      protected void setUp()
96          throws Exception
97      {
98          if ( !installed )
99          {
100             System.out.println( "*** Running test builds; output will be directed to: " + BUILD_OUTPUT_DIRECTORY + "\n" );
101         }
102 
103         super.setUp();
104 
105         buildTool = (BuildTool) lookup( BuildTool.ROLE, "default" );
106 
107         projectTool = (ProjectTool) lookup( ProjectTool.ROLE, "default" );
108 
109         String mavenHome = System.getProperty( "maven.home" );
110 
111         // maven.home is set by surefire when the test is run with maven, but better make the test run in IDEs without
112         // the need of additional properties
113         if ( mavenHome == null )
114         {
115             String path = System.getProperty( "java.library.path" );
116             String[] paths = StringUtils.split( path, System.getProperty( "path.separator" ) );
117             for ( int j = 0; j < paths.length; j++ )
118             {
119                 String pt = paths[j];
120                 if ( new File( pt, "mvn" ).exists() )
121                 {
122                     System.setProperty( "maven.home", new File( pt ).getAbsoluteFile().getParent() );
123                     break;
124                 }
125 
126             }
127         }
128 
129         System.setProperty( "MAVEN_TERMINATE_CMD", "on" );
130 
131         synchronized ( AbstractEclipsePluginTestCase.class )
132         {
133             if ( !installed )
134             {
135                 PluginTestTool pluginTestTool = (PluginTestTool) lookup( PluginTestTool.ROLE, "default" );
136 
137                 localRepositoryDirectory =
138                     pluginTestTool.preparePluginForUnitTestingWithMavenBuilds( PomFile, "test",
139                                                                                localRepositoryDirectory );
140 
141                 System.out.println( "*** Installed test-version of the Eclipse plugin to: " + localRepositoryDirectory +
142                     "\n" );
143 
144                 installed = true;
145             }
146         }
147 
148     }
149 
150     /**
151      * @see org.codehaus.plexus.PlexusTestCase#tearDown()
152      */
153     protected void tearDown()
154         throws Exception
155     {
156         super.tearDown();
157 
158         List containers = new ArrayList();
159 
160         containers.add( getContainer() );
161 
162         for ( Iterator iter = containers.iterator(); iter.hasNext(); )
163         {
164             PlexusContainer container = (PlexusContainer) iter.next();
165 
166             if ( container != null )
167             {
168                 container.dispose();
169 
170                 ClassRealm realm = container.getContainerRealm();
171 
172                 if ( realm != null )
173                 {
174                     realm.getWorld().disposeRealm( realm.getId() );
175                 }
176             }
177         }
178     }
179 
180     /**
181      * Execute the eclipse:eclipse goal on a test project and verify generated files.
182      * 
183      * @param projectName project directory
184      * @throws Exception any exception generated during test
185      */
186     protected void testProject( String projectName )
187         throws Exception
188     {
189         testProject( projectName, new Properties(), "clean", "eclipse" );
190     }
191 
192     /**
193      * Execute the eclipse:eclipse goal on a test project and verify generated files.
194      * 
195      * @param projectName project directory
196      * @param properties additional properties
197      * @throws Exception any exception generated during test
198      * @deprecated Use {@link #testProject(String,Properties,String,String)} instead
199      */
200     protected void testProject( String projectName, Properties properties )
201         throws Exception
202     {
203         testProject( projectName, properties, "clean", "eclipse" );
204     }
205 
206     /**
207      * Execute the eclipse:eclipse goal on a test project and verify generated files.
208      * 
209      * @param projectName project directory
210      * @param properties additional properties
211      * @param cleanGoal TODO
212      * @param genGoal TODO
213      * @throws Exception any exception generated during test
214      */
215     protected void testProject( String projectName, Properties properties, String cleanGoal, String genGoal )
216         throws Exception
217     {
218         File basedir = getTestFile( "target/test-classes/projects/" + projectName );
219 
220         File pom = new File( basedir, "pom.xml" );
221 
222         String pluginSpec = getPluginCLISpecification();
223 
224         List goals = new ArrayList();
225 
226         goals.add( pluginSpec + cleanGoal );
227         goals.add( pluginSpec + genGoal );
228 
229         executeMaven( pom, properties, goals );
230 
231         MavenProject project = readProject( pom );
232 
233         String outputDirPath =
234             IdeUtils.getPluginSetting( project, "org.apache.maven.plugins:maven-eclipse-plugin", "outputDir", null );
235         File outputDir;
236         File projectOutputDir = basedir;
237 
238         if ( outputDirPath == null )
239         {
240             outputDir = basedir;
241         }
242         else
243         {
244             outputDir = new File( basedir, outputDirPath );
245             outputDir.mkdirs();
246             projectOutputDir = new File( outputDir, project.getArtifactId() );
247         }
248 
249         compareDirectoryContent( basedir, projectOutputDir, "" );
250         compareDirectoryContent( basedir, projectOutputDir, ".settings/" );
251         compareDirectoryContent( basedir, projectOutputDir, ".externalToolBuilders/" );
252         compareDirectoryContent( basedir, projectOutputDir, "META-INF/" );
253 
254     }
255 
256     /**
257      * Execute the eclipse:configure-workspace goal on a test project and verify generated files.
258      * 
259      * @param projectName project directory
260      * @throws Exception any exception generated during test
261      */
262     protected void testWorkspace( String projectName )
263         throws Exception
264     {
265         testWorkspace( projectName, new Properties(), "configure-workspace" );
266     }
267 
268     /**
269      * Execute the eclipse:configure-workspace goal on a test project and verify generated files.
270      * 
271      * @param projectName project directory
272      * @throws Exception any exception generated during test
273      */
274     protected void testWorkspace( String projectName, String goal )
275         throws Exception
276     {
277         testWorkspace( projectName, new Properties(), goal );
278     }
279 
280     /**
281      * Execute the eclipse:configure-workspace goal on a test project and verify generated files.
282      * 
283      * @param projectName project directory
284      * @param properties additional properties
285      * @param cleanGoal TODO
286      * @param genGoal TODO
287      * @throws Exception any exception generated during test
288      */
289     protected void testWorkspace( String projectName, Properties properties, String genGoal )
290         throws Exception
291     {
292         File basedir = getOutputDirectory( projectName );
293 
294         File pom = new File( basedir, "pom.xml" );
295 
296         String pluginSpec = getPluginCLISpecification();
297 
298         List goals = new ArrayList();
299 
300         goals.add( pluginSpec + genGoal );
301 
302         executeMaven( pom, properties, goals );
303 
304         MavenProject project = readProject( pom );
305 
306         String outputDirPath =
307             IdeUtils.getPluginSetting( project, "org.apache.maven.plugins:maven-eclipse-plugin", "outputDir", null );
308         File outputDir;
309         File projectOutputDir = basedir;
310 
311         if ( outputDirPath == null )
312         {
313             outputDir = basedir;
314         }
315         else
316         {
317             outputDir = new File( basedir, outputDirPath );
318             outputDir.mkdirs();
319             projectOutputDir = new File( outputDir, project.getArtifactId() );
320         }
321 
322         compareDirectoryContent( basedir, projectOutputDir, EclipseWorkspaceWriter.ECLIPSE_CORE_RUNTIME_SETTINGS_DIR +
323             "/" );
324 
325     }
326 
327     protected File getOutputDirectory( String projectName )
328     {
329         return getTestFile( "target/test-classes/projects/" + projectName );
330     }
331 
332     protected File getTestWorkspaceWorkDirectory( String projectName )
333     {
334         return new File( this.getOutputDirectory( projectName ), ".metadata" );
335     }
336 
337     protected void executeMaven( File pom, Properties properties, List goals )
338         throws TestToolsException, ExecutionFailedException
339     {
340         executeMaven( pom, properties, goals, true );
341     }
342 
343     protected void executeMaven( File pom, Properties properties, List goals, boolean switchLocalRepo )
344         throws TestToolsException, ExecutionFailedException
345     {
346         new File( BUILD_OUTPUT_DIRECTORY ).mkdirs();
347 
348         NullPointerException npe = new NullPointerException();
349         StackTraceElement[] trace = npe.getStackTrace();
350 
351         File buildLog = null;
352 
353         for ( int i = 0; i < trace.length; i++ )
354         {
355             StackTraceElement element = trace[i];
356 
357             String methodName = element.getMethodName();
358 
359             if ( methodName.startsWith( "test" ) && !methodName.equals( "testProject" ) )
360             {
361                 String classname = element.getClassName();
362 
363                 buildLog = new File( BUILD_OUTPUT_DIRECTORY, classname + "_" + element.getMethodName() + ".build.log" );
364 
365                 break;
366             }
367         }
368 
369         if ( buildLog == null )
370         {
371             buildLog = new File( BUILD_OUTPUT_DIRECTORY, "unknown.build.log" );
372         }
373 
374         InvocationRequest request = buildTool.createBasicInvocationRequest( pom, properties, goals, buildLog );
375         request.setUpdateSnapshots( false );
376         request.setShowErrors( true );
377 
378         request.setDebug( true );
379 
380         if ( switchLocalRepo )
381         {
382             request.setLocalRepositoryDirectory( localRepositoryDirectory );
383         }
384 
385         InvocationResult result = buildTool.executeMaven( request );
386 
387         if ( result.getExitCode() != 0 )
388         {
389             String buildLogUrl = buildLog.getAbsolutePath();
390 
391             try
392             {
393                 buildLogUrl = buildLog.toURL().toExternalForm();
394             }
395             catch ( MalformedURLException e )
396             {
397             }
398 
399             throw new ExecutionFailedException( "Failed to execute build.\nPOM: " + pom + "\nGoals: " +
400                 StringUtils.join( goals.iterator(), ", " ) + "\nExit Code: " + result.getExitCode() + "\nError: " +
401                 result.getExecutionException() + "\nBuild Log: " + buildLogUrl + "\n", result );
402         }
403     }
404 
405     protected MavenProject readProject( File pom )
406         throws TestToolsException
407     {
408         return projectTool.readProject( pom, localRepositoryDirectory );
409     }
410 
411     protected String getPluginCLISpecification()
412     {
413         String pluginSpec = GROUP_ID + ":" + ARTIFACT_ID + ":";
414 
415         // String pluginVersion = System.getProperty( "pluginVersion" );
416         //        
417         // if ( pluginVersion != null )
418         // {
419         // pluginSpec += pluginVersion + ":";
420         // }
421         //
422         // System.out.println( "\n\nUsing Eclipse plugin version: " + pluginVersion + "\n\n" );
423 
424         // try using the test-version installed during setUp()
425         pluginSpec += VERSION + ":";
426 
427         return pluginSpec;
428     }
429 
430     /**
431      * @param basedir
432      * @param projectOutputDir
433      * @throws IOException
434      */
435     protected void compareDirectoryContent( File basedir, File projectOutputDir, String additionalDir )
436         throws IOException
437     {
438         File expectedConfigDir = new File( basedir, "expected/" + additionalDir );
439 
440         if ( expectedConfigDir.isDirectory() )
441         {
442             File[] files = expectedConfigDir.listFiles( new FileFilter()
443             {
444                 public boolean accept( File file )
445                 {
446                     return !file.isDirectory();
447                 }
448             } );
449 
450             for ( int j = 0; j < files.length; j++ )
451             {
452                 File expectedFile = files[j];
453                 File actualFile =
454                     new File( projectOutputDir, additionalDir + expectedFile.getName() ).getCanonicalFile();
455 
456                 if ( !actualFile.exists() )
457                 {
458                     throw new AssertionFailedError( "Expected file not found: " + actualFile.getAbsolutePath() );
459                 }
460 
461                 assertFileEquals( localRepositoryDirectory.getCanonicalPath(), expectedFile, actualFile );
462 
463             }
464         }
465     }
466 
467     protected void assertFileEquals( String mavenRepo, File expectedFile, File actualFile )
468         throws IOException
469     {
470         List expectedLines = getLines( mavenRepo, expectedFile );
471 
472         if ( !actualFile.exists() )
473         {
474             throw new AssertionFailedError( "Expected file not found: " + actualFile.getAbsolutePath() );
475         }
476 
477         List actualLines = getLines( mavenRepo, actualFile );
478         String filename = actualFile.getName();
479 
480         String basedir = new File( getBasedir() ).getCanonicalPath().replace( '\\', '/' );
481 
482         for ( int i = 0; i < expectedLines.size(); i++ )
483         {
484             String expected = expectedLines.get( i ).toString();
485 
486             // replace some vars in the expected line, to account
487             // for absolute paths that are different on each installation.
488             expected = StringUtils.replace( expected, "${basedir}", basedir );
489             expected =
490                 StringUtils.replace( expected, "${M2_REPO}", localRepositoryDirectory.getCanonicalPath().replace( '\\',
491                                                                                                                   '/' ) );
492 
493             if ( actualLines.size() <= i )
494             {
495                 fail( "Too few lines in the actual file. Was " + actualLines.size() + ", expected: " +
496                     expectedLines.size() );
497             }
498 
499             String actual = actualLines.get( i ).toString();
500 
501             if ( expected.startsWith( "#" ) && actual.startsWith( "#" ) )
502             {
503                 // ignore comments, for settings file
504                 continue;
505             }
506 
507             /*
508              * NOTE: This is to account for the unfortunate fact that "file:" URIs differ between Windows and Unix. On a
509              * Windows box, the path "C:\dir" is mapped to "file:/C:/dir". On a Unix box, the path "/home/dir" is mapped
510              * to "file:/home/dir". So, in the first case the slash after "file:" is not part of the corresponding
511              * filesystem path while in the later case it is. This discrepancy makes verifying the javadoc attachments
512              * in ".classpath" a little tricky.
513              */
514             if ( !expected.equals( actual ) )
515             {
516                 // convert "file:C:/dir" to "file:/C:/dir"
517                 expected = expected.replaceAll( "file:([a-zA-Z])", "file:/$1" );
518             }
519 
520             /*
521              * NOTE: This is another hack to compensate for some metadata files that contain a complete XML file as the
522              * value for a key like "org.eclipse.jdt.ui.formatterprofiles" from "org.eclipse.jdt.ui.prefs". Line
523              * terminators in this value are platform-dependent.
524              */
525             if ( !expected.equals( actual ) && expectedFile.getName().endsWith( ".prefs" ) )
526             {
527                 // normalize line terminators
528                 expected = expected.replaceAll( "(\\\\r\\\\n)|(\\\\n)|(\\\\r)", "\\n" );
529                 actual = actual.replaceAll( "(\\\\r\\\\n)|(\\\\n)|(\\\\r)", "\\n" );
530             }
531 
532             assertEquals( "Checking " + filename + ", line #" + ( i + 1 ), expected, actual );
533         }
534 
535         assertTrue( "Unequal number of lines.", expectedLines.size() == actualLines.size() );
536     }
537 
538     protected void assertContains( String message, String full, String substring )
539     {
540         if ( full == null || full.indexOf( substring ) == -1 )
541         {
542             StringBuffer buf = new StringBuffer();
543             if ( message != null )
544             {
545                 buf.append( message );
546             }
547             buf.append( ". " );
548             buf.append( "Expected \"" );
549             buf.append( substring );
550             buf.append( "\" not found" );
551             fail( buf.toString() );
552         }
553     }
554 
555     protected void assertDoesNotContain( String message, String full, String substring )
556     {
557         if ( full == null || full.indexOf( substring ) != -1 )
558         {
559             StringBuffer buf = new StringBuffer();
560             if ( message != null )
561             {
562                 buf.append( message );
563             }
564             buf.append( ". " );
565             buf.append( "Unexpected \"" );
566             buf.append( substring );
567             buf.append( "\" found" );
568             fail( buf.toString() );
569         }
570     }
571 
572     private List getLines( String mavenRepo, File file )
573         throws IOException
574     {
575         List lines = new ArrayList();
576 
577         BufferedReader reader = new BufferedReader( new InputStreamReader( new FileInputStream( file ), "UTF-8" ) );
578 
579         String line;
580 
581         while ( ( line = reader.readLine() ) != null )
582         {
583             lines.add( line );
584         }
585 
586         IOUtil.close( reader );
587 
588         return lines;
589     }
590 
591 }