Beispiel #1
0
def load_module_from_module_name(
    fullname, errors_out=None, reload_mod=False, include_import_error=False, include_test_functions=True
):
    """Load a module.

  Errors which occurred while importing the module are appended to errors_out.
  An error is appended as (fullname, error_traceback) tuple.

  Args:
    fullname: The full module name, e.g., package.subpackage.module.
    errors_out: A list to which import error tracebacks are appended, or None
        to ignore errors.
    reload_mod: Try to remove module before reloading it.
    include_import_error: Whether to include an error tuple in case the module
        does not exist.
    include_test_functions:  Whether to wrap test functions into a test case
        class.  Note that if this is False and the module has already been
        imported with include_test_functions=True, then the module will still
        have the wrapped test functions from before.

  Returns:
    The loaded module or None if the module could not be imported.

  Raises:
    TypeError: Wrong input arguments.
  """
    utils.check_type(fullname, "fullname", str)
    utils.check_type(errors_out, "errors_out", (types.NoneType, list))
    utils.check_type(reload_mod, "reload_mod", bool)
    utils.check_type(include_import_error, "include_import_error", bool)
    utils.check_type(include_test_functions, "include_test_functions", bool)
    module = None
    try:
        loaded_by_import = False
        if fullname not in sys.modules:
            __import__(fullname)
            loaded_by_import = True
        module = sys.modules[fullname]
        if reload_mod and not loaded_by_import:
            module = reload(module)
        if include_test_functions:
            wrap_test_functions(module)
    # pylint: disable-msg=W0703
    except:
        if errors_out is not None:
            if include_import_error:
                errors_out.append((fullname, traceback.format_exc()))
            else:
                # The error should only be noted if the exception was raised from
                # within the imported module, rather than being raised because the
                # module did not exist.  To check this, walk the traceback stack and
                # look for a module with __name__ == fullname (or None due to the
                # broken module being cleared).
                tb = sys.exc_info()[2]
                while tb:
                    if tb.tb_frame.f_globals["__name__"] in [None, fullname]:
                        errors_out.append((fullname, traceback.format_exc()))
                        break
                    tb = tb.tb_next
    return module
Beispiel #2
0
def start_batch(fullname, conf):
  """Creates a TestBatch for all the given tests and returns it.

  Eventually, all tests will automatically run in the background.

  Args:
    fullname: The full name of the group of tests to run.  This should be the
        period-separated name of a test package, module, class, or method, or
        the empty string to run all tests.
    conf: The configuration to use.
    run_immediately: If set to True, tests will be run immediately rather than
        in the task queue.  They will be finished before this function returns.

  Returns:
    The TestBatch created for the run.

  Raises:
    TypeError: Wrong input arguments.
  """
  utils.check_type(fullname, 'fullname', str)
  utils.check_type(conf, 'conf', config.Config)
  ctx_options = models.get_ctx_options(conf)
  # It's necessary to set the key because if ctx_options['use_datastore'] ==
  # False, the key will not be set to something reasonable automatically.
  batch_key = ndb.Key(models.TestBatch, utils.rand_unique_id())
  batch = models.TestBatch(fullname=fullname, key=batch_key)
  batch.put(**ctx_options)
  call = deferred.DeferredCall(_initialize_batch, fullname, batch_key, conf)
  if conf.storage == 'immediate':
    call.run()
    # _initialize_batch() should have updated batch data
    return batch.key.get(**ctx_options)
  else:
    deferred.defer_multi([call], queue=conf.test_queue)
    return batch
Beispiel #3
0
def get_abs_path_from_package_name(packagename):
    """Get absolute file path of the package.

  In order to retrieve the path, the package module will be imported.

  Args:
    packagename: The full package name, e.g., package.subpackage.

  Returns:
    An absolute path or None if path does not exist.

  Raises:
    TypeError: Wrong input arguments.
  """
    utils.check_type(packagename, "packagename", str)
    errors = []
    mod = load_module_from_module_name(packagename, errors, reload_mod=False, include_test_functions=False)
    # The __init__ module is not a package, but anything else whose file's name
    # ends with __init__.py(c) is.
    if not mod or mod.__name__.split(".")[-1] == "__init__":
        return None
    filename = inspect.getfile(mod)
    if filename.endswith("__init__.py"):
        return filename[: -len("__init__.py")]
    elif filename.endswith("__init__.pyc"):
        return filename[: -len("__init__.pyc")]
    else:
        return None
