initial commit for regression testing

This commit is contained in:
bancika
2020-04-19 16:50:25 +02:00
parent ac606bb472
commit 7754922711
26 changed files with 858 additions and 20 deletions
@@ -17,6 +17,8 @@
*/
package org.diylc.common;
import java.io.Serializable;
import javax.swing.Icon;
import org.diylc.core.CreationMethod;
@@ -31,8 +33,11 @@ import org.diylc.core.annotations.KeywordPolicy;
*
* @see IDIYComponent
*/
public class ComponentType {
public class ComponentType implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String description;
private CreationMethod creationMethod;
@@ -49,6 +54,9 @@ public class ComponentType {
private KeywordPolicy keywordPolicy;
private String keywordTag;
private boolean enableCache;
public ComponentType() {
}
public ComponentType(String name, String description, CreationMethod creationMethod, String category,
String namePrefix, String author, Icon icon, Class<? extends IDIYComponent<?>> instanceClass, double zOrder,
@@ -40,6 +40,7 @@ import org.diylc.core.IDIYComponent;
import org.diylc.core.Project;
import org.diylc.core.Template;
import org.diylc.core.Theme;
import org.diylc.test.ITestProcessor;
/**
* Interface for communication between plug-ins and the application. Plug-ins may acquire
@@ -54,7 +55,7 @@ import org.diylc.core.Theme;
* @see EventType
*/
public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyProcessor, IVariantProcessor,
IBlockProcessor, INetlistProcessor {
IBlockProcessor, INetlistProcessor, ITestProcessor {
public static final String ANTI_ALIASING_KEY = "antiAliasing";
public static final String HI_QUALITY_RENDER_KEY = "hiQualityRender";
@@ -195,11 +195,12 @@ public abstract class AbstractComponent<T> implements IDIYComponent<T> {
Field[] fields = clazz.getDeclaredFields();
clazz = clazz.getSuperclass();
// fields = this.getClass().getDeclaredFields();
// Copy over all non-static, non-final fields that are declared
// Copy over all non-static, non-final and non-transient fields that are declared
// in
// AbstractComponent or one of it's child classes
for (Field field : fields) {
if (!Modifier.isStatic(field.getModifiers()) && !Modifier.isFinal(field.getModifiers())) {
if (!Modifier.isStatic(field.getModifiers()) && !Modifier.isFinal(field.getModifiers()) &&
!Modifier.isTransient(field.getModifiers())) {
field.setAccessible(true);
try {
Object value = field.get(this);
@@ -56,7 +56,9 @@ public interface IView {
int showConfirmDialog(String message, String title, int optionType, int messageType);
boolean editProperties(List<PropertyWrapper> properties, Set<PropertyWrapper> defaultedProperties);
boolean editProperties(List<PropertyWrapper> properties, Set<PropertyWrapper> defaultedProperties);
String showInputDialog(String message, String title);
File promptFileSave();
}
@@ -277,7 +277,7 @@ public class Project implements Serializable, Cloneable {
if (font == null) {
if (other.font != null)
return false;
} else if (!font.equals(other.font))
} else if (!font.getFamily().equals(other.font.getFamily()) || font.getSize() != other.font.getSize() || font.getStyle() != other.font.getStyle())
return false;
if (groups == null) {
if (other.groups != null)
@@ -0,0 +1,135 @@
/*
DIY Layout Creator (DIYLC).
Copyright (c) 2009-2018 held jointly by the individual authors.
This file is part of DIYLC.
DIYLC is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
DIYLC 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diylc.presenter;
import java.awt.Point;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.util.ArrayList;
import java.util.List;
import java.awt.geom.Path2D.Double;
import org.diylc.utils.Constants;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
/**
* Serializes {@link Point} objects by converting coordinates from pixels to inches, thus avoiding
* the resolution to affect point placement.
*
* @author Branislav Stojkovic
*/
public class AreaConverter implements Converter {
@Override
public void marshal(Object object, HierarchicalStreamWriter writer, MarshallingContext context) {
Area area = (Area) object;
PathIterator iterator = area.getPathIterator(null);
// writer.startNode("area");
writer.addAttribute("wind", String.valueOf(iterator.getWindingRule()));
while (!iterator.isDone()) {
writer.startNode("segment");
double[] coord = new double[6];
int type = iterator.currentSegment(coord);
writer.addAttribute("type", String.valueOf(type));
int n = 0;
switch (type) {
case PathIterator.SEG_MOVETO:
case PathIterator.SEG_LINETO:
n = 2;
break;
case PathIterator.SEG_CUBICTO:
n = 6;
break;
case PathIterator.SEG_QUADTO:
n = 4;
break;
}
for (int i = 0; i < n; i++) {
writer.startNode("coordinate");
writer.setValue(String.valueOf(coord[i]));
writer.endNode();
}
writer.endNode();
iterator.next();
}
// writer.endNode();
}
@Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Area area = new Area();
// reader.moveDown();
int wind = Integer.parseInt(reader.getAttribute("wind"));
Path2D path = null;
while (reader.hasMoreChildren()) {
reader.moveDown();
int type = Integer.parseInt(reader.getAttribute("type"));
List<java.lang.Double> values = new ArrayList<java.lang.Double>();
while (reader.hasMoreChildren()) {
reader.moveDown();
values.add(java.lang.Double.parseDouble(reader.getValue()));
reader.moveUp();
}
java.lang.Double[] coord = values.toArray(new java.lang.Double[0]);
switch (type) {
case PathIterator.SEG_MOVETO:
if (path != null) {
Area partArea = new Area(path);
area.add(partArea);
}
path = new Path2D.Double(wind);
path.moveTo(coord[0], coord[1]);
break;
case PathIterator.SEG_LINETO:
path.lineTo(coord[0], coord[1]);
break;
case PathIterator.SEG_CUBICTO:
path.curveTo(coord[0], coord[1], coord[2], coord[3], coord[4], coord[5]);
break;
case PathIterator.SEG_QUADTO:
path.quadTo(coord[0], coord[1], coord[2], coord[3]);
break;
}
reader.moveUp();
}
// reader.moveUp();
// add residual area
if (path != null) {
Area partArea = new Area(path);
area.add(partArea);
}
return area;
}
@SuppressWarnings("rawtypes")
@Override
public boolean canConvert(Class clazz) {
return Area.class.isAssignableFrom(clazz);
}
}
@@ -22,13 +22,19 @@
package org.diylc.presenter;
import java.awt.geom.Area;
import java.io.Serializable;
import java.util.Collection;
public class ComponentArea {
public class ComponentArea implements Serializable {
private static final long serialVersionUID = 1L;
private Area outlineArea;
private Collection<Area> continuityPositiveAreas;
private Collection<Area> continuityNegativeAreas;
public ComponentArea() {
}
public ComponentArea(Area outlineArea, Collection<Area> continuityPositiveAreas, Collection<Area> continuityNegativeAreas) {
super();
@@ -53,6 +53,7 @@ import java.awt.image.renderable.RenderableImage;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.AttributedCharacterIterator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@@ -159,7 +160,7 @@ class G2DWrapper extends Graphics2D implements IDrawingObserver {
canvasGraphics.setComposite(originalComposite);
canvasGraphics.setFont(originalFont);
// Map<String, Integer> sorted = sortByComparator(trackingStacks, false);
return new ComponentArea(currentArea, continuityPositiveAreas.values(), continuityNegativeAreas.values());
return new ComponentArea(currentArea, new ArrayList<Area>(continuityPositiveAreas.values()), new ArrayList<Area>(continuityNegativeAreas.values()));
}
// start - used for caching
@@ -87,6 +87,8 @@ import org.diylc.netlist.NetlistAnalyzer;
import org.diylc.netlist.Node;
import org.diylc.netlist.Position;
import org.diylc.netlist.SwitchSetup;
import org.diylc.test.DIYTest;
import org.diylc.test.Snapshot;
import org.diylc.utils.Constants;
import com.thoughtworks.xstream.XStream;
@@ -175,6 +177,8 @@ public class Presenter implements IPlugInPort {
private Project preDragProject = null;
private int dragAction;
private Point previousScaledPoint;
private DIYTest test = null;
public Presenter(IView view) {
super();
@@ -547,6 +551,19 @@ public class Presenter implements IPlugInPort {
@Override
public void mouseClicked(Point point, int button, boolean ctrlDown, boolean shiftDown, boolean altDown, int clickCount) {
LOG.debug(String.format("mouseClicked(%s, %s, %s, %s, %s)", point, button, ctrlDown, shiftDown, altDown));
// record a test step if needed
if (test != null) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("point", point);
params.put("button", button);
params.put("ctrlDown", ctrlDown);
params.put("shiftDown", shiftDown);
params.put("altDown", altDown);
params.put("clickCount", clickCount);
test.addStep(DIYTest.MOUSE_CLICKED, params);
}
Point scaledPoint = scalePoint(point);
if (clickCount >= 2) {
editSelection();
@@ -827,6 +844,16 @@ public class Presenter implements IPlugInPort {
public void mouseMoved(Point point, boolean ctrlDown, boolean shiftDown, boolean altDown) {
if (point == null)
return;
// record a test step if needed
if (test != null) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("point", point);
params.put("ctrlDown", ctrlDown);
params.put("shiftDown", shiftDown);
params.put("altDown", altDown);
test.addStep(DIYTest.MOUSE_MOVED, params);
}
if (shiftDown) {
dragAction = IPlugInPort.DND_TOGGLE_SNAP;
@@ -1035,6 +1062,16 @@ public class Presenter implements IPlugInPort {
@Override
public void dragStarted(Point point, int dragAction, boolean forceSelectionRect) {
LOG.debug(String.format("dragStarted(%s, %s)", point, dragAction));
// record a test step if needed
if (test != null) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("point", point);
params.put("dragAction", dragAction);
params.put("forceSelectionRect", forceSelectionRect);
test.addStep(DIYTest.DRAG_START, params);
}
if (instantiationManager.getComponentTypeSlot() != null) {
LOG.debug("Cannot start drag because a new component is being created.");
mouseClicked(point, IPlugInPort.BUTTON1, dragAction == DnDConstants.ACTION_COPY,
@@ -1051,7 +1088,7 @@ public class Presenter implements IPlugInPort {
Point scaledPoint = scalePoint(point);
this.previousDragPoint = scaledPoint;
List<IDIYComponent<?>> components = forceSelectionRect ? null : findComponentsAtScaled(scaledPoint);
if (!this.controlPointMap.isEmpty()) {
if (controlPointMap != null && !this.controlPointMap.isEmpty()) {
// If we're dragging control points reset selection.
updateSelection(new ArrayList<IDIYComponent<?>>(this.controlPointMap.keySet()));
// messageDispatcher.dispatchMessage(EventType.SELECTION_CHANGED,
@@ -1193,11 +1230,18 @@ public class Presenter implements IPlugInPort {
@Override
public boolean dragOver(Point point) {
// record a test step if needed
if (test != null) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("point", point);
test.addStep(DIYTest.DRAG_OVER, params);
}
if (point == null || ConfigurationManager.getInstance().readBoolean(HIGHLIGHT_CONTINUITY_AREA, false)) {
return false;
}
Point scaledPoint = scalePoint(point);
if (!controlPointMap.isEmpty()) {
if (controlPointMap != null && !controlPointMap.isEmpty()) {
// We're dragging control point(s).
int dx = (scaledPoint.x - previousDragPoint.x);
int dy = (scaledPoint.y - previousDragPoint.y);
@@ -1522,6 +1566,14 @@ public class Presenter implements IPlugInPort {
@Override
public void dragEnded(Point point) {
LOG.debug(String.format("dragEnded(%s)", point));
// record a test step if needed
if (test != null) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("point", point);
params.put("dragAction", dragAction);
test.addStep(DIYTest.DRAG_END, params);
}
Point scaledPoint = scalePoint(point);
@@ -2320,7 +2372,17 @@ public class Presenter implements IPlugInPort {
@Override
public void setNewComponentTypeSlot(ComponentType componentType, Template template, boolean forceInstatiate) {
LOG.info(String.format("setNewComponentSlot(%s)", componentType == null ? null : componentType.getName()));
LOG.info(String.format("setNewComponentSlot(%s)", componentType == null ? null : componentType.getName()));
// record a test step if needed
if (test != null) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("componentType", componentType);
params.put("template", template);
params.put("forceInstatiate", forceInstatiate);
test.addStep(DIYTest.SET_COMPONENT_SLOT, params);
}
if (componentType != null && componentType.getInstanceClass() == null) {
LOG.info("Cannot set new component type slot for type " + componentType.getName());
setNewComponentTypeSlot(null, null, false);
@@ -3214,4 +3276,34 @@ public class Presenter implements IPlugInPort {
return null;
}
}
// Test stuff
@Override
public void startRecording(String name) {
test = new DIYTest(name);
}
@Override
public DIYTest stopRecording() {
DIYTest res = test;
test = null;
return res;
}
@Override
public void addValidation(Map<String, Object> params) {
if (test != null)
test.addStep(DIYTest.VALIDATION, params);
}
@Override
public Snapshot createTestSnapshot() {
Map<IDIYComponent<?>, ComponentArea> areas = new HashMap<IDIYComponent<?>, ComponentArea>();
for (IDIYComponent<?> c : currentProject.getComponents()) {
ComponentArea componentArea = drawingManager.getComponentArea(c);
areas.put(c, componentArea);
}
return new Snapshot(currentProject.clone(), new HashSet<IDIYComponent<?>>(selectedComponents), areas, new HashSet<Area>(drawingManager.getContinuityAreas(currentProject)));
}
}
@@ -48,6 +48,7 @@ import org.diylc.appframework.update.VersionNumber;
import org.diylc.common.EventType;
import org.diylc.core.Project;
import org.diylc.parsing.IOldFileParser;
import org.diylc.test.DIYTest;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
@@ -104,6 +105,7 @@ public class ProjectFileManager {
xStream.registerConverter(new ColorConverter());
xStream.registerConverter(new FontConverter());
xStream.registerConverter(new MeasureConverter());
xStream.registerConverter(new AreaConverter());
xStream.addImmutableType(Color.class);
xStream.addImmutableType(java.awt.Point.class);
xStream.addImmutableType(org.diylc.core.measures.Voltage.class);
@@ -192,6 +194,27 @@ public class ProjectFileManager {
this.currentFileName = fileName;
this.modified = false;
return project;
}
public void serializeTestToFile(DIYTest test, String fileName) throws IOException {
LOG.info(String.format("serializeTestToFile(%s)", fileName));
FileOutputStream fos;
fos = new FileOutputStream(fileName);
Writer writer = new OutputStreamWriter(fos, "UTF-8");
writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
xStream.toXML(test, writer);
fos.close();
}
public DIYTest deserializeTestFromFile(String fileName)
throws SAXException, IOException, ParserConfigurationException {
LOG.info(String.format("deserializeTestFromFile(%s)", fileName));
DIYTest project = null;
FileInputStream fis = new FileInputStream(fileName);
Reader reader = new InputStreamReader(fis, "UTF-8");
project = (DIYTest) xStream.fromXML(reader);
fis.close();
return project;
}
public void notifyFileChange() {
@@ -0,0 +1,78 @@
package org.diylc.test;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class DIYTest implements Serializable {
private static final long serialVersionUID = 1L;
public static final String MOUSE_MOVED = "mouseMoved";
public static final String MOUSE_CLICKED = "mouseClicked";
public static final String SET_COMPONENT_SLOT = "setComponentSlot";
public static final String VALIDATION = "validation";
public static final String DRAG_START = "dragStart";
public static final String DRAG_OVER = "dragOver";
public static final String DRAG_END = "dragEnd";
// validations
public static final String SNAPSHOT = "snapshot";
public static final String COMPLETE = "complete";
public static final String PROJECT = "project";
public static final String SELECTION = "selection";
private String name;
private List<TestStep> steps;
public DIYTest() {
}
public DIYTest(String name) {
this.name = name;
steps = new ArrayList<DIYTest.TestStep>();
}
public void addStep(String action, Map<String, Object> params) {
steps.add(new TestStep(action, params));
}
public String getName() {
return name;
}
public List<TestStep> getSteps() {
return steps;
}
public class TestStep implements Serializable {
private static final long serialVersionUID = 1L;
private String action;
private Map<String, Object> params;
public TestStep() {
}
public TestStep(String action, Map<String, Object> params) {
super();
this.action = action;
this.params = params;
}
public String getAction() {
return action;
}
public Map<String, Object> getParams() {
return params;
}
@Override
public String toString() {
return action;
}
}
}
@@ -0,0 +1,35 @@
/*
DIY Layout Creator (DIYLC).
Copyright (c) 2009-2018 held jointly by the individual authors.
This file is part of DIYLC.
DIYLC is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
DIYLC 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diylc.test;
import java.util.Map;
public interface ITestProcessor {
void startRecording(String name);
DIYTest stopRecording();
void addValidation(Map<String, Object> params);
Snapshot createTestSnapshot();
}
@@ -0,0 +1,76 @@
package org.diylc.test;
import java.awt.geom.Area;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.diylc.core.IDIYComponent;
import org.diylc.core.Project;
import org.diylc.presenter.ComponentArea;
public class Snapshot implements Serializable {
private static final long serialVersionUID = 1L;
private Project project;
private Set<IDIYComponent<?>> selection;
private Map<IDIYComponent<?>, ComponentArea> areas;
private Set<Area> continuityAreas;
public Snapshot() {
}
public Snapshot(Project project, Set<IDIYComponent<?>> selection,
Map<IDIYComponent<?>, ComponentArea> areas, Set<Area> continuityAreas) {
super();
this.project = project;
this.selection = selection;
this.areas = areas;
this.continuityAreas = continuityAreas;
}
public Project getProject() {
return project;
}
public Set<IDIYComponent<?>> getSelection() {
return selection;
}
public Map<IDIYComponent<?>, ComponentArea> getAreas() {
return areas;
}
public Set<Area> getContinuityAreas() {
return continuityAreas;
}
public boolean equalsTo(Snapshot other) {
if (!project.equals(other.getProject()))
return false;
if (selection == null) {
if (other.selection != null)
return false;
} else if (selection.size() != other.selection.size()) {
return false;
} else {
// IDIYComponent<?> c1= selection.iterator().next();
// IDIYComponent<?> c2=other.selection.iterator().next();
// if (!c1.equalsTo(c2))
// return false;
// for(IDIYComponent<?> c : selection)
// if (!other.selection.contains(c))
// return false;
// for(IDIYComponent<?> c : other.selection)
// if (!selection.contains(c))
// return false;
}
// if (!areas.equals(other.getAreas()))
// return false;
// if (!continuityAreas.equals(other.getContinuityAreas()))
// return false;
return true;
}
}
@@ -0,0 +1,112 @@
package org.diylc.test;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.diylc.common.ComponentType;
import org.diylc.common.IPlugInPort;
import org.diylc.core.Template;
import org.diylc.test.DIYTest.TestStep;
public class TestPlayer {
private static final Logger LOG = Logger.getLogger(TestPlayer.class);
public static final int DELAY_MS = 5;
public List<StepResult> play(IPlugInPort plugInPort, DIYTest test) {
LOG.info("Playing test: " + test.getName());
List<TestStep> steps = test.getSteps();
List<StepResult> res = new ArrayList<TestPlayer.StepResult>();
for (int i = 0; i < steps.size(); i++) {
TestStep step = steps.get(i);
try {
switch(step.getAction()) {
case DIYTest.SET_COMPONENT_SLOT:
plugInPort.setNewComponentTypeSlot((ComponentType)step.getParams().get("componentType"), (Template)step.getParams().get("template"), (boolean)step.getParams().get("forceInstatiate"));
res.add(new StepResult(i, step.getAction(), "OK", false));
break;
case DIYTest.MOUSE_MOVED:
plugInPort.mouseMoved((Point)step.getParams().get("point"), (boolean)step.getParams().get("ctrlDown"), (boolean)step.getParams().get("shiftDown"), (boolean)step.getParams().get("altDown"));
res.add(new StepResult(i, step.getAction(), "OK", false));
break;
case DIYTest.MOUSE_CLICKED:
plugInPort.mouseClicked((Point)step.getParams().get("point"), (int)step.getParams().get("button"), (boolean)step.getParams().get("ctrlDown"),
(boolean)step.getParams().get("shiftDown"), (boolean)step.getParams().get("altDown"), (int)step.getParams().get("clickCount"));
res.add(new StepResult(i, step.getAction(), "OK", false));
break;
case DIYTest.DRAG_START:
plugInPort.dragStarted((Point)step.getParams().get("point"), (int)step.getParams().get("dragAction"), (boolean)step.getParams().get("forceSelectionRect"));
res.add(new StepResult(i, step.getAction(), "OK", false));
break;
case DIYTest.DRAG_OVER:
plugInPort.dragOver((Point)step.getParams().get("point"));
res.add(new StepResult(i, step.getAction(), "OK", false));
break;
case DIYTest.DRAG_END:
plugInPort.dragEnded((Point)step.getParams().get("point"));
res.add(new StepResult(i, step.getAction(), "OK", false));
break;
case DIYTest.VALIDATION:
res.add(runValidation(i, plugInPort, step.getParams()));
break;
default:
res.add(new StepResult(i, step.getAction(), "Invalid action: " + step.getAction(), true));
}
} catch (Exception e) {
res.add(new StepResult(i, step.getAction(), "Step failed with error: " + e.getMessage(), true));
}
try {
Thread.sleep(DELAY_MS);
} catch (InterruptedException e) {
}
}
LOG.info("Finished test: " + test.getName());
return res;
}
private StepResult runValidation(int step, IPlugInPort plugInPort, Map<String, Object> params) {
if (params.containsKey(DIYTest.SNAPSHOT)) {
Snapshot snap = (Snapshot) params.get(DIYTest.SNAPSHOT);
if (!snap.equalsTo(plugInPort.createTestSnapshot())) {
return new StepResult(step, DIYTest.VALIDATION, "Snapshots do not match", true);
}
}
return new StepResult(step, DIYTest.VALIDATION, "OK", false);
}
public class StepResult {
private int step;
private String action;
private String status;
private boolean hasError;
public StepResult(int step, String action, String status, boolean hasError) {
super();
this.step = step;
this.action = action;
this.status = status;
this.hasError = hasError;
}
public int getStep() {
return step;
}
public String getAction() {
return action;
}
public String getStatus() {
return status;
}
public boolean hasError() {
return hasError;
}
}
}