View Javadoc
1   package org.apache.maven.surefire.booter;
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.apache.maven.plugin.surefire.log.api.ConsoleLogger;
23  import org.apache.maven.plugin.surefire.log.api.NullConsoleLogger;
24  import org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
25  import org.apache.maven.surefire.api.booter.Shutdown;
26  import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
27  import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
28  import org.apache.maven.surefire.api.testset.TestSetFailedException;
29  import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
30  import org.junit.After;
31  import org.junit.Before;
32  import org.junit.Test;
33  import org.junit.runner.RunWith;
34  
35  import java.io.ByteArrayOutputStream;
36  import java.io.IOException;
37  import java.io.InputStream;
38  import java.io.PrintStream;
39  import java.util.Iterator;
40  import java.util.NoSuchElementException;
41  import java.util.concurrent.BlockingQueue;
42  import java.util.concurrent.CountDownLatch;
43  import java.util.concurrent.ExecutionException;
44  import java.util.concurrent.FutureTask;
45  import java.util.concurrent.LinkedBlockingQueue;
46  import java.util.concurrent.TimeUnit;
47  
48  import static org.apache.maven.surefire.api.util.internal.Channels.newBufferedChannel;
49  import static org.apache.maven.surefire.api.util.internal.Channels.newChannel;
50  import static org.fest.assertions.Assertions.assertThat;
51  import static org.hamcrest.MatcherAssert.assertThat;
52  import static org.hamcrest.Matchers.is;
53  import static org.junit.Assert.assertFalse;
54  import static org.junit.Assert.assertTrue;
55  import static org.junit.Assert.fail;
56  
57  /**
58   * Testing singleton {@code MasterProcessReader} in multiple class loaders.
59   *
60   * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
61   * @since 2.19
62   */
63  @RunWith( NewClassLoaderRunner.class )
64  public class CommandReaderTest
65  {
66      private static final long DELAY = 200L;
67      private static final long TEST_TIMEOUT = 15_000L;
68  
69      private final BlockingQueue<Byte> blockingStream = new LinkedBlockingQueue<>();
70      private CommandReader reader;
71  
72      static class A
73      {
74      }
75  
76      static class B
77      {
78      }
79  
80      static class C
81      {
82      }
83  
84      static class D
85      {
86      }
87  
88      @Before
89      public void init()
90      {
91          //noinspection ResultOfMethodCallIgnored
92          Thread.interrupted();
93          InputStream realInputStream = new SystemInputStream();
94          addTestToPipeline( getClass().getName() );
95          ConsoleLogger logger = new NullConsoleLogger();
96          MasterProcessChannelDecoder decoder = new LegacyMasterProcessChannelDecoder( newChannel( realInputStream ) );
97          reader = new CommandReader( decoder, Shutdown.DEFAULT, logger );
98      }
99  
100     @After
101     public void deinit()
102     {
103         reader.stop();
104     }
105 
106     @Test
107     public void readJustOneClass()
108     {
109         Iterator<String> it = reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
110         assertTrue( it.hasNext() );
111         assertThat( it.next(), is( getClass().getName() ) );
112         reader.stop();
113         assertFalse( it.hasNext() );
114         try
115         {
116             it.next();
117             fail();
118         }
119         catch ( NoSuchElementException e )
120         {
121             // expected
122         }
123     }
124 
125     @Test
126     public void manyClasses()
127     {
128         Iterator<String> it1 = reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
129         assertThat( it1.next(), is( getClass().getName() ) );
130         addTestToPipeline( A.class.getName() );
131         assertThat( it1.next(), is( A.class.getName() ) );
132         addTestToPipeline( B.class.getName() );
133         assertThat( it1.next(), is( B.class.getName() ) );
134         addTestToPipeline( C.class.getName() );
135         assertThat( it1.next(), is( C.class.getName() ) );
136         addEndOfPipeline();
137         addTestToPipeline( D.class.getName() );
138         assertFalse( it1.hasNext() );
139     }
140 
141     @Test
142     public void twoIterators() throws Exception
143     {
144         Iterator<String> it1 = reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
145 
146         assertThat( it1.next(), is( getClass().getName() ) );
147         addTestToPipeline( A.class.getName() );
148         assertThat( it1.next(), is( A.class.getName() ) );
149         addTestToPipeline( B.class.getName() );
150 
151         TimeUnit.MILLISECONDS.sleep( DELAY ); // give the test chance to fail
152 
153         Iterator<String> it2 = reader.iterated();
154 
155         assertThat( it1.next(), is( B.class.getName() ) );
156         addTestToPipeline( C.class.getName() );
157 
158         assertThat( it2.hasNext(), is( true ) );
159         assertThat( it2.next(), is( getClass().getName() ) );
160         assertThat( it2.hasNext(), is( true ) );
161         assertThat( it2.next(), is( A.class.getName() ) );
162         assertThat( it2 ).isEmpty();
163 
164         assertThat( it1.next(), is( C.class.getName() ) );
165         addEndOfPipeline();
166         assertThat( it1 ).isEmpty();
167     }
168 
169     @Test( expected = NoSuchElementException.class )
170     public void stopBeforeReadInThread() throws Throwable
171     {
172         Runnable runnable = new Runnable()
173         {
174             @Override
175             public void run()
176             {
177                 Iterator<String> it =
178                     reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
179                 assertThat( it.next(), is( CommandReaderTest.class.getName() ) );
180             }
181         };
182         FutureTask<Object> futureTask = new FutureTask<>( runnable, null );
183         Thread t = new Thread( futureTask );
184         reader.stop();
185         t.start();
186         try
187         {
188             futureTask.get();
189         }
190         catch ( ExecutionException e )
191         {
192             throw e.getCause();
193         }
194     }
195 
196     @Test
197     public void readTwoClassesInThread() throws Throwable
198     {
199         final CountDownLatch counter = new CountDownLatch( 1 );
200         Runnable runnable = new Runnable()
201         {
202             @Override
203             public void run()
204             {
205                 Iterator<String> it =
206                     reader.getIterableClasses( new LegacyMasterProcessChannelEncoder( nul() ) ).iterator();
207                 assertThat( it.next(), is( CommandReaderTest.class.getName() ) );
208                 counter.countDown();
209                 assertThat( it.next(), is( Foo.class.getName() ) );
210             }
211         };
212         FutureTask<Object> futureTask = new FutureTask<>( runnable, null );
213         Thread t = new Thread( futureTask );
214         t.start();
215         counter.await();
216         addTestToPipeline( Foo.class.getName() );
217         try
218         {
219             futureTask.get();
220         }
221         catch ( ExecutionException e )
222         {
223             throw e.getCause();
224         }
225     }
226 
227     @Test( timeout = TEST_TIMEOUT )
228     public void shouldAwaitReaderUp() throws TestSetFailedException
229     {
230         assertTrue( reader.awaitStarted() );
231         reader.stop();
232         assertFalse( reader.awaitStarted() );
233     }
234 
235     private class SystemInputStream extends InputStream
236     {
237         @Override
238         public int read() throws IOException
239         {
240             try
241             {
242                 return CommandReaderTest.this.blockingStream.take();
243             }
244             catch ( InterruptedException e )
245             {
246                 throw new IOException( e );
247             }
248         }
249     }
250 
251     private void addTestToPipeline( String cls )
252     {
253         for ( byte cmdByte : ( ":maven-surefire-command:run-testclass:" + cls + ":" ).getBytes() )
254         {
255             blockingStream.add( cmdByte );
256         }
257     }
258 
259     private void addEndOfPipeline()
260     {
261         for ( byte cmdByte : ":maven-surefire-command:testset-finished:".getBytes() )
262         {
263             blockingStream.add( cmdByte );
264         }
265     }
266 
267     private static WritableBufferedByteChannel nul()
268     {
269         return newBufferedChannel( new PrintStream( new ByteArrayOutputStream() ) );
270     }
271 }