| # 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 |