/******************************************************************************** * * CruiseControl, a Continuous Integration Toolkit * Copyright (c) 2003, ThoughtWorks, Inc. * 200 E. Randolph, 25th Floor * Chicago, IL 60601 USA * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * + Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * + Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the * names of its contributors may be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ********************************************************************************/ package net.sourceforge.cruisecontrol.util; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Random; import junit.framework.TestCase; /** * Class testing the {@link StdoutBuffer} implementation. */ public class StdoutBufferTest extends TestCase { /** Random number generator (to randomize texts) */ private static final Random RANDOM_GENER = new Random(); /** The maximum sleeping time in msec to make occasional pauses during reading/writing */ private static final int MAX_SLEEP_TIME = 30; /** The required number of lines for one test case */ private static final int NUM_LINES = 500; /** * Setup test environment. */ @Override public void setUp() throws Exception { super.setUp(); StringBuilder letters = new StringBuilder(); try { /* Find the file with letters from which to create the texts among resources and read them */ URL path = this.getClass().getClassLoader().getResource( "net/sourceforge/cruisecontrol/util/charlist.utf8.txt"); BufferedReader reader = new BufferedReader(new InputStreamReader( new FileInputStream(path.getPath()), "utf-8")); String line; while ((line = reader.readLine()) != null) { letters.append(line); } reader.close(); } catch(Exception e) { fail("Cannot read letters resource: " + e.getMessage()); } /* Lines with texts */ lines = generateTexts(letters.toString(), NUM_LINES, false); /* Create the lists of readers and feeders */ readers = new LinkedList<BufferReader>(); feeder = new BufferFeeder("Feeder"); } // setUp /** * Clears test environment. */ @Override public void tearDown() throws Exception { /* Wait until all reader threads end */ for (Thread tThr : readers) { join(tThr); } /* Wait until all feeder threads end */ join(feeder); super.tearDown(); } // tearDown public void testBufferReaderClose() throws Exception { final StdoutBuffer stdoutBuffer = new StdoutBuffer(null); final InputStream is = stdoutBuffer.getContent(); assertEquals(0, is.available()); // read would block forever.. assertEquals(1, is.read()); // read would block forever.. assertEquals(2, is.read(null, 0, 1)); is.reset(); is.close(); try { is.available(); fail("already closed should fail. class: " + this.getClass().getName()); } catch (IOException e) { assertEquals(StdoutBuffer.MSG_READER_ALREADY_CLOSED, e.getMessage()); } try { //noinspection ResultOfMethodCallIgnored is.read(); fail("already closed should fail."); } catch (IOException e) { assertEquals(StdoutBuffer.MSG_READER_ALREADY_CLOSED, e.getMessage()); } try { //noinspection ResultOfMethodCallIgnored is.read(null, 0, 1); fail("already closed should fail."); } catch (IOException e) { assertEquals(StdoutBuffer.MSG_READER_ALREADY_CLOSED, e.getMessage()); } try { //noinspection ResultOfMethodCallIgnored is.reset(); fail("already closed should fail."); } catch (IOException e) { assertEquals(StdoutBuffer.MSG_READER_ALREADY_CLOSED, e.getMessage()); } } /** * Tests one feeder to one reader - start feeder (F), wait for finish (W) and start reader (R) * @throws IOException if test fails */ public void testOne2One_FWR() throws IOException { /* Start feeder */ feeder.start(); /* Wait a second */ sleep(500); /* Start the reader */ readers.add(new BufferReader("Reader_1", feeder.getContent())); readers.getLast().start(); join(readers.getLast()); } // testOne2One_FWR /** * Tests one feeder to one reader - start feeder (F) and start reader immediately after (R) * @throws IOException if test fails */ public void testOne2One_FR() throws IOException { /* Start feeder */ feeder.start(); /* Start the reader */ readers.add(new BufferReader("Reader_1", feeder.getContent())); readers.getLast().start(); /* Wait until all lines are filled */ join(readers.getLast()); } // testOne2One_FR /** * Tests one feeder to one reader - start reeder (R), wait (W), and start feeder (F) * @throws IOException if test fails */ public void testOne2One_RWF() throws IOException { /* Start the reader */ readers.add(new BufferReader("Reader_1", feeder.getContent())); readers.getLast().start(); /* Wait a second */ sleep(500); /* Start feeder */ feeder.start(); /* Wait until all lines are filled */ join(readers.getLast()); } // testOne2One_RWF /** * Tests one feeder to many readers - start readers (R), wait (W), and start some more readers * (R), start feeder immediately after them (F), start readers immediately after it (R), * wait (W) and start even more readers (R). * * @throws IOException if test fails */ public void testOne2Many_RWRFRWR() throws IOException { /* Start the reader */ readers.add(new BufferReader("Reader_1", feeder.getContent())); readers.getLast().start(); readers.add(new BufferReader("Reader_2", feeder.getContent())); readers.getLast().start(); /* Wait a second */ sleep(500); /* Start one more reader */ readers.add(new BufferReader("Reader_3", feeder.getContent())); readers.getLast().start(); /* Start feeder */ feeder.start(); /* Start some more readers */ readers.add(new BufferReader("Reader_4", feeder.getContent())); readers.getLast().start(); readers.add(new BufferReader("Reader_5", feeder.getContent())); readers.getLast().start(); /* Wait a second */ sleep(500); /* Start one more reader */ readers.add(new BufferReader("Reader_6", feeder.getContent())); readers.getLast().start(); /* Wait 5 seconds */ sleep(2000); /* Start one more readers */ readers.add(new BufferReader("Reader_7", feeder.getContent())); readers.getLast().start(); readers.add(new BufferReader("Reader_8", feeder.getContent())); readers.getLast().start(); /* Wait for feeder to finish */ join(feeder); /* And start one more reader */ readers.add(new BufferReader("Reader_8", feeder.getContent())); readers.getLast().start(); /* Wait until all readers are finished */ for (Thread tThr : readers) { join(tThr); } } // testOne2Many_RWRFRWR /* * ----------- PROTECTED BLOCK ----------- */ /** * @return Creates the new instance of StdoutBuffer class. All methods MUST use this factory * method! * */ protected StdoutBuffer stdoutBufferFactory() { return new StdoutBuffer(null); } /* * ----------- PRIVATE BLOCK ----------- */ /** The list of lines to test */ private List<String> lines; /** The list to store buffer readers used in a test */ private LinkedList<BufferReader> readers; /** The feeder used in a test */ private BufferFeeder feeder; /* * ----------- INNER CLASSES ----------- */ /** * Filler of the buffer. */ private class BufferFeeder extends Thread { /** Constructor. Takes input stream from which the output of the command executed is read. * @param tName name of feeder. */ BufferFeeder(String tName) { buffer = stdoutBufferFactory(); name = tName; } // constructor /** Loop in which the data are fed into the buffer. */ @Override public void run() { int iNextSleep = 0; try { /* Write lines */ for (String line : lines) { buffer.write((line + "\n").getBytes()); /* Make occasional pauses ... */ if (iNextSleep-- <= 0) { StdoutBufferTest.sleep(RANDOM_GENER.nextInt(MAX_SLEEP_TIME)); iNextSleep = RANDOM_GENER.nextInt(10); } } /* Closes buffer */ StdoutBufferTest.sleep(RANDOM_GENER.nextInt(MAX_SLEEP_TIME * 5)); buffer.close(); } catch (IOException e) { fail("Feeding the StdoutBuffer failed: " + e.getMessage()); } } // run /** Gets the stream from which the content can be read (just calls {@link StdoutBuffer#getContent()}). * @return the string representation. * @throws IOException if there is a problem with buffer reading. */ public final InputStream getContent() throws IOException { return buffer.getContent(); } // getContent /** Gets the string representation of this feeder. * @return the string representation. */ @Override public final String toString() { return getClass().getName() + "[" + name + "]"; } // toString /** The name of Feeder. */ private final String name; /** The buffer to feed. */ private final StdoutBuffer buffer; } /** * Reader and checked of the buffer. */ private class BufferReader extends Thread { /** Constructor. Takes input stream from which the output of the command executed is read. * @param tName name of feeder. * @param tInput input stream to read */ BufferReader(String tName, InputStream tInput) { dataReader = new BufferedReader(new InputStreamReader(tInput)); name = tName; } // constuctor /** Loop in which the data are read from the stream. */ @Override public void run() { try { int iNextSleep = 0; int iLinesRead = 0; String tLine; /* Read line by line */ while ((tLine = dataReader.readLine()) != null) { /* Check the line */ assertEquals(toString(), lines.get(iLinesRead), tLine); /* Next line */ iLinesRead++; /* Make occasional pauses ... */ if (iNextSleep-- <= 0) { StdoutBufferTest.sleep(RANDOM_GENER.nextInt(MAX_SLEEP_TIME)); iNextSleep = RANDOM_GENER.nextInt(10); } } /* Everything read, check the size of data */ assertEquals(toString(), lines.size(), iLinesRead); } catch (IOException tExc) { tExc.printStackTrace(); fail("Exception when reading data from buffer, " + toString()); } } // run() /** * Gets the string representation of this command. * @return the string representation. */ @Override public final String toString() { return getClass().getName() + "[" + name + "]"; } // toString /** The name of reader. */ private final String name; /** The input stream reader. */ private final BufferedReader dataReader; } /* * ----------- PUBLIC STATIC METHODS ----------- */ /** * Generates the given number of lines filled by the random sequences of characters which * are taken from the given list. * * @param charsAllowed the list of characters to choose from * @param numlines the number of lines to generate. * @param trim {@link String#trim()} be called for each line before put into the array? In * case that white chars are among the allowed characters but must not start/finish * the sequence. * @return the array of lines. */ public static List<String> generateTexts(String charsAllowed, int numlines, boolean trim) { List<String> lines = new ArrayList<String>(NUM_LINES); /* Fill the buffer by a sequence of random letters */ for (int i = 0; i < numlines; i++) { final StringBuilder line = new StringBuilder(); /* Create the line */ for (int j = 0; j < RANDOM_GENER.nextInt(200) + 20; j++) { line.append(charsAllowed.charAt(RANDOM_GENER.nextInt(charsAllowed.length()))); } lines.add(trim ? line.toString().trim() : line.toString()); } return lines; } // generateTexts /** * Method waiting the given time. It catches (and ignores) {@link InterruptedException} thrown by * {@link Thread#join()}.. * @param iNumMSecs the number of milliseconds to wait. */ private static void sleep(int iNumMSecs) { try { Thread.sleep(iNumMSecs); } catch (InterruptedException e) { /* Ignore */ } } // sleep /** * Method joining the given thread. It catches (and ignores) {@link InterruptedException} thrown by * {@link Thread#join()}. * @param tThread the instance of thread to join */ private static void join(Thread tThread) { try { tThread.join(); } catch (InterruptedException e) { /* Ignore */ } } // join }