blob: db9cefeb0d092d99639e556923e57e1d3bba89bc [file] [log] [blame]
# Copyright 2017 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.
"""A stub test waiting for external fixture to finish testing.
Description
-----------
If you want to add a test driven by external fixture or instruments and still
want to track test results in Chrome OS Factory Software, this test provides an
easy way for test integration.
This test will check and wait for a file ``/run/factory/external/<NAME>`` to
be available. If the file content is ``PASS`` then the test item in test list
will be set as "passed", otherwise it will fail with the content from file.
Empty file is also considered as failure.
A script is included in factory software toolkit to help doing this:
``bin/factory_external_result``. It needs at least two parameters - ``NAME`` and
``RESULT``. For example, to pass a ``RF1`` test, do::
/usr/local/factory/bin/factory_external_result RF1 PASS
To fail a ``VSWR`` test with message, do::
/usr/local/factory/bin/factory_external_result RF1 "Failed to init instrument"
In summary, to design and implement a test item with external fixture:
1. Decide a test name, for example ``RF1``.
2. In Chrome OS Factory Software test list, add a test item with ``pytest_name``
set to ``wait_external_test``, and ``run_factory_external_name`` argument set
to the test name.
3. In the fixture side, detect if the DUT is connected. For Chromebooks, this
is usually done by ethernet dongle. For Android devices, try ADB.
4. Fixture should drive the test and implement all logic and test procedure.
To access Chromebooks, execute programs using SSH (you can find the private
key for root in
https://chromium.googlesource.com/chromiumos/platform/factory/+/master/misc/sshkeys/testing_rsa
). For Android, use ``adb shell``.
5. When the test by fixture is finished, invoke the ``factory_external_result``
to set result or manually create the files under ``/run/factory/external``.
Test Procedure
--------------
This is an automated test without user interaction.
When started, the test will wait for specified file to become available,
and pass or fail according to the file content.
The test procure will depend on remote fixture.
Dependency
----------
None.
Examples
--------
To add an entry for external fixture with name ``RF1``, add this in test list::
{
"pytest_name": "wait_external_test",
"args": {
"run_factory_external_name": "RF1"
}
}
To add a test for external fixture with name ``VSWR``, with customized message::
{
"pytest_name": "wait_external_test",
"args": {
"msg": "i18n! Move DUT to station {name}",
"run_factory_external_name": "VSWR"
}
}
In the fixture side, it should do something like this:
.. code-block:: sh
SSH_KEY=PATH_TO/testing_rsa
TEST_NAME=VSWR
SET_RESULT=/usr/local/factory/bin/factory_external_result
chmod go-rwx "${SSH_KEY}" # SSH needs private key to be restricted.
ssh root@dut -i "${SSH_KEY}" "${SET_RESULT} ${TEST_NAME} PASS"
"""
import os
import factory_common # pylint: disable=unused-import
from cros.factory.test.i18n import _
from cros.factory.test.i18n import arg_utils as i18n_arg_utils
from cros.factory.test import test_case
from cros.factory.utils.arg_utils import Arg
from cros.factory.utils import file_utils
from cros.factory.utils import sync_utils
_EXTERNAL_DIR = '/run/factory/external'
# Usually external tests will take a long time to run so check duration can be
# longer.
_CHECK_PERIOD_SECS = 1
class WaitExternalTest(test_case.TestCase):
"""Wait for a test by external fixture to finish."""
ARGS = [
Arg('run_factory_external_name', str,
'File name to check in /run/factory/external.'),
i18n_arg_utils.I18nArg(
'msg', 'Instruction for running external test',
default=_('Please run external test: {name}'))
]
def setUp(self):
self.ui.ToggleTemplateClass('font-large', True)
self._name = self.args.run_factory_external_name
self.ui.SetState(_(self.args.msg, name=self._name))
self._file_path = os.path.join(
_EXTERNAL_DIR, self.args.run_factory_external_name)
self.RemoveFile(self._file_path)
def FileExists(self):
return os.path.exists(self._file_path)
def runTest(self):
sync_utils.PollForCondition(
poll_method=self.FileExists,
poll_interval_secs=_CHECK_PERIOD_SECS,
timeout_secs=None,
condition_name='WaitForExternalFile')
# Ideally external hosts should do atomic write, but since it's probably
# done by 3rd party vendors with arbitrary implementation, so a quick and
# simple solution is to wait for one more check period so the file should be
# flushed.
self.Sleep(_CHECK_PERIOD_SECS)
with open(self._file_path) as f:
result = f.read().strip()
self.assertEqual(result.lower(), 'pass',
'Test %s completed with failure: %s' %
(self._name, result or 'unknown'))
def RemoveFile(self, file_path):
try:
file_dir = os.path.dirname(file_path)
file_utils.TryMakeDirs(file_dir)
os.remove(file_path)
except OSError:
if os.path.exists(file_path) or not os.path.exists(file_dir):
raise