View Javadoc

1   package org.apache.maven.surefire.junitcore.pc;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.junit.AfterClass;
23  import org.junit.Before;
24  import org.junit.BeforeClass;
25  import org.junit.Rule;
26  import org.junit.Test;
27  import org.junit.runner.JUnitCore;
28  import org.junit.runner.Result;
29  import org.junit.runner.RunWith;
30  import org.junit.runners.Suite;
31  
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Collection;
35  import java.util.Iterator;
36  import java.util.concurrent.ConcurrentLinkedQueue;
37  
38  import static org.junit.Assert.assertFalse;
39  import static org.junit.Assert.assertThat;
40  import static org.junit.Assert.assertTrue;
41  import static org.junit.Assert.fail;
42  import static org.hamcrest.core.AnyOf.anyOf;
43  import static org.hamcrest.core.Is.is;
44  import static org.hamcrest.core.IsNot.not;
45  import static org.apache.maven.surefire.junitcore.pc.RangeMatcher.between;
46  
47  /**
48   * @author Tibor Digana (tibor17)
49   * @since 2.16
50   */
51  public class ParallelComputerBuilderTest
52  {
53      private static volatile boolean beforeShutdown;
54  
55      private static volatile Runnable shutdownTask;
56  
57      @Rule
58      public final Stopwatch runtime = new Stopwatch();
59  
60      private static void testKeepBeforeAfter( ParallelComputerBuilder builder, Class<?>... classes )
61      {
62          JUnitCore core = new JUnitCore();
63          for ( int round = 0; round < 5; round++ )
64          {
65              NothingDoingTest1.methods.clear();
66              Result result = core.run( builder.buildComputer(), classes );
67              assertTrue( result.wasSuccessful() );
68              Iterator<String> methods = NothingDoingTest1.methods.iterator();
69              for ( Class<?> clazz : classes )
70              {
71                  String a = clazz.getName() + "#a()";
72                  String b = clazz.getName() + "#b()";
73                  assertThat( methods.next(), is( "init" ) );
74                  assertThat( methods.next(), anyOf( is( a ), is( b ) ) );
75                  assertThat( methods.next(), anyOf( is( a ), is( b ) ) );
76                  assertThat( methods.next(), is( "deinit" ) );
77              }
78          }
79      }
80  
81      @Before
82      public void beforeTest()
83      {
84          Class1.maxConcurrentMethods = 0;
85          Class1.concurrentMethods = 0;
86          shutdownTask = null;
87      }
88  
89      @Test
90      public void parallelMethodsReuseOneOrTwoThreads()
91      {
92          ParallelComputerBuilder parallelComputerBuilder = new ParallelComputerBuilder();
93          parallelComputerBuilder.useOnePool( 4 );
94  
95          // One thread because one suite: TestSuite, however the capacity is 5.
96          parallelComputerBuilder.parallelSuites( 5 );
97  
98          // Two threads because TestSuite has two classes, however the capacity is 5.
99          parallelComputerBuilder.parallelClasses( 5 );
100 
101         // One or two threads because one threads comes from '#useOnePool(4)'
102         // and next thread may be reused from finished class, however the capacity is 3.
103         parallelComputerBuilder.parallelMethods( 3 );
104 
105         assertFalse( parallelComputerBuilder.isOptimized() );
106 
107         ParallelComputerBuilder.PC computer = (ParallelComputerBuilder.PC) parallelComputerBuilder.buildComputer();
108         Result result = new JUnitCore().run( computer, TestSuite.class );
109         long timeSpent = runtime.stop();
110 
111         assertThat( computer.suites.size(), is( 1 ) );
112         assertThat( computer.classes.size(), is( 0 ) );
113         assertThat( computer.nestedClasses.size(), is( 2 ) );
114         assertThat( computer.nestedSuites.size(), is( 0 ) );
115         assertFalse( computer.splitPool );
116         assertThat( computer.poolCapacity, is( 4 ) );
117         assertTrue( result.wasSuccessful() );
118         if ( Class1.maxConcurrentMethods == 1 )
119         {
120             assertThat( timeSpent, between( 1950, 2250 ) );
121         }
122         else if ( Class1.maxConcurrentMethods == 2 )
123         {
124             assertThat( timeSpent, between( 1450, 1750 ) );
125         }
126         else
127         {
128             fail();
129         }
130     }
131 
132     @Test
133     public void suiteAndClassInOnePool()
134     {
135         ParallelComputerBuilder parallelComputerBuilder = new ParallelComputerBuilder();
136         parallelComputerBuilder.useOnePool( 5 );
137         parallelComputerBuilder.parallelSuites( 5 );
138         parallelComputerBuilder.parallelClasses( 5 );
139         parallelComputerBuilder.parallelMethods( 3 );
140         assertFalse( parallelComputerBuilder.isOptimized() );
141 
142         ParallelComputerBuilder.PC computer = (ParallelComputerBuilder.PC) parallelComputerBuilder.buildComputer();
143         Result result = new JUnitCore().run( computer, TestSuite.class, Class1.class );
144         long timeSpent = runtime.stop();
145 
146         assertThat( computer.suites.size(), is( 1 ) );
147         assertThat( computer.classes.size(), is( 1 ) );
148         assertThat( computer.nestedClasses.size(), is( 2 ) );
149         assertThat( computer.nestedSuites.size(), is( 0 ) );
150         assertFalse( computer.splitPool );
151         assertThat( computer.poolCapacity, is( 5 ) );
152         assertTrue( result.wasSuccessful() );
153         assertThat( Class1.maxConcurrentMethods, is( 2 ) );
154         assertThat( timeSpent, anyOf( between( 1450, 1750 ), between( 1950, 2250 ), between( 2450, 2750 ) ) );
155     }
156 
157     @Test
158     public void onePoolWithUnlimitedParallelMethods()
159     {
160         // see ParallelComputerBuilder Javadoc
161         ParallelComputerBuilder parallelComputerBuilder = new ParallelComputerBuilder();
162         parallelComputerBuilder.useOnePool( 8 );
163         parallelComputerBuilder.parallelSuites( 2 );
164         parallelComputerBuilder.parallelClasses( 4 );
165         parallelComputerBuilder.parallelMethods();
166         assertFalse( parallelComputerBuilder.isOptimized() );
167 
168         ParallelComputerBuilder.PC computer = (ParallelComputerBuilder.PC) parallelComputerBuilder.buildComputer();
169         Result result = new JUnitCore().run( computer, TestSuite.class, Class1.class );
170         long timeSpent = runtime.stop();
171 
172         assertThat( computer.suites.size(), is( 1 ) );
173         assertThat( computer.classes.size(), is( 1 ) );
174         assertThat( computer.nestedClasses.size(), is( 2 ) );
175         assertThat( computer.nestedSuites.size(), is( 0 ) );
176         assertFalse( computer.splitPool );
177         assertThat( computer.poolCapacity, is( 8 ) );
178         assertTrue( result.wasSuccessful() );
179         assertThat( Class1.maxConcurrentMethods, is( 4 ) );
180         assertThat( timeSpent, between( 950, 1250 ) );
181     }
182 
183     @Test
184     public void underflowParallelism()
185     {
186         ParallelComputerBuilder parallelComputerBuilder = new ParallelComputerBuilder();
187         parallelComputerBuilder.useOnePool( 3 );
188 
189         // One thread because one suite: TestSuite.
190         parallelComputerBuilder.parallelSuites( 5 );
191 
192         // One thread because of the limitation which is bottleneck.
193         parallelComputerBuilder.parallelClasses( 1 );
194 
195         // One thread remains from '#useOnePool(3)'.
196         parallelComputerBuilder.parallelMethods( 3 );
197 
198         assertFalse( parallelComputerBuilder.isOptimized() );
199 
200         ParallelComputerBuilder.PC computer = (ParallelComputerBuilder.PC) parallelComputerBuilder.buildComputer();
201         Result result = new JUnitCore().run( computer, TestSuite.class );
202         long timeSpent = runtime.stop();
203 
204         assertThat( computer.suites.size(), is( 1 ) );
205         assertThat( computer.classes.size(), is( 0 ) );
206         assertThat( computer.nestedClasses.size(), is( 2 ) );
207         assertThat( computer.nestedSuites.size(), is( 0 ) );
208         assertFalse( computer.splitPool );
209         assertThat( computer.poolCapacity, is( 3 ) );
210         assertTrue( result.wasSuccessful() );
211         assertThat( Class1.maxConcurrentMethods, is( 1 ) );
212         assertThat( timeSpent, between( 1950, 2250 ) );
213     }
214 
215     @Test
216     public void separatePoolsWithSuite()
217     {
218         ParallelComputerBuilder parallelComputerBuilder = new ParallelComputerBuilder();
219         parallelComputerBuilder.parallelSuites( 5 );
220         parallelComputerBuilder.parallelClasses( 5 );
221         parallelComputerBuilder.parallelMethods( 3 );
222         assertFalse( parallelComputerBuilder.isOptimized() );
223 
224         ParallelComputerBuilder.PC computer = (ParallelComputerBuilder.PC) parallelComputerBuilder.buildComputer();
225         Result result = new JUnitCore().run( computer, TestSuite.class );
226         long timeSpent = runtime.stop();
227 
228         assertThat( computer.suites.size(), is( 1 ) );
229         assertThat( computer.classes.size(), is( 0 ) );
230         assertThat( computer.nestedClasses.size(), is( 2 ) );
231         assertThat( computer.nestedSuites.size(), is( 0 ) );
232         assertTrue( computer.splitPool );
233         assertThat( computer.poolCapacity, is( ParallelComputerBuilder.TOTAL_POOL_SIZE_UNDEFINED ) );
234         assertTrue( result.wasSuccessful() );
235         assertThat( Class1.maxConcurrentMethods, is( 3 ) );
236         assertThat( timeSpent, between( 950, 1250 ) );
237     }
238 
239     @Test
240     public void separatePoolsWithSuiteAndClass()
241     {
242         ParallelComputerBuilder parallelComputerBuilder = new ParallelComputerBuilder();
243         parallelComputerBuilder.parallelSuites( 5 );
244         parallelComputerBuilder.parallelClasses( 5 );
245         parallelComputerBuilder.parallelMethods( 3 );
246         assertFalse( parallelComputerBuilder.isOptimized() );
247 
248         // 6 methods altogether.
249         // 2 groups with 3 threads.
250         // Each group takes 0.5s.
251         ParallelComputerBuilder.PC computer = (ParallelComputerBuilder.PC) parallelComputerBuilder.buildComputer();
252         Result result = new JUnitCore().run( computer, TestSuite.class, Class1.class );
253         long timeSpent = runtime.stop();
254 
255         assertThat( computer.suites.size(), is( 1 ) );
256         assertThat( computer.classes.size(), is( 1 ) );
257         assertThat( computer.nestedClasses.size(), is( 2 ) );
258         assertThat( computer.nestedSuites.size(), is( 0 ) );
259         assertTrue( computer.splitPool );
260         assertThat( computer.poolCapacity, is( ParallelComputerBuilder.TOTAL_POOL_SIZE_UNDEFINED ) );
261         assertTrue( result.wasSuccessful() );
262         assertThat( Class1.maxConcurrentMethods, is( 3 ) );
263         assertThat( timeSpent, between( 950, 1250 ) );
264     }
265 
266     @Test
267     public void separatePoolsWithSuiteAndSequentialClasses()
268     {
269         ParallelComputerBuilder parallelComputerBuilder = new ParallelComputerBuilder();
270         parallelComputerBuilder.parallelSuites( 5 );
271         parallelComputerBuilder.parallelClasses( 1 );
272         parallelComputerBuilder.parallelMethods( 3 );
273         assertFalse( parallelComputerBuilder.isOptimized() );
274 
275         ParallelComputerBuilder.PC computer = (ParallelComputerBuilder.PC) parallelComputerBuilder.buildComputer();
276         Result result = new JUnitCore().run( computer, TestSuite.class, Class1.class );
277         long timeSpent = runtime.stop();
278 
279         assertThat( computer.suites.size(), is( 1 ) );
280         assertThat( computer.classes.size(), is( 1 ) );
281         assertThat( computer.nestedClasses.size(), is( 2 ) );
282         assertThat( computer.nestedSuites.size(), is( 0 ) );
283         assertTrue( computer.splitPool );
284         assertThat( computer.poolCapacity, is( ParallelComputerBuilder.TOTAL_POOL_SIZE_UNDEFINED ) );
285         assertTrue( result.wasSuccessful() );
286         assertThat( Class1.maxConcurrentMethods, is( 2 ) );
287         assertThat( timeSpent, between( 1450, 1750 ) );
288     }
289 
290     @Test( timeout = 2000 )
291     public void shutdown()
292     {
293         Result result = new ShutdownTest().run( false );
294         long timeSpent = runtime.stop();
295         assertTrue( result.wasSuccessful() );
296         assertTrue( beforeShutdown );
297         assertThat( timeSpent, between( 450, 1250 ) );
298     }
299 
300     @Test( timeout = 2000 )
301     public void shutdownWithInterrupt()
302     {
303         new ShutdownTest().run( true );
304         long timeSpent = runtime.stop();
305         assertTrue( beforeShutdown );
306         assertThat( timeSpent, between( 450, 1250 ) );
307     }
308 
309     @Test
310     public void nothingParallel()
311     {
312         JUnitCore core = new JUnitCore();
313         ParallelComputerBuilder builder = new ParallelComputerBuilder();
314         assertFalse( builder.isOptimized() );
315 
316         Result result = core.run( builder.buildComputer(), NothingDoingTest1.class, NothingDoingTest2.class );
317         assertTrue( result.wasSuccessful() );
318 
319         result = core.run( builder.buildComputer(), NothingDoingTest1.class, NothingDoingSuite.class );
320         assertTrue( result.wasSuccessful() );
321 
322         builder.useOnePool( 1 );
323         assertFalse( builder.isOptimized() );
324         result = core.run( builder.buildComputer(), NothingDoingTest1.class, NothingDoingTest2.class );
325         assertTrue( result.wasSuccessful() );
326 
327         builder.useOnePool( 1 );
328         assertFalse( builder.isOptimized() );
329         result = core.run( builder.buildComputer(), NothingDoingTest1.class, NothingDoingSuite.class );
330         assertTrue( result.wasSuccessful() );
331 
332         builder.useOnePool( 2 );
333         assertFalse( builder.isOptimized() );
334         result = core.run( builder.buildComputer(), NothingDoingTest1.class, NothingDoingSuite.class );
335         assertTrue( result.wasSuccessful() );
336 
337         Class<?>[] classes = { NothingDoingTest1.class, NothingDoingSuite.class };
338 
339         builder.useOnePool( 2 ).parallelSuites( 1 ).parallelClasses( 1 );
340         assertFalse( builder.isOptimized() );
341         result = core.run( builder.buildComputer(), classes );
342         assertTrue( result.wasSuccessful() );
343 
344         builder.useOnePool( 2 ).parallelSuites( 1 ).parallelClasses();
345         assertFalse( builder.isOptimized() );
346         result = core.run( builder.buildComputer(), classes );
347         assertTrue( result.wasSuccessful() );
348 
349         classes = new Class<?>[]{ NothingDoingSuite.class, NothingDoingSuite.class, NothingDoingTest1.class,
350             NothingDoingTest2.class, NothingDoingTest3.class };
351 
352         builder.useOnePool( 2 ).parallelSuites( 1 ).parallelClasses( 1 );
353         assertFalse( builder.isOptimized() );
354         result = core.run( builder.buildComputer(), classes );
355         assertTrue( result.wasSuccessful() );
356 
357         builder.useOnePool( 2 ).parallelSuites( 1 ).parallelClasses();
358         assertFalse( builder.isOptimized() );
359         result = core.run( builder.buildComputer(), classes );
360         assertTrue( result.wasSuccessful() );
361     }
362 
363     @Test
364     public void keepBeforeAfterOneClass()
365     {
366         ParallelComputerBuilder builder = new ParallelComputerBuilder();
367         builder.parallelMethods();
368         assertFalse( builder.isOptimized() );
369         testKeepBeforeAfter( builder, NothingDoingTest1.class );
370     }
371 
372     @Test
373     public void keepBeforeAfterTwoClasses()
374     {
375         ParallelComputerBuilder builder = new ParallelComputerBuilder();
376         builder.useOnePool( 5 ).parallelClasses( 1 ).parallelMethods( 2 );
377         assertFalse( builder.isOptimized() );
378         testKeepBeforeAfter( builder, NothingDoingTest1.class, NothingDoingTest2.class );
379     }
380 
381     @Test
382     public void keepBeforeAfterTwoParallelClasses()
383     {
384         ParallelComputerBuilder builder = new ParallelComputerBuilder();
385         builder.useOnePool( 8 ).parallelClasses( 2 ).parallelMethods( 2 );
386         assertFalse( builder.isOptimized() );
387         JUnitCore core = new JUnitCore();
388         NothingDoingTest1.methods.clear();
389         Class<?>[] classes = { NothingDoingTest1.class, NothingDoingTest2.class, NothingDoingTest3.class };
390         Result result = core.run( builder.buildComputer(), classes );
391         assertTrue( result.wasSuccessful() );
392         ArrayList<String> methods = new ArrayList<String>( NothingDoingTest1.methods );
393         assertThat( methods.size(), is( 12 ) );
394         assertThat( methods.subList( 9, 12 ), is( not( Arrays.asList( "deinit", "deinit", "deinit" ) ) ) );
395     }
396 
397     private static class ShutdownTest
398     {
399         Result run( final boolean useInterrupt )
400         {
401             ParallelComputerBuilder parallelComputerBuilder = new ParallelComputerBuilder().useOnePool( 8 );
402             parallelComputerBuilder.parallelSuites( 2 );
403             parallelComputerBuilder.parallelClasses( 3 );
404             parallelComputerBuilder.parallelMethods( 3 );
405             assertFalse( parallelComputerBuilder.isOptimized() );
406 
407             final ParallelComputerBuilder.PC computer =
408                 (ParallelComputerBuilder.PC) parallelComputerBuilder.buildComputer();
409             shutdownTask = new Runnable()
410             {
411                 public void run()
412                 {
413                     Collection<org.junit.runner.Description> startedTests = computer.shutdown( useInterrupt );
414                     assertThat( startedTests.size(), is( not( 0 ) ) );
415                 }
416             };
417             return new JUnitCore().run( computer, TestSuite.class, Class2.class, Class3.class );
418         }
419     }
420 
421     public static class Class1
422     {
423         static volatile int concurrentMethods = 0;
424 
425         static volatile int maxConcurrentMethods = 0;
426 
427         @Test
428         public void test1()
429             throws InterruptedException
430         {
431             synchronized ( Class1.class )
432             {
433                 ++concurrentMethods;
434                 Class1.class.wait( 500 );
435                 maxConcurrentMethods = Math.max( maxConcurrentMethods, concurrentMethods-- );
436             }
437         }
438 
439         @Test
440         public void test2()
441             throws InterruptedException
442         {
443             test1();
444             Runnable shutdownTask = ParallelComputerBuilderTest.shutdownTask;
445             if ( shutdownTask != null )
446             {
447                 beforeShutdown = true;
448                 shutdownTask.run();
449             }
450         }
451     }
452 
453     public static class Class2
454         extends Class1
455     {
456     }
457 
458     public static class Class3
459         extends Class1
460     {
461     }
462 
463     public static class NothingDoingTest1
464     {
465         static final Collection<String> methods = new ConcurrentLinkedQueue<String>();
466 
467         @BeforeClass
468         public static void init()
469         {
470             methods.add( "init" );
471         }
472 
473         @AfterClass
474         public static void deinit()
475         {
476             methods.add( "deinit" );
477         }
478 
479         @Test
480         public void a()
481             throws InterruptedException
482         {
483             Thread.sleep( 5 );
484             methods.add( getClass().getName() + "#a()" );
485         }
486 
487         @Test
488         public void b()
489             throws InterruptedException
490         {
491             Thread.sleep( 5 );
492             methods.add( getClass().getName() + "#b()" );
493         }
494     }
495 
496     public static class NothingDoingTest2
497         extends NothingDoingTest1
498     {
499     }
500 
501     public static class NothingDoingTest3
502         extends NothingDoingTest1
503     {
504     }
505 
506     @RunWith( Suite.class )
507     @Suite.SuiteClasses( { NothingDoingTest1.class, NothingDoingTest2.class } )
508     public static class NothingDoingSuite
509     {
510     }
511 
512     @RunWith( Suite.class )
513     @Suite.SuiteClasses( { Class2.class, Class1.class } )
514     public class TestSuite
515     {
516     }
517 }