Beispiel #4
0
def get_root_relative_path(path, root):
    """Get the root-relative URL path.

  Example:
    (path='/home/user/app/dir/templates/tests.py',
     root='/home/user/app/dir') -> 'templates/tests.py'

  Args:
    path: An absolute path of a file or directory.
    root: The root directory which equals the websites root path.

  Returns:
    A string representing the root-relative URL path or None if path is not
    relative to root.

  Raises:
    TypeError: Wrong input arguments.
  """
    utils.check_type(path, "path", str)
    utils.check_type(root, "root", str)
    if not root or not os.path.isdir(root):
        return None
    path_parts = path.split(os.sep)
    root_parts = root.split(os.sep)
    if not root_parts[-1]:
        del root_parts[-1]
    if root_parts != path_parts[: len(root_parts)]:
        return None
    return "/".join(path_parts[len(root_parts) :])
Beispiel #5
0
def get_batch_results(batch, start):
  """Gets new test results from a batch.

  Args:
    batch: The models.TestBatch instance whose tests to get.
    start: The lowest index of the test result to return.

  Returns:
    A list of JSON-converted test result data for all consecutive completed
        tests starting from start.

  Raises:
    MemcacheFailureError: If test results are unavailable due to memcache
        failure.
  """
  utils.check_type(batch, 'batch', models.TestBatch)
  utils.check_type(start, 'start', int)
  tasks = ndb.get_multi([models.RunTestUnitTask.get_key(batch.key, i)
                         for i in range(start, batch.num_units)])
  results = []
  if batch.num_units is not None:
    for task in tasks:
      if not task:
        raise MemcacheFailureError()
      result = task.get_json()
      if not result:
        break
      results.append(result)
  return results
Beispiel #6
0
def _run_test_and_capture_output(test):
  """Run a test and capture the printed output.

  By default, the unittest framework only writes test related data to the given
  stream and ignores other output, e.g., from print statements. This function
  wraps the test execution and captures only the printed output.

  Args:
    test: The test to run (can be a TestSuite or a TestCase).

  Returns:
    A (testresult, output) tuple. 'testresult' is the return value of
    the TestRunner, 'output' the print output emitted during the test run.

  Raises:
    TypeError: Wrong input arguments.
  """
  utils.check_type(test, 'test', (unittest.TestSuite, unittest.TestCase))
  output = StringIO.StringIO()
  original_stdout = sys.stdout
  original_stderr = sys.stderr
  sys.stdout = output
  sys.stderr = output
  try:
    # Ignore output from unittest.
    runner = unittest.TextTestRunner(stream=StringIO.StringIO(), verbosity=2)
    testresult = runner.run(test)
  finally:
    sys.stdout = original_stdout
    sys.stderr = original_stderr
  return testresult, output.getvalue()
Beispiel #7
0
def _delete_batch(batch_key, prev_done, conf):
  """Deletes the given batch and its tasks if no progress has been made.

  Otherwise, it will check back in _DELETE_TIME_SECS seconds.

  Note that any blobs used in this batch will eventually be deleted after their
  respective TestBatch/RunTestUnitTask objects are deleted; see
  models._delete_blob_if_done().

  Args:
    batch_key: The key to the TestBatch to delete.
    prev_done: How many tests were done _DELETE_TIME_SECS seconds ago.  If the
        number of completed tests has not increased, the batch is deleted.
    conf: The configuration to use.
  """
  ctx_options = models.get_ctx_options(conf)
  batch = batch_key.get(**ctx_options)
  if batch is None: return  # For idempotency.
  utils.check_type(batch, 'batch', models.TestBatch)
  tasks = [task for task in batch.get_tasks(conf) if task]
  num_done = len(tasks)
  if num_done == prev_done:
    task_keys = [task.key for task in tasks]
    ndb.delete_multi([batch_key] + task_keys, **ctx_options)
  else:
    deferred.defer(_delete_batch, batch_key, num_done, conf,
                   _queue=conf.test_queue, _countdown=_DELETE_TIME_SECS)
Beispiel #8
0
 def __init__(self, fullname, exists, load_errors):
     utils.check_type(exists, "exists", bool)
     utils.check_type(load_errors, "load_errors", list)
     if not load_errors:
         raise ValueError("No load errors specified for BadTest")
     super(BadTest, self).__init__(fullname)
     self.exists = exists
     self.load_errors = load_errors
Beispiel #9
0
    def __init__(self, fullname):
        """Initializes the object.

    Args:
      fullname: The full name of the test object.
    """
        utils.check_type(fullname, "fullname", str)
        self.fullname = fullname
