View Javadoc

1   // Copyright (C) 2005 - 2012 Philip Aston
2   // All rights reserved.
3   //
4   // This file is part of The Grinder software distribution. Refer to
5   // the file LICENSE which is part of The Grinder distribution for
6   // licensing details. The Grinder distribution is available on the
7   // Internet at http://grinder.sourceforge.net/
8   //
9   // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
10  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
11  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
12  // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
13  // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
14  // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
15  // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
16  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
17  // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
18  // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
19  // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
20  // OF THE POSSIBILITY OF SUCH DAMAGE.
21  
22  package net.grinder.engine.agent;
23  
24  import static net.grinder.testutility.SocketUtilities.findFreePort;
25  import static org.junit.Assert.assertTrue;
26  import static org.mockito.Matchers.contains;
27  import static org.mockito.Matchers.isA;
28  import static org.mockito.Mockito.reset;
29  import static org.mockito.Mockito.timeout;
30  import static org.mockito.Mockito.verify;
31  import static org.mockito.Mockito.verifyNoMoreInteractions;
32  
33  import java.io.File;
34  import java.io.IOException;
35  import java.io.InputStream;
36  
37  import net.grinder.common.GrinderProperties;
38  import net.grinder.communication.Acceptor;
39  import net.grinder.communication.CommunicationException;
40  import net.grinder.communication.ConnectionIdentity;
41  import net.grinder.communication.ConnectionType;
42  import net.grinder.communication.FanOutServerSender;
43  import net.grinder.communication.Message;
44  import net.grinder.communication.Sender;
45  import net.grinder.communication.ServerReceiver;
46  import net.grinder.communication.StreamReceiver;
47  import net.grinder.engine.agent.DebugThreadWorker.IsolateGrinderProcessRunner;
48  import net.grinder.messages.agent.ResetGrinderMessage;
49  import net.grinder.messages.agent.StartGrinderMessage;
50  import net.grinder.messages.agent.StopGrinderMessage;
51  import net.grinder.testutility.AbstractJUnit4FileTestCase;
52  
53  import org.junit.After;
54  import org.junit.Before;
55  import org.junit.Test;
56  import org.mockito.Mock;
57  import org.mockito.MockitoAnnotations;
58  import org.slf4j.Logger;
59  
60  
61  /**
62   * Unit tests for <code>Agent</code>
63   * TestAgent.
64   *
65   * @author Philip Aston
66   */
67  public class TestAgentImplementation extends AbstractJUnit4FileTestCase {
68  
69    @Mock private Logger m_logger;
70  
71    @Before public void setUp() throws Exception {
72      DebugThreadWorkerFactory.setIsolatedRunnerClass(TestRunner.class.getName());
73      MockitoAnnotations.initMocks(this);
74    }
75  
76    @After public void tearDown() throws Exception {
77      super.tearDown();
78      DebugThreadWorkerFactory.setIsolatedRunnerClass(null);
79    }
80  
81    @Test public void testConstruction() throws Exception {
82      final File propertyFile = new File(getDirectory(), "properties");
83      final Agent agent = new AgentImplementation(m_logger, propertyFile, true);
84      agent.shutdown();
85  
86      verify(m_logger).info("finished");
87      verifyNoMoreInteractions(m_logger);
88    }
89  
90    @Test public void testRunDefaultProperties() throws Exception {
91      // Files in cwd.
92      final File propertyFile = new File("grinder.properties");
93      propertyFile.deleteOnExit();
94  
95      final File relativeScriptFile = new File("script/blah");
96      relativeScriptFile.deleteOnExit();
97      relativeScriptFile.getParentFile().mkdirs();
98      relativeScriptFile.getParentFile().deleteOnExit();
99      relativeScriptFile.createNewFile();
100 
101     try {
102       final GrinderProperties properties = new GrinderProperties(propertyFile);
103 
104       final Agent agent = new AgentImplementation(m_logger, null, true);
105 
106       verifyNoMoreInteractions(m_logger);
107 
108       agent.run();
109 
110       verify(m_logger).info(contains("The Grinder"));
111       verify(m_logger).warn(contains("proceeding"),
112                             contains("Failed to connect"));
113       verify(m_logger).error(contains("does not exist"));
114       verifyNoMoreInteractions(m_logger);
115       reset(m_logger);
116 
117       properties.setBoolean("grinder.useConsole", false);
118       properties.save();
119 
120       properties.setFile("grinder.script", relativeScriptFile);
121       properties.setInt("grinder.processes", 0);
122       properties.save();
123 
124       agent.run();
125 
126       verify(m_logger).info(contains("The Grinder"));
127       verify(m_logger).info(contains("command line"),
128                             isA(WorkerProcessCommandLine.class));
129       verifyNoMoreInteractions(m_logger);
130       reset(m_logger);
131 
132       properties.setFile("grinder.logDirectory",
133                          getDirectory().getAbsoluteFile());
134       properties.save();
135 
136       agent.run();
137 
138       verify(m_logger).info(contains("The Grinder"));
139       verify(m_logger).info(contains("command line"),
140                             isA(WorkerProcessCommandLine.class));
141       reset(m_logger);
142 
143       agent.shutdown();
144     }
145     finally {
146       assertTrue(propertyFile.delete());
147       assertTrue(relativeScriptFile.delete());
148       assertTrue(relativeScriptFile.getParentFile().delete());
149     }
150   }
151 
152   @Test public void testRun() throws Exception {
153     final File propertyFile = new File(getDirectory(), "properties");
154     final GrinderProperties properties = new GrinderProperties(propertyFile);
155 
156     final Agent agent = new AgentImplementation(m_logger, propertyFile, true);
157 
158     verifyNoMoreInteractions(m_logger);
159 
160     agent.run();
161 
162     verify(m_logger).info(contains("The Grinder"));
163     verify(m_logger).warn(contains("proceeding"),
164                           contains("Failed to connect"));
165     verify(m_logger).error(contains("does not exist"));
166     verifyNoMoreInteractions(m_logger);
167     reset(m_logger);
168 
169     properties.setBoolean("grinder.useConsole", false);
170     properties.save();
171 
172     agent.run();
173 
174     verify(m_logger).info(contains("The Grinder"));
175     verify(m_logger).error(contains("does not exist"));
176     verifyNoMoreInteractions(m_logger);
177     reset(m_logger);
178 
179     final File scriptFile = new File(getDirectory(), "script");
180     assertTrue(scriptFile.createNewFile());
181 
182     final File badFile = new File(scriptFile.getAbsoluteFile(), "blah");
183     properties.setFile("grinder.script", badFile);
184     properties.save();
185 
186     agent.run();
187 
188     verify(m_logger).info(contains("The Grinder"));
189     verify(m_logger).error(contains("does not exist"));
190     verifyNoMoreInteractions(m_logger);
191     reset(m_logger);
192 
193     properties.setFile("grinder.script", scriptFile);
194     properties.setInt("grinder.processes", 0);
195     properties.save();
196 
197     agent.run();
198 
199     verify(m_logger).info(contains("The Grinder"));
200     verify(m_logger).info(contains("command line"),
201                           isA(WorkerProcessCommandLine.class));
202     verifyNoMoreInteractions(m_logger);
203     reset(m_logger);
204 
205     properties.setBoolean("grinder.debug.singleprocess", true);
206     properties.save();
207 
208     agent.run();
209 
210     verify(m_logger).info(contains("The Grinder"));
211     verify(m_logger).info(contains("threads rather than processes"));
212     verifyNoMoreInteractions(m_logger);
213     reset(m_logger);
214 
215     properties.setProperty("grinder.jvm.arguments", "-Dsome_stuff=blah");
216     properties.save();
217 
218     agent.run();
219 
220     verify(m_logger).info(contains("The Grinder"));
221     verify(m_logger).info(contains("threads rather than processes"));
222     verify(m_logger).warn(contains("grinder.jvm.arguments"),
223                           contains("some_stuff"));
224     verifyNoMoreInteractions(m_logger);
225     reset(m_logger);
226 
227     agent.shutdown();
228 
229     verify(m_logger).info(contains("finished"));
230     verifyNoMoreInteractions(m_logger);
231   }
232 
233   @Test public void testWithConsole() throws Exception {
234     final ConsoleStub console = new ConsoleStub() {
235       public void onConnect() throws Exception {
236         // After we accept an agent connection...
237         verify(m_logger).info(contains("The Grinder"));
238 
239         verify(m_logger, timeout(5000)).info(contains("connected"),
240                                              contains("localhost"));
241         verify(m_logger, timeout(5000)).info(contains("waiting"));
242 
243         // ...send a start message...
244         reset(m_logger);
245         final GrinderProperties grinderProperties = new GrinderProperties();
246         getSender().send(new StartGrinderMessage(grinderProperties, 99));
247 
248         verify(m_logger, timeout(5000)).info("received a start message");
249 
250         verify(m_logger, timeout(5000)).error(contains("grinder.py"));
251 
252         verify(m_logger, timeout(5000)).info(contains("waiting"));
253 
254         // ...send another start message...
255         reset(m_logger);
256         getSender().send(new StartGrinderMessage(grinderProperties, 99));
257 
258         verify(m_logger, timeout(5000)).info("received a start message");
259 
260         verify(m_logger, timeout(5000)).info(contains("The Grinder"));
261 
262         verify(m_logger, timeout(5000)).error(contains("grinder.py"));
263 
264         verify(m_logger, timeout(5000)).info(contains("waiting"));
265 
266         // ...then a reset message...
267         reset(m_logger);
268         getSender().send(new ResetGrinderMessage());
269 
270         verify(m_logger, timeout(5000)).info("received a reset message");
271 
272         // Version string.
273         verify(m_logger, timeout(5000)).info(contains("The Grinder"));
274 
275         verify(m_logger, timeout(5000)).info(contains("waiting"));
276 
277         // ...now try specifying the script...
278         reset(m_logger);
279         grinderProperties.setFile(GrinderProperties.SCRIPT, new File("foo.py"));
280         getSender().send(new StartGrinderMessage(grinderProperties, 99));
281 
282         verify(m_logger, timeout(5000)).info("received a start message");
283 
284         verify(m_logger, timeout(5000)).error(contains("foo.py"));
285 
286         verify(m_logger, timeout(5000)).info(contains("waiting"));
287 
288         // ..then a stop message.
289         getSender().send(new StopGrinderMessage());
290       }
291     };
292 
293     final File propertyFile = new File(getDirectory(), "properties");
294     final GrinderProperties properties = new GrinderProperties(propertyFile);
295 
296     final Agent agent = new AgentImplementation(m_logger, propertyFile, true);
297 
298     properties.setInt("grinder.consolePort", console.getPort());
299     properties.save();
300 
301     agent.run();
302 
303     console.shutdown();
304 
305     verify(m_logger).info("received a stop message");
306 
307     verify(m_logger, timeout(5000)).info("communication shut down");
308 
309     agent.shutdown();
310 
311     verify(m_logger).info("finished");
312 
313     verifyNoMoreInteractions(m_logger);
314   }
315 
316   @Test public void testRampUp() throws Exception {
317     final ConsoleStub console = new ConsoleStub() {
318       public void onConnect() throws Exception {
319         // After we accept an agent connection...
320         verify(m_logger).info(contains("The Grinder"));
321 
322         verify(m_logger, timeout(5000)).info(contains("connected"),
323                                              contains("localhost"));
324 
325         verify(m_logger, timeout(5000)).info(contains("waiting"));
326 
327         // ...send a start message...
328         reset(m_logger);
329         final GrinderProperties grinderProperties = new GrinderProperties();
330         getSender().send(new StartGrinderMessage(grinderProperties, 99));
331 
332         verify(m_logger, timeout(5000)).info("received a start message");
333 
334         verify(m_logger, timeout(5000)).info(contains("DEBUG MODE"));
335 
336         // 10 workers started.
337         verify(m_logger, timeout(5000).times(10)).info(contains("started"));
338 
339         // Interrupt our workers.
340         reset(m_logger);
341         getSender().send(new ResetGrinderMessage());
342 
343         verify(m_logger, timeout(5000)).info(contains("reset"));
344 
345         verify(m_logger, timeout(5000)).info(contains("The Grinder"));
346 
347         verify(m_logger, timeout(5000)).info(contains("waiting"));
348 
349         // Now try again, with no ramp up.
350         reset(m_logger);
351         grinderProperties.setInt("grinder.initialProcesses", 10);
352 
353         getSender().send(new StartGrinderMessage(grinderProperties, 99));
354 
355         verify(m_logger, timeout(5000)).info("received a start message");
356 
357         verify(m_logger, timeout(5000)).info(contains("DEBUG MODE"));
358 
359         // 10 workers started.
360         verify(m_logger, timeout(5000).times(10)).info(contains("started"));
361 
362         // Shut down our workers.
363         getSender().send(new StopGrinderMessage());
364       }
365     };
366 
367     final File propertyFile = new File(getDirectory(), "properties");
368     final GrinderProperties properties = new GrinderProperties(propertyFile);
369 
370     final Agent agent = new AgentImplementation(m_logger, propertyFile, true);
371 
372     final File script = new File(getDirectory(), "grinder.py");
373     assertTrue(script.createNewFile());
374 
375     properties.setInt("grinder.consolePort", console.getPort());
376     properties.setInt("grinder.initialProcesses", 0);
377     properties.setInt("grinder.processes", 10);
378     properties.setInt("grinder.processIncrement", 1);
379     properties.setInt("grinder.processIncrementInterval", 10);
380     properties.setBoolean("grinder.debug.singleprocess", true);
381     properties.setFile("grinder.script", script);
382     properties.save();
383 
384     agent.run();
385 
386     console.shutdown();
387     agent.shutdown();
388   }
389 
390   @Test public void testReconnect() throws Exception {
391     final File propertyFile = new File(getDirectory(), "properties");
392     final GrinderProperties properties = new GrinderProperties(propertyFile);
393 
394     final boolean[] secondConsoleContacted = new boolean[1];
395 
396     final GrinderProperties startProperties = new GrinderProperties();
397 
398     final ConsoleStub console2 = new ConsoleStub() {
399       public void onConnect() throws Exception {
400 
401         startProperties.setFile("grinder.script", new File("not there"));
402 
403         getSender().send(new StartGrinderMessage(startProperties, 99));
404 
405         synchronized (secondConsoleContacted) {
406           secondConsoleContacted[0] = true;
407           secondConsoleContacted.notifyAll();
408         }
409 
410         getSender().send(new StopGrinderMessage());
411       }
412     };
413 
414     final ConsoleStub console1 = new ConsoleStub() {
415       public void onConnect() throws Exception {
416         startProperties.setInt("grinder.consolePort", console2.getPort());
417 
418         getSender().send(new StartGrinderMessage(startProperties, 22));
419       }
420     };
421 
422     properties.setInt("grinder.consolePort", console1.getPort());
423     properties.save();
424 
425     final Agent agent = new AgentImplementation(m_logger, propertyFile, true);
426 
427     agent.run();
428 
429     synchronized (secondConsoleContacted) {
430       final long start = System.currentTimeMillis();
431 
432       while (!secondConsoleContacted[0] &&
433              System.currentTimeMillis() < start + 10000) {
434         secondConsoleContacted.wait(500);
435       }
436     }
437 
438     assertTrue(secondConsoleContacted[0]);
439 
440     console1.shutdown();
441     console2.shutdown();
442 
443     agent.shutdown();
444   }
445 
446   private abstract class ConsoleStub {
447     private final Acceptor m_acceptor;
448     private final ServerReceiver m_receiver;
449     private final Sender m_sender;
450 
451     public ConsoleStub() throws CommunicationException, IOException {
452       final int port = findFreePort();
453 
454       m_acceptor = new Acceptor("", port, 1, null);
455       m_receiver = new ServerReceiver();
456       m_receiver.receiveFrom(
457         m_acceptor, new ConnectionType[] { ConnectionType.AGENT }, 1, 10, 1000);
458       m_sender = new FanOutServerSender(m_acceptor, ConnectionType.AGENT, 3);
459 
460       m_acceptor.addListener(ConnectionType.AGENT, new Acceptor.Listener() {
461         public void connectionAccepted(ConnectionType connectionType,
462                                        ConnectionIdentity connection) {
463           try {
464             onConnect();
465           }
466           catch (Throwable e) {
467             e.printStackTrace();
468           }
469         }
470 
471         public void connectionClosed(ConnectionType connectionType,
472                                      ConnectionIdentity connection) { }
473       });
474     }
475 
476     public int getPort() {
477       return m_acceptor.getPort();
478     }
479 
480     public final void shutdown() throws CommunicationException {
481       m_acceptor.shutdown();
482       m_receiver.shutdown();
483       getSender().shutdown();
484     }
485 
486     public final Sender getSender() {
487       return m_sender;
488     }
489 
490     public abstract void onConnect() throws Exception;
491   }
492 
493   public static class TestRunner implements IsolateGrinderProcessRunner {
494 
495     public int run(InputStream in) {
496       try {
497         final StreamReceiver receiver = new StreamReceiver(in);
498         while (true) {
499           final Message message = receiver.waitForMessage();
500           if (message == null ||
501               message instanceof ResetGrinderMessage ||
502               message instanceof StopGrinderMessage) {
503             return 0;
504           }
505         }
506       }
507       catch (Exception e) {
508         e.printStackTrace();
509         return -1;
510       }
511     }
512   }
513 }