Initial implementation of Test Planner Test View.
Trying again because generated patch was malformed.
Signed-off-by: James Ren <jamesren@google.com>
git-svn-id: http://test.kernel.org/svn/autotest/trunk@4556 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/frontend/afe/frontend_test_utils.py b/frontend/afe/frontend_test_utils.py
index 5aba4f4..34a3e59 100644
--- a/frontend/afe/frontend_test_utils.py
+++ b/frontend/afe/frontend_test_utils.py
@@ -63,7 +63,7 @@
def _frontend_common_setup(self, fill_data=True):
- self.god = mock.mock_god()
+ self.god = mock.mock_god(ut=self)
setup_test_environment.set_up()
if fill_data:
self._fill_in_test_data()
diff --git a/frontend/client/src/autotest/common/JsonManipulator.java b/frontend/client/src/autotest/common/JsonManipulator.java
index 8dfdd86..e69de29 100644
--- a/frontend/client/src/autotest/common/JsonManipulator.java
+++ b/frontend/client/src/autotest/common/JsonManipulator.java
@@ -1,21 +0,0 @@
-package autotest.common;
-
-import com.google.gwt.json.client.JSONArray;
-import com.google.gwt.json.client.JSONObject;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class JsonManipulator {
- public static interface IFactory<T> {
- public T fromJsonObject(JSONObject object);
- }
-
- public static <T> List<T> createList(JSONArray objects, IFactory<T> factory) {
- List<T> list = new ArrayList<T>();
- for (JSONObject object : new JSONArrayList<JSONObject>(objects)) {
- list.add(factory.fromJsonObject(object));
- }
- return list;
- }
-}
diff --git a/frontend/client/src/autotest/common/Utils.java b/frontend/client/src/autotest/common/Utils.java
index d6c9b55..2991190 100644
--- a/frontend/client/src/autotest/common/Utils.java
+++ b/frontend/client/src/autotest/common/Utils.java
@@ -3,6 +3,7 @@
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.http.client.URL;
+import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONNumber;
import com.google.gwt.json.client.JSONObject;
@@ -13,6 +14,7 @@
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -39,7 +41,7 @@
}
return result;
}
-
+
/**
* Converts a collection of Java <code>Integers</code>s into a <code>JSONArray
* </code> of <code>JSONNumber</code>s.
@@ -53,7 +55,7 @@
}
/**
- * Converts a <code>JSONArray</code> of <code>JSONStrings</code> to an
+ * Converts a <code>JSONArray</code> of <code>JSONStrings</code> to an
* array of Java <code>Strings</code>.
*/
public static String[] JSONtoStrings(JSONArray strings) {
@@ -65,7 +67,7 @@
}
/**
- * Converts a <code>JSONArray</code> of <code>JSONObjects</code> to an
+ * Converts a <code>JSONArray</code> of <code>JSONObjects</code> to an
* array of Java <code>Strings</code> by grabbing the specified field from
* each object.
*/
@@ -77,7 +79,7 @@
}
return result;
}
-
+
public static JSONObject mapToJsonObject(Map<String, String> map) {
JSONObject result = new JSONObject();
for (Map.Entry<String, String> entry : map.entrySet()) {
@@ -86,6 +88,14 @@
return result;
}
+ public static Map<String, String> jsonObjectToMap(JSONObject object) {
+ Map<String, String> result = new HashMap<String, String>();
+ for (String key : object.keySet()) {
+ result.put(key, jsonToString(object.get(key)));
+ }
+ return result;
+ }
+
/**
* Get a value out of a JSONObject list of size 1.
* @return list[0]
@@ -97,7 +107,7 @@
}
return list.get(0);
}
-
+
public static JSONObject getSingleObjectFromArray(JSONArray array) {
return getSingleObjectFromList(new JSONArrayList<JSONObject>(array));
}
@@ -109,14 +119,14 @@
}
return dest;
}
-
+
public static String escape(String text) {
for (String[] mapping : escapeMappings) {
text = text.replace(mapping[0], mapping[1]);
}
return text;
}
-
+
public static String unescape(String text) {
// must iterate in reverse order
for (int i = escapeMappings.length - 1; i >= 0; i--) {
@@ -124,13 +134,13 @@
}
return text;
}
-
+
public static <T> List<T> wrapObjectWithList(T object) {
List<T> list = new ArrayList<T>();
list.add(object);
return list;
}
-
+
public static <T> String joinStrings(String joiner, List<T> objects, boolean wantBlanks) {
StringBuilder result = new StringBuilder();
boolean first = true;
@@ -148,12 +158,12 @@
}
return result.toString();
}
-
+
public static <T> String joinStrings(String joiner, List<T> objects) {
return joinStrings(joiner, objects, false);
}
-
- public static Map<String,String> decodeUrlArguments(String urlArguments,
+
+ public static Map<String,String> decodeUrlArguments(String urlArguments,
Map<String, String> arguments) {
String[] components = urlArguments.split("&");
for (String component : components) {
@@ -174,7 +184,7 @@
private static String decodeComponent(String component) {
return URL.decodeComponent(component.replace("%27", "'"));
}
-
+
public static String encodeUrlArguments(Map<String, String> arguments) {
List<String> components = new ArrayList<String>();
for (Map.Entry<String, String> entry : arguments.entrySet()) {
@@ -214,7 +224,7 @@
return Integer.toString((int) doubleValue);
}
return Double.toString(doubleValue);
-
+
}
if (value.isNull() != null) {
return JSON_NULL;
@@ -229,7 +239,7 @@
map.put(key, defaultValue);
return defaultValue;
}
-
+
public static JSONValue setDefaultValue(JSONObject object, String key, JSONValue defaultValue) {
if (object.containsKey(key)) {
return object.get(key);
@@ -237,7 +247,7 @@
object.put(key, defaultValue);
return defaultValue;
}
-
+
public static List<String> splitList(String list, String splitRegex) {
String[] parts = list.split(splitRegex);
List<String> finalParts = new ArrayList<String>();
@@ -248,11 +258,11 @@
}
return finalParts;
}
-
+
public static List<String> splitList(String list) {
return splitList(list, ",");
}
-
+
public static List<String> splitListWithSpaces(String list) {
return splitList(list, "[,\\s]+");
}
@@ -269,15 +279,39 @@
public static void openUrlInNewWindow(String url) {
Window.open(url, "_blank", "");
}
-
+
public static HTMLPanel divToPanel(String elementId) {
Element divElement = Document.get().getElementById(elementId);
divElement.getParentElement().removeChild(divElement);
return new HTMLPanel(divElement.getInnerHTML());
}
-
+
public static void setElementVisible(String elementId, boolean visible) {
String display = visible ? "block" : "none";
Document.get().getElementById(elementId).getStyle().setProperty("display", display);
}
+
+ public static String percentage(int num, int total) {
+ if (total == 0) {
+ return "N/A";
+ }
+ NumberFormat format = NumberFormat.getFormat("##0.#%");
+ return format.format(num / (double) total);
+ }
+
+ public static String numberAndPercentage(int num, int total) {
+ return num + " (" + percentage(num, total) + ")";
+ }
+
+ public static interface JsonObjectFactory<T> {
+ public T fromJsonObject(JSONObject object);
+ }
+
+ public static <T> List<T> createList(JSONArray objects, JsonObjectFactory<T> factory) {
+ List<T> list = new ArrayList<T>();
+ for (JSONObject object : new JSONArrayList<JSONObject>(objects)) {
+ list.add(factory.fromJsonObject(object));
+ }
+ return list;
+ }
}
diff --git a/frontend/client/src/autotest/planner/TestPlannerClient.java b/frontend/client/src/autotest/planner/TestPlannerClient.java
index 4a60e76..46e2949 100644
--- a/frontend/client/src/autotest/planner/TestPlannerClient.java
+++ b/frontend/client/src/autotest/planner/TestPlannerClient.java
@@ -8,6 +8,7 @@
import autotest.common.ui.NotifyManager;
import autotest.planner.machine.MachineViewTab;
import autotest.planner.overview.OverviewTab;
+import autotest.planner.test.TestViewTab;
import autotest.planner.triage.TriageViewTab;
import com.google.gwt.core.client.EntryPoint;
@@ -20,11 +21,9 @@
private OverviewTab overviewTab = new OverviewTab(planSelector);
private MachineViewTab machineViewTab = new MachineViewTab(planSelector);
+ private TestViewTab testViewTab = new TestViewTab(planSelector);
private TriageViewTab triageViewTab = new TriageViewTab(planSelector);
- private TestViewTab testViewTab = new TestViewTab();
- private TestViewTabDisplay testViewTabDisplay = new TestViewTabDisplay();
-
private AutoprocessedTab autoprocessedTab = new AutoprocessedTab();
private AutoprocessedTabDisplay autoprocessedTabDisplay = new AutoprocessedTabDisplay();
@@ -49,7 +48,6 @@
private void finishLoading() {
SiteCommonClassFactory.globalInitialize();
- testViewTab.bindDisplay(testViewTabDisplay);
autoprocessedTab.bindDisplay(autoprocessedTabDisplay);
historyTab.bindDisplay(historyTabDisplay);
@@ -59,7 +57,7 @@
mainTabPanel.addTabView(overviewTab);
mainTabPanel.addTabView(machineViewTab);
- mainTabPanel.addTabView(testViewTabDisplay);
+ mainTabPanel.addTabView(testViewTab);
mainTabPanel.addTabView(triageViewTab);
mainTabPanel.addTabView(autoprocessedTabDisplay);
mainTabPanel.addTabView(historyTabDisplay);
diff --git a/frontend/client/src/autotest/planner/TestPlannerTableDisplay.java b/frontend/client/src/autotest/planner/TestPlannerTableDisplay.java
new file mode 100644
index 0000000..4c7070c
--- /dev/null
+++ b/frontend/client/src/autotest/planner/TestPlannerTableDisplay.java
@@ -0,0 +1,27 @@
+package autotest.planner;
+
+import autotest.common.AbstractStatusSummary;
+
+import java.util.Collection;
+
+public interface TestPlannerTableDisplay {
+
+ public static class RowDisplay {
+ String content;
+ String cssClass = AbstractStatusSummary.BLANK_COLOR;
+
+ public RowDisplay(String content) {
+ this.content = content;
+ }
+
+ public RowDisplay(String content, String cssClass) {
+ this.content = content;
+ this.cssClass = cssClass;
+ }
+ }
+
+ public void addRow(Collection<RowDisplay> rowData);
+ public void finalRender();
+ public void clearData();
+ public void setHeaders(Collection<String> headers);
+}
diff --git a/frontend/client/src/autotest/planner/TestPlannerTableImpl.java b/frontend/client/src/autotest/planner/TestPlannerTableImpl.java
new file mode 100644
index 0000000..7e6e909
--- /dev/null
+++ b/frontend/client/src/autotest/planner/TestPlannerTableImpl.java
@@ -0,0 +1,78 @@
+package autotest.planner;
+
+import autotest.common.Utils;
+
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.logical.shared.ResizeHandler;
+import com.google.gwt.gen2.table.client.FixedWidthFlexTable;
+import com.google.gwt.gen2.table.client.FixedWidthGrid;
+import com.google.gwt.gen2.table.client.ScrollTable;
+import com.google.gwt.gen2.table.client.AbstractScrollTable.ResizePolicy;
+import com.google.gwt.gen2.table.client.AbstractScrollTable.ScrollPolicy;
+import com.google.gwt.gen2.table.client.AbstractScrollTable.SortPolicy;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Composite;
+
+import java.util.Collection;
+
+public class TestPlannerTableImpl extends Composite
+ implements TestPlannerTableDisplay, ResizeHandler {
+
+ private FixedWidthFlexTable headerTable = new FixedWidthFlexTable();
+ private FixedWidthGrid dataTable = new FixedWidthGrid();
+ private ScrollTable scrollTable = new ScrollTable(dataTable, headerTable);
+
+ public TestPlannerTableImpl() {
+ scrollTable.setSortPolicy(SortPolicy.DISABLED);
+ scrollTable.setResizePolicy(ResizePolicy.UNCONSTRAINED);
+ scrollTable.setScrollPolicy(ScrollPolicy.BOTH);
+ dataTable.setSelectionEnabled(false);
+
+ scrollTable.setSize("100%", TestPlannerUtils.getHeightParam(Window.getClientHeight()));
+ scrollTable.setVisible(false);
+
+ initWidget(scrollTable);
+ }
+
+ public void addRow(Collection<RowDisplay> rowData) {
+ assert rowData.size() == dataTable.getColumnCount();
+
+ int row = dataTable.insertRow(dataTable.getRowCount());
+
+ int column = 0;
+ for (RowDisplay data : rowData) {
+ String content = Utils.escape(data.content);
+ content = content.replaceAll("\n", "<br />");
+
+ dataTable.setHTML(row, column, content);
+ dataTable.getCellFormatter().addStyleName(row, column++, data.cssClass);
+ }
+ }
+
+ public void finalRender() {
+ TestPlannerUtils.resizeScrollTable(scrollTable);
+ }
+
+ public void clearData() {
+ scrollTable.setVisible(false);
+ headerTable.clear();
+ dataTable.clear();
+ }
+
+ public void setHeaders(Collection<String> headers) {
+ int column = 0;
+ for (String header : headers) {
+ headerTable.setText(0, column, header);
+ scrollTable.setHeaderColumnTruncatable(column++, false);
+ }
+
+ dataTable.resize(0, headers.size());
+
+ scrollTable.setVisible(true);
+ }
+
+ @Override
+ public void onResize(ResizeEvent event) {
+ scrollTable.setHeight(TestPlannerUtils.getHeightParam(event.getHeight()));
+ }
+}
diff --git a/frontend/client/src/autotest/planner/TestViewTab.java b/frontend/client/src/autotest/planner/TestViewTab.java
index 0afcb37..e69de29 100644
--- a/frontend/client/src/autotest/planner/TestViewTab.java
+++ b/frontend/client/src/autotest/planner/TestViewTab.java
@@ -1,17 +0,0 @@
-package autotest.planner;
-
-
-public class TestViewTab {
-
- public static interface Display {
- // Not yet implemented
- }
-
- @SuppressWarnings("unused")
- private Display display;
-
- public void bindDisplay(Display display) {
- this.display = display;
- }
-
-}
diff --git a/frontend/client/src/autotest/planner/TestViewTabDisplay.java b/frontend/client/src/autotest/planner/TestViewTabDisplay.java
index 15f9d83..e69de29 100644
--- a/frontend/client/src/autotest/planner/TestViewTabDisplay.java
+++ b/frontend/client/src/autotest/planner/TestViewTabDisplay.java
@@ -1,13 +0,0 @@
-package autotest.planner;
-
-import autotest.common.ui.TabView;
-
-
-public class TestViewTabDisplay extends TabView implements TestViewTab.Display {
-
- @Override
- public String getElementId() {
- return "test_view";
- }
-
-}
diff --git a/frontend/client/src/autotest/planner/machine/MachineViewDisplay.java b/frontend/client/src/autotest/planner/machine/MachineViewDisplay.java
index af6f95f..7d6eb0d 100644
--- a/frontend/client/src/autotest/planner/machine/MachineViewDisplay.java
+++ b/frontend/client/src/autotest/planner/machine/MachineViewDisplay.java
@@ -2,7 +2,8 @@
import autotest.common.ui.NotifyManager;
import autotest.planner.TestPlannerDisplay;
-import autotest.planner.machine.MachineViewTable.Display;
+import autotest.planner.TestPlannerTableDisplay;
+import autotest.planner.TestPlannerTableImpl;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Panel;
@@ -30,8 +31,8 @@
}
@Override
- public Display generateMachineViewTableDisplay() {
- MachineViewTableDisplay display = new MachineViewTableDisplay();
+ public TestPlannerTableDisplay generateMachineViewTableDisplay() {
+ TestPlannerTableImpl display = new TestPlannerTableImpl();
container.add(display);
return display;
}
diff --git a/frontend/client/src/autotest/planner/machine/MachineViewPresenter.java b/frontend/client/src/autotest/planner/machine/MachineViewPresenter.java
index 495883c..fb76f74 100644
--- a/frontend/client/src/autotest/planner/machine/MachineViewPresenter.java
+++ b/frontend/client/src/autotest/planner/machine/MachineViewPresenter.java
@@ -3,6 +3,7 @@
import autotest.common.JsonRpcCallback;
import autotest.common.JsonRpcProxy;
import autotest.planner.TestPlannerPresenter;
+import autotest.planner.TestPlannerTableDisplay;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONString;
@@ -14,7 +15,7 @@
public static interface Display {
public void setLoading(boolean loading);
public void clearAllData();
- public MachineViewTable.Display generateMachineViewTableDisplay();
+ public TestPlannerTableDisplay generateMachineViewTableDisplay();
}
private Display display;
diff --git a/frontend/client/src/autotest/planner/machine/MachineViewTable.java b/frontend/client/src/autotest/planner/machine/MachineViewTable.java
index b315892..3cc4440 100644
--- a/frontend/client/src/autotest/planner/machine/MachineViewTable.java
+++ b/frontend/client/src/autotest/planner/machine/MachineViewTable.java
@@ -1,14 +1,14 @@
package autotest.planner.machine;
-import autotest.common.AbstractStatusSummary;
import autotest.common.Utils;
+import autotest.planner.TestPlannerTableDisplay;
+import autotest.planner.TestPlannerTableDisplay.RowDisplay;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -16,19 +16,6 @@
import java.util.TreeSet;
public class MachineViewTable {
- public static class RowDisplay {
- String content;
- String cssClass = AbstractStatusSummary.BLANK_COLOR;
-
- public RowDisplay(String content) {
- this.content = content;
- }
-
- public RowDisplay(String content, String cssClass) {
- this.content = content;
- this.cssClass = cssClass;
- }
- }
public static class Row {
String machine;
@@ -68,17 +55,10 @@
}
}
- public static interface Display {
- public void clearData();
- public void setHeaders(Collection<String> headers);
- public void addRow(Collection<RowDisplay> rowData);
- public void finalRender();
- }
-
- private Display display;
+ private TestPlannerTableDisplay display;
private List<Row> rows = new ArrayList<Row>();
- public void bindDisplay(Display display) {
+ public void bindDisplay(TestPlannerTableDisplay display) {
this.display = display;
}
diff --git a/frontend/client/src/autotest/planner/machine/MachineViewTableDisplay.java b/frontend/client/src/autotest/planner/machine/MachineViewTableDisplay.java
index 069fe58..e69de29 100644
--- a/frontend/client/src/autotest/planner/machine/MachineViewTableDisplay.java
+++ b/frontend/client/src/autotest/planner/machine/MachineViewTableDisplay.java
@@ -1,80 +0,0 @@
-package autotest.planner.machine;
-
-import autotest.planner.TestPlannerUtils;
-import autotest.planner.machine.MachineViewTable.RowDisplay;
-
-import com.google.gwt.event.logical.shared.ResizeEvent;
-import com.google.gwt.event.logical.shared.ResizeHandler;
-import com.google.gwt.gen2.table.client.FixedWidthFlexTable;
-import com.google.gwt.gen2.table.client.FixedWidthGrid;
-import com.google.gwt.gen2.table.client.ScrollTable;
-import com.google.gwt.gen2.table.client.AbstractScrollTable.ResizePolicy;
-import com.google.gwt.gen2.table.client.AbstractScrollTable.ScrollPolicy;
-import com.google.gwt.gen2.table.client.AbstractScrollTable.SortPolicy;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.Composite;
-
-import java.util.Collection;
-
-public class MachineViewTableDisplay extends Composite
- implements MachineViewTable.Display, ResizeHandler {
-
- private FixedWidthFlexTable headerTable = new FixedWidthFlexTable();
- private FixedWidthGrid dataTable = new FixedWidthGrid();
- private ScrollTable scrollTable = new ScrollTable(dataTable, headerTable);
-
- public MachineViewTableDisplay() {
- scrollTable.setSortPolicy(SortPolicy.DISABLED);
- scrollTable.setResizePolicy(ResizePolicy.UNCONSTRAINED);
- scrollTable.setScrollPolicy(ScrollPolicy.BOTH);
- dataTable.setSelectionEnabled(false);
-
- scrollTable.setSize("100%", TestPlannerUtils.getHeightParam(Window.getClientHeight()));
- scrollTable.setVisible(false);
-
- initWidget(scrollTable);
- }
-
- @Override
- public void addRow(Collection<RowDisplay> rowData) {
- assert rowData.size() == dataTable.getColumnCount();
-
- int row = dataTable.insertRow(dataTable.getRowCount());
-
- int column = 0;
- for (RowDisplay data : rowData) {
- dataTable.setText(row, column, data.content);
- dataTable.getCellFormatter().addStyleName(row, column++, data.cssClass);
- }
- }
-
- @Override
- public void finalRender() {
- TestPlannerUtils.resizeScrollTable(scrollTable);
- }
-
- @Override
- public void clearData() {
- scrollTable.setVisible(false);
- headerTable.clear();
- dataTable.clear();
- }
-
- @Override
- public void setHeaders(Collection<String> headers) {
- int column = 0;
- for (String header : headers) {
- headerTable.setText(0, column, header);
- scrollTable.setHeaderColumnTruncatable(column++, false);
- }
-
- dataTable.resize(0, headers.size());
-
- scrollTable.setVisible(true);
- }
-
- @Override
- public void onResize(ResizeEvent event) {
- scrollTable.setHeight(TestPlannerUtils.getHeightParam(event.getHeight()));
- }
-}
diff --git a/frontend/client/src/autotest/planner/overview/OverviewTable.java b/frontend/client/src/autotest/planner/overview/OverviewTable.java
index a7c2b8e..e60233a 100644
--- a/frontend/client/src/autotest/planner/overview/OverviewTable.java
+++ b/frontend/client/src/autotest/planner/overview/OverviewTable.java
@@ -1,12 +1,10 @@
package autotest.planner.overview;
import autotest.common.JSONArrayList;
-import autotest.common.JsonManipulator;
import autotest.common.StaticDataRepository;
import autotest.common.Utils;
-import autotest.common.JsonManipulator.IFactory;
+import autotest.common.Utils.JsonObjectFactory;
-import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONString;
@@ -26,7 +24,7 @@
}
private static class Machine {
- static IFactory<Machine> FACTORY = new IFactory<Machine>() {
+ static JsonObjectFactory<Machine> FACTORY = new JsonObjectFactory<Machine>() {
public Machine fromJsonObject(JSONObject object) {
return Machine.fromJsonObject(object);
}
@@ -58,7 +56,7 @@
}
private static class TestConfig {
- static IFactory<TestConfig> FACTORY = new IFactory<TestConfig>() {
+ static JsonObjectFactory<TestConfig> FACTORY = new JsonObjectFactory<TestConfig>() {
public TestConfig fromJsonObject(JSONObject object) {
return TestConfig.fromJsonObject(object);
}
@@ -91,14 +89,14 @@
static PlanData fromJsonObject(JSONObject planData) {
JSONArray machinesJson = planData.get("machines").isArray();
- List<Machine> machines = JsonManipulator.createList(machinesJson, Machine.FACTORY);
+ List<Machine> machines = Utils.createList(machinesJson, Machine.FACTORY);
JSONArray bugsJson = planData.get("bugs").isArray();
List<String> bugs = Arrays.asList(Utils.JSONtoStrings(bugsJson));
JSONArray testConfigsJson = planData.get("test_configs").isArray();
List<TestConfig> testConfigs =
- JsonManipulator.createList(testConfigsJson, TestConfig.FACTORY);
+ Utils.createList(testConfigsJson, TestConfig.FACTORY);
return new PlanData(machines, bugs, testConfigs);
}
@@ -154,7 +152,7 @@
remainingHours += remaining * config.estimatedRuntime;
}
- String progress = percentage(runHours, runHours + remainingHours);
+ String progress = Utils.percentage(runHours, runHours + remainingHours);
String bugs = String.valueOf(planData.bugs.size());
int passed = 0;
@@ -179,26 +177,14 @@
displayData.add(progress);
displayData.add(bugs);
for (String status : statuses) {
- displayData.add(numberAndPercentage(statusCounts.get(status), total));
+ displayData.add(Utils.numberAndPercentage(statusCounts.get(status), total));
}
- displayData.add(numberAndPercentage(passed, passFailTotal));
- displayData.add(numberAndPercentage(passFailTotal - passed, passFailTotal));
+ displayData.add(Utils.numberAndPercentage(passed, passFailTotal));
+ displayData.add(Utils.numberAndPercentage(passFailTotal - passed, passFailTotal));
displayData.add(runNumber + " (" + runHours + " hours)");
displayData.add(remainingNumber + " (" + remainingHours + " hours)");
display.addData(entry.getKey(), displayData);
}
}
-
- private String percentage(int num, int total) {
- if (total == 0) {
- return "N/A";
- }
- NumberFormat format = NumberFormat.getFormat("##0.#%");
- return format.format(num / (double) total);
- }
-
- private String numberAndPercentage(int num, int total) {
- return num + " (" + percentage(num, total) + ")";
- }
}
diff --git a/frontend/client/src/autotest/planner/test/TestViewDisplay.java b/frontend/client/src/autotest/planner/test/TestViewDisplay.java
new file mode 100644
index 0000000..10351b0
--- /dev/null
+++ b/frontend/client/src/autotest/planner/test/TestViewDisplay.java
@@ -0,0 +1,39 @@
+package autotest.planner.test;
+
+import autotest.common.ui.NotifyManager;
+import autotest.planner.TestPlannerDisplay;
+import autotest.planner.TestPlannerTableDisplay;
+import autotest.planner.TestPlannerTableImpl;
+
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.SimplePanel;
+
+
+public class TestViewDisplay implements TestPlannerDisplay, TestViewPresenter.Display {
+
+ private Panel container = new SimplePanel();
+
+ @Override
+ public void initialize(HTMLPanel htmlPanel) {
+ htmlPanel.add(container, "test_view_table");
+ }
+
+ @Override
+ public void clearAllData() {
+ container.clear();
+ }
+
+ @Override
+ public void setLoading(boolean loading) {
+ NotifyManager.getInstance().setLoading(loading);
+ container.setVisible(!loading);
+ }
+
+ @Override
+ public TestPlannerTableDisplay generateTestViewTableDisplay() {
+ TestPlannerTableImpl display = new TestPlannerTableImpl();
+ container.add(display);
+ return display;
+ }
+}
diff --git a/frontend/client/src/autotest/planner/test/TestViewPresenter.java b/frontend/client/src/autotest/planner/test/TestViewPresenter.java
new file mode 100644
index 0000000..d92220d
--- /dev/null
+++ b/frontend/client/src/autotest/planner/test/TestViewPresenter.java
@@ -0,0 +1,52 @@
+package autotest.planner.test;
+
+import autotest.common.JsonRpcCallback;
+import autotest.common.JsonRpcProxy;
+import autotest.planner.TestPlannerPresenter;
+import autotest.planner.TestPlannerTableDisplay;
+
+import com.google.gwt.json.client.JSONObject;
+import com.google.gwt.json.client.JSONString;
+import com.google.gwt.json.client.JSONValue;
+
+public class TestViewPresenter extends TestPlannerPresenter {
+
+ public static interface Display {
+ public void setLoading(boolean loading);
+ public void clearAllData();
+ public TestPlannerTableDisplay generateTestViewTableDisplay();
+ }
+
+ private Display display;
+
+ public void bindDisplay(Display display) {
+ this.display = display;
+ }
+
+ @Override
+ public void refresh(String planId) {
+ display.setLoading(true);
+
+ JSONObject params = new JSONObject();
+ params.put("plan_id", new JSONString(planId));
+
+ JsonRpcProxy.getProxy().rpcCall("get_test_view_data", params, new JsonRpcCallback() {
+ @Override
+ public void onSuccess(JSONValue result) {
+ display.clearAllData();
+ display.setLoading(false);
+
+ TestViewTable table = new TestViewTable();
+ table.bindDisplay(display.generateTestViewTableDisplay());
+ table.setData(result.isObject());
+ }
+
+ @Override
+ public void onError(JSONObject errorObject) {
+ super.onError(errorObject);
+ display.setLoading(false);
+ }
+ });
+ }
+
+}
diff --git a/frontend/client/src/autotest/planner/test/TestViewTab.java b/frontend/client/src/autotest/planner/test/TestViewTab.java
new file mode 100644
index 0000000..f35b1ce
--- /dev/null
+++ b/frontend/client/src/autotest/planner/test/TestViewTab.java
@@ -0,0 +1,37 @@
+package autotest.planner.test;
+
+import autotest.planner.TestPlanSelector;
+import autotest.planner.TestPlannerDisplay;
+import autotest.planner.TestPlannerPresenter;
+import autotest.planner.TestPlannerTab;
+
+public class TestViewTab extends TestPlannerTab {
+
+ private TestViewPresenter presenter = new TestViewPresenter();
+ private TestViewDisplay display = new TestViewDisplay();
+
+ public TestViewTab(TestPlanSelector selector) {
+ super(selector);
+ }
+
+ @Override
+ public String getElementId() {
+ return "test_view";
+ }
+
+ @Override
+ protected TestPlannerDisplay getDisplay() {
+ return display;
+ }
+
+ @Override
+ protected TestPlannerPresenter getPresenter() {
+ return presenter;
+ }
+
+ @Override
+ protected void bindDisplay() {
+ presenter.bindDisplay(display);
+ }
+
+}
diff --git a/frontend/client/src/autotest/planner/test/TestViewTable.java b/frontend/client/src/autotest/planner/test/TestViewTable.java
new file mode 100644
index 0000000..b047cdd
--- /dev/null
+++ b/frontend/client/src/autotest/planner/test/TestViewTable.java
@@ -0,0 +1,132 @@
+package autotest.planner.test;
+
+import autotest.common.JSONArrayList;
+import autotest.common.Utils;
+import autotest.planner.TestPlannerTableDisplay;
+import autotest.planner.TestPlannerTableDisplay.RowDisplay;
+
+import com.google.gwt.json.client.JSONObject;
+import com.google.gwt.json.client.JSONString;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+public class TestViewTable {
+
+ private static final List<String> HEADERS = Arrays.asList(new String[] {
+ "Test alias",
+ "Machine status",
+ "Machines passed (% of finished)",
+ "Test statistics",
+ "Bugs filed"
+ });
+
+ private static class Test implements Comparable<Test> {
+ String alias;
+ int totalMachines;
+ Map<String, String> machineStatus;
+ int totalRuns;
+ int totalPasses;
+ List<String> bugs;
+
+ private Test(String alias, int totalMachines, Map<String, String> machineStatus,
+ int totalRuns, int totalPasses, List<String> bugs) {
+ this.alias = alias;
+ this.totalMachines = totalMachines;
+ this.machineStatus = machineStatus;
+ this.totalRuns = totalRuns;
+ this.totalPasses = totalPasses;
+ this.bugs = bugs;
+ }
+
+ static Test fromJsonObject(String alias, JSONObject test) {
+ List<String> bugs = new ArrayList<String>();
+ for (JSONString bug : new JSONArrayList<JSONString>(test.get("bugs").isArray())) {
+ bugs.add(Utils.jsonToString(bug));
+ }
+
+ return new Test(alias,
+ (int) test.get("total_machines").isNumber().doubleValue(),
+ Utils.jsonObjectToMap(test.get("machine_status").isObject()),
+ (int) test.get("total_runs").isNumber().doubleValue(),
+ (int) test.get("total_passes").isNumber().doubleValue(),
+ bugs);
+ }
+
+ @Override
+ public int compareTo(Test o) {
+ return alias.compareTo(o.alias);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof Test)) {
+ return false;
+ }
+
+ return alias.equals(((Test) other).alias);
+ }
+
+ @Override
+ public int hashCode() {
+ return alias.hashCode();
+ }
+ }
+
+ private TestPlannerTableDisplay display;
+
+ public void bindDisplay(TestPlannerTableDisplay display) {
+ this.display = display;
+ }
+
+ public void setData(JSONObject data) {
+ Set<Test> tests = new TreeSet<Test>();
+ for (String alias : data.keySet()) {
+ tests.add(Test.fromJsonObject(alias, data.get(alias).isObject()));
+ }
+
+ display.clearData();
+ display.setHeaders(HEADERS);
+
+ for (Test test : tests) {
+ int running = 0;
+ int finished = 0;
+ int pass = 0;
+ for (String status : test.machineStatus.values()) {
+ if (status.equals("Running")) {
+ running++;
+ } else if (status.equals("Pass")) {
+ finished++;
+ pass++;
+ } else if (status.equals("Fail")) {
+ finished++;
+ }
+ }
+
+ List<RowDisplay> row = new ArrayList<RowDisplay>();
+ row.add(new RowDisplay(test.alias));
+
+ String runningStr = "Running: " + Utils.numberAndPercentage(running, test.totalMachines);
+ String finishedStr =
+ "Finished: " + Utils.numberAndPercentage(finished, test.totalMachines);
+ row.add(new RowDisplay(runningStr + "\n" + finishedStr));
+
+ row.add(new RowDisplay(Utils.numberAndPercentage(pass, finished)));
+
+ String runsStr = "Runs: " + test.totalRuns;
+ String passesStr =
+ "Passes: " + Utils.numberAndPercentage(test.totalPasses, test.totalRuns);
+ row.add(new RowDisplay(runsStr + "\n" + passesStr));
+
+ row.add(new RowDisplay(String.valueOf(test.bugs.size())));
+
+ display.addRow(row);
+ }
+
+ display.finalRender();
+ }
+}
diff --git a/frontend/client/src/autotest/public/TestPlannerClient.html b/frontend/client/src/autotest/public/TestPlannerClient.html
index 9b90401..886e3f5 100644
--- a/frontend/client/src/autotest/public/TestPlannerClient.html
+++ b/frontend/client/src/autotest/public/TestPlannerClient.html
@@ -41,10 +41,11 @@
</div>
<div id="machine_view" title="Machine View">
- <div id="machine_view_main"></div>
+ <div id="machine_view_main"></div>
</div>
<div id="test_view" title="Test View">
+ <div id="test_view_table"></div>
</div>
<div id="triage_view" title="Triage View">
diff --git a/frontend/planner/models.py b/frontend/planner/models.py
index 9334a09..76aa4a2 100644
--- a/frontend/planner/models.py
+++ b/frontend/planner/models.py
@@ -5,7 +5,7 @@
from autotest_lib.frontend.afe import model_logic, rpc_utils
from autotest_lib.frontend.tko import models as tko_models
from autotest_lib.frontend.planner import model_attributes
-from autotest_lib.client.common_lib import utils
+from autotest_lib.client.common_lib import utils, host_queue_entry_states
class Plan(dbmodels.Model, model_logic.ModelExtensions):
@@ -174,6 +174,29 @@
db_table = 'planner_test_jobs'
+ def active(self):
+ for hqe in self.afe_job.hostqueueentry_set.all():
+ if not hqe.complete:
+ return True
+ return False
+
+
+ def all_tests_passed(self):
+ if self.active():
+ return False
+
+ Status = host_queue_entry_states.Status
+ if self.afe_job.hostqueueentry_set.exclude(status=Status.COMPLETED):
+ return False
+
+ tko_tests = tko_models.Test.objects.filter(
+ job__afe_job_id=self.afe_job.id)
+ for tko_test in tko_tests:
+ if tko_test.status.word != 'GOOD':
+ return False
+ return True
+
+
def _get_details_unicode(self):
return 'AFE job %s' % self.afe_job.id
diff --git a/frontend/planner/models_test.py b/frontend/planner/models_test.py
index 8a68f77..2121992 100755
--- a/frontend/planner/models_test.py
+++ b/frontend/planner/models_test.py
@@ -4,7 +4,9 @@
import common
from autotest_lib.frontend import setup_django_environment
from autotest_lib.frontend.afe import frontend_test_utils, rpc_utils
+from autotest_lib.frontend.tko import models as tko_models
from autotest_lib.frontend.planner import models, model_attributes
+from autotest_lib.frontend.planner import planner_test_utils
class ModelWithHashTestBase(frontend_test_utils.FrontendTestMixin):
@@ -113,5 +115,54 @@
self.assertEqual(None, found)
+class JobTest(planner_test_utils.PlannerTestMixin,
+ unittest.TestCase):
+ def setUp(self):
+ self._planner_common_setup()
+ self._setup_active_plan()
+
+
+ def tearDown(self):
+ self._planner_common_teardown()
+
+
+ def test_active(self):
+ self.assertEqual(True, self._planner_job.active())
+ self._afe_job.hostqueueentry_set.update(complete=True)
+ self.assertEqual(False, self._planner_job.active())
+
+
+ def test_all_tests_passed_active(self):
+ self.assertEqual(True, self._planner_job.active())
+ self.assertEqual(False, self._planner_job.all_tests_passed())
+
+
+ def test_all_tests_passed_failed_queue_entry(self):
+ self._afe_job.hostqueueentry_set.update(complete=True, status='Failed')
+ self.assertEqual(False, self._planner_job.active())
+
+ self.assertEqual(False, self._planner_job.all_tests_passed())
+
+
+ def _setup_test_all_tests_passed(self, status):
+ self._afe_job.hostqueueentry_set.update(complete=True,
+ status='Completed')
+ tko_test = tko_models.Test.objects.create(job=self._tko_job,
+ status=status,
+ kernel=self._tko_kernel,
+ machine=self._tko_machine)
+ self.assertEqual(False, self._planner_job.active())
+
+
+ def test_all_tests_passed_success(self):
+ self._setup_test_all_tests_passed(self._good_status)
+ self.assertEqual(True, self._planner_job.all_tests_passed())
+
+
+ def test_all_tests_passed_failure(self):
+ self._setup_test_all_tests_passed(self._fail_status)
+ self.assertEqual(False, self._planner_job.all_tests_passed())
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/frontend/planner/rpc_interface.py b/frontend/planner/rpc_interface.py
index 51284b0..b97c3d2 100644
--- a/frontend/planner/rpc_interface.py
+++ b/frontend/planner/rpc_interface.py
@@ -367,6 +367,48 @@
bugs=bugs, reason=reason, invalidate=invalidate)
+def get_machine_view_data(plan_id):
+ """
+ Gets the data required for the web frontend Machine View.
+
+ @param plan_id: The ID of the test plan
+ @return An array. Each element is a dictionary:
+ machine: The name of the machine
+ status: The machine's status (one of
+ model_attributes.HostStatus)
+ bug_ids: List of the IDs for the bugs filed
+ tests_run: An array of dictionaries:
+ test_name: The TKO name of the test
+ success: True if the test passed
+ """
+ plan = models.Plan.smart_get(plan_id)
+ result = []
+ for host in plan.host_set.all():
+ tests_run = []
+
+ machine = host.host.hostname
+ host_status = host.status()
+ bug_ids = set()
+
+ testruns = plan.testrun_set.filter(host=host, invalidated=False,
+ finalized=True)
+ for testrun in testruns:
+ test_name = testrun.tko_test.test
+ test_status = testrun.tko_test.status.word
+ testrun_bug_ids = testrun.bugs.all().values_list(
+ 'external_uid', flat=True)
+
+ tests_run.append({'test_name': test_name,
+ 'status': test_status})
+ bug_ids.update(testrun_bug_ids)
+
+ result.append({'machine': machine,
+ 'status': host_status,
+ 'tests_run': tests_run,
+ 'bug_ids': list(bug_ids)})
+ return result
+
+
def generate_test_config(alias, afe_test_name=None,
estimated_runtime=0, **kwargs):
"""
@@ -446,48 +488,6 @@
'param_values': param_values}
-def get_machine_view_data(plan_id):
- """
- Gets the data required for the web frontend Machine View.
-
- @param plan_id: The ID of the test plan
- @return An array. Each element is a dictionary:
- machine: The name of the machine
- status: The machine's status (one of
- model_attributes.HostStatus)
- bug_ids: List of the IDs for the bugs filed
- tests_run: An array of dictionaries:
- test_name: The TKO name of the test
- success: True if the test passed
- """
- plan = models.Plan.smart_get(plan_id)
- result = []
- for host in plan.host_set.all():
- tests_run = []
-
- machine = host.host.hostname
- host_status = host.status()
- bug_ids = set()
-
- testruns = plan.testrun_set.filter(host=host, invalidated=False,
- finalized=True)
- for testrun in testruns:
- test_name = testrun.tko_test.test
- test_status = testrun.tko_test.status.word
- testrun_bug_ids = testrun.bugs.all().values_list(
- 'external_uid', flat=True)
-
- tests_run.append({'test_name': test_name,
- 'status': test_status})
- bug_ids.update(testrun_bug_ids)
-
- result.append({'machine': machine,
- 'status': host_status,
- 'tests_run': tests_run,
- 'bug_ids': list(bug_ids)})
- return result
-
-
def get_overview_data(plan_ids):
"""
Gets the data for the Overview tab
@@ -516,9 +516,16 @@
for plan in plans:
machines = []
for host in plan.host_set.all():
+ pass_status = rpc_utils.compute_test_config_status(host)
+ if pass_status == rpc_utils.ComputeTestConfigStatusResult.PASS:
+ passed = True
+ elif pass_status == rpc_utils.ComputeTestConfigStatusResult.FAIL:
+ passed = False
+ else:
+ passed = None
machines.append({'hostname': host.host.hostname,
'status': host.status(),
- 'passed': rpc_utils.compute_passed(host)})
+ 'passed': passed})
bugs = set()
for testrun in plan.testrun_set.all():
@@ -526,9 +533,8 @@
test_configs = []
for test_config in plan.testconfig_set.all():
- complete_statuses = afe_models.HostQueueEntry.COMPLETE_STATUSES
complete_jobs = test_config.job_set.filter(
- afe_job__hostqueueentry__status__in=complete_statuses)
+ afe_job__hostqueueentry__complete=True)
complete_afe_jobs = afe_models.Job.objects.filter(
id__in=complete_jobs.values_list('afe_job', flat=True))
@@ -548,6 +554,61 @@
return result
+def get_test_view_data(plan_id):
+ """
+ Gets the data for the Test View tab
+
+ @param plan_id: The name or ID of the test plan
+ @return A dictionary - Keys are test config aliases, values are dictionaries
+ of data:
+ total_machines: Total number of machines scheduled for this test
+ config. Excludes machines that are set to skip
+ this config.
+ machine_status: A dictionary:
+ key: The hostname
+ value: The status of the machine: one of 'Scheduled',
+ 'Running', 'Pass', or 'Fail'
+ total_runs: Total number of runs of this test config. Includes
+ repeated runs (from triage re-run)
+ total_passes: Number of runs that resulted in a 'pass', meaning
+ that none of the tests in the test config had any
+ status other than GOOD.
+ bugs: List of bugs that were filed under this test config
+ """
+ plan = models.Plan.smart_get(plan_id)
+ result = {}
+ for test_config in plan.testconfig_set.all():
+ skipped_host_ids = test_config.skipped_hosts.values_list('id',
+ flat=True)
+ hosts = plan.host_set.exclude(host__id__in=skipped_host_ids)
+ total_machines = hosts.count()
+
+ machine_status = {}
+ for host in hosts:
+ machine_status[host.host.hostname] = (
+ rpc_utils.compute_test_config_status(host, test_config))
+
+ planner_jobs = test_config.job_set.all()
+ total_runs = planner_jobs.count()
+ total_passes = 0
+ for planner_job in planner_jobs:
+ if planner_job.all_tests_passed():
+ total_passes += 1
+
+ test_runs = plan.testrun_set.filter(
+ test_job__in=test_config.job_set.all())
+ bugs = set()
+ for test_run in test_runs:
+ bugs.update(test_run.bugs.values_list('external_uid', flat=True))
+
+ result[test_config.alias] = {'total_machines': total_machines,
+ 'machine_status': machine_status,
+ 'total_runs': total_runs,
+ 'total_passes': total_passes,
+ 'bugs': list(bugs)}
+ return result
+
+
def get_motd():
return afe_rpc_utils.get_motd()
diff --git a/frontend/planner/rpc_interface_unittest.py b/frontend/planner/rpc_interface_unittest.py
index f36d458..30366bf 100644
--- a/frontend/planner/rpc_interface_unittest.py
+++ b/frontend/planner/rpc_interface_unittest.py
@@ -164,28 +164,6 @@
self.god.check_playback()
- def test_generate_test_config(self):
- control = {'control_file': object(),
- 'is_server': object()}
- test = 'test'
- alias = 'test alias'
- estimated_runtime = object()
-
- self.god.stub_function(afe_rpc_interface, 'generate_control_file')
- afe_rpc_interface.generate_control_file.expect_call(
- tests=[test]).and_return(control)
-
- result = rpc_interface.generate_test_config(
- alias=alias, afe_test_name=test,
- estimated_runtime=estimated_runtime)
-
- self.assertEqual(result['alias'], 'test_alias')
- self.assertEqual(result['control_file'], control['control_file'])
- self.assertEqual(result['is_server'], control['is_server'])
- self.assertEqual(result['estimated_runtime'], estimated_runtime)
- self.god.check_playback()
-
-
def test_get_machine_view_data(self):
self._setup_active_plan()
@@ -232,12 +210,34 @@
self.assertEqual(sorted(actual), sorted(expected))
+ def test_generate_test_config(self):
+ control = {'control_file': object(),
+ 'is_server': object()}
+ test = 'test'
+ alias = 'test alias'
+ estimated_runtime = object()
+
+ self.god.stub_function(afe_rpc_interface, 'generate_control_file')
+ afe_rpc_interface.generate_control_file.expect_call(
+ tests=[test]).and_return(control)
+
+ result = rpc_interface.generate_test_config(
+ alias=alias, afe_test_name=test,
+ estimated_runtime=estimated_runtime)
+
+ self.assertEqual(result['alias'], 'test_alias')
+ self.assertEqual(result['control_file'], control['control_file'])
+ self.assertEqual(result['is_server'], control['is_server'])
+ self.assertEqual(result['estimated_runtime'], estimated_runtime)
+ self.god.check_playback()
+
+
def _test_get_overview_data_helper(self, stage):
self._setup_active_plan()
- self.god.stub_function(rpc_utils, 'compute_passed')
- rpc_utils.compute_passed.expect_call(
+ self.god.stub_function(rpc_utils, 'compute_test_config_status')
+ rpc_utils.compute_test_config_status.expect_call(
self._plan.host_set.get(host=self.hosts[0])).and_return(None)
- rpc_utils.compute_passed.expect_call(
+ rpc_utils.compute_test_config_status.expect_call(
self._plan.host_set.get(host=self.hosts[1])).and_return(None)
data = {'test_configs': [{'complete': 0, 'estimated_runtime': 1}],
@@ -257,7 +257,7 @@
test_run = self._plan.testrun_set.create(test_job=self._planner_job,
tko_test=tko_test,
host=self._planner_host)
- self._afe_job.hostqueueentry_set.update(status='Completed')
+ self._afe_job.hostqueueentry_set.update(complete=True)
self._planner_host.complete = True
self._planner_host.save()
test_run.bugs.create(external_uid='bug')
@@ -279,5 +279,53 @@
self.god.check_playback()
+ def _test_get_test_view_data_helper(self, stage):
+ self._setup_active_plan()
+ self.god.stub_function(rpc_utils, 'compute_test_config_status')
+ hosts = self._plan.host_set.filter(host__in=self.hosts[0:2])
+ rpc_utils.compute_test_config_status.expect_call(
+ hosts[0], self._test_config).and_return(None)
+
+ data = {'total_machines': 2,
+ 'machine_status': {'host1': None,
+ 'host2': None},
+ 'total_runs': 1,
+ 'total_passes': 0,
+ 'bugs': []}
+ if stage < 1:
+ rpc_utils.compute_test_config_status.expect_call(
+ hosts[1], self._test_config).and_return(None)
+ return {self._test_config.alias: data}
+
+ fail_status = rpc_utils.ComputeTestConfigStatusResult.FAIL
+ rpc_utils.compute_test_config_status.expect_call(
+ hosts[1], self._test_config).and_return(fail_status)
+ tko_test = self._tko_job.test_set.create(kernel=self._tko_kernel,
+ machine=self._tko_machine,
+ status=self._fail_status)
+ test_run = self._plan.testrun_set.create(test_job=self._planner_job,
+ tko_test=tko_test,
+ host=self._planner_host)
+ self._afe_job.hostqueueentry_set.update(complete=True)
+
+ test_run.bugs.create(external_uid='bug')
+
+ data['machine_status']['host2'] = fail_status
+ data['bugs'] = ['bug']
+ return {self._test_config.alias: data}
+
+
+ def test_get_test_view_data_no_progress(self):
+ self.assertEqual(self._test_get_test_view_data_helper(0),
+ rpc_interface.get_test_view_data(self._plan.id))
+ self.god.check_playback()
+
+
+ def test_get_test_view_data_one_failed_with_bug(self):
+ self.assertEqual(self._test_get_test_view_data_helper(1),
+ rpc_interface.get_test_view_data(self._plan.id))
+ self.god.check_playback()
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/frontend/planner/rpc_utils.py b/frontend/planner/rpc_utils.py
index bcdca53..f314fa7 100644
--- a/frontend/planner/rpc_utils.py
+++ b/frontend/planner/rpc_utils.py
@@ -5,6 +5,7 @@
from autotest_lib.frontend.planner import failure_actions, control_file
from autotest_lib.frontend.tko import models as tko_models
from autotest_lib.client.common_lib import global_config, utils, global_config
+from autotest_lib.client.common_lib import enum
PLANNER_LABEL_PREFIX = 'planner_'
@@ -114,21 +115,27 @@
if host.blocked:
return None
- test_configs = plan.testconfig_set.order_by('execution_order')
- for test_config in test_configs:
- afe_jobs = plan.job_set.filter(test_config=test_config)
- afe_job_ids = afe_jobs.values_list('afe_job', flat=True)
- hqes = afe_models.HostQueueEntry.objects.filter(job__id__in=afe_job_ids,
- host=host.host)
- if not hqes and not bool(test_config.skipped_hosts.filter(host=host)):
- return test_config
- for hqe in hqes:
- if not hqe.complete:
- # HostQueueEntry still active for this host,
- # should not run another test
- return None
+ test_configs = plan.testconfig_set.exclude(
+ skipped_hosts=host.host).order_by('execution_order')
+ result = None
- # All HQEs related to this host are complete
+ for test_config in test_configs:
+ planner_jobs = test_config.job_set.filter(
+ afe_job__hostqueueentry__host=host.host)
+ for planner_job in planner_jobs:
+ if planner_job.active():
+ # There is a job active; do not start another one
+ return None
+ try:
+ planner_job = planner_jobs.get(requires_rerun=False)
+ except models.Job.DoesNotExist:
+ if not result:
+ result = test_config
+
+ if result:
+ return result
+
+ # All jobs related to this host are complete
host.complete = True
host.save()
return None
@@ -319,24 +326,40 @@
**additional_wrap_arguments)
-def compute_passed(host):
+ComputeTestConfigStatusResult = enum.Enum('Pass', 'Fail', 'Scheduled',
+ 'Running', string_values=True)
+def compute_test_config_status(host, test_config=None):
"""
- Returns True if the host can be considered to have passed its test plan
+ Returns a value of ComputeTestConfigStatusResult:
+ Pass: This host passed the test config
+ Fail: This host failed the test config
+ Scheduled: This host has not yet run this test config
+ Running: This host is currently running this test config
A 'pass' means that, for every test configuration in the plan, the machine
had at least one AFE job with no failed tests. 'passed' could also be None,
meaning that this host is still running tests.
- """
- if not host.complete:
- return None
- test_configs = host.plan.testconfig_set.exclude(skipped_hosts=host.host)
+ @param test_config: A test config to check. None to check all test configs
+ in the plan
+ """
+ if test_config:
+ test_configs = [test_config]
+ else:
+ test_configs = host.plan.testconfig_set.exclude(skipped_hosts=host.host)
+
for test_config in test_configs:
- for planner_job in test_config.job_set.all():
- bad = planner_job.testrun_set.exclude(tko_test__status__word='GOOD')
- if not bad:
- break
- else:
- # Didn't break out of loop; this test config had no good jobs
- return False
- return True
+ try:
+ planner_job = test_config.job_set.get(
+ afe_job__hostqueueentry__host=host.host,
+ requires_rerun=False)
+ except models.Job.DoesNotExist:
+ return ComputeTestConfigStatusResult.SCHEDULED
+
+ if planner_job.active():
+ return ComputeTestConfigStatusResult.RUNNING
+
+ if planner_job.testrun_set.exclude(tko_test__status__word='GOOD'):
+ return ComputeTestConfigStatusResult.FAIL
+
+ return ComputeTestConfigStatusResult.PASS
diff --git a/frontend/planner/rpc_utils_unittest.py b/frontend/planner/rpc_utils_unittest.py
index a13b6d4..6d97dd1 100644
--- a/frontend/planner/rpc_utils_unittest.py
+++ b/frontend/planner/rpc_utils_unittest.py
@@ -323,14 +323,27 @@
self.assertEqual(actual, expected)
- def test_compute_passed_incomplete(self):
+ def test_compute_test_config_status_scheduled(self):
self._setup_active_plan()
- self._planner_host.complete = False
- self._planner_host.save()
- self.assertEqual(rpc_utils.compute_passed(self._planner_host), None)
+ self._planner_job.delete()
+
+ self.assertEqual(
+ rpc_utils.compute_test_config_status(self._planner_host),
+ rpc_utils.ComputeTestConfigStatusResult.SCHEDULED)
- def test_compute_passed_good(self):
+ def test_compute_test_config_status_running(self):
+ self._setup_active_plan()
+ self.god.stub_function(models.Job, 'active')
+ models.Job.active.expect_call().and_return(True)
+
+ self.assertEqual(
+ rpc_utils.compute_test_config_status(self._planner_host),
+ rpc_utils.ComputeTestConfigStatusResult.RUNNING)
+ self.god.check_playback()
+
+
+ def test_compute_test_config_status_good(self):
self._setup_active_plan()
tko_test = self._tko_job.test_set.create(kernel=self._tko_kernel,
status=self._good_status,
@@ -340,11 +353,16 @@
host=self._planner_host)
self._planner_host.complete = True
self._planner_host.save()
+ self.god.stub_function(models.Job, 'active')
+ models.Job.active.expect_call().and_return(False)
- self.assertEqual(rpc_utils.compute_passed(self._planner_host), True)
+ self.assertEqual(
+ rpc_utils.compute_test_config_status(self._planner_host),
+ rpc_utils.ComputeTestConfigStatusResult.PASS)
+ self.god.check_playback()
- def test_compute_passed_bad(self):
+ def test_compute_test_config_status_bad(self):
self._setup_active_plan()
tko_test = self._tko_job.test_set.create(kernel=self._tko_kernel,
status=self._fail_status,
@@ -354,8 +372,13 @@
host=self._planner_host)
self._planner_host.complete = True
self._planner_host.save()
+ self.god.stub_function(models.Job, 'active')
+ models.Job.active.expect_call().and_return(False)
- self.assertEqual(rpc_utils.compute_passed(self._planner_host), False)
+ self.assertEqual(
+ rpc_utils.compute_test_config_status(self._planner_host),
+ rpc_utils.ComputeTestConfigStatusResult.FAIL)
+ self.god.check_playback()
if __name__ == '__main__':