/*********************************************************************************
* TotalCross Software Development Kit *
* Copyright (C) 2000-2012 SuperWaba Ltda. *
* All Rights Reserved *
* *
* This library and virtual machine 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. *
* *
* This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 *
* A copy of this license is located in file license.txt at the root of this *
* SDK or can be downloaded here: *
* http://www.gnu.org/licenses/lgpl-3.0.txt *
* *
*********************************************************************************/
package totalcross.unit;
import totalcross.ui.dialog.*;
import totalcross.ui.event.*;
import totalcross.ui.gfx.*;
import totalcross.sys.*;
import totalcross.ui.*;
import totalcross.util.*;
/** JUnit implementation for TotalCross, to be used in the device (or in the desktop).
* It simulates the same output that you get when using JUnit under Eclipse.
* To use it, you must extend this class and add the test cases using the <code>addTestCase</code> method.
* See an example in the Litebase SDK, in samples/sys/testcases folder.
* <br><br>
* You can also create a test suite without this user interface. Here's a sample:
* <pre>
public class Testes extends TestSuite
{
public Testes()
{
super("UI Tests");
Settings.showDebugTimestamp = false;
addTestCase(Teste1.class);
addTestCase(TesteChamado509.class);
runTests();
Settings.showDebugTimestamp = true;
}
}
* </pre>
*/
public abstract class TestSuite extends MainWindow
{
private ProgressBar bar;
private Label lErrors,lElapsed;
private static Label lMsg,lMem;
private Button btn;
private Vector v = new Vector(10);
private static Vector vDump=new Vector(1000);
private static ListBox lbDump;
protected MenuBar mbar;
private static MenuItem miDump,miLoop;
private TestChooser chooser;
private static IntHashtable ihtForeColors;
private static IntHashtable ihtBackColors;
private static final int green = 0x00F000;
private static final int red = 0xF00000;
private static final int errorFore = red;
private static final int errorBack = 0xF8F800;
private int errors,loopCount;
private static boolean hasUI;
protected static boolean assertionFailed;
protected static final int OUTPUT_MSG = 0;
protected static final int OUTPUT_TEST_FAILED = 1;
protected static final int OUTPUT_ASSERT_FAILED = 2;
public TestSuite(String title)
{
super(title, TAB_ONLY_BORDER);
}
/** Add a new testcase. */
public void addTestCase(Class<?> c)
{
v.addElement(c);
}
static void output(String s, int type)
{
if (hasUI)
{
vDump.addElement(s);
switch (type)
{
case OUTPUT_TEST_FAILED:
ihtBackColors.put(vDump.size()-1,errorBack);
case OUTPUT_ASSERT_FAILED:
ihtForeColors.put(vDump.size()-1,errorFore);
break;
}
}
if (!hasUI || miDump.isChecked)
Vm.debug(s);
}
static void status(String s)
{
if (hasUI)
{
lMsg.setText(s);
lMsg.repaintNow();
}
}
private void updateMsg()
{
byte[]s = Settings.appSettingsBin; // guich@580_9: use appSettingsBin instead of appSecretKey because AllTests use the last
int skip = 0;
if (s != null)
for (int i = s.length-1; i >= 0; i--)
skip += 1-s[i];
lMsg.setText(skip == 0 ? "" : ("Skipping "+skip+" tests"));
}
protected void runTests()
{
hasUI = btn != null;
totalcross.sys.Vm.setAutoOff(false);
int n = v.size();
errors = 0;
if (hasUI)
{
btn.setEnabled(false);
repaintNow();
// clear the output views
if (miDump.isChecked) Vm.debug(Vm.ERASE_DEBUG);
vDump.removeAllElements();
lbDump.removeAll();
ihtForeColors = new IntHashtable(10);
ihtBackColors = new IntHashtable(10);
bar.setForeColor(green);
bar.setValue(0);
bar.repaintNow();
lErrors.setText("0/"+n);
updateMem();
}
int start = Vm.getTimeStamp();
for (int i = 0; i < n; i++)
{
Class<?> c = (Class<?>)v.items[i];
output("=============================",OUTPUT_MSG);
if (!canRun(i))
output("SKIPPING "+c,OUTPUT_MSG);
else
{
boolean error = false;
output("RUNNING "+c,OUTPUT_MSG);
// show the test in the msg area. aligning to the right makes it always show the class name, and maybe cut the package.
if (hasUI)
{
lMsg.align = RIGHT;
lMsg.setText(c.toString());
lMsg.repaintNow();
lMsg.align = LEFT;
}
int ustart = Vm.getTimeStamp();
try // guich@tc110_3: this strange code is to deal with Blackberry's odd behaviour on "catch Throwable".
{
try
{
updateMem();
Object o = c.newInstance();
((TestCase)o).run(); // guich@565_6: don't start the tests on the constructor
error = false;
}
catch (Throwable e) // was another exception but AssertionFailedException thrown?
{
error = true;
showException(e);
}
}
catch (Error err) {}
catch (Exception ee) {}
updateMem();
if (assertionFailed)
error = true;
if (error)
{
if (errors == 0 && hasUI)
bar.setForeColor(red);
errors++;
if (hasUI)
{
lErrors.setText(errors + "/"+n);
lErrors.repaintNow();
}
output("###### TEST FAILED ######",OUTPUT_TEST_FAILED);
}
else
{
int uend = Vm.getTimeStamp();
output("Test elapsed "+(uend-ustart)+"ms",OUTPUT_MSG);
output("###### TEST PASSED",OUTPUT_MSG);
}
}
if (hasUI) bar.setValue(i+1);
TestCase.learning = false; // reset after each testcase so that one do not change other's behaviour
}
int end = Vm.getTimeStamp();
if (hasUI)
{
updateMem();
updateMsg(); // if the tests have issued any "status"
lElapsed.setText(Convert.toString(end-start)); // guich@560_27
lElapsed.repaintNow();
// now show all in the listbox
lbDump.add(vDump.toObjectArray());
lbDump.ihtForeColors = ihtForeColors.size() > 0 ? ihtForeColors : null;
lbDump.ihtBackColors = ihtBackColors.size() > 0 ? ihtBackColors : null;
btn.setEnabled(true);
lbDump.requestFocus(); // let user navigate using scroll buttons
}
totalcross.sys.Vm.setAutoOff(true);
}
/** If you override this method, you must call
* super.initUI(); this call must be the
* first statement
*/
public void initUI() //rnovais@570_77 : Now is not final anymore
{
add(new Label(" free"),RIGHT,0,PREFERRED,PREFERRED-4);
add(lMem = new Label("99999999",RIGHT),BEFORE,0,PREFERRED,PREFERRED-4);
lMem.setText("memory");
add(new Label("Errors: "),LEFT,TOP+3);
add(lErrors = new Label("999/999"),AFTER,SAME);
add(new Label("ms"),RIGHT,SAME);
add(lElapsed = new Label("99999999",RIGHT),BEFORE-1,SAME); // guich@550_26
add(new Label("Elapsed: "),BEFORE,SAME);
lElapsed.setText("0");
add(bar = new ProgressBar(),CENTER,AFTER+3);
add(btn = new Button(" Start tests "),RIGHT,BOTTOM);
add(lbDump = new ListBox());
lbDump.enableHorizontalScroll();
lbDump.setRect(LEFT,AFTER+2,FILL,FIT,bar);
add(lMsg = new Label(""));
lMsg.setRect(LEFT,BOTTOM,FILL-btn.getPreferredWidth()-2,PREFERRED);
updateMsg();
bar.setBackForeColors(Color.DARK, Color.WHITE);
bar.textColor = Color.WHITE;
bar.max = v.size();
bar.suffix = "/"+v.size();
lErrors.setText("0/"+v.size());
if (Settings.appSettings == null || Settings.appSettings.length() < 3 || Settings.appSettings.equals("yes"))
Settings.appSettings = "nn10"; // default
String s = Settings.appSettings;
try {loopCount = Convert.toInt(s.substring(2));} catch (InvalidNumberException ine) {}
MenuItem []menu0 =
{
new MenuItem("File"),
new MenuItem("Select tests"),
miDump = new MenuItem("Dump to console", s.charAt(0) == 'y'),
new MenuItem(),
miLoop = new MenuItem("Loop "+loopCount+"x", s.charAt(1) == 'y'),
new MenuItem("Select loop count"),
new MenuItem(),
new MenuItem("Exit"),
};
bar.prefix= miLoop.isChecked ? "0/"+loopCount+" - " : "";
setMenuBar(mbar = new MenuBar(new MenuItem[][]{menu0})); // if a new vertical menu is added, AllTests must be changed!
mbar.setBackForeColors(Color.BLUE, Color.WHITE);
mbar.setCursorColor(0x6464FF);
mbar.setBorderStyle(NO_BORDER);
mbar.setPopColors(0x0078FF,Color.CYAN,-1);
btn.setBackColor(Color.GREEN);
updateMem();
if ("/autorun".equals(getCommandLine())) // guich@565_3
runTests(); // guich@570_90: now is safe to run from here, because initUI is already started from a thread.
else
btn.requestFocus();
}
private static String getLineNumber(Throwable e)
{
// convert the stack trace into String
String s = Vm.getStackTrace(e);
if (s == null || s.length() == 0)
return null;
// split into lines
String lines[] = Convert.tokenizeString(s,'\n');
// skip the ones containing totalcross(.unit)
for (int i = 0; i < lines.length; i++)
{
String l = lines[i];
if (Settings.onJavaSE && l.startsWith(" at "))
l = l.substring(4);
if (!l.startsWith("totalcross.") && !l.startsWith("litebase.") && (l.indexOf(' ') >= 0 || l.indexOf(':') >= 0)) // not from totalcross/litebase packages and also contains a valid line number?
return l;
}
return null;
}
static void showException(Throwable e)
{
String msg = e.getMessage();
if (msg == null) msg = "";
if (!(e instanceof AssertionFailedError))
msg += " ("+e.getClass()+")";
String at = getLineNumber(e);
if (at != null)
msg += " at "+at;
else
msg = "#"+TestCase.assertionCounter+": "+msg;
output(msg,OUTPUT_ASSERT_FAILED);
e.printStackTrace();
}
/** Updates the label with the available memory. */
public static void updateMem()
{
if (hasUI)
{
lMem.setText(Convert.toString(Vm.getFreeMemory()));
lMem.repaintNow();
}
}
/** If you override this method, you must call
* super.onEvent(); this call must be the
* first statement
*/
public void onEvent(Event e) //rnovais@570_77 : Now is not final anymore
{
try
{
switch (e.type)
{
case ControlEvent.PRESSED:
if (e.target == btn)
{
if (!miLoop.isChecked)
{
bar.prefix = "";
runTests();
}
else
{
errors = 0;
for (int loop = 0; loop < loopCount && errors == 0; loop++)
{
bar.prefix = (loop+1)+"/"+loopCount+" - ";
runTests();
}
}
}
else
if (e.target == mbar)
{
switch (mbar.getSelectedIndex())
{
case 1:
if (chooser == null)
chooser = new TestChooser();
chooser.popupNonBlocking();
break;
case 2: // dump
case 4: // loop
updateSettings();
break;
case 5:
SpinList sl = new SpinList(new String[]{"[1,100]"}, !Settings.fingerTouch);
sl.setSelectedIndex(loopCount-1);
sl.timerInterval = 100;
ControlBox cb = new ControlBox("Loop count", "Select the number of\ntimes the tests will run", sl, new String[]{"Ok"});
cb.popup();
loopCount = sl.getSelectedIndex()+1; // index 0 = 1x
miLoop.caption = "Loop "+loopCount+"x";
updateSettings();
break;
case 7:
exit(0);
break;
}
}
break;
}
}
catch (Exception ee)
{
MessageBox.showException(ee,true);
}
}
private void updateSettings()
{
Settings.appSettings = (miDump.isChecked ? "y" : "n") + (miLoop.isChecked ? "y" : "n") + loopCount;
bar.prefix= miLoop.isChecked ? "0/"+loopCount+" - " : "";
Window.needsPaint = true;
}
private boolean canRun(int i)
{
byte[] s = Settings.appSettingsBin;
return s == null || (s.length > i && s[i] == 1);
}
class TestChooser extends Window // guich@567_8
{
Grid grid;
Button btn;
public TestChooser()
{
super("Choose the tests",RECT_BORDER);
setRect(CENTER,CENTER,Settings.screenWidth-10,Settings.screenHeight*4/5);
setBackColor(0xA0FFEC);
}
public void initUI()
{
add(btn = new Button("Close"),RIGHT-3,BOTTOM-1);
add(grid = new Grid(new String[]{" # ","Class name"},true));
grid.setRect(LEFT+3,TOP+3,FILL-3,FIT-3);
int n = v.size();
for (int i =0; i < n; i++)
grid.add(new String[]{Convert.toString(i+1),prepareName(v.items[i].toString())});
}
public void onPopup()
{
int n = v.size();
for (int i =0; i < n; i++)
grid.setChecked(i, canRun(i));
}
private String prepareName(String c)
{
int dot = c.lastIndexOf('.');
if (dot > 0)
dot = c.lastIndexOf('.',dot-1);
if (dot > 0)
c = c.substring(dot+1);
if (c.startsWith("class "))
c = c.substring(6);
return c;
}
public void onEvent(Event e)
{
if (e.type == ControlEvent.PRESSED && e.target == btn)
{
int n = v.size();
byte[]c = new byte[n];
int checked = 0;
for (int i =0; i < n; i++)
if (grid.isChecked(i))
{
try
{
String num = grid.getCellText(i,1); // get the real index, because the user may have sorted the grid
c[Convert.toInt(num)-1] = (byte)1;
checked++;
}
catch (InvalidNumberException ine) {}
}
Settings.appSettingsBin = checked == n ? null : c; // if everyone is checked, then we just store null - run everybody
updateMsg();
unpop();
}
}
}
}