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