Beispiel #10
0
def get_handler_mapping(urlprefix):
  """Get mapping of URL prefix to handler."""
  utils.check_type(urlprefix, 'urlprefix', basestring)
  mapping = (('%sget_methods/(.*)' % urlprefix, GetMethodsRequestHandler),
             ('%sstart_batch/(.*)' % urlprefix, StartBatchRequestHandler),
             ('%sbatch_info/(.*)' % urlprefix, BatchInfoRequestHandler),
             ('%sbatch_results/(.*)' % urlprefix, BatchResultsRequestHandler),
            )
  return mapping
Beispiel #11
0
  def get_key(cls, batch_key, index):
    """Gets the key associated with a RunTestUnitTask.

    Args:
      batch_key: The key to the TestBatch the RunTestUnitTask is part of.
      index: The integer index assigned to the task, which is in the range
          [0, batch.num_units).

    Returns:
      A ndb.Key instance corresponding to the RunTestUnitTask.
    """
    utils.check_type(batch_key, 'batch_key', ndb.Key)
    utils.check_type(index, 'index', int)
    return ndb.Key(cls, str(index), parent=batch_key)
Beispiel #12
0
  def render_page(self, template_file, values):
    """Render values into template_file and write result to self.response.

    Args:
      template_file: The filename of the template file to use.
      values: A dictionary of template variable names and values.

    Raises:
      ValueError: Wrong input arguments.
    """
    utils.check_type(template_file, 'template_file', str)
    utils.check_type(values, 'values', dict)
    if 'title' not in values:
      values['title'] = os.environ['APPLICATION_ID']
    self.response.out.write(template.render(template_file, values))
Beispiel #13
0
  def render_error(self, msg, status):
    """Render msg into an error page and write result to self.response.

    Args:
      msg: The error message presented to the user.
      status: HTTP status code of the error.

    Raises:
      ValueError: Wrong input arguments.
    """
    if isinstance(msg, unicode):
      msg = msg.encode('utf-8')
    utils.check_type(msg, 'msg', str)
    utils.check_type(status, 'status', (int, long))
    self.response.out.write(template.render(_get_template_path('error.html'),
                                            {'message': msg}))
    self.response.set_status(status)
Beispiel #14
0
  def set_info(self, load_errors, test_unit_methods, conf):
    """Sets batch information.

    This information can be retrieved as JSON using get_json().  This will also
    set num_units according to the size of test_unit_methods.

    Args:
      load_errors: A list of (object name, error string) pairs for load errors.
      test_unit_methods: A mapping from test unit fullname to a list of method
          fullnames in that object.
      conf: The configuration to use.
    """
    utils.check_type(load_errors, 'load_errors', list)
    utils.check_type(test_unit_methods, 'test_unit_methods', dict)
    self.num_units = len(test_unit_methods)
    data = {'num_units': self.num_units,
            'load_errors': load_errors,
            'test_unit_methods': test_unit_methods,
           }
    self.set_json(data, conf)
Beispiel #15
0
  def set_test_result(self, load_errors, testresult, output, conf):
    """Sets test result information.

    This information can be retrieved as JSON using get_json().

    Args:
      load_errors: A list of (object name, error string) pairs for load errors.
      testresult: The unittest.TestResult for this test run.
      output: The output of print statements in the test.
      conf: The configuration to use.
    """
    utils.check_type(load_errors, 'load_errors', list)
    utils.check_type(testresult, 'testresult', unittest.TestResult)
    utils.check_type(output, 'output', basestring)

    data = {
        'fullname': self.fullname,
        'load_errors': load_errors,
        'errors': [(tc.fullname, exc) for (tc, exc) in testresult.errors],
        'failures': [(tc.fullname, exc) for (tc, exc) in testresult.failures],
        'output': output,
    }
    self.set_json(data, conf)
