| <html devsite><head> |
| <title>设备 shell 命令</title> |
| <meta name="project_path" value="/_project.yaml"/> |
| <meta name="book_path" value="/_book.yaml"/> |
| </head> |
| <body> |
| <!-- |
| Copyright 2018 The Android Open Source Project |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| --> |
| |
| <p>在 VTS 测试期间,shell 命令用于执行目标端测试二进制文件,获取/设置属性、环境变量和系统信息以及启动/停止 Android 框架。您可以使用 <code>adb shell</code> 命令或设备上运行的 VTS shell 驱动程序(推荐本方式)执行 VTS 设备 shell 命令。</p> |
| |
| <h2 id="adb-shell">使用 ADB shell</h2> |
| <p>如果在测试期间需要关闭 USB 端口或重新启动设备,则必须使用 ADB shell,因为如果没有持久的 USB 连接,VTS shell 驱动程序将不可用。您可以从 Python 测试脚本中的 <code>AndroidDevice</code> 对象调用 ADB shell。例如:</p> |
| <ul> |
| <li>获取 Android 设备对象: |
| <pre class="devsite-click-to-copy"> |
| self.device = self.android_devices[0] |
| </pre> |
| </li> |
| <li>发出一个 shell 命令: |
| <pre class="devsite-click-to-copy"> |
| result = self.device.adb.shell(‘ls') |
| </pre> |
| </li> |
| </ul> |
| |
| <h2 id="vts-shell-driver">使用 VTS shell 驱动程序</h2> |
| <p>VTS shell 驱动程序是一个代理二进制文件,该文件会在设备上运行并执行 shell 命令。默认情况下,如果 shell 驱动程序在设备上运行,则 VTS 将使用该驱动程序,因为此方法比使用 <code>adb |
| shell</code> 命令的延迟更短。</p> |
| |
| <p><img src="images/vts_shell_driver.png"/></p> |
| <figcaption><strong>图 1.</strong> VTS shell 驱动程序。</figcaption> |
| |
| <p>VTS 框架支持多设备测试,其中每部 Android 设备都在基础运行器中显示为一个 AndroidDevice 对象。默认情况下,VTS 框架会将 VTS 代理和 VTS shell 驱动程序二进制文件推送到每部 Android 设备,并与这些设备上的 VTS 代理建立 TCP 连接。</p> |
| |
| <p>要执行 shell 命令,主机端 Python 脚本会对 AndroidDevice 对象内的 ShellMirror 对象发出函数调用。ShellMirror 对象将 shell 命令文本打包成 <a href="https://developers.google.com/protocol-buffers/" class="external">protobuf</a> 消息,并通过 TCP 通道将其发送至 Android 设备上的 VTS 代理。然后,设备上运行的代理通过 Unix 套接字将 shell 命令转发给 VTS shell 驱动程序。</p> |
| |
| <p>当 VTS shell 驱动程序收到一个 shell 命令时,它会在设备 shell 上通过 <a href="https://en.wikipedia.org/wiki/Nohup" class="external">nohup</a> 执行该命令以防止中断。然后从 <code>nohup</code> 检索 stdout、stderr 和返回代码并将其发送回 VTS 代理。最后,代理通过将命令结果封装成 <code>protobuf</code> 消息来回复主机。</p> |
| |
| <h3 id="advantages">优点</h3> |
| <p>使用 VTS shell 驱动程序(而不是 <code>adb |
| shell</code>)的优点包括:</p> |
| <ul> |
| <li><strong>可靠性。</strong> VTS shell 驱动程序使用 <code>nohup</code> 执行命令(采用默认设置)。由于 VTS 测试主要是较低级别的 HAL 和内核测试,因此 <code>nohup</code> 确保 shell 命令在执行期间不会中断。</li> |
| <li><strong>性能</strong>。虽然 <code>adb shell</code> 命令会缓存一些结果(例如列出目录中的文件),但在执行任务(例如执行测试二进制文件)时,它会产生连接开销。VTS shell 驱动程序在整个测试过程中保持有效连接,所以唯一的开销是 USB 通信。在我们的测试中,使用 VTS shell 驱动程序执行一个具有 100 个空 Gtest 二进制文件调用的命令比使用 <code>adb shell</code> 快 20%。由于 VTS shell 通信需要完成大量日志记录工作,因此实际差异更大。</li> |
| <li><strong>状态保持</strong>。VTS shell 驱动程序为每个终端名称(默认终端名称为“default”)保持一个终端会话。<em></em>在某个终端会话中设置的环境变量仅适用于同一会话中的后续命令。</li> |
| <li><strong>可扩展性</strong>。VTS 框架和设备驱动程序之间的 shell 命令通信封装在 protobuf 中,以便日后能够进行压缩、远程控制、加密等。还存在其他提高性能的可能性,包括通信开销大于结果字符串解析时的设备端结果解析。</li> |
| </ul> |
| |
| <h3 id="disadvantages">缺点</h3> |
| <p>使用 VTS shell 驱动程序(而不是 <code>adb |
| shell</code>)的缺点包括:</p> |
| <ul> |
| <li><strong>额外的二进制文件</strong>。VTS 代理文件必须推送到设备并在测试执行之后清理干净。</li> |
| <li><strong>需要有效连接</strong>。如果主机和代理之间的 TCP 连接在测试期间有意或无意地断开(由于 USB 连接断开、端口关闭、设备崩溃等),则 shell 命令无法传输到 VTS 代理。即使自动切换到 <code>adb shell</code>,断开连接之前的命令结果和状态也是未知的。</li> |
| </ul> |
| |
| <h3 id="examples">示例</h3> |
| <p>在 VTS 主机端 Python 测试脚本中使用 shell 命令的示例:</p> |
| <ul> |
| <li>获取 Android 设备对象: |
| <pre class="devsite-click-to-copy"> |
| self.device = self.android_devices[0] |
| </pre> |
| </li> |
| <li>为所选设备获取 shell 对象: |
| <pre class="devsite-click-to-copy"> |
| self.shell = self.device.shell |
| </pre> |
| </li> |
| <li>发出一个 shell 命令: |
| <pre class="devsite-click-to-copy"> |
| results = self.shell.Execute(‘ls') |
| </pre> |
| </li> |
| <li>发出一串 shell 命令: |
| <pre class="devsite-click-to-copy"> |
| results = self.shell.Execute([‘cd /data/local/tmp', ‘ls']) |
| </pre> |
| </li> |
| </ul> |
| |
| <h3 id="command-result-object">命令结果对象</h3> |
| <p>执行 shell 命令后的返回对象是一个包含键 <code>stdouts</code>、<code>stderrs</code> 和 <code>return_codes</code> 的字典。无论 shell 命令是作为单个字符串还是命令字符串列表提供,结果字典的每个值都始终是一个列表。</p> |
| |
| <p>要验证命令列表的返回代码,测试脚本必须检查索引。例如:</p> |
| |
| <pre class="devsite-click-to-copy"> |
| asserts.assertFalse(any(results[‘return_codes']), ‘some command failed.') |
| </pre> |
| |
| <p>或者,脚本可以单独检查每个命令索引。例如:</p> |
| |
| <pre class="devsite-click-to-copy"> |
| asserts.assertEqual(results[‘return_codes'][0], 0, ‘first command failed')<br /> |
| asserts.assertEqual(results[‘return_codes'][1], 0, ‘second command failed') |
| </pre> |
| |
| </body></html> |