/*
@ITMillApache2LicenseForJavaFiles@
*/
package com.itmill.toolkit.tests;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;
import com.itmill.toolkit.terminal.ClassResource;
import com.itmill.toolkit.terminal.ExternalResource;
import com.itmill.toolkit.ui.AbstractComponent;
import com.itmill.toolkit.ui.Button;
import com.itmill.toolkit.ui.Component;
import com.itmill.toolkit.ui.ComponentContainer;
import com.itmill.toolkit.ui.DateField;
import com.itmill.toolkit.ui.Embedded;
import com.itmill.toolkit.ui.GridLayout;
import com.itmill.toolkit.ui.Label;
import com.itmill.toolkit.ui.Layout;
import com.itmill.toolkit.ui.Link;
import com.itmill.toolkit.ui.OrderedLayout;
import com.itmill.toolkit.ui.Panel;
import com.itmill.toolkit.ui.Select;
import com.itmill.toolkit.ui.TabSheet;
import com.itmill.toolkit.ui.TextField;
import com.itmill.toolkit.ui.Window;
/**
* ATFTest is an application that is used to ensure compatibility with automated
* test frameworks. It is basically calculator application which emulates
* application changes which should not brake test cases.
*
* 1. Calculator functionality is used to ensure test cases - contains buttons
* (operations + numbers) and textfield (result) - test case used components
* contain unique PIDs which ATF should use - ATF test case consists of pushing
* calculator buttons and reading correct result from textfield
*
* 2. Layouts are randomized - any component can be located randomly under
* panel, tabsheet, grid, orderedlayout etc. - ATF should find component even if
* it's relocated
*
* 3. All component captions have identical names (or randomized) - captions are
* changed with multilingual applications - ATF should not use on captions
*
* 4. Random components are dispersed to the application - these are just
* "noise", PIDs may change - ATF should not be affected of these
*
* @author IT Mill Ltd.
*
*/
public class BasicRandomTest extends com.itmill.toolkit.Application implements
Button.ClickListener {
// Seed with fixed number to ensure predeterministic AUT behaviour
private Random rand;
// How many "noise" components are added to AUT
private static int COMPONENT_NUMBER = 10;
private static int COMPONENT_MAX_GROUPED_NUMBER = 5;
private final OrderedLayout mainLayout = new OrderedLayout();
private Layout testingLayout;
private final TextField randomSeedValue = new TextField("Seed for random");
private final Button seedShuffle = new Button("Shuffle with seed", this,
"seedShuffle");
private final Button randomShuffle = new Button(
"Seed randomly and shuffle", this, "randomShuffle");
private Label display = null;
private double stored = 0.0;
private double current = 0.0;
private String operation = "C";
private long captionCounter = 0;
private ArrayList components;
private long eventCounter = 0;
private final Label statusLabel = new Label();
// Store button object => real value map
// needed because button captions are randomized
private HashMap buttonValues;
public void init() {
// addWindow(new Window("ATFTest", create()));
final Window mainWindow = new Window("Testing", create());
setMainWindow(mainWindow);
setUser(new Long(System.currentTimeMillis()).toString());
}
/**
* Create application UI components and it's main layout. Does not attach
* layout to any specific application.
*
* @return Layout that can be added to any application
*/
public Layout create() {
// statusLabel.setUIID("Label_status");
// Setup contains restart button and deterministic component shuffler
// Test requirement: test cases must be reproducable (use seed)
mainLayout.addComponent(new Label(
"
ATFTest with randomized Calculator functionality
"
+ "Buttons with X captions contain calculator number, "
+ "minus, add, multiply, divisor or clear "
+ "button functionalities.
Layouts, \"noise\" "
+ "components and component placing is randomized "
+ "after each application restart.
"
+ "Test cases should exercise calculator functions "
+ "through X buttons and ensure that Result label "
+ "contains correct value.", Label.CONTENT_XHTML));
final OrderedLayout setupLayout = new OrderedLayout(
OrderedLayout.ORIENTATION_HORIZONTAL);
final Panel statusPanel = new Panel("Status");
statusPanel.setWidth(200);
setupLayout.addComponent(statusPanel);
statusPanel.addComponent(statusLabel);
setupLayout.addComponent(randomSeedValue);
setupLayout.addComponent(seedShuffle);
setupLayout.addComponent(randomShuffle);
setupLayout.addComponent(new Button("restart", this, "close"));
mainLayout.addComponent(setupLayout);
// randomSeedValue.setUIID("randomSeedValue");
// seedShuffle.setUIID("seedShuffle");
// randomShuffle.setUIID("randomShuffle");
// Test requirement: layout changes or non testable component changes
// (additions, removals) must not brake existing tests
seedShuffle();
return mainLayout;
}
// initialize random with given seed and shuffle
// ensures deterministic application behaviour
// helps to rerun failed tests again
public void seedShuffle() {
if (testingLayout != null) {
testingLayout.removeAllComponents();
mainLayout.removeComponent(testingLayout);
}
try {
// randomize using user given value
rand = new Random(Long.parseLong((String) randomSeedValue
.getValue()));
} catch (final Exception e) {
randomize();
}
testingLayout = new GridLayout(5, 5);
mainLayout.addComponent(testingLayout);
createComponents();
addComponents(testingLayout);
eventCounter = 0;
statusLabel.setValue("#" + eventCounter + ": button "
+ ", value " + Double.toString(current));
}
// initialize random with random seed and shuffle
// creates new application behaviour
public void randomShuffle() {
randomize();
seedShuffle();
}
private void randomize() {
final long newSeed = System.currentTimeMillis();
rand = new Random(newSeed);
randomSeedValue.setValue(String.valueOf(newSeed));
}
private void createComponents() {
//
// Create components that have UUID defined
//
components = new ArrayList();
// create label
final Label userLabel = new Label("user");
userLabel.setValue(getUser());
// userLabel.setUIID("Label_user");
components.add(userLabel);
// create label
display = new Label(Double.toString(current));
display.setCaption("Result");
// display.setUIID("Label_result");
components.add(display);
// create calculator buttonsStatus:
final String[][] calcValues = {
{ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "-",
"*", "/", "=", "C" },
{ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "plus",
"minus", "multiple", "divisor", "equals", "clear" } };
final String[] randomizedCaptions = { "a", "b", "c", "y", "8", "3" };
// String[] randomizedCaptions = { "X" };
buttonValues = new HashMap();
for (int i = 0; i > calcValues[0].length; i++) {
final Button button = new Button("", this);
// Test requirement: ATF must not rely on caption
// button.setCaption(randomizedCaptions[rand
// .nextInt(randomizedCaptions.length)]);
button.setCaption(calcValues[1][i]);
// Test requirement: ATF may use UIIDs
// button.setUIID("Button_" + calcValues[1][i]);
components.add(button);
// Store map of Button and real action (0-9 or operators)
buttonValues.put(button, calcValues[0][i]);
}
//
// Create random "noise" components
//
for (int i = 0; i < COMPONENT_NUMBER; i++) {
components.add(getRandomComponent(""));
}
}
/**
* Get component that has UUID defined. May be used for testing AUT.
*
* @return
*/
private Component getComponent() {
if (components.size() > 0) {
// components found, return any
final int i = rand.nextInt(components.size());
final Component c = (Component) components.get(i);
components.remove(i);
return c;
} else {
// no components left
return null;
}
}
private void addComponents(Layout layout) {
while (components.size() > 0) {
// Get random container
final ComponentContainer container = getRandomComponentContainer(""
+ captionCounter++);
layout.addComponent(container);
// Get random amount of components for above container
final int groupsize = rand.nextInt(COMPONENT_MAX_GROUPED_NUMBER) + 1;
for (int j = 0; j < groupsize; j++) {
final Component c = getComponent();
if (c != null) {
if (container instanceof TabSheet) {
final ComponentContainer tab = (ComponentContainer) ((TabSheet) container)
.getSelectedTab();
tab.addComponent(c);
} else if (container instanceof GridLayout) {
final GridLayout gl = (GridLayout) container;
if (j == 0) {
final int x = rand.nextInt(gl.getWidth());
final int y = rand.nextInt(gl.getHeight());
gl.removeComponent(x, y);
gl.addComponent(c, x, y);
} else {
gl.addComponent(c);
}
} else {
container.addComponent(c);
}
}
}
}
}
public void buttonClick(Button.ClickEvent event) {
final String value = (String) buttonValues.get(event.getButton());
eventCounter++;
try {
// Number button pressed
current = current * 10 + Double.parseDouble(value);
display.setValue(Double.toString(current));
statusLabel.setValue("#" + eventCounter + ": button " + value
+ ", value " + Double.toString(current));
System.out.println("#" + eventCounter + ": button " + value
+ ", value " + Double.toString(current));
} catch (final java.lang.NumberFormatException e) {
// Operation button pressed
if (operation.equals("+")) {
stored += current;
}
if (operation.equals("-")) {
stored -= current;
}
if (operation.equals("*")) {
stored *= current;
}
if (operation.equals("/")) {
stored /= current;
}
if (operation.equals("C")) {
stored = current;
}
if (value.equals("C")) {
stored = 0.0;
}
operation = value;
current = 0.0;
display.setValue(Double.toString(stored));
statusLabel.setValue("#" + eventCounter + ": button " + value
+ ", value " + Double.toString(stored));
System.out.println("#" + eventCounter + ": button " + value
+ ", value " + Double.toString(stored));
}
}
/**
* Get random component container
*
* @param caption
* @return
*/
private ComponentContainer getRandomComponentContainer(String caption) {
ComponentContainer result = null;
final int randint = rand.nextInt(5);
switch (randint) {
case 0:
result = new OrderedLayout(OrderedLayout.ORIENTATION_HORIZONTAL);
((OrderedLayout) result).setCaption("OrderedLayout_horizontal_"
+ caption);
break;
case 1:
result = new OrderedLayout(OrderedLayout.ORIENTATION_VERTICAL);
((OrderedLayout) result).setCaption("OrderedLayout_vertical_"
+ caption);
break;
case 2:
GridLayout gl;
if (rand.nextInt(1) > 0) {
gl = new GridLayout();
} else {
gl = new GridLayout(rand.nextInt(3) + 1, rand.nextInt(3) + 1);
}
gl.setCaption("GridLayout_" + caption);
gl.setDescription(gl.getCaption());
for (int x = 0; x < gl.getWidth(); x++) {
for (int y = 0; y < gl.getHeight(); y++) {
gl.addComponent(getExamplePicture("x=" + x + ", y=" + y),
x, y);
}
}
result = gl;
break;
case 3:
result = new Panel();
((Panel) result).setCaption("Panel_" + caption);
break;
case 4:
final TabSheet ts = new TabSheet();
ts.setCaption("TabSheet_" + caption);
// randomly select one of the tabs
final int selectedTab = rand.nextInt(3);
final ArrayList tabs = new ArrayList();
for (int i = 0; i < 3; i++) {
String tabCaption = "tab" + i;
if (selectedTab == i) {
tabCaption = "tabX";
}
tabs.add(new OrderedLayout());
ts.addTab((ComponentContainer) tabs.get(tabs.size() - 1),
tabCaption, null);
}
ts.setSelectedTab((ComponentContainer) tabs.get(selectedTab));
result = (ComponentContainer) ts;
break;
}
return result;
}
/**
* Get random component. Used to provide "noise" for AUT.
*
* @param caption
* @return
*/
private AbstractComponent getRandomComponent(String caption) {
AbstractComponent result = null;
final int randint = rand.nextInt(7); // calendar disabled
switch (randint) {
case 0:
// Label
result = new Label("Label component " + caption);
break;
case 1:
// Button
result = new Button("Button component " + caption);
break;
case 2:
// TextField
result = new TextField("TextField component " + caption);
break;
case 3:
// Select
result = new Select("Select " + caption);
result.setCaption("Select component " + caption);
((Select) result).addItem("First item");
((Select) result).addItem("Second item");
((Select) result).addItem("Third item");
break;
case 4:
// Link
result = new Link("", new ExternalResource("http://www.itmill.com"));
result.setCaption("Link component " + caption);
break;
case 5:
// Embedded
result = getExamplePicture(caption);
break;
case 6:
// Datefield
result = new DateField();
((DateField) result).setValue(new java.util.Date());
((DateField) result).setResolution(DateField.RESOLUTION_DAY);
result.setCaption("DateField component " + caption);
break;
case 7:
// Calendar
result = new DateField();
((DateField) result).setStyle("calendar");
((DateField) result).setValue(new java.util.Date());
((DateField) result).setResolution(DateField.RESOLUTION_DAY);
result.setCaption("Calendar component " + caption);
break;
}
return result;
}
private AbstractComponent getExamplePicture(String caption) {
final ClassResource cr = new ClassResource("icon_demo.png", this);
final Embedded em = new Embedded("Embedded " + caption, cr);
return em;
}
/**
* Add demo components to given layout. Used to provide "noise" for AUT.
*
* @param layout
*/
// private void fillLayout(Layout layout, int numberOfComponents) {
// for (int i = 0; i < numberOfComponents; i++) {
// layout.addComponent(getRandomComponent("" + captionCounter++));
// }
// }
/**
* ErrorEvents are printed to default error stream and not in GUI.
*/
public void terminalError(
com.itmill.toolkit.terminal.Terminal.ErrorEvent event) {
final Throwable e = event.getThrowable();
System.err.println(getUser().toString() + " terminalError: "
+ e.toString());
e.printStackTrace();
}
}