chromium/build/android/pylib/results/json_results.py

# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.


import collections
import itertools
import json
import logging
import time

from pylib.base import base_test_result

def GenerateResultsDict(test_run_results, global_tags=None):
  """Create a results dict from |test_run_results| suitable for writing to JSON.
  Args:
    test_run_results: a list of base_test_result.TestRunResults objects.
  Returns:
    A results dict that mirrors the one generated by
      base/test/launcher/test_results_tracker.cc:SaveSummaryAsJSON.
  """
  # Example json output.
  # {
  #   "global_tags": [],
  #   "all_tests": [
  #     "test1",
  #     "test2",
  #    ],
  #   "disabled_tests": [],
  #   "per_iteration_data": [
  #     {
  #       "test1": [
  #         {
  #           "status": "SUCCESS",
  #           "elapsed_time_ms": 1,
  #           "output_snippet": "",
  #           "output_snippet_base64": "",
  #           "losless_snippet": "",
  #         },
  #         ...
  #       ],
  #       "test2": [
  #         {
  #           "status": "FAILURE",
  #           "elapsed_time_ms": 12,
  #           "output_snippet": "",
  #           "output_snippet_base64": "",
  #           "losless_snippet": "",
  #         },
  #         ...
  #       ],
  #     },
  #     {
  #       "test1": [
  #         {
  #           "status": "SUCCESS",
  #           "elapsed_time_ms": 1,
  #           "output_snippet": "",
  #           "output_snippet_base64": "",
  #           "losless_snippet": "",
  #         },
  #       ],
  #       "test2": [
  #         {
  #           "status": "FAILURE",
  #           "elapsed_time_ms": 12,
  #           "output_snippet": "",
  #           "output_snippet_base64": "",
  #           "losless_snippet": "",
  #         },
  #       ],
  #     },
  #     ...
  #   ],
  # }

  all_tests = set()
  per_iteration_data = []
  test_run_links = {}

  for test_run_result in test_run_results:
    iteration_data = collections.defaultdict(list)
    if isinstance(test_run_result, list):
      results_iterable = itertools.chain(*(t.GetAll() for t in test_run_result))
      for tr in test_run_result:
        test_run_links.update(tr.GetLinks())

    else:
      results_iterable = test_run_result.GetAll()
      test_run_links.update(test_run_result.GetLinks())

    for r in results_iterable:
      result_dict = {
          'status': r.GetType(),
          'elapsed_time_ms': r.GetDuration(),
          'output_snippet': r.GetLog(),
          'losless_snippet': True,
          'output_snippet_base64': '',
          'links': r.GetLinks(),
      }
      iteration_data[r.GetName()].append(result_dict)

    all_tests = all_tests.union(set(iteration_data.keys()))
    per_iteration_data.append(iteration_data)

  return {
    'global_tags': global_tags or [],
    'all_tests': sorted(list(all_tests)),
    # TODO(jbudorick): Add support for disabled tests within base_test_result.
    'disabled_tests': [],
    'per_iteration_data': per_iteration_data,
    'links': test_run_links,
  }


def GenerateJsonTestResultFormatDict(test_run_results, interrupted):
  """Create a results dict from |test_run_results| suitable for writing to JSON.

  Args:
    test_run_results: a list of base_test_result.TestRunResults objects.
    interrupted: True if tests were interrupted, e.g. timeout listing tests
  Returns:
    A results dict that mirrors the standard JSON Test Results Format.
  """

  tests = {}
  counts = {'PASS': 0, 'FAIL': 0, 'SKIP': 0, 'CRASH': 0, 'TIMEOUT': 0}

  for test_run_result in test_run_results:
    if isinstance(test_run_result, list):
      results_iterable = itertools.chain(*(t.GetAll() for t in test_run_result))
    else:
      results_iterable = test_run_result.GetAll()

    for r in results_iterable:
      element = tests
      for key in r.GetName().split('.'):
        if key not in element:
          element[key] = {}
        element = element[key]

      element['expected'] = 'PASS'

      if r.GetType() == base_test_result.ResultType.PASS:
        result = 'PASS'
      elif r.GetType() == base_test_result.ResultType.SKIP:
        result = 'SKIP'
      elif r.GetType() == base_test_result.ResultType.CRASH:
        result = 'CRASH'
      elif r.GetType() == base_test_result.ResultType.TIMEOUT:
        result = 'TIMEOUT'
      else:
        result = 'FAIL'

      if 'actual' in element:
        element['actual'] += ' ' + result
      else:
        counts[result] += 1
        element['actual'] = result
        if result == 'FAIL':
          element['is_unexpected'] = True

      if r.GetDuration() != 0:
        element['time'] = r.GetDuration()

  # Fill in required fields.
  return {
      'interrupted': interrupted,
      'num_failures_by_type': counts,
      'path_delimiter': '.',
      'seconds_since_epoch': time.time(),
      'tests': tests,
      'version': 3,
  }


def GenerateJsonResultsFile(test_run_result, file_path, global_tags=None,
                            **kwargs):
  """Write |test_run_result| to JSON.

  This emulates the format of the JSON emitted by
  base/test/launcher/test_results_tracker.cc:SaveSummaryAsJSON.

  Args:
    test_run_result: a base_test_result.TestRunResults object.
    file_path: The path to the JSON file to write.
  """
  with open(file_path, 'w') as json_result_file:
    json_result_file.write(json.dumps(
        GenerateResultsDict(test_run_result, global_tags=global_tags),
        **kwargs))
    logging.info('Generated json results file at %s', file_path)


def GenerateJsonTestResultFormatFile(test_run_result, interrupted, file_path,
                                     **kwargs):
  """Write |test_run_result| to JSON.

  This uses the official Chromium Test Results Format.

  Args:
    test_run_result: a base_test_result.TestRunResults object.
    interrupted: True if tests were interrupted, e.g. timeout listing tests
    file_path: The path to the JSON file to write.
  """
  with open(file_path, 'w') as json_result_file:
    json_result_file.write(
        json.dumps(
            GenerateJsonTestResultFormatDict(test_run_result, interrupted),
            **kwargs))
    logging.info('Generated json results file at %s', file_path)


def ParseResultsFromJson(json_results):
  """Creates a list of BaseTestResult objects from JSON.

  Args:
    json_results: A JSON dict in the format created by
                  GenerateJsonResultsFile.
  """

  def string_as_status(s):
    if s in base_test_result.ResultType.GetTypes():
      return s
    return base_test_result.ResultType.UNKNOWN

  results_list = []
  testsuite_runs = json_results['per_iteration_data']
  for testsuite_run in testsuite_runs:
    for test, test_runs in testsuite_run.items():
      results_list.extend(
          [base_test_result.BaseTestResult(test,
                                           string_as_status(tr['status']),
                                           duration=tr['elapsed_time_ms'],
                                           log=tr.get('output_snippet'))
          for tr in test_runs])
  return results_list