1#!/usr/bin/env python
2#
3# Copyright (C) 2016 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 inspect
19import logging
20import os
21import re
22import subprocess
23import sys
24
25from vts.runners.host import asserts
26from vts.runners.host import base_test
27from vts.runners.host import const
28from vts.runners.host import test_runner
29
30
31class CameraITSTest(base_test.BaseTestClass):
32    """Running CameraITS tests in VTS.
33
34    Attributes:
35        _TABLET_TYPES: a list of strings, known tablet product types.
36        dut1: AndroidDevice instance, for 1st device whose front-facing camera
37              is tested.
38        dut2: AndroidDevice instance, for 2nd device whose back-facing camera
39              is tested.
40        display_device: AndroidDevice instance, for a tablet device used as
41                        a display screen.
42    """
43    _TABLET_TYPES = ["dragon"]
44
45    # TODO: use config file to pass in:
46    #          - serial for dut and screen
47    #          - camera id
48    #       so that we can run other test scenes with ITS-in-a-box
49    def setUpClass(self):
50        """Setup ITS running python environment and check for required python modules
51        """
52        # When VTS lab infra is used, the provided device order is:
53        #   DUT (front-facing camera),
54        #   DUT (back-facing camera),
55        #   Tablet (display).
56        # This order shall be preserved when a custom test serving infra is
57        # used.
58        self.dut1 = self.android_devices[0]
59        self.dut2 = self.android_devices[1]
60        self.display_device = self.android_devices[2]
61        # In a local run (e.g., using a VTS test harness), the device order
62        # can be arbitrary. So tablet is detected and chosen as a display
63        # device. Similarly, we need a mechanism to detect DUT which uses
64        # front-facing camera (that can be done here or inside another layer).
65
66        if self.dut1.product_type in self._TABLET_TYPES:
67            temp_device = self.dut1
68            self.dut1 = self.dut2
69            self.dut2 = self.dut3
70            self.display_device = temp_device
71        elif self.dut2.product_type in self._TABLET_TYPES:
72            temp_device = self.dut2
73            self.dut2 = self.display_device
74            self.display_device = temp_device
75
76        self.device_arg = "device=%s" % self.dut1.serial
77        self.display_device_arg = "chart=%s" % self.display_device.serial
78        self.its_path = str(
79            os.path.abspath(os.path.join(self.data_file_path, 'CameraITS')))
80        logging.info("cwd: %s", os.getcwd())
81        logging.info("its_path: %s", self.its_path)
82        self.out_path = logging.log_path
83        os.environ["CAMERA_ITS_TOP"] = self.its_path
84        # Below module check code assumes tradefed is running python 2.7
85        # If tradefed switches to python3, then we will be checking modules in python3 while ITS
86        # scripts should be ran in 2.7.
87        if sys.version_info[:2] != (2, 7):
88            logging.warning("Python version %s found; "
89                            "CameraITSTest only tested with Python 2.7." %
90                            (str(sys.version_info[:3])))
91        logging.info("===============================")
92        logging.info("Python path is: %s" % (sys.executable))
93        logging.info("PYTHONPATH env is: " + os.environ["PYTHONPATH"])
94        import PIL
95        if hasattr(PIL, "__version__"):
96            logging.info("PIL version is %s", PIL.__version__)
97        logging.info("PIL path is " + inspect.getfile(PIL))
98        from PIL import Image
99        logging.info("Image path is " + inspect.getfile(Image))
100        import numpy
101        logging.info("numpy version is " + numpy.__version__)
102        logging.info("numpy path is " + inspect.getfile(numpy))
103        import scipy
104        logging.info("scipy version is " + scipy.__version__)
105        logging.info("scipy path is " + inspect.getfile(scipy))
106        import matplotlib
107        logging.info("matplotlib version is " + matplotlib.__version__)
108        logging.info("matplotlib path is " + inspect.getfile(matplotlib))
109        from matplotlib import pylab
110        logging.info("pylab path is " + inspect.getfile(pylab))
111        logging.info("===============================")
112        modules = [
113            "numpy", "PIL", "Image", "matplotlib", "pylab", "scipy.stats",
114            "scipy.spatial"
115        ]
116        for m in modules:
117            try:
118                if m == "Image":
119                    # Image modules are now imported from PIL
120                    exec ("from PIL import Image")
121                elif m == "pylab":
122                    exec ("from matplotlib import pylab")
123                else:
124                    exec ("import " + m)
125            except ImportError as e:
126                asserts.fail("Cannot import python module %s: %s" % (m,
127                                                                     str(e)))
128
129        # Add ITS module path to path
130        its_path = os.path.join(self.its_path, "pymodules")
131        env_python_path = os.environ["PYTHONPATH"]
132        self.pythonpath = env_python_path if its_path in env_python_path else \
133                "%s:%s" % (its_path, env_python_path)
134        os.environ["PYTHONPATH"] = self.pythonpath
135        logging.info("new PYTHONPATH: %s", self.pythonpath)
136
137    def RunTestcase(self, testpath):
138        """Runs the given testcase and asserts the result.
139
140        Args:
141            testpath: string, format tests/[scenename]/[testname].py
142        """
143        testname = re.split("/|\.", testpath)[-2]
144        cmd = [
145            'python', os.path.join(self.its_path, testpath), self.device_arg,
146            self.display_device_arg
147        ]
148        outdir = self.out_path
149        outpath = os.path.join(outdir, testname + "_stdout.txt")
150        errpath = os.path.join(outdir, testname + "_stderr.txt")
151        logging.info("cwd: %s", os.getcwd())
152        logging.info("cmd: %s", cmd)
153        logging.info("outpath: %s", outpath)
154        logging.info("errpath: %s", errpath)
155        with open(outpath, "w") as fout, open(errpath, "w") as ferr:
156            retcode = subprocess.call(
157                cmd, stderr=ferr, stdout=fout, cwd=outdir)
158        if retcode != 0 and retcode != 101:
159            # Dump all logs to host log if the test failed
160            with open(outpath, "r") as fout, open(errpath, "r") as ferr:
161                logging.info(fout.read())
162                logging.error(ferr.read())
163
164        asserts.assertTrue(retcode == 0 or retcode == 101,
165                           "ITS %s retcode %d" % (testname, retcode))
166
167    def FetchTestPaths(self, scene):
168        """Returns a list of test paths for a given test scene.
169
170        Args:
171            scnee: one of ITS test scene name.
172        """
173        its_path = self.its_path
174        paths = [
175            os.path.join("tests", scene, s)
176            for s in os.listdir(os.path.join(its_path, "tests", scene))
177            if s[-3:] == ".py" and s[:4] == "test"
178        ]
179        paths.sort()
180        return paths
181
182    def generateAllTestCases(self):
183        # Unused packages:
184        # * No plan to support in the near future
185        #   - "dng_noise_model"
186        #   - "inprog"
187        #   - "rolling_shutter_skew"
188        # * Not for ITS box (but for diffuser plate)
189        #   - "scene5"
190        # * Not for ITS box (but for sensor_fusion rotation rig, high-end only)
191        #   - "sensor_fusion"
192        # * Add those scenes to the below list after scene swapping is ported:
193        #     "scene1"
194        #     "scene2"
195        #     "scene3"
196        #     "scene4"
197        for test_package_name in ["scene0"]:
198            testpaths = self.FetchTestPaths(test_package_name)
199            self.runGeneratedTests(
200                test_func=self.RunTestcase,
201                settings=testpaths,
202                name_func=lambda path: "%s_%s" % (re.split("/|\.", path)[-3], re.split("/|\.", path)[-2]))
203
204
205if __name__ == "__main__":
206    test_runner.main()
207