Chromium OS Autotest 服务端测试(Server side test)
概述
在这篇文档中,我们将会论述服务端的测试。在下面我么你将会创建一个测试用例,用来测试被测机器的背光亮度
在以下情况下是否可以恢复到初始值:
- 用户注销和重新登陆情况下
- 设备暂停和恢复的情况下
在开始下面的工作之前你需要具备以下条件:
- 懂得python的语法
- 有一个Chromium OS的被测设备(DUT)
- 有支持编译Chromium OS的chroot环境
客户端测试(client side test)与服务端测试(server side test)的区别
自动测试具有客户端测试和服务器端测试的概念。所有测试通常都由安装了test_that
工具的Autotest服务器机器管理。
test_that
是一个运行autoserv进程的脚本,它在远程计算机上部署(deploy)和执行(execute)测试。
客户端测试(client side test)中的代码仅在被测设备(DUT)上运行,因此不能在重启之后继续维持状态,处理失败的
挂起/恢复等操作。由于客户端测试更加简单和可重用,因而应尽可能地将自动测试编写为客户端测试。
服务端测试(server side test)在自动测试服务器上运行,并被分配一个或多个被测设备(DUT),就像客户端测试一样。
服务端测试可以使用很多由Cros团队编写的库代码中的autotest原语来操作被测设备(DUT)。一般来说,那些使用Servo
或者远程电源管理的测试应该写成服务端测试。举个例子,如果被测设备需要在测试期间进行重启操作,则应该将测试写
为服务端测试。
服务端的测试例程一般放置在下面两个为位置:
third_party/autotest/files/server/site_tests
- 这些是Chromium OS的特定测试third_party/autotest/files/server/tests
- 这些是一般Linux测试的服务器测试(不是Chromium OS特定的)
客户端的测试例程一般放置在下面两个为位置:
third_party/autotest/files/client/site_tests
- 这些是Chromium OS的特定测试third_party/autotest/files/client/tests
- 这些是一般Linux测试的服务器测试(不是Chromium OS特定的)
创建一个服务端测试
1.命名测试
在目录src/third_party/autotest/files/server/site_tests/
下创建一个新的测试目录。命名为power_MyBacklightTest
。为了将测试
名命名更加准确,你首先需要确认你的测试类型,比如电源测试(power),平台测试(platform),或者网络测试(network)等。
查看client/site_tests
或者 server/site
你会发现很多测试都有同样的开头,这其实是一组测试,因此通常的测试命名原则是:
- 测试目录名:
$(TEST_ROOT)/$(LOWERCASE_AREA)_$(TEST_NAME).
- 测试control文件:`` T E S T R O O T / {TEST_ROOT}/ TESTROOT/{LOWERCASE_AREA}_${TEST_NAME}/control`
- 测试python脚本:
${TEST_ROOT}/${LOWERCASE_AREA}_${TEST_NAME}/${LOWERCASE_AREA}_${TEST_NAME}.py
control文件是用来运行测试例程和设置一些默认参数的。python脚本是真正的测试例程代码。你可以为同一个测试创建不同
的control文件,用来配置不同的参数。例如,power_SuspendStress 测试具有多个控制文件,可用于多种用途,同时使用不同
的配置共享相同的测试逻辑。
在control文件中的TEST_CLASS
变量应该设置为类似于“power”的测试组名(${LOWERCASE_AREA})。命名约定只是为了更容易
找到其他类似的测试。
2.创建一个控制文件(control file)
控制(control)文件包含有关测试的重要元数据(名称,作者,描述,持续时间,它所在的套件等),然后拉入并执行实际的测试代码。
字段(变量) | 是否必须 | 值 |
---|---|---|
AUTHOR | Y | 作者,若有多人可由逗号分隔,例:AUTHOR = ‘xx, xx’ |
DOC | Y | 测试用例的长描述 |
NAME | Y | 测试的名称 |
TIME | Y | 测试时常,‘FAST’ (<1min), ‘MEDIUM’ (<10min), ‘LONG’ (<20min), ‘LENGTHY’ (>30min) |
TEST_TYPE | Y | 测试端(client or server) |
TEST_CATEGORY | Y | 测试功能类型,比如压力测试(Stress),功能测试(Functional) |
ATTRIBUTES | N | 属性标记列表,用于组合套件。例如,‘suite:audio’ |
SYNC_COUNT | N | 一个整数,用来表示需要同时运行测试的设备数 |
SUITE | N | 套件名称 |
DEPENDENCIES | N | 依赖项 |
*注:在control文件中不应该包含主要的测试逻辑,主要的测试逻辑应写在测试用例的python脚本文件中。
更详细的control文件内容含义及编写方法请查看此处。
# Copyright information
AUTHOR = "your name, and at least one person ([ldap]@chromium.org) or a mailing list"
NAME = "power_MyBacklightTest" # test name
TIME = "SHORT" # can be MEDIUM, LONG
TEST_CATEGORY = "Benchmark" # can be General, Stress
TEST_CLASS = "power" # testing area, e.g., platform, network etc
TEST_TYPE = "server" # indicating it's a server side test
SUITE = "bvt" # if the test should be a part of the BVT
# EXPERIMENTAL = "True" # Experiment is now not in use
DOC = """
This test measures the backlight level before and after certain system events
and verifies that the backlight level has not changed.
"""
def run_system_power_MyBacklightTest(machine):
job.run_test('power_MyBacklightTest', client_ip=machine)
# run the test in multiple machines
job.parallel_simple(run_system_power_MyBacklightTest, machines)
上面的control文件用于服务端测试,功能是在每个被测设备(DUT)上运行名为power_MyBacklightTest
的测试例程。
3.创建测试例程的python脚本
测试包装器文件包含测试逻辑的代码。以下是用于准系统服务器端测试的测试包装器的源代码,该测试包含所有必要的方法
覆盖,但没有任何测试逻辑。基本上,测试包装器包含一个派生自基类“test”(server / test.py)
的类。Autotest服务器将获
取此类并执行方法“run_once”
以执行实际测试工作,并在完成此测试包装器中的所有测试后调用方法清理。
注意,control文件中的NAME值和测试名称必须与类名匹配。要创建测试包装器,请在power_MyBacklightTest
目录下创建名为power_MyBacklightTest.py
的测试文件。
from autotest_lib.server import test
class power_MyBacklightTest(test.test):
"""Test to check backlight brightness restored after various actions."""
version = 1
def initialize(self):
"""Initialize for this particular test."""
pass
def setup(self):
pass
def run_once(self, client_ip):
"""Where the actual test logic lives."""
pass
def cleanup(self):
"""Called at the end of the test."""
pass
测试包装器的基类设置为test.test(server / test.py
)。自动测试服务器将选择此类并按顺序调用以下方法:
- initialize, 测试的初始化
- run_once,测试的实际逻辑
- cleanup,在测试的最后调用清理工作
注意:在测试起降不会调用Setup()方法。以下是有关每种方法和属性版本的一些详细信息:
version:
必须定义类变量的版本。
initilize:
每个作业运行一次初始化方法,该方法相当于_init_ ,需要在实际运行测试之前调用它。虽然看起来作用相同,但是不要使用_init_
进行初始化,而是使用initialize ,因为_init_ 被定义用来在基类(base_test)用于初始化的基类的属性,如果覆盖_init_ 将破坏测试。
Setup:
这个方法只用在build_packages --withautotest
时才会被调用,其他方法是在测试期间被调用的。
run_once:
你必须在你的类中提供一个run_once()方法,该方法包含实际的测试逻辑。run_once()方法可以接受任何你想要的参数。这些参数
将作为*dargs传入control文件中的 job.run_test()中。在此方法结束时,您需要在此方法中验证测试结果,并记录任何失败信息。
clean_up:
该方法用来进行最后清理工作,比如恢复系统状态到原始值。
4.运行服务端测试
手动运行测试:
在chroot环境下转到src / scripts
运行:test_that $ {ClientIP} $ {LOWERCASE_AREA} _ $ {TEST_NAME}
将测试添加到套件中:
如果您希望自动测试自动运行测试,则必须将其添加到一个或多个现有测试套件中。例如,要让它作为BVT(构建验证测试)的
一部分运行,您可以编辑控制文件中的“SUITE”属性,并将其设置为“bvt”。然后,自动测试将接受此测试并将其作为bvt套件的一部
分运行。控制文件和测试包装器必须存储在src / third_party / autotest / files / server / site_tests /
目录的子目录中。请注意,目录名称
必须与测试代码的类名称匹配(该类是在测试包装器中定义的类,并从基类“test”派生)。
5.实现测试逻辑
下面将会进行背光亮度的自动测试的例程编写,需要实现以下几个功能:
- 获取/设置背光亮度
- 注销/登陆用户
- 暂停/恢复设备
将在上面创建的power_MyBacklightTest
目录中的power_MyBacklightTest.py
文件中编写。
> 从Chromium OS的特定测试例程中导入有用的模块
import logging, os, subprocess, tempfile, time
from autotest_lib.client.common_lib import error
from autotest_lib.server import autotest
from autotest_lib.server import hosts
from autotest_lib.server import test
from autotest_lib.server import utils
这些模块包含一些有用的类,用来在被测设备运行命令或者控制服务端设备。
> 辅助方法(helper methods)
在power_MyBacklightTest
类中增加如下方法。它们是一些辅助方法,用于执行来自DUT的命令,或用于检查DUT的电源状态。
def _client_cmd(self, cmd):
"""Execute a command on the client.
@param cmd: command to execute on client.
@return: The output object, with stdout and stderr fields.
"""
logging.info('Client cmd: [%s]', cmd)
return self._client.run(cmd)
def _client_cmd_and_wait_for_restart(self, cmd):
boot_id = self._client.get_boot_id()
self._client_cmd('sh -c "sync; sleep 1; %s" >/dev/null 2>&1 &' % cmd)
self._client.wait_for_restart(old_boot_id=boot_id)
def _check_power_status(self):
cmd_result = self._client_cmd('status powerd')
if 'running' not in cmd_result.stdout:
raise error.TestError('powerd must be running.')
result = self._client_cmd('power_supply_info | grep online')
if 'yes' not in result.stdout:
raise error.TestError('power must be plugged in.')
> 获取/设置背光亮度
在类中增加如下两个方法,在这两种方法中,通过_client_cmd
方法传递命令以从测试设备获取当前亮度,并使用_set_brightness
来在DUT上设置背光亮度。
def _get_brightness(self):
"""Get brightness in integer value. It's not a percentage value and
may be over 100"""
result = self._client_cmd('backlight_tool --get_brightness')
return int(result.stdout.rstrip())
def _set_brightness_percent(self, brightness=100):
result = self._client_cmd('backlight_tool --set_brightness_percent %d'
% brightness)
> 在测试期间的行为
在类中增加下面的方法,用来在测试运行期间调用shell命令,包括登陆/注销,关机/恢复等
def _do_logout(self):
self._client_cmd('restart ui')
def _do_suspend(self):
# The method calls a client side test 'power_resume' to do a power
# suspend on the DUT. It is easy yet powerful for one test to run
# another test to implement certain action without duplicating code.
self._client_at.run_test('power_Resume')
> 禁用自动光照传感器(ALS)
自动光线传感器可能会干扰亮度,因此需要在测试开始前将其禁用,并在测试完成后重新启用。在类power_MyBacklightTest
中
添加以下两个方法。
def _set_als_disable(self):
"""Turns off ALS in power manager. Saves the old has_ambient_light_sensor
flag if it exists.
"""
# Basic use of shell code via ssh run command is acceptable, rather than
# shipping over a small script to perform the same task.
als_path = '/var/lib/power_manager/has_ambient_light_sensor'
self._client_cmd('if [ -e %s ]; then mv %s %s_backup; fi' %
(als_path, als_path, als_path))
self._client_cmd('echo 0 > %s' % als_path)
self._client_cmd('restart powerd')
self._als_disabled = True
def _restore_als_disable(self):
"""Restore the has_ambient_light_sensor flag setting that was overwritten in
_set_als_disable.
"""
if not self._als_disabled:
return
als_path = '/var/lib/power_manager/has_ambient_light_sensor'
self._client_cmd('rm %s' % als_path)
self._client_cmd('if [ -e %s_backup ]; then mv %s_backup %s; fi' %
(als_path, als_path, als_path))
self._client_cmd('restart powerd')
> 运行测试并验证测试结果
这是运行实际测试逻辑的方法。在此方法结束时,您将验证测试结果,并记录任何失败。它会覆盖基类test.test中的run_once方法。
# The dictionary of test scenarios and its corresponding method to
# apply each action to the DUT.
_transitions = {
'logout': _do_logout,
'suspend': _do_suspend,
}
def run_once(self, client_ip):
"""Run the test.
For each system transition event in |_transitions|:
Read the brightness.
Trigger transition event.
Wait for client to come back up.
Check new brightness against previous brightness.
@param client_ip: string of client's ip address (required)
"""
if not client_ip:
error.TestError("Must provide client's IP address to test")
# Create a custom host class for this machine, which is used to execute
# commands and other functions.
self._client = hosts.create_host(client_ip)
# Create an Autotest instance which you can run method like run_test,
# which can execute another client side test to facilitate this test.
self._client_at = autotest.Autotest(self._client)
self._results = {}
self._check_power_status()
self._set_als_disable()
# Save the original brightness, to be restored after the test ends.
self._original_brightness = self._get_brightness()
# Set the brightness to a random number which is different from
# system default value.
self._set_brightness_percent(71)
# Run the transition event tests.
for test_name in self._transitions:
self._old_brightness = self._get_brightness()
self._transitions[test_name](self)
# Save the before and after backlight values.
self._results[test_name] = { 'old': self._old_brightness,
'new': self._get_brightness() }
# Check results to make sure backlight levels were preserved across
# transition events.
num_failed = 0
for test_name in self._results:
old_brightness = self._results[test_name]['old']
new_brightness = self._results[test_name]['new']
if old_brightness == new_brightness:
logging.info('Transition event [ PASSED ]: %s', test_name)
else:
logging.info('Transition event [ FAILED ]: %s', test_name)
logging.info(' Brightness changed: %d -> %d',
old_brightness, new_brightness)
num_failed += 1
if num_failed > 0:
raise error.TestFail(('Failed to preserve backlight over %d '
'transition event(s).') % num_failed)
> Cleanup
def cleanup(self):
"""Restore DUT's condition before the test starts, and check the test
results.
"""
self._restore_als_disable()
self._set_brightness_percent(self._original_brightness)
6.通过手动运行自动测试来验证测试
之后在chroot环境下运行,test_that ip地址 power_MyBacklightTest
如果正确运行,在被测机器上将会进行以下操作:
- 用户注销
- 用户登录
- 屏幕亮度保持在71%。
- 屏幕变黑(系统暂停/恢复)。
- 系统再次出现。
- 屏幕亮度保持在71%。
- 在清理过程中,屏幕亮度将重置为其原始值。
测试运行生成的所有日志都可以在tmp文件夹中找到,例如/ tmp / test_that_results_WZ_eVZ。
- status和status.log,包含每个测试的开始/结束时间和测试结果的结果。
- keyval,一些设置信息,例如无人机,DUT的主机名
- debug / autoserv.DEBUG,包含测试期间autoserv收集的所有日志。
测试完成后,将在输出结尾显示以下消息,表示所有测试均已通过:
------------------------------------------------------------------------------------------------------------
/tmp/test_that_results_Bdfrwv/results-1-experimental_power_MyBacklightTest [ PASSED ]
/tmp/test_that_results_Bdfrwv/results-1-experimental_power_MyBacklightTest/power_MyBacklightTest [ PASSED ]
------------------------------------------------------------------------------------------------------------
Total PASS: 2/2 (100%)
18:16:28 INFO | Finished running tests. Results can be found in /tmp/test_that_results_Bdfrwv