Enabled create test job out of private test build.
We now allow user to have a test source build containing tests not
present in the moblab and its database. The user could fetch those tests
and select them to create jobs.
BUG=chromium:603774
TEST=manually tests and some unit test.
Change-Id: I4aecb922b006a0743ef4cb223f555fac7343c6c0
Reviewed-on: https://chromium-review.googlesource.com/351480
Commit-Ready: Michael Tang <ntang@chromium.org>
Tested-by: Michael Tang <ntang@chromium.org>
Reviewed-by: Michael Tang <ntang@chromium.org>
Reviewed-by: Dan Shi <dshi@google.com>
diff --git a/frontend/afe/control_file.py b/frontend/afe/control_file.py
index 90b34ef..a88a8b4 100644
--- a/frontend/afe/control_file.py
+++ b/frontend/afe/control_file.py
@@ -8,6 +8,7 @@
import common
from autotest_lib.frontend.afe import model_logic
+from autotest_lib.frontend.afe import site_rpc_interface
import frontend.settings
AUTOTEST_DIR = os.path.abspath(os.path.join(
@@ -147,6 +148,13 @@
def kernel_config_file(kernel, platform):
+ """Gets the kernel config.
+
+ @param kernel The kernel rpm .
+ @param platform The platform object.
+
+ @return The kernel config string or None.
+ """
if (not kernel.endswith('.rpm') and platform and
platform.kernel_config):
return platform.kernel_config
@@ -154,6 +162,12 @@
def read_control_file(test):
+ """Reads the test control file from local disk.
+
+ @param test The test name.
+
+ @return The test control file string.
+ """
control_file = open(os.path.join(AUTOTEST_DIR, test.path))
control_contents = control_file.read()
control_file.close()
@@ -197,6 +211,12 @@
def add_boilerplate_to_nested_steps(lines):
+ """Adds boilerplate magic.
+
+ @param lines The string of lines.
+
+ @returns The string lines.
+ """
# Look for a line that begins with 'def step_init():' while
# being flexible on spacing. If it's found, this will be
# a nested set of steps, so add magic to make it work.
@@ -208,13 +228,19 @@
def format_step(item, lines):
+ """Format a line item.
+ @param item The item number.
+ @param lines The string of lines.
+
+ @returns The string lines.
+ """
lines = indent_text(lines, ' ')
lines = 'def step%d():\n%s' % (item, lines)
return lines
def get_tests_stanza(tests, is_server, prepend=None, append=None,
- client_control_file=''):
+ client_control_file='', test_source_build=None):
""" Constructs the control file test step code from a list of tests.
@param tests A sequence of test control files to run.
@@ -225,6 +251,8 @@
Defaults to [].
@param client_control_file If specified, use this text as the body of a
final client control file to run after tests. is_server must be False.
+ @param test_source_build: Build to be used to retrieve test code. Default
+ to None.
@returns The control file test code to be run.
"""
@@ -233,7 +261,11 @@
prepend = []
if not append:
append = []
- raw_control_files = [read_control_file(test) for test in tests]
+ if test_source_build:
+ raw_control_files = site_rpc_interface.get_test_control_files_by_build(
+ tests, test_source_build)
+ else:
+ raw_control_files = [read_control_file(test) for test in tests]
return _get_tests_stanza(raw_control_files, is_server, prepend, append,
client_control_file=client_control_file)
@@ -277,7 +309,13 @@
def indent_text(text, indent):
"""Indent given lines of python code avoiding indenting multiline
- quoted content (only for triple " and ' quoting for now)."""
+ quoted content (only for triple " and ' quoting for now).
+
+ @param text The string of lines.
+ @param indent The indent string.
+
+ @return The indented string.
+ """
regex = re.compile('(\\\\*)("""|\'\'\')')
res = []
@@ -355,7 +393,7 @@
def generate_control(tests, kernels=None, platform=None, is_server=False,
profilers=(), client_control_file='', profile_only=None,
- upload_kernel_config=False):
+ upload_kernel_config=False, test_source_build=None):
"""
Generate a control file for a sequence of tests.
@@ -373,6 +411,8 @@
file code that uploads the kernel config file to the client and
tells the client of the new (local) path when compiling the kernel;
the tests must be server side tests
+ @param test_source_build: Build to be used to retrieve test code. Default
+ to None.
@returns The control file text as a string.
"""
@@ -391,5 +431,6 @@
prepend, append = _get_profiler_commands(profilers, is_server, profile_only)
control_file_text += get_tests_stanza(tests, is_server, prepend, append,
- client_control_file)
+ client_control_file,
+ test_source_build)
return control_file_text
diff --git a/frontend/afe/rpc_interface.py b/frontend/afe/rpc_interface.py
index dff4165..116a327 100644
--- a/frontend/afe/rpc_interface.py
+++ b/frontend/afe/rpc_interface.py
@@ -751,7 +751,7 @@
def generate_control_file(tests=(), kernel=None, label=None, profilers=(),
client_control_file='', use_container=False,
profile_only=None, upload_kernel_config=False,
- db_tests=True):
+ db_tests=True, test_source_build=None):
"""
Generates a client-side control file to load a kernel and run tests.
@@ -781,6 +781,8 @@
of test IDs which are used to retrieve the test objects
from the database. If False, tests is a tuple of test
dictionaries stored client-side in the AFE.
+ @param test_source_build: Build to be used to retrieve test code. Default
+ to None.
@returns a dict with the following keys:
control_file: str, The control file text.
@@ -800,7 +802,8 @@
tests=test_objects, kernels=kernel, platform=label,
profilers=profiler_objects, is_server=cf_info['is_server'],
client_control_file=client_control_file, profile_only=profile_only,
- upload_kernel_config=upload_kernel_config)
+ upload_kernel_config=upload_kernel_config,
+ test_source_build=test_source_build)
return cf_info
@@ -892,7 +895,7 @@
def create_job_page_handler(name, priority, control_file, control_type,
image=None, hostless=False, firmware_rw_build=None,
firmware_ro_build=None, test_source_build=None,
- is_cloning = False, **kwargs):
+ is_cloning=False, **kwargs):
"""\
Create and enqueue a job.
@@ -1303,7 +1306,7 @@
image = get_parameterized_autoupdate_image_url(job)
else:
keyvals = job.keyval_dict()
- image = keyvals.get('build')
+ image = keyvals.get('build')
if not image:
value = keyvals.get('builds')
builds = None
diff --git a/frontend/afe/rpc_utils.py b/frontend/afe/rpc_utils.py
index f2965d2..eea15b9 100644
--- a/frontend/afe/rpc_utils.py
+++ b/frontend/afe/rpc_utils.py
@@ -268,6 +268,23 @@
return type('TestObject', (object,), numerized_dict)
+def _check_is_server_test(test_type):
+ """Checks if the test type is a server test.
+
+ @param test_type The test type in enum integer or string.
+
+ @returns A boolean to identify if the test type is server test.
+ """
+ if test_type is not None:
+ if isinstance(test_type, basestring):
+ try:
+ test_type = control_data.CONTROL_TYPE.get_value(test_type)
+ except AttributeError:
+ return False
+ return (test_type == control_data.CONTROL_TYPE.SERVER)
+ return False
+
+
def prepare_generate_control_file(tests, kernel, label, profilers,
db_tests=True):
if db_tests:
@@ -287,7 +304,7 @@
'tests together (tests %s and %s differ' % (
test1.name, test2.name)})
- is_server = (test_type == control_data.CONTROL_TYPE.SERVER)
+ is_server = _check_is_server_test(test_type)
if test_objects:
synch_count = max(test.sync_count for test in test_objects)
else:
diff --git a/frontend/afe/rpc_utils_unittest.py b/frontend/afe/rpc_utils_unittest.py
new file mode 100755
index 0000000..83d6f16
--- /dev/null
+++ b/frontend/afe/rpc_utils_unittest.py
@@ -0,0 +1,43 @@
+#!/usr/bin/python
+#
+# Copyright (c) 2016 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Unit tests for frontend/afe/rpc_utils.py."""
+
+
+import unittest
+
+import common
+from autotest_lib.client.common_lib import control_data
+from autotest_lib.frontend import setup_django_environment
+from autotest_lib.frontend.afe import frontend_test_utils
+from autotest_lib.frontend.afe import rpc_utils
+
+
+class RpcUtilsTest(unittest.TestCase,
+ frontend_test_utils.FrontendTestMixin):
+ """Unit tests for functions in rpc_utils.py."""
+ def setUp(self):
+ self._frontend_common_setup()
+
+
+ def tearDown(self):
+ self._frontend_common_teardown()
+
+
+ def testCheckIsServer(self):
+ """Ensure that test type check is correct."""
+ self.assertFalse(rpc_utils._check_is_server_test(None))
+ self.assertFalse(rpc_utils._check_is_server_test(
+ control_data.CONTROL_TYPE.CLIENT))
+ self.assertFalse(rpc_utils._check_is_server_test('Client'))
+ self.assertTrue(rpc_utils._check_is_server_test(
+ control_data.CONTROL_TYPE.SERVER))
+ self.assertTrue(rpc_utils._check_is_server_test('Server'))
+ self.assertFalse(rpc_utils._check_is_server_test('InvalidType'))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/frontend/afe/site_rpc_interface.py b/frontend/afe/site_rpc_interface.py
index 6077326..e8f8a4e 100644
--- a/frontend/afe/site_rpc_interface.py
+++ b/frontend/afe/site_rpc_interface.py
@@ -991,13 +991,12 @@
stable_version_utils.delete(board=board)
-def get_tests_by_build(build, ignore_invalid_tests=False):
- """Get the tests that are available for the specified build.
+def _initialize_control_file_getter(build):
+ """Get the remote control file getter.
- @param build: unique name by which to refer to the image.
- @param ignore_invalid_tests: flag on if unparsable tests are ignored.
+ @param build: unique name by which to refer to a remote build image.
- @return: A sorted list of all tests that are in the build specified.
+ @return: A control file getter object.
"""
# Stage the test artifacts.
try:
@@ -1013,7 +1012,19 @@
'Failed to stage %s: %s' % (build, e))
# Collect the control files specified in this build
- cfile_getter = control_file_getter.DevServerGetter.create(build, ds)
+ return control_file_getter.DevServerGetter.create(build, ds)
+
+
+def get_tests_by_build(build, ignore_invalid_tests=True):
+ """Get the tests that are available for the specified build.
+
+ @param build: unique name by which to refer to the image.
+ @param ignore_invalid_tests: flag on if unparsable tests are ignored.
+
+ @return: A sorted list of all tests that are in the build specified.
+ """
+ # Collect the control files specified in this build
+ cfile_getter = _initialize_control_file_getter(build)
if SuiteBase.ENABLE_CONTROLS_IN_BATCH:
control_file_info_list = cfile_getter.get_suite_info()
control_file_list = control_file_info_list.keys()
@@ -1078,3 +1089,32 @@
test_objects = sorted(test_objects, key=lambda x: x.get('name'))
return rpc_utils.prepare_for_serialization(test_objects)
+
+
+def get_test_control_files_by_build(tests, build, ignore_invalid_tests=False):
+ """Get the test control files that are available for the specified build.
+
+ @param tests A sequence of test objects to run.
+ @param build: unique name by which to refer to the image.
+ @param ignore_invalid_tests: flag on if unparsable tests are ignored.
+
+ @return: A sorted list of all tests that are in the build specified.
+ """
+ raw_control_files = []
+ # shortcut to avoid staging the image.
+ if not tests:
+ return raw_control_files
+
+ cfile_getter = _initialize_control_file_getter(build)
+ if SuiteBase.ENABLE_CONTROLS_IN_BATCH:
+ control_file_info_list = cfile_getter.get_suite_info()
+
+ for test in tests:
+ # Read and parse the control file
+ if SuiteBase.ENABLE_CONTROLS_IN_BATCH:
+ control_file = control_file_info_list[test.path]
+ else:
+ control_file = cfile_getter.get_control_file_contents(
+ test.path)
+ raw_control_files.append(control_file)
+ return raw_control_files
diff --git a/frontend/client/src/autotest/afe/create/CreateJobViewDisplay.java b/frontend/client/src/autotest/afe/create/CreateJobViewDisplay.java
index b308edb..2508860 100644
--- a/frontend/client/src/autotest/afe/create/CreateJobViewDisplay.java
+++ b/frontend/client/src/autotest/afe/create/CreateJobViewDisplay.java
@@ -57,8 +57,15 @@
"Name of the test image to use. Example: \"x86-alex-release/R27-3837.0.0\". " +
"If no image is specified, regular tests will use current image on the Host. " +
"Please note that an image is required to run a test suite.");
- private Button fetchImageTestsButton = new Button("Fetch Tests from Build");
- private CheckBoxImpl ignoreInvalidTestsCheckBox = new CheckBoxImpl("Ignore Invalid Tests");
+ /**
+ * - When the fetch tests from build checkbox is unchecked (by default), the tests
+ * selection dropdown list is filled up by using the build on Moblab device.
+ * - You can only check the checkbox if a build is selected in the test source build
+ * dropdown list
+ * - Whenever the source build dropdown selection changes, automatically switch back
+ * to fetch tests from Moblab device.
+ */
+ private CheckBoxImpl fetchTestsCheckBox = new CheckBoxImpl("Fetch Tests from Build");
private TextBox timeout = new TextBox();
private ToolTip timeoutToolTip = new ToolTip(
"?",
@@ -331,6 +338,7 @@
testSourceBuildList.getElement().getStyle().setProperty("minWidth", "15em");
+ fetchTestsCheckBox.setEnabled(false);
// Add the remaining widgets to the main panel
panel.add(jobName, "create_job_name");
panel.add(jobNameToolTip, "create_job_name");
@@ -338,8 +346,7 @@
panel.add(image_urlToolTip, "create_image_url");
panel.add(testSourceBuildList, "create_test_source_build");
panel.add(testSourceBuildListToolTip, "create_test_source_build");
- panel.add(fetchImageTestsButton, "fetch_image_tests");
- panel.add(ignoreInvalidTestsCheckBox, "ignore_invalid_tests");
+ panel.add(fetchTestsCheckBox, "fetch_tests_from_build");
panel.add(testSelector, "create_tests");
panel.add(controlFilePanel, "create_edit_control");
panel.add(hostSelector, "create_host_selector");
@@ -480,12 +487,8 @@
controlFilePanel.setOpen(isOpen);
}
- public HasClickHandlers getFetchImageTestsButton() {
- return fetchImageTestsButton;
- }
-
- public ICheckBox getIgnoreInvalidTestsCheckBox() {
- return ignoreInvalidTestsCheckBox;
+ public ICheckBox getFetchTestsFromBuildCheckBox() {
+ return fetchTestsCheckBox;
}
public ITextBox getFirmwareRWBuild() {
diff --git a/frontend/client/src/autotest/afe/create/CreateJobViewPresenter.java b/frontend/client/src/autotest/afe/create/CreateJobViewPresenter.java
index 0a37005..5219678 100644
--- a/frontend/client/src/autotest/afe/create/CreateJobViewPresenter.java
+++ b/frontend/client/src/autotest/afe/create/CreateJobViewPresenter.java
@@ -37,6 +37,8 @@
import com.google.gwt.event.logical.shared.HasOpenHandlers;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.OpenHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONBoolean;
import com.google.gwt.json.client.JSONNull;
@@ -91,8 +93,7 @@
public IButton getSubmitJobButton();
public HasClickHandlers getCreateTemplateJobButton();
public HasClickHandlers getResetButton();
- public HasClickHandlers getFetchImageTestsButton();
- public ICheckBox getIgnoreInvalidTestsCheckBox();
+ public ICheckBox getFetchTestsFromBuildCheckBox();
public ITextBox getFirmwareRWBuild();
public ITextBox getFirmwareROBuild();
public ExtendedListBox getTestSourceBuildList();
@@ -354,6 +355,12 @@
boolean testsFromBuild = testSelector.usingTestsFromBuild();
params.put("db_tests", JSONBoolean.getInstance(!testsFromBuild));
+ if (testsFromBuild) {
+ String testSourceBuild = display.getTestSourceBuildList().getSelectedValue();
+ if (testSourceBuild != null) {
+ params.put("test_source_build", new JSONString(testSourceBuild));
+ }
+ }
JSONArray tests = new JSONArray();
for (JSONObject test : testSelector.getSelectedTests()) {
@@ -465,6 +472,10 @@
@Override
public void onChange(ChangeEvent event) {
String image = display.getImageUrl().getText();
+ ICheckBox fetchTestsFromBuildCheckBox = display.getFetchTestsFromBuildCheckBox();
+ Boolean fetchedFromBuild = fetchTestsFromBuildCheckBox.getValue();
+ String currentTestSourceBuild = display.getTestSourceBuildList().getSelectedValue();
+ boolean sourceBuildChanged = false;
if (image.isEmpty()) {
display.getFirmwareRWBuild().setText("");
display.getFirmwareROBuild().setText("");
@@ -472,6 +483,7 @@
display.getFirmwareRWBuild().setEnabled(false);
display.getFirmwareROBuild().setEnabled(false);
display.getTestSourceBuildList().setEnabled(false);
+ sourceBuildChanged = (currentTestSourceBuild != null);
}
else {
display.getFirmwareRWBuild().setEnabled(true);
@@ -483,7 +495,6 @@
builds.add(display.getFirmwareRWBuild().getText());
if (!display.getFirmwareROBuild().getText().isEmpty())
builds.add(display.getFirmwareROBuild().getText());
- String currentTestSourceBuild = display.getTestSourceBuildList().getSelectedValue();
int testSourceBuildIndex = builds.indexOf(currentTestSourceBuild);
display.getTestSourceBuildList().clear();
for (String build : builds) {
@@ -491,8 +502,20 @@
}
if (testSourceBuildIndex >= 0) {
display.getTestSourceBuildList().setSelectedIndex(testSourceBuildIndex);
+ } else {
+ sourceBuildChanged = true;
}
}
+
+ // Updates the fetch test checkbox UI
+ fetchTestsFromBuildCheckBox.setEnabled(!image.isEmpty());
+ if (sourceBuildChanged) {
+ fetchTestsFromBuildCheckBox.setValue(false);
+ // Fetch the test again from default Moblab device if necessary
+ if (fetchedFromBuild) {
+ fetchImageTests();
+ }
+ }
}
};
@@ -635,17 +658,30 @@
}
});
- display.getFetchImageTestsButton().addClickHandler(new ClickHandler() {
- public void onClick(ClickEvent event) {
- String imageUrl = display.getImageUrl().getText();
- if (imageUrl == null || imageUrl.isEmpty()) {
- NotifyManager.getInstance().showMessage(
- "No build was specified for fetching tests.");
- }
+ display.getTestSourceBuildList().addChangeHandler(new ChangeHandler() {
+ @Override
+ public void onChange(ChangeEvent event) {
+ Boolean fetchedTests = display.getFetchTestsFromBuildCheckBox().getValue();
+ display.getFetchTestsFromBuildCheckBox().setValue(false);
+ if (fetchedTests) {
fetchImageTests();
- }
+ }
+ }
});
+ display.getFetchTestsFromBuildCheckBox()
+ .addValueChangeHandler(new ValueChangeHandler<Boolean>() {
+ @Override
+ public void onValueChange(ValueChangeEvent<Boolean> event) {
+ String imageUrl = display.getImageUrl().getText();
+ if (imageUrl == null || imageUrl.isEmpty()) {
+ NotifyManager.getInstance().showMessage(
+ "No build was specified for fetching tests.");
+ }
+ fetchImageTests();
+ }
+ });
+
handleBuildChange();
reset();
@@ -973,16 +1009,19 @@
private void fetchImageTests() {
testSelector.setImageTests(new JSONArray());
- String imageUrl = display.getImageUrl().getText();
- if (imageUrl == null || imageUrl.isEmpty()) {
- testSelector.reset();
- return;
+ String currentTestSourceBuild = display.getTestSourceBuildList().getSelectedValue();
+ Boolean fetchedFromBuild = display.getFetchTestsFromBuildCheckBox().getValue();
+ if (!fetchedFromBuild ||
+ currentTestSourceBuild == null || currentTestSourceBuild.isEmpty()) {
+ // Tests are from static Moblab build.
+ testSelector.setImageTests(new JSONArray());
+ testSelector.reset();
+ return;
}
JSONObject params = new JSONObject();
- params.put("build", new JSONString(imageUrl));
- params.put("ignore_invalid_tests", JSONBoolean.getInstance(
- display.getIgnoreInvalidTestsCheckBox().getValue()));
+ params.put("build", new JSONString(currentTestSourceBuild));
+ params.put("ignore_invalid_tests", JSONBoolean.getInstance(true));
rpcProxy.rpcCall("get_tests_by_build", params, new JsonRpcCallback() {
@Override
public void onSuccess(JSONValue result) {
diff --git a/frontend/client/src/autotest/public/AfeClient.html b/frontend/client/src/autotest/public/AfeClient.html
index 4f1f738..c84ad01 100644
--- a/frontend/client/src/autotest/public/AfeClient.html
+++ b/frontend/client/src/autotest/public/AfeClient.html
@@ -197,8 +197,7 @@
<tr class="data-row data-row-alternate">
<td class="field-name">Test source build: (optional)</td>
<td class="has-tooltip" id="create_test_source_build"></td>
- <td class="button" id="fetch_image_tests"></td>
- <td class="checkbox" id="ignore_invalid_tests"></td>
+ <td class="checkbox" id="fetch_tests_from_build"></td>
</tr>
</table>
<br>