/*
* TestMultithreading.java of project jchart2d - Tests jchart2d in
* multithreading use.
* Copyright (C) Achim Westermann, created on 10.05.2005, 21:33:24
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* If you modify or optimize the code in a useful way please let me know.
* Achim.Westermann@gmx.de
*
*/
package info.monitorenter.gui.chart;
import info.monitorenter.gui.chart.traces.Trace2DLtd;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.WeakHashMap;
import junit.framework.Assert;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
/**
* Junit test that tests jchart2d in multithreading mode: Several Threads write
* data to a weak map - a single consumer periodically renders the data on a
* <code>{@link ITrace2D}</code> the chart.
* <p>
*
* @author <a href="mailto:Achim.Westermann@gmx.de">Achim Westermann </a>
*
*/
public class TestMultithreading extends TestCase {
/**
* Thread that invokes paint operations with a mock graphics context (thus
* consumes pending unscaled points) interrupted by a sleep between 0 and a
* configurable amount of milliseconds.
* <p>
*
* @author <a href="mailto:Achim.Westermann@gmx.de">Achim Westermann </a>
*
*/
class Consumer extends Thread {
/** The maximum sleep time between two paint operations. */
private long m_sleepRange;
/** Flag to allow termination from outside. */
private boolean m_stop = false;
/**
* Creates an instance that mock-paints the chart every
* <code>0 .. sleeprange</code> ms.
* <p>
*
* @param sleepRange
* the maximum sleep range between two rendering operations.
*/
Consumer(final long sleepRange) {
this.m_sleepRange = sleepRange;
}
/**
* Do the job.
* <p>
*/
@Override
public void run() {
MockGraphics2D mockGraphics = new MockGraphics2D();
while (!(this.m_stop || TestMultithreading.this.isAllProducersFinished())) {
try {
Thread.sleep((long) (Math.random() * this.m_sleepRange));
} catch (InterruptedException e) {
e.printStackTrace();
this.m_stop = true;
}
System.out.println('[' + this.getClass().getName() + "] painting...");
TestMultithreading.this.m_chart.paint(mockGraphics);
}
}
}
/**
* Thread implementation that adds random points to the trace of the outer
* classes's chart and takes a random sleep time between 0 and a constructor
* given value between two add operations.
* <p>
*
* @author <a href="mailto:Achim.Westermann@gmx.de">Achim Westermann </a>
*
*/
class Producer extends Thread {
/** The amount of points to add before termination. */
private long m_toAdd;
/** The maximum sleep time between two add operations. */
private long m_sleepRange;
/** Flag to allow stopping this Thread from outside. */
private boolean m_stop = false;
/**
* <p>
* Constructs a producer that will add <code>toAdd</code> points with random
* breaks of milliseconds between <code>maxSleep</code> and zero.
* </p>
*
* @param toAdd
* the amount of points to add.
*
* @param sleepRange
* the maxium time in milliseconds the Thread will sleep between
* two points added.
*
*/
Producer(final long toAdd, final long sleepRange) {
this.m_toAdd = toAdd;
this.m_sleepRange = sleepRange;
}
/**
* Does the job.
* <p>
*/
@Override
public void run() {
ITracePoint2D point;
while (this.m_toAdd > 0 && !this.m_stop) {
try {
Thread.sleep((long) (Math.random() * this.m_sleepRange));
} catch (InterruptedException e) {
e.printStackTrace();
this.m_stop = true;
}
if (this.m_toAdd % 10 == 0) {
System.out.println('[' + this.getName() + "] adding point... " + this.m_toAdd
+ " to go...");
}
point = new TracePoint2D(this.m_toAdd, this.m_toAdd);
TestMultithreading.this.m_weakMap.put(point, point.toString());
TestMultithreading.this.m_trace.addPoint(point);
this.m_toAdd--;
}
}
}
/**
* Test suite for this test class.
* <p>
*
* @return the test suite
*/
public static Test suite() {
TestSuite suite = new TestSuite();
suite.setName(TestMultithreading.class.getName());
suite.addTest(new TestMultithreading("testTrace2DLtd"));
return suite;
}
/** The chart used for testing. */
protected Chart2D m_chart;
/** The trace used for testing. */
protected ITrace2D m_trace;
/** Weak storage of points to render. */
protected WeakHashMap<ITracePoint2D, String> m_weakMap;
/** List of the producer Threads (produce points to render) for statistics. */
protected List<Producer> m_producers;
// test configuration
/** Amount of producers of <code>{@link TracePoint2D}</code>. */
protected static final int PRODUCER_AMOUNT = 10;
/**
* Range of milliseconds to pick a random sleep time out between producing two
* <code>{@link TracePoint2D}</code> instances.
*/
protected static final int PRODUCER_SLEEPRANGE = 100;
/** Amount of <code>{@link TracePoint2D}</code> to create per producer. */
protected static final int PRODUCER_ADD_POINT_AMOUNT = 500;
/**
* Range of milliseconds to pick a random sleep time out between consuming two
* <code>{@link TracePoint2D}</code>.
*/
protected static final int CONSUMER_SLEEPRANGE = 1000;
/**
* The <code>{@link ITrace2D}</code> class to use an instance of for the test.
*/
private static final Class<Trace2DLtd> TRACE_CLASS = Trace2DLtd.class;
/**
* Default constructor.
* <p>
*/
public TestMultithreading() {
super();
}
/**
* Constructor with test name.
* <p>
*
* @param testName
*/
public TestMultithreading(final String testName) {
super(testName);
}
// ////////////////////////////
// Test methods
// ////////////////////////////
// ////////////////////////////
// Helper methods
// ////////////////////////////
/**
* Returns true if all producer threads have finished their work.
* <p>
*
* @return true if all producer threads have finished their work.
*/
protected boolean isAllProducersFinished() {
boolean ret = true;
Iterator<Producer> it = this.m_producers.iterator();
Producer producer;
while (it.hasNext()) {
producer = it.next();
if (!producer.isAlive()) {
it.remove();
} else {
ret = false;
}
}
return ret;
}
/**
* Prints a report on <code>{@link System#out}</code>.
* <p>
*/
void report() {
long keys = this.m_weakMap.size();
System.out.println("Points remaining in the weakMap: " + keys);
System.out.println("System.runFinalization()... ");
System.runFinalization();
System.out.println("System.gc()... ");
System.gc();
System.out.println("Points remaining in the weakMap: " + keys);
keys = 0;
for (ITracePoint2D point : this.m_weakMap.keySet()) {
keys++;
System.out.println("Point " + point.toString() + " was not dropped.");
}
System.out.println("Points remaining in the weakMap: " + keys);
Assert.assertFalse("There are " + keys
+ " TracePoint2D instances not deleted from the WeakHashMap.", keys > this.m_trace
.getMaxSize());
}
/**
* Creates producers, consumers and initializes further members.
* <p>
*
* @see junit.framework.TestCase#setUp()
*
* @throws Exception
* if something goes wrong.
*/
@Override
public void setUp() throws Exception {
super.setUp();
this.m_chart = new Chart2D();
this.m_weakMap = new WeakHashMap<ITracePoint2D, String>();
this.m_producers = new LinkedList<Producer>();
for (int add = TestMultithreading.PRODUCER_AMOUNT; add > 0; add--) {
this.m_producers.add(new Producer(TestMultithreading.PRODUCER_ADD_POINT_AMOUNT,
TestMultithreading.PRODUCER_SLEEPRANGE));
}
this.m_trace = TestMultithreading.TRACE_CLASS.newInstance();
this.m_chart.addTrace(this.m_trace);
}
/**
* Start the Producer Threads and one Consumer Thread and blocks until all
* Threads are finished to avoid that teardown will be called and further
* tests are executed at the same time the calling test method has initiated
* the Threads for it's test.
* <p>
*
*/
protected void startThreads() {
for (Thread producer : this.m_producers) {
producer.start();
}
Consumer consumer = new Consumer(TestMultithreading.CONSUMER_SLEEPRANGE);
consumer.start();
while (!this.isAllProducersFinished() || consumer.isAlive()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.report();
}
// ////////////////////////////
// Worker classes
// ////////////////////////////
/**
* @see junit.framework.TestCase#tearDown()
*
* @throws Exception
* if something goes wrong.
*/
@Override
protected void tearDown() throws Exception {
super.tearDown();
this.m_weakMap = null;
this.m_chart = null;
this.m_producers = null;
}
/**
* Tests the producer / consumer scenario.
* <p>
*/
public void testTrace2DLtd() {
this.startThreads();
}
}