Beispiel #16
0
def get_module_names_in_package(packagename, module_pattern, depth=0):
    """Get names of all modules in the package that match module_pattern.

  Since all modules found at the location of package and below are
  considered, a traversal of the entire directory structure is
  needed. This can be an expansive operation if your path will contain
  many subdirectories and/or files.

  You can limit the depth of the traveral with the depth argument. 1
  means only the first level is considered, 2, the first and the
  second level is considered, and so on. A value of 0 indicates that
  the entire directory tree should be traversed.

  Args:
    packagename: The name of the package, e.g., package.subpackage.
    module_pattern: The pattern of modules to look at.
    depth: Maximum depth of directory traversal.

  Returns:
    A list of full names of modules in this package that match the pattern.

  Raises:
    TypeError: Wrong input arguments.
    ValueError: If depth is smaller than 0.
  """
    utils.check_type(packagename, "packagename", str)
    utils.check_type(module_pattern, "module_pattern", str)
    utils.check_type(depth, "depth", int)
    if depth < 0:
        raise ValueError('"depth" must be at least 0.')
    path = get_abs_path_from_package_name(packagename)
    if not path:
        return []
    path_default_depth = len([x for x in path.split(os.sep) if x])
    res = []
    packagename_split = packagename.split(".")
    path_split = path.split(os.sep)
    for root, _, files in os.walk(path):
        if depth != 0:
            current_depth = len([x for x in root.split(os.sep) if x])
            if current_depth >= path_default_depth + depth:
                continue
        for file_ in files:
            short_modulename, ext = os.path.splitext(file_)
            # Only Python modules should be considered and they should be
            # considered only once. This means we have to ensure to not use
            # source *and* compiled module files of the same module.
            # At first we check if the current file is a sourcefile. If it
            # is, no further checks are needed and we go ahead and use it.
            if ext != ".py":
                if ext != ".pyc":
                    # If it is not a source file nor a compiled file, we ignore it.
                    continue
                if ext == ".pyc" and os.path.isfile(os.path.join(root, file_[:-1])):
                    # If it is a compiled file and there is a source file, too,
                    # we ignore this file, because we are using the source file
                    # already.
                    continue
            # In addition, only modules matching a certain pattern will be
            # loaded.
            if re.match(module_pattern, short_modulename):
                # The module name = packagename + diff between path and root
                # (=subpackage name) + current file's name.
                root_split = root.split(os.sep)
                if root_split == path_split:
                    subpackage_split = []
                else:
                    subpackage_split = root_split[len(path_split) - 1 :]
                module_split = packagename_split + subpackage_split
                modulename = ".".join(module_split + [short_modulename])
                res.append(modulename)
    res.sort()
    return res
Beispiel #17
0
 def __init__(self, fullname, module):
     utils.check_type(module, "module", types.ModuleType)
     super(Module, self).__init__(fullname)
     self.module = module
Beispiel #18
0
 def __init__(self, fullname, class_):
     utils.check_type(class_, "class_", type)
     super(Class, self).__init__(fullname)
     self.class_ = class_
Beispiel #19
0
 def __init__(self, fullname, class_, method_name):
     utils.check_type(class_, "class_", type)
     utils.check_type(method_name, "method_name", str)
     super(Method, self).__init__(fullname)
     self.class_ = class_
     self.method_name = method_name
Beispiel #20
0
def get_requested_object(fullname, conf):
    """Gets the TestObject with the particular name.

  Args:
    fullname: Name of the object, e.g. package.module.class.method.
    conf: The configuration to use.

  Returns:
    A TestObject, which might be BadTest if the object cannot be found or
        loaded correctly.

  Raises:
    TypeError: Wrong input arguments.
  """
    utils.check_type(fullname, "fullname", str)
    utils.check_type(conf, "conf", config.Config)
    if not fullname:
        return Root()
    if not _is_in_test_package(fullname, conf):
        msg = "Test object %s is not contained in one of the configured " "test_package_names in aeta.yaml." % fullname
        return BadTest(fullname, False, [(fullname, msg)])
    errors_out = []
    # package or module
    module = load_module_from_module_name(
        fullname, errors_out, include_import_error=False, include_test_functions=conf.include_test_functions
    )
    if errors_out:
        return BadTest(fullname, True, errors_out)
    if module:
        if get_abs_path_from_package_name(fullname):
            return Package(fullname)
        return Module(fullname, module)
    elements = fullname.split(".")
    # test case class
    module = load_module_from_module_name(
        ".".join(elements[:-1]),
        errors_out,
        include_import_error=False,
        include_test_functions=conf.include_test_functions,
    )
    if errors_out:
        return BadTest(fullname, True, errors_out)
    if module:
        cls = getattr(module, elements[-1], None)
        if cls and inspect.isclass(cls):
            return Class(fullname, cls)
    module = load_module_from_module_name(
        ".".join(elements[:-2]),
        errors_out,
        include_import_error=False,
        include_test_functions=conf.include_test_functions,
    )
    if errors_out:
        return BadTest(fullname, True, errors_out)
    if module:
        cls_name, method_name = elements[-2:]
        cls = getattr(module, cls_name, None)
        if cls and inspect.isclass(cls):
            method = getattr(cls, method_name, None)
            if method and inspect.ismethod(method):
                return Method(fullname, cls, method_name)
    return BadTest(fullname, False, [(fullname, "No test object %s." % fullname)])