1#!/usr/bin/env python
2#
3# Copyright (C) 2018 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18import logging
19import time
20
21from vts.proto import ComponentSpecificationMessage_pb2 as CompSpecMsg
22from vts.runners.host import asserts
23from vts.runners.host import base_test
24from vts.runners.host import const
25from vts.runners.host import test_runner
26from vts.utils.python.file import target_file_utils
27
28
29class VtsCodelabHidlHandleTest(base_test.BaseTestClass):
30    """This class tests hidl_handle API calls from host side.
31
32    Attributes:
33        TEST_DIR_PATH: string, directory where the test file will be created.
34        TEST_FILE_PATH: string, path to the file that host side uses.
35        SERVICE_NAME: string, dumpstate HAL full service name.
36        START_COMMAND: string, command to start dumpstate HAL service
37                       since it is a lazy HAL.
38        CHECK_COMMAND: string, command to check if dumpstate HAL service
39                       is started.
40        MAX_RETRY: int, number of times to check if dumpstate HAL service
41                   is started.
42        _created: bool, whether we created TEST_DIR_PATH.
43        _writer: ResourceHidlHandleMirror, writer who writes to TEST_FILE_PATH.
44        _reader: ResourceHidlHandleMirror, reader who reads from TEST_FILE_PATH.
45        _dumpstate: dumpstate HAL server instance.
46        _permission: SELinux permission, either enforcing or permissive.
47                     This needs to set to permissive on the target side
48                     when HAL server writes to user-defined destination.
49                     This class will restore the permission level after
50                     the test finishes.
51    """
52
53    TEST_DIR_PATH = "/data/local/tmp/vts_codelab_tmp/"
54    TEST_FILE_PATH = TEST_DIR_PATH + "test.txt"
55    SERVICE_NAME = "android.hardware.dumpstate@1.0::IDumpstateDevice/default"
56    START_COMMAND = "setprop ctl.interface_start " + SERVICE_NAME
57    CHECK_COMMAND = "lshal --types=b | grep \"" + SERVICE_NAME + "\""
58    MAX_RETRY = 3
59
60    def setUpClass(self):
61        """Necessary setup for the test environment.
62
63        We need to start dumpstate HAL service manually because it is a lazy
64        HAL service, then load it in our target-side driver.
65        Create a tmp directory in /data to create test files in it if the tmp
66        directory doesn't exist.
67        We also need to set SELinux permission to permissive because
68        HAL server and host side are communicating via a file.
69        We will recover SELinux permission during tearDown of this class.
70        """
71        self.dut = self.android_devices[0]
72        # Execute shell command to start dumpstate HAL service because
73        # it is a lazy HAL service.
74        self.dut.shell.Execute(self.START_COMMAND)
75
76        start_hal_success = False
77        # Wait until service is started.
78        # Retry at most three times.
79        for _ in range(self.MAX_RETRY):
80            result = self.dut.shell.Execute(self.CHECK_COMMAND)
81            if result[const.STDOUT][0] != "":
82                start_hal_success = True  # setup successful
83                break
84            time.sleep(1)  # wait one second.
85        # Dumpstate HAL service is still not started after waiting for
86        # self.MAX_RETRY times, stop the testcase.
87        if not start_hal_success:
88            logging.error("Failed to start dumpstate HAL service.")
89            return False
90
91        # Initialize a hal driver to start all managers on the target side,
92        # not used for other purposes.
93        self.dut.hal.InitHidlHal(
94            target_type="dumpstate",
95            target_basepaths=self.dut.libPaths,
96            target_version_major=1,
97            target_version_minor=0,
98            target_package="android.hardware.dumpstate",
99            target_component_name="IDumpstateDevice",
100            bits=int(self.abi_bitness))
101        # Make a shortcut name for the dumpstate HAL server.
102        self._dumpstate = self.dut.hal.dumpstate
103
104        # In order for dumpstate service to write to file, need to set
105        # SELinux to permissive.
106        permission_result = self.dut.shell.Execute("getenforce")
107        self._permission = permission_result[const.STDOUT][0].strip()
108        if self._permission == "Enforcing":
109            self.dut.shell.Execute("setenforce permissive")
110
111        # Check if a tmp directory under /data exists.
112        self._created = False
113        if not target_file_utils.Exists(self.TEST_DIR_PATH, self.dut.shell):
114            # Create a tmp directory under /data.
115            self.dut.shell.Execute("mkdir " + self.TEST_DIR_PATH)
116            # Verify it succeeds. Stop test if it fails.
117            if not target_file_utils.Exists(self.TEST_DIR_PATH,
118                                            self.dut.shell):
119                logging.error("Failed to create " + self.TEST_DIR_PATH +
120                              " directory. Stopping test.")
121                return False
122            # Successfully created the directory.
123            logging.info("Manually created " + self.TEST_DIR_PATH +
124                         " for the test.")
125            self._created = True
126
127    def setUp(self):
128        """Initialize a writer and a reader for each test case.
129
130        We open the file with w+ in every test case, which will create the file
131        if it doesn't exist, or truncate the file if it exists, because some
132        test case won't fully read the content of the file, causing dependency
133        between test cases.
134        """
135        self._writer = self.dut.resource.InitHidlHandleForSingleFile(
136            self.TEST_FILE_PATH,
137            "w+",
138            client=self.dut.hal.GetTcpClient("dumpstate"))
139        self._reader = self.dut.resource.InitHidlHandleForSingleFile(
140            self.TEST_FILE_PATH,
141            "r",
142            client=self.dut.hal.GetTcpClient("dumpstate"))
143        asserts.assertTrue(self._writer is not None,
144                           "Writer should be initialized successfully.")
145        asserts.assertTrue(self._reader is not None,
146                           "Reader should be initialized successfully.")
147        asserts.assertNotEqual(self._writer.handleId, -1)
148        asserts.assertNotEqual(self._reader.handleId, -1)
149
150    def tearDown(self):
151        """Cleanup for each test case.
152
153        This method closes all open file descriptors on target side.
154        """
155        # Close all file descriptors by calling CleanUp().
156        self.dut.resource.CleanUp()
157
158    def tearDownClass(self):
159        """Cleanup after all tests.
160
161        Remove self.TEST_FILE_DIR directory if we created it at the beginning.
162        If SELinux permission level is changed, restore it here.
163        """
164        # Delete self.TEST_DIR_PATH if we created it.
165        if self._created:
166            logging.info("Deleting " + self.TEST_DIR_PATH +
167                         " directory created for this test.")
168            self.dut.shell.Execute("rm -rf " + self.TEST_DIR_PATH)
169
170        # Restore SELinux permission level.
171        if self._permission == "Enforcing":
172            self.dut.shell.Execute("setenforce enforcing")
173
174    def testInvalidWrite(self):
175        """Test writing to a file with no write permission should fail. """
176        read_data = self._reader.writeFile("abc", 3)
177        asserts.assertTrue(
178            read_data is None,
179            "Reader should fail because it doesn't have write permission to the file."
180        )
181
182    def testOpenNonexistingFile(self):
183        """Test opening a nonexisting file with read-only flag should fail.
184
185        This test case first checks if the file exists. If it exists, it skips
186        this test case. If it doesn't, it will try to open the non-existing file
187        with 'r' flag.
188        """
189        if not target_file_utils.Exists(self.TEST_DIR_PATH + "abc.txt",
190                                        self.dut.shell):
191            logging.info("Test opening a non-existing file with 'r' flag.")
192            failed_reader = self.dut.resource.InitHidlHandleForSingleFile(
193                self.TEST_DIR_PATH + "abc.txt",
194                "r",
195                client=self.dut.hal.GetTcpClient("dumpstate"))
196            asserts.assertTrue(
197                failed_reader is None,
198                "Open a non-existing file with 'r' flag should fail.")
199
200    def testSimpleReadWrite(self):
201        """Test a simple read/write interaction between reader and writer. """
202        write_data = "Hello World!"
203        asserts.assertEqual(
204            len(write_data), self._writer.writeFile(write_data,
205                                                    len(write_data)))
206        read_data = self._reader.readFile(len(write_data))
207        asserts.assertEqual(write_data, read_data)
208
209    def testLargeReadWrite(self):
210        """Test consecutive reads/writes between reader and writer. """
211        write_data = "Android VTS"
212
213        for i in range(10):
214            asserts.assertEqual(
215                len(write_data),
216                self._writer.writeFile(write_data, len(write_data)))
217            curr_read_data = self._reader.readFile(len(write_data))
218            asserts.assertEqual(curr_read_data, write_data)
219
220    def testHidlHandleArgument(self):
221        """Test calling APIs in dumpstate HAL server.
222
223        Host side specifies a handle object in resource_manager, ans pass
224        it to dumpstate HAL server to write debug message into it.
225        Host side then reads part of the debug message.
226        """
227        # Prepare a VariableSpecificationMessage to specify the handle object
228        # that will be passed into the HAL service.
229        var_msg = CompSpecMsg.VariableSpecificationMessage()
230        var_msg.type = CompSpecMsg.TYPE_HANDLE
231        var_msg.handle_value.handle_id = self._writer.handleId
232
233        self._dumpstate.dumpstateBoard(var_msg)
234        # Read 1000 bytes to retrieve part of debug message.
235        debug_msg = self._reader.readFile(1000)
236        logging.info("Below are part of result from dumpstate: ")
237        logging.info(debug_msg)
238        asserts.assertNotEqual(debug_msg, "")
239
240
241if __name__ == "__main__":
242    test_runner.main()
243