File: test_walker.py

package info (click to toggle)
android-platform-development 7.0.0%2Br33-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 30,092 kB
  • sloc: ansic: 161,291; java: 15,681; cpp: 7,721; xml: 6,419; python: 5,456; sh: 1,748; lisp: 261; ruby: 183; asm: 132; perl: 88; makefile: 22
file content (263 lines) | stat: -rwxr-xr-x 8,815 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
#!/usr/bin/python2.4
#
#
# Copyright 2009, 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.

"""Utility to find instrumentation test definitions from file system."""

# python imports
import os

# local imports
import android_build
import android_mk
import gtest
import instrumentation_test
import logger


class TestWalker(object):
  """Finds Android tests from filesystem."""

  def FindTests(self, path):
    """Gets list of Android tests found at given path.

    Tests are created from info found in Android.mk and AndroidManifest.xml
    files relative to the given path.

    Currently supported tests are:
    - Android application tests run via instrumentation
    - native C/C++ tests using GTest framework. (note Android.mk must follow
      expected GTest template)

    FindTests will first scan sub-folders of path for tests. If none are found,
    it will scan the file system upwards until a valid test Android.mk is found
    or the Android build root is reached.

    Some sample values for path:
    - a parent directory containing many tests:
    ie development/samples will return tests for instrumentation's in ApiDemos,
    ApiDemos/tests, Notepad/tests etc
    - a java test class file
    ie ApiDemos/tests/src/../ApiDemosTest.java will return a test for
    the instrumentation in ApiDemos/tests, with the class name filter set to
    ApiDemosTest
    - a java package directory
    ie ApiDemos/tests/src/com/example/android/apis will return a test for
    the instrumentation in ApiDemos/tests, with the java package filter set
    to com.example.android.apis.

    TODO: add GTest examples

    Args:
      path: file system path to search

    Returns:
      list of test suites that support operations defined by
      test_suite.AbstractTestSuite
    """
    if not os.path.exists(path):
      logger.Log('%s does not exist' % path)
      return []
    realpath = os.path.realpath(path)
    # ensure path is in ANDROID_BUILD_ROOT
    self._build_top = os.path.realpath(android_build.GetTop())
    if not self._IsPathInBuildTree(realpath):
      logger.Log('%s is not a sub-directory of build root %s' %
                 (path, self._build_top))
      return []

    # first, assume path is a parent directory, which specifies to run all
    # tests within this directory
    tests = self._FindSubTests(realpath, [])
    if not tests:
      logger.SilentLog('No tests found within %s, searching upwards' % path)
      tests = self._FindUpstreamTests(realpath)
    return tests

  def _IsPathInBuildTree(self, path):
    """Return true if given path is within current Android build tree.

    Args:
      path: absolute file system path

    Returns:
      True if path is within Android build tree
    """
    return os.path.commonprefix([self._build_top, path]) == self._build_top

  def _MakePathRelativeToBuild(self, path):
    """Convert given path to one relative to build tree root.

    Args:
      path: absolute file system path to convert.

    Returns:
      The converted path relative to build tree root.

    Raises:
      ValueError: if path is not within build tree
    """
    if not self._IsPathInBuildTree(path):
      raise ValueError
    build_path_len = len(self._build_top) + 1
    # return string with common build_path removed
    return path[build_path_len:]

  def _FindSubTests(self, path, tests, upstream_build_path=None):
    """Recursively finds all tests within given path.

    Args:
      path: absolute file system path to check
      tests: current list of found tests
      upstream_build_path: the parent directory where Android.mk that builds
        sub-folders was found

    Returns:
      updated list of tests
    """
    if not os.path.isdir(path):
      return tests
    android_mk_parser = android_mk.CreateAndroidMK(path)
    if android_mk_parser:
      build_rel_path = self._MakePathRelativeToBuild(path)
      if not upstream_build_path:
        # haven't found a parent makefile which builds this dir. Use current
        # dir as build path
        tests.extend(self._CreateSuites(
            android_mk_parser, path, build_rel_path))
      else:
        tests.extend(self._CreateSuites(android_mk_parser, path,
                                        upstream_build_path))
      # TODO: remove this logic, and rely on caller to collapse build
      # paths via make_tree

      # Try to build as much of original path as possible, so
      # keep track of upper-most parent directory where Android.mk was found
      # that has rule to build sub-directory makefiles.
      # this is also necessary in case of overlapping tests
      # ie if a test exists at 'foo' directory  and 'foo/sub', attempting to
      # build both 'foo' and 'foo/sub' will fail.

      if android_mk_parser.IncludesMakefilesUnder():
        # found rule to build sub-directories. The parent path can be used,
        # or if not set, use current path
        if not upstream_build_path:
          upstream_build_path = self._MakePathRelativeToBuild(path)
      else:
        upstream_build_path = None
    for filename in os.listdir(path):
      self._FindSubTests(os.path.join(path, filename), tests,
                         upstream_build_path)
    return tests

  def _FindUpstreamTests(self, path):
    """Find tests defined upward from given path.

    Args:
      path: the location to start searching.

    Returns:
      list of test_suite.AbstractTestSuite found, may be empty
    """
    factory = self._FindUpstreamTestFactory(path)
    if factory:
      return factory.CreateTests(sub_tests_path=path)
    else:
      return []

  def _GetTestFactory(self, android_mk_parser, path, build_path):
    """Get the test factory for given makefile.

    If given path is a valid tests build path, will return the TestFactory
    for creating tests.

    Args:
      android_mk_parser: the android mk to evaluate
      path: the filesystem path of the makefile
      build_path: filesystem path for the directory
      to build when running tests, relative to source root.

    Returns:
      the TestFactory or None if path is not a valid tests build path
    """
    if android_mk_parser.HasGTest():
      return gtest.GTestFactory(path, build_path)
    elif instrumentation_test.HasInstrumentationTest(path):
      return instrumentation_test.InstrumentationTestFactory(path,
          build_path)
    else:
      # somewhat unusual, but will continue searching
      logger.SilentLog('Found makefile at %s, but did not detect any tests.'
                       % path)

    return None

  def _GetTestFactoryForPath(self, path):
    """Get the test factory for given path.

    If given path is a valid tests build path, will return the TestFactory
    for creating tests.

    Args:
      path: the filesystem path to evaluate

    Returns:
      the TestFactory or None if path is not a valid tests build path
    """
    android_mk_parser = android_mk.CreateAndroidMK(path)
    if android_mk_parser:
      build_path = self._MakePathRelativeToBuild(path)
      return self._GetTestFactory(android_mk_parser, path, build_path)
    else:
      return None

  def _FindUpstreamTestFactory(self, path):
    """Recursively searches filesystem upwards for a test factory.

    Args:
      path: file system path to search

    Returns:
      the TestFactory found or None
    """
    factory = self._GetTestFactoryForPath(path)
    if factory:
      return factory
    dirpath = os.path.dirname(path)
    if self._IsPathInBuildTree(path):
      return self._FindUpstreamTestFactory(dirpath)
    logger.Log('A tests Android.mk was not found')
    return None

  def _CreateSuites(self, android_mk_parser, path, upstream_build_path):
    """Creates TestSuites from a AndroidMK.

    Args:
      android_mk_parser: the AndroidMK
      path: absolute file system path of the makefile to evaluate
      upstream_build_path: the build path to use for test. This can be
        different than the 'path', in cases where an upstream makefile
        is being used.

    Returns:
      the list of tests created
    """
    factory = self._GetTestFactory(android_mk_parser, path,
                                   build_path=upstream_build_path)
    if factory:
      return factory.CreateTests(path)
    else:
      return []