001: /*
002: * Javolution - Java(TM) Solution for Real-Time and Embedded Systems
003: * Copyright (C) 2007 - Javolution (http://javolution.org/)
004: * All rights reserved.
005: *
006: * Permission to use, copy, modify, and distribute this software is
007: * freely granted, provided that this notice is preserved.
008: */
009: package javolution.testing;
010:
011: import j2me.lang.CharSequence;
012: import javolution.context.Context;
013: import javolution.context.LogContext;
014: import javolution.context.ObjectFactory;
015: import javolution.lang.Configurable;
016: import javolution.lang.MathLib;
017: import javolution.text.TextBuilder;
018:
019: /**
020: * <p> This class represents a {@link TestContext test context} specialized
021: * for measuring execution time.</p>
022: *
023: * <p> {@link TimeContext} implementations may perform assertions based upon the
024: * execution time. For example:[code]
025: * class MyTestCase extends TestCase() {
026: * ...
027: * public void validate() {
028: * long ns = TimeContext.getAverageTime("ns");
029: * TimeContext.assertTrue(ns < 100); // Error if execution time is more than 100 ns.
030: * ...
031: * }
032: * }[/code]</p>
033: *
034: * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
035: * @version 5.2, August 5, 2007
036: */
037: public abstract class TimeContext extends TestContext {
038:
039: /**
040: * Holds an implementation which does not perform any logging but raises an
041: * {@link AssertionException} when an assertion fails, including any timing
042: * assertion.
043: */
044: public static final Class/*<TimeContext>*/REGRESSION = Regression.CLASS;
045:
046: /**
047: * Holds the minimum duration of each test case execution (default 2000 ms).
048: * The larger the number the more accurate is the average time result; but
049: * the longer it takes to run the test.
050: */
051: public static final Configurable/*<Integer>*/
052: TEST_DURATION_MS = new Configurable(new Integer(1000));
053:
054: /**
055: * Holds the time context default implementation (by default logs
056: * average and minimum execution time to <code>System.out</code>).
057: */
058: public static final Configurable/*<Class<? extends TimeContext>>*/
059: DEFAULT = new Configurable(Default.CLASS);
060:
061: /**
062: * Holds the minimum execution time in picoseconds.
063: */
064: private long _minimumPs;
065:
066: /**
067: * Holds the average execution time in picoseconds.
068: */
069: private long _averagePs;
070:
071: /**
072: * Holds the maximum execution time in picoseconds.
073: */
074: private long _maximumPs;
075:
076: /**
077: * Enters the {@link #DEFAULT} time context.
078: *
079: * @return the time context being entered.
080: */
081: public static TimeContext enter() {
082: return (TimeContext) Context.enter((Class) DEFAULT.get());
083: }
084:
085: /**
086: * Exits the current time context.
087: *
088: * @return the time context being exited.
089: * @throws ClassCastException if the context is not a stack context.
090: */
091: public static/*TimeContext*/Context exit() {
092: return (TimeContext) Context.exit();
093: }
094:
095: /**
096: * Returns the minimum execution time of the latest execution performed or
097: * <code>-1</code> if the current context is not a time context.
098: *
099: * @param unit one of <code>"s", "ms", "us", "ns", "ps"</code>
100: * @return the minimum execution time stated in the specified unit.
101: */
102: public static long getMinimumTime(String unit) {
103: LogContext ctx = (LogContext) LogContext.getCurrent();
104: if (ctx instanceof TimeContext) {
105: return TimeContext.picosecondTo(unit, ((TimeContext) ctx)
106: .getMinimumTimeInPicoSeconds());
107: } else {
108: return -1;
109: }
110: }
111:
112: /**
113: * Returns the average execution time of the latest execution performed or
114: * <code>-1</code> if the current context is not a time context.
115: *
116: * @param unit one of <code>"s", "ms", "us", "ns", "ps"</code>
117: * @return the average execution time stated in the specified unit.
118: */
119: public static long getAverageTime(String unit) {
120: LogContext ctx = (LogContext) LogContext.getCurrent();
121: if (ctx instanceof TimeContext) {
122: return TimeContext.picosecondTo(unit, ((TimeContext) ctx)
123: .getAverageTimeInPicoSeconds());
124: } else {
125: return -1;
126: }
127: }
128:
129: /**
130: * Returns the maximum execution time of the latest execution performed or
131: * <code>-1</code> if the current context is not a time context.
132: *
133: * @param unit one of <code>"s", "ms", "us", "ns", "ps"</code>
134: * @return the maximum execution time stated in the specified unit.
135: */
136: public static long getMaximumTime(String unit) {
137: LogContext ctx = (LogContext) LogContext.getCurrent();
138: if (ctx instanceof TimeContext) {
139: return TimeContext.picosecondTo(unit, ((TimeContext) ctx)
140: .getMaximumTimeInPicoSeconds());
141: } else {
142: return -1;
143: }
144: }
145:
146: private static long picosecondTo(String unit, long picoseconds) {
147: if (unit.equals("ps"))
148: return picoseconds;
149: if (unit.equals("ns"))
150: return picoseconds / 1000;
151: if (unit.equals("us"))
152: return picoseconds / 1000000;
153: if (unit.equals("ms"))
154: return picoseconds / 1000000000;
155: if (unit.equals("s"))
156: return picoseconds / 1000000000000L;
157: throw new IllegalArgumentException("Unit " + unit
158: + " not recognized");
159: }
160:
161: /**
162: * Returns the minimum execution time of the latest execution stated in
163: * pico-seconds.
164: *
165: * @return the time in pico-seconds.
166: */
167: public long getMinimumTimeInPicoSeconds() {
168: return _minimumPs;
169: }
170:
171: /**
172: * Returns the average execution time of the latest execution stated in
173: * pico-seconds.
174: *
175: * @return the time in pico-seconds.
176: */
177: public long getAverageTimeInPicoSeconds() {
178: return _averagePs;
179: }
180:
181: /**
182: * Returns the maximmum execution time of the latest execution stated in
183: * pico-seconds.
184: *
185: * @return the time in pico-seconds.
186: */
187: public long getMaximumTimeInPicoSeconds() {
188: return _maximumPs;
189: }
190:
191: // Overrides.
192: public void doTest(TestCase testCase) {
193: _testCase = testCase;
194: System.gc();
195: try {
196: Thread.sleep(200); // For GC to run.
197: } catch (InterruptedException e) {
198: }
199:
200: _minimumPs = Long.MAX_VALUE;
201: _maximumPs = 0;
202: _averagePs = 0;
203: long totalCount = 0;
204: long totalDuration = 0;
205: long maximumDurationPs = ((Integer) TEST_DURATION_MS.get())
206: .intValue() * 1000000000L;
207: do {
208: testCase.prepare(); // Prepare
209: try {
210: long start = TimeContext.nanoTime();
211: testCase.execute(); // Execute.
212: long duration = (TimeContext.nanoTime() - start) * 1000; // Picoseconds
213: int count = testCase.count();
214: totalCount += count;
215: totalDuration += duration;
216: long singleExecutionDuration = duration / count;
217: if (singleExecutionDuration < _minimumPs) {
218: _minimumPs = singleExecutionDuration;
219: }
220: if (singleExecutionDuration > _maximumPs) {
221: _maximumPs = singleExecutionDuration;
222: }
223: if (totalDuration >= maximumDurationPs) {
224: _averagePs = totalDuration / totalCount;
225: testCase.validate(); // Validate only at last iteration.
226: break;
227: }
228: } finally {
229: testCase.cleanup(); // Cleanup.
230: }
231: } while (true);
232: }
233:
234: private TestCase _testCase;
235:
236: // Override.
237: public boolean doAssertEquals(String message, Object expected,
238: Object actual) {
239: if (((expected == null) && (actual != null))
240: || ((expected != null) && (!expected.equals(actual)))) {
241: LogContext.error(_testCase.toString());
242: throw new AssertionException(message, expected, actual);
243: }
244: return true;
245: }
246:
247: // Holds the default implementation.
248: private static final class Default extends TimeContext {
249:
250: private static final Class CLASS = new Default().getClass();
251:
252: // Overrides.
253: public void doTest(TestCase testCase) {
254: super .doTest(testCase);
255: TextBuilder tb = TextBuilder.newInstance();
256: tb.append("[test] ");
257: tb.append(testCase.toString());
258: tb.append(": ");
259: Default.appendTime(this .getAverageTimeInPicoSeconds(), tb);
260: tb.append(" (minimum ");
261: Default.appendTime(this .getMinimumTimeInPicoSeconds(), tb);
262: tb.append(")");
263: tb.println();
264: TextBuilder.recycle(tb);
265: }
266:
267: private static TextBuilder appendTime(long picoseconds,
268: TextBuilder tb) {
269: long divisor;
270: String unit;
271: if (picoseconds > 1000 * 1000 * 1000 * 1000L) { // 1 s
272: unit = " s";
273: divisor = 1000 * 1000 * 1000 * 1000L;
274: } else if (picoseconds > 1000 * 1000 * 1000L) {
275: unit = " ms";
276: divisor = 1000 * 1000 * 1000L;
277: } else if (picoseconds > 1000 * 1000L) {
278: unit = " us";
279: divisor = 1000 * 1000L;
280: } else if (picoseconds > 1000L) {
281: unit = " ns";
282: divisor = 1000L;
283: } else {
284: unit = " ps";
285: divisor = 1L;
286: }
287: long value = picoseconds / divisor;
288: tb.append(value);
289: int fracDigits = 3 - MathLib.digitLength(value); // 3 digits
290: // precision
291: if (fracDigits > 0)
292: tb.append(".");
293: for (int i = 0, j = 10; i < fracDigits; i++, j *= 10) {
294: tb.append((picoseconds * j / divisor) % 10);
295: }
296: return tb.append(unit);
297: }
298:
299: public boolean isInfoLogged() {
300: return true;
301: }
302:
303: public void logInfo(CharSequence message) {
304: System.out.print("[info] ");
305: System.out.println(message);
306: }
307:
308: public boolean isWarningLogged() {
309: return true;
310: }
311:
312: public void logWarning(CharSequence message) {
313: System.out.print("[warning] ");
314: System.out.println(message);
315: }
316:
317: public boolean isErrorLogged() {
318: return true;
319: }
320:
321: public void logError(Throwable error, CharSequence message) {
322: System.out.print("[error] ");
323: if (error != null) {
324: System.out.print(error.getClass().getName());
325: System.out.print(" - ");
326: }
327: String description = (message != null) ? message.toString()
328: : (error != null) ? error.getMessage() : "";
329: System.out.println(description);
330: if (error != null) {
331: error.printStackTrace();
332: }
333: }
334:
335: }
336:
337: private static long nanoTime() {
338: /*@JVM-1.5+@
339: if (true) return System.nanoTime();
340: /**/
341: return System.currentTimeMillis() * 1000000;
342: }
343:
344: // TestContext implementation with no output (just validation).
345: private static final class Regression extends TimeContext {
346:
347: private static final Class CLASS = new Regression().getClass();
348:
349: public boolean isErrorLogged() {
350: return false;
351: }
352:
353: public boolean isInfoLogged() {
354: return false;
355: }
356:
357: public boolean isWarningLogged() {
358: return false;
359: }
360:
361: public void logError(Throwable error, CharSequence message) {
362: }
363:
364: public void logInfo(CharSequence message) {
365: }
366:
367: public void logWarning(CharSequence message) {
368: }
369: }
370:
371: // Allows instances of private classes to be factory produced.
372: static {
373: ObjectFactory.setInstance(new ObjectFactory() {
374: protected Object create() {
375: return new Default();
376: }
377: }, Default.CLASS);
378: ObjectFactory.setInstance(new ObjectFactory() {
379: protected Object create() {
380: return new Regression();
381: }
382: }, Regression.CLASS);
383: }
384:
385: }
|