• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2017 Google Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import logging
16
17from collections import defaultdict
18from enum import Enum
19from mobly import base_test
20from mobly import records
21from mobly import signals
22from mobly import utils
23
24
25class _InstrumentationStructurePrefixes:
26  """Class containing prefixes that structure insturmentation output.
27
28  Android instrumentation generally follows the following format:
29
30  .. code-block:: none
31
32    INSTRUMENTATION_STATUS: ...
33    ...
34    INSTRUMENTATION_STATUS: ...
35    INSTRUMENTATION_STATUS_CODE: ...
36    INSTRUMENTATION_STATUS: ...
37    ...
38    INSTRUMENTATION_STATUS: ...
39    INSTRUMENTATION_STATUS_CODE: ...
40    ...
41    INSTRUMENTATION_RESULT: ...
42    ...
43    INSTRUMENTATION_RESULT: ...
44    ...
45    INSTRUMENTATION_CODE: ...
46
47  This means that these prefixes can be used to guide parsing
48  the output of the instrumentation command into the different
49  instrumetnation test methods.
50
51  Refer to the following Android Framework package for more details:
52
53  .. code-block:: none
54
55    com.android.commands.am.AM
56
57  """
58
59  STATUS = 'INSTRUMENTATION_STATUS:'
60  STATUS_CODE = 'INSTRUMENTATION_STATUS_CODE:'
61  RESULT = 'INSTRUMENTATION_RESULT:'
62  CODE = 'INSTRUMENTATION_CODE:'
63  FAILED = 'INSTRUMENTATION_FAILED:'
64
65
66class _InstrumentationKnownStatusKeys:
67  """Commonly used keys used in instrumentation output for listing
68  instrumentation test method result properties.
69
70  An instrumenation status line usually contains a key-value pair such as
71  the following:
72
73  .. code-block:: none
74
75    INSTRUMENTATION_STATUS: <key>=<value>
76
77  Some of these key-value pairs are very common and represent test case
78  properties. This mapping is used to handle each of the corresponding
79  key-value pairs different than less important key-value pairs.
80
81  Refer to the following Android Framework packages for more details:
82
83  .. code-block:: none
84
85    android.app.Instrumentation
86    android.support.test.internal.runner.listener.InstrumentationResultPrinter
87
88  TODO: Convert android.support.* to androidx.*,
89  (https://android-developers.googleblog.com/2018/05/hello-world-androidx.html).
90  """
91
92  CLASS = 'class'
93  ERROR = 'Error'
94  STACK = 'stack'
95  TEST = 'test'
96  STREAM = 'stream'
97
98
99class _InstrumentationStatusCodes:
100  """A mapping of instrumentation status codes to test method results.
101
102  When instrumentation runs, at various points output is created in a series
103  of blocks that terminate as follows:
104
105  .. code-block:: none
106
107    INSTRUMENTATION_STATUS_CODE: 1
108
109  These blocks typically have several status keys in them, and they indicate
110  the progression of a particular instrumentation test method. When the
111  corresponding instrumentation test method finishes, there is generally a
112  line which includes a status code that gives thes the test result.
113
114  The UNKNOWN status code is not an actual status code and is only used to
115  represent that a status code has not yet been read for an instrumentation
116  block.
117
118  Refer to the following Android Framework package for more details:
119
120  .. code-block:: none
121
122    android.support.test.internal.runner.listener.InstrumentationResultPrinter
123
124  TODO: Convert android.support.* to androidx.*,
125  (https://android-developers.googleblog.com/2018/05/hello-world-androidx.html).
126  """
127
128  UNKNOWN = None
129  OK = '0'
130  START = '1'
131  IN_PROGRESS = '2'
132  ERROR = '-1'
133  FAILURE = '-2'
134  IGNORED = '-3'
135  ASSUMPTION_FAILURE = '-4'
136
137
138class _InstrumentationStatusCodeCategories:
139  """A mapping of instrumentation test method results to categories.
140
141  Aside from the TIMING category, these categories roughly map to Mobly
142  signals and are used for determining how a particular instrumentation test
143  method gets recorded.
144  """
145
146  TIMING = [
147      _InstrumentationStatusCodes.START,
148      _InstrumentationStatusCodes.IN_PROGRESS,
149  ]
150  PASS = [
151      _InstrumentationStatusCodes.OK,
152  ]
153  FAIL = [
154      _InstrumentationStatusCodes.ERROR,
155      _InstrumentationStatusCodes.FAILURE,
156  ]
157  SKIPPED = [
158      _InstrumentationStatusCodes.IGNORED,
159      _InstrumentationStatusCodes.ASSUMPTION_FAILURE,
160  ]
161
162
163class _InstrumentationKnownResultKeys:
164  """Commonly used keys for outputting instrumentation errors.
165
166  When instrumentation finishes running all of the instrumentation test
167  methods, a result line will appear as follows:
168
169  .. code-block:: none
170
171    INSTRUMENTATION_RESULT:
172
173  If something wrong happened during the instrumentation run such as an
174  application under test crash, the line will appear similarly as thus:
175
176  .. code-block:: none
177
178    INSTRUMENTATION_RESULT: shortMsg=Process crashed.
179
180  Since these keys indicate that something wrong has happened to the
181  instrumentation run, they should be checked for explicitly.
182
183  Refer to the following documentation page for more information:
184
185  .. code-block:: none
186
187    https://developer.android.com/reference/android/app/ActivityManager.ProcessErrorStateInfo.html
188
189  """
190
191  LONGMSG = 'longMsg'
192  SHORTMSG = 'shortMsg'
193
194
195class _InstrumentationResultSignals:
196  """Instrumenttion result block strings for signalling run completion.
197
198  The final section of the instrumentation output generally follows this
199  format:
200
201  .. code-block:: none
202
203    INSTRUMENTATION_RESULT: stream=
204    ...
205    INSTRUMENTATION_CODE -1
206
207  Inside of the ellipsed section, one of these signaling strings should be
208  present. If they are not present, this usually means that the
209  instrumentation run has failed in someway such as a crash. Because the
210  final instrumentation block simply summarizes information, simply roughly
211  checking for a particilar string should be sufficient to check to a proper
212  run completion as the contents of the instrumentation result block don't
213  really matter.
214
215  Refer to the following JUnit package for more details:
216
217  .. code-block:: none
218
219    junit.textui.ResultPrinter
220
221  """
222
223  FAIL = 'FAILURES!!!'
224  PASS = 'OK ('
225
226
227class _InstrumentationBlockStates(Enum):
228  """States used for determing what the parser is currently parsing.
229
230  The parse always starts and ends a block in the UNKNOWN state, which is
231  used to indicate that either a method or a result block (matching the
232  METHOD and RESULT states respectively) are valid follow ups, which means
233  that parser should be checking for a structure prefix that indicates which
234  of those two states it should transition to. If the parser is in the
235  METHOD state, then the parser will be parsing input into test methods.
236  Otherwise, the parse can simply concatenate all the input to check for
237  some final run completion signals.
238  """
239
240  UNKNOWN = 0
241  METHOD = 1
242  RESULT = 2
243
244
245class _InstrumentationBlock:
246  """Container class for parsed instrumentation output for instrumentation
247  test methods.
248
249  Instrumentation test methods typically follow the follwoing format:
250
251  .. code-block:: none
252
253    INSTRUMENTATION_STATUS: <key>=<value>
254    ...
255    INSTRUMENTATION_STATUS: <key>=<value>
256    INSTRUMENTATION_STATUS_CODE: <status code #>
257
258  The main issue with parsing this however is that the key-value pairs can
259  span multiple lines such as this:
260
261  .. code-block:: none
262
263    INSTRUMENTATION_STATUS: stream=
264    Error in ...
265    ...
266
267  Or, such as this:
268
269  .. code-block:: none
270
271    INSTRUMENTATION_STATUS: stack=...
272    ...
273
274  Because these keys are poentially very long, constant string contatention
275  is potentially inefficent. Instead, this class builds up a buffer to store
276  the raw output until it is processed into an actual test result by the
277  _InstrumentationBlockFormatter class.
278
279  Additionally, this class also serves to store the parser state, which
280  means that the BaseInstrumentationTestClass does not need to keep any
281  potentially volatile instrumentation related state, so multiple
282  instrumentation runs should have completely separate parsing states.
283
284  This class is also used for storing result blocks although very little
285  needs to be done for those.
286
287  Attributes:
288    begin_time: string, optional timestamp for when the test corresponding
289      to the instrumentation block began.
290    current_key: string, the current key that is being parsed, default to
291      _InstrumentationKnownStatusKeys.STREAM.
292    error_message: string, an error message indicating that something
293      unexpected happened during a instrumentatoin test method.
294    known_keys: dict, well known keys that are handled uniquely.
295    prefix: string, a prefix to add to the class name of the
296      instrumentation test methods.
297    previous_instrumentation_block: _InstrumentationBlock, the last parsed
298      instrumentation block.
299    state: _InstrumentationBlockStates, the current state of the parser.
300    status_code: string, the state code for an instrumentation method
301      block.
302    unknown_keys: dict, arbitrary keys that are handled generically.
303  """
304
305  def __init__(self,
306               state=_InstrumentationBlockStates.UNKNOWN,
307               prefix=None,
308               previous_instrumentation_block=None):
309    self.state = state
310    self.prefix = prefix
311    self.previous_instrumentation_block = previous_instrumentation_block
312    if previous_instrumentation_block:
313      # The parser never needs lookback for two previous blocks,
314      # so unset to allow previous blocks to get garbage collected.
315      previous_instrumentation_block.previous_instrumentation_block = None
316
317    self._empty = True
318    self.error_message = ''
319    self.status_code = _InstrumentationStatusCodes.UNKNOWN
320
321    self.current_key = _InstrumentationKnownStatusKeys.STREAM
322    self.known_keys = {
323        _InstrumentationKnownStatusKeys.STREAM: [],
324        _InstrumentationKnownStatusKeys.CLASS: [],
325        _InstrumentationKnownStatusKeys.ERROR: [],
326        _InstrumentationKnownStatusKeys.STACK: [],
327        _InstrumentationKnownStatusKeys.TEST: [],
328        _InstrumentationKnownResultKeys.LONGMSG: [],
329        _InstrumentationKnownResultKeys.SHORTMSG: [],
330    }
331    self.unknown_keys = defaultdict(list)
332
333    self.begin_time = None
334
335  @property
336  def is_empty(self):
337    """Deteremines whether or not anything has been parsed with this
338    instrumentation block.
339
340    Returns:
341      A boolean indicating whether or not the this instrumentation block
342      has parsed and contains any output.
343    """
344    return self._empty
345
346  def set_error_message(self, error_message):
347    """Sets an error message on an instrumentation block.
348
349    This method is used exclusively to indicate that a test method failed
350    to complete, which is usually cause by a crash of some sort such that
351    the test method is marked as error instead of ignored.
352
353    Args:
354      error_message: string, an error message to be added to the
355        TestResultRecord to explain that something wrong happened.
356    """
357    self._empty = False
358    self.error_message = error_message
359
360  def _remove_structure_prefix(self, prefix, line):
361    """Helper function for removing the structure prefix for parsing.
362
363    Args:
364      prefix: string, a _InstrumentationStructurePrefixes to remove from
365        the raw output.
366      line: string, the raw line from the instrumentation output.
367
368    Returns:
369      A string containing a key value pair descripting some property
370      of the current instrumentation test method.
371    """
372    return line[len(prefix):].strip()
373
374  def set_status_code(self, status_code_line):
375    """Sets the status code for the instrumentation test method, used in
376    determining the test result.
377
378    Args:
379      status_code_line: string, the raw instrumentation output line that
380        contains the status code of the instrumentation block.
381    """
382    self._empty = False
383    self.status_code = self._remove_structure_prefix(
384        _InstrumentationStructurePrefixes.STATUS_CODE,
385        status_code_line,
386    )
387    if self.status_code == _InstrumentationStatusCodes.START:
388      self.begin_time = utils.get_current_epoch_time()
389
390  def set_key(self, structure_prefix, key_line):
391    """Sets the current key for the instrumentation block.
392
393    For unknown keys, the key is added to the value list in order to
394    better contextualize the value in the output.
395
396    Args:
397      structure_prefix: string, the structure prefix that was matched
398        and that needs to be removed.
399      key_line: string, the raw instrumentation output line that contains
400        the key-value pair.
401    """
402    self._empty = False
403    key_value = self._remove_structure_prefix(
404        structure_prefix,
405        key_line,
406    )
407    if '=' in key_value:
408      (key, value) = key_value.split('=', 1)
409      self.current_key = key
410      if key in self.known_keys:
411        self.known_keys[key].append(value)
412      else:
413        self.unknown_keys[key].append(key_value)
414
415  def add_value(self, line):
416    """Adds unstructured or multi-line value output to the current parsed
417    instrumentation block for outputting later.
418
419    Usually, this will add extra lines to the value list for the current
420    key-value pair. However, sometimes, such as when instrumentation
421    failed to start, output does not follow the structured prefix format.
422    In this case, adding all of the output is still useful so that a user
423    can debug the issue.
424
425    Args:
426      line: string, the raw instrumentation line to append to the value
427        list.
428    """
429    # Don't count whitespace only lines.
430    if line.strip():
431      self._empty = False
432
433    if self.current_key in self.known_keys:
434      self.known_keys[self.current_key].append(line)
435    else:
436      self.unknown_keys[self.current_key].append(line)
437
438  def transition_state(self, new_state):
439    """Transitions or sets the current instrumentation block to the new
440    parser state.
441
442    Args:
443      new_state: _InstrumentationBlockStates, the state that the parser
444        should transition to.
445
446    Returns:
447      A new instrumentation block set to the new state, representing
448      the start of parsing a new instrumentation test method.
449      Alternatively, if the current instrumentation block represents the
450      start of parsing a new instrumentation block (state UNKNOWN), then
451      this returns the current instrumentation block set to the now
452      known parsing state.
453    """
454    if self.state == _InstrumentationBlockStates.UNKNOWN:
455      self.state = new_state
456      return self
457    else:
458      next_block = _InstrumentationBlock(
459          state=new_state,
460          prefix=self.prefix,
461          previous_instrumentation_block=self,
462      )
463      if self.status_code in _InstrumentationStatusCodeCategories.TIMING:
464        next_block.begin_time = self.begin_time
465      return next_block
466
467
468class _InstrumentationBlockFormatter:
469  """Takes an instrumentation block and converts it into a Mobly test
470  result.
471  """
472
473  DEFAULT_INSTRUMENTATION_METHOD_NAME = 'instrumentation_method'
474
475  def __init__(self, instrumentation_block):
476    self._prefix = instrumentation_block.prefix
477    self._status_code = instrumentation_block.status_code
478    self._error_message = instrumentation_block.error_message
479    self._known_keys = {}
480    self._unknown_keys = {}
481    for key, value in instrumentation_block.known_keys.items():
482      self._known_keys[key] = '\n'.join(
483          instrumentation_block.known_keys[key]).rstrip()
484    for key, value in instrumentation_block.unknown_keys.items():
485      self._unknown_keys[key] = '\n'.join(
486          instrumentation_block.unknown_keys[key]).rstrip()
487    self._begin_time = instrumentation_block.begin_time
488
489  def _get_name(self):
490    """Gets the method name of the test method for the instrumentation
491    method block.
492
493    Returns:
494      A string containing the name of the instrumentation test method's
495      test or a default name if no name was parsed.
496    """
497    if self._known_keys[_InstrumentationKnownStatusKeys.TEST]:
498      return self._known_keys[_InstrumentationKnownStatusKeys.TEST]
499    else:
500      return self.DEFAULT_INSTRUMENTATION_METHOD_NAME
501
502  def _get_class(self):
503    """Gets the class name of the test method for the instrumentation
504    method block.
505
506    Returns:
507      A string containing the class name of the instrumentation test
508      method's test or empty string if no name was parsed. If a prefix
509      was specified, then the prefix will be prepended to the class
510      name.
511    """
512    class_parts = [
513        self._prefix, self._known_keys[_InstrumentationKnownStatusKeys.CLASS]
514    ]
515    return '.'.join(filter(None, class_parts))
516
517  def _get_full_name(self):
518    """Gets the qualified name of the test method corresponding to the
519    instrumentation block.
520
521    Returns:
522      A string containing the fully qualified name of the
523      instrumentation test method. If parts are missing, then degrades
524      steadily.
525    """
526    full_name_parts = [self._get_class(), self._get_name()]
527    return '#'.join(filter(None, full_name_parts))
528
529  def _get_details(self):
530    """Gets the output for the detail section of the TestResultRecord.
531
532    Returns:
533      A string to set for a TestResultRecord's details.
534    """
535    detail_parts = [self._get_full_name(), self._error_message]
536    return '\n'.join(filter(None, detail_parts))
537
538  def _get_extras(self):
539    """Gets the output for the extras section of the TestResultRecord.
540
541    Returns:
542      A string to set for a TestResultRecord's extras.
543    """
544    # Add empty line to start key-value pairs on a new line.
545    extra_parts = ['']
546
547    for value in self._unknown_keys.values():
548      extra_parts.append(value)
549
550    extra_parts.append(self._known_keys[_InstrumentationKnownStatusKeys.STREAM])
551    extra_parts.append(
552        self._known_keys[_InstrumentationKnownResultKeys.SHORTMSG])
553    extra_parts.append(
554        self._known_keys[_InstrumentationKnownResultKeys.LONGMSG])
555    extra_parts.append(self._known_keys[_InstrumentationKnownStatusKeys.ERROR])
556
557    if self._known_keys[
558        _InstrumentationKnownStatusKeys.STACK] not in self._known_keys[
559            _InstrumentationKnownStatusKeys.STREAM]:
560      extra_parts.append(
561          self._known_keys[_InstrumentationKnownStatusKeys.STACK])
562
563    return '\n'.join(filter(None, extra_parts))
564
565  def _is_failed(self):
566    """Determines if the test corresponding to the instrumentation block
567    failed.
568
569    This method can not be used to tell if a test method passed and
570    should not be used for such a purpose.
571
572    Returns:
573      A boolean indicating if the test method failed.
574    """
575    if self._status_code in _InstrumentationStatusCodeCategories.FAIL:
576      return True
577    elif (self._known_keys[_InstrumentationKnownStatusKeys.STACK] and
578          self._status_code != _InstrumentationStatusCodes.ASSUMPTION_FAILURE):
579      return True
580    elif self._known_keys[_InstrumentationKnownStatusKeys.ERROR]:
581      return True
582    elif self._known_keys[_InstrumentationKnownResultKeys.SHORTMSG]:
583      return True
584    elif self._known_keys[_InstrumentationKnownResultKeys.LONGMSG]:
585      return True
586    else:
587      return False
588
589  def create_test_record(self, mobly_test_class):
590    """Creates a TestResultRecord for the instrumentation block.
591
592    Args:
593      mobly_test_class: string, the name of the Mobly test case
594        executing the instrumentation run.
595
596    Returns:
597      A TestResultRecord with an appropriate signals exception
598      representing the instrumentation test method's result status.
599    """
600    details = self._get_details()
601    extras = self._get_extras()
602
603    tr_record = records.TestResultRecord(
604        t_name=self._get_full_name(),
605        t_class=mobly_test_class,
606    )
607    if self._begin_time:
608      tr_record.begin_time = self._begin_time
609
610    if self._is_failed():
611      tr_record.test_fail(e=signals.TestFailure(details=details, extras=extras))
612    elif self._status_code in _InstrumentationStatusCodeCategories.SKIPPED:
613      tr_record.test_skip(e=signals.TestSkip(details=details, extras=extras))
614    elif self._status_code in _InstrumentationStatusCodeCategories.PASS:
615      tr_record.test_pass(e=signals.TestPass(details=details, extras=extras))
616    elif self._status_code in _InstrumentationStatusCodeCategories.TIMING:
617      if self._error_message:
618        tr_record.test_error(
619            e=signals.TestError(details=details, extras=extras))
620      else:
621        tr_record = None
622    else:
623      tr_record.test_error(e=signals.TestError(details=details, extras=extras))
624    if self._known_keys[_InstrumentationKnownStatusKeys.STACK]:
625      tr_record.termination_signal.stacktrace = self._known_keys[
626          _InstrumentationKnownStatusKeys.STACK]
627    return tr_record
628
629  def has_completed_result_block_format(self, error_message):
630    """Checks the instrumentation result block for a signal indicating
631    normal completion.
632
633    Args:
634      error_message: string, the error message to give if the
635        instrumentation run did not complete successfully.-
636
637    Returns:
638      A boolean indicating whether or not the instrumentation run passed
639      or failed overall.
640
641    Raises:
642      signals.TestError: Error raised if the instrumentation run did not
643        complete because of a crash or some other issue.
644    """
645    extras = self._get_extras()
646    if _InstrumentationResultSignals.PASS in extras:
647      return True
648    elif _InstrumentationResultSignals.FAIL in extras:
649      return False
650    else:
651      raise signals.TestError(details=error_message, extras=extras)
652
653
654class InstrumentationTestMixin:
655  """A mixin for Mobly test classes to inherit from for instrumentation tests.
656
657  This class should be used in a subclass of both BaseTestClass and this class
658  in order to provide instrumentation test capabilities. This mixin is
659  explicitly for the case where the underlying BaseTestClass cannot be
660  replaced with BaseInstrumentationTestClass. In general, prefer using
661  BaseInstrumentationTestClass instead.
662
663  Attributes:
664    DEFAULT_INSTRUMENTATION_OPTION_PREFIX: string, the default prefix for
665      instrumentation params contained within user params.
666    DEFAULT_INSTRUMENTATION_ERROR_MESSAGE: string, the default error
667      message to set if something has prevented something in the
668      instrumentation test run from completing properly.
669  """
670
671  DEFAULT_INSTRUMENTATION_OPTION_PREFIX = 'instrumentation_option_'
672  DEFAULT_INSTRUMENTATION_ERROR_MESSAGE = ('instrumentation run exited '
673                                           'unexpectedly')
674
675  def _previous_block_never_completed(self, current_block, previous_block,
676                                      new_state):
677    """Checks if the previous instrumentation method block completed.
678
679    Args:
680      current_block: _InstrumentationBlock, the current instrumentation
681        block to check for being a different instrumentation test
682        method.
683      previous_block: _InstrumentationBlock, rhe previous
684        instrumentation block to check for an incomplete status.
685      new_state: _InstrumentationBlockStates, the next state for the
686        parser, used to check for the instrumentation run ending
687        with an incomplete test.
688
689    Returns:
690      A boolean indicating whether the previous instrumentation block
691      completed executing.
692    """
693    if previous_block:
694      previously_timing_block = (previous_block.status_code
695                                 in _InstrumentationStatusCodeCategories.TIMING)
696      currently_new_block = (current_block.status_code
697                             == _InstrumentationStatusCodes.START or
698                             new_state == _InstrumentationBlockStates.RESULT)
699      return all([previously_timing_block, currently_new_block])
700    else:
701      return False
702
703  def _create_formatters(self, instrumentation_block, new_state):
704    """Creates the _InstrumentationBlockFormatters for outputting the
705    instrumentation method block that have finished parsing.
706
707    Args:
708      instrumentation_block: _InstrumentationBlock, the current
709        instrumentation method block to create formatters based upon.
710      new_state: _InstrumentationBlockState, the next state that the
711        parser will transition to.
712
713    Returns:
714      A list of the formatters tha need to create and add
715      TestResultRecords to the test results.
716    """
717    formatters = []
718    if self._previous_block_never_completed(
719        current_block=instrumentation_block,
720        previous_block=instrumentation_block.previous_instrumentation_block,
721        new_state=new_state):
722      instrumentation_block.previous_instrumentation_block.set_error_message(
723          self.DEFAULT_INSTRUMENTATION_ERROR_MESSAGE)
724      formatters.append(
725          _InstrumentationBlockFormatter(
726              instrumentation_block.previous_instrumentation_block))
727
728    if not instrumentation_block.is_empty:
729      formatters.append(_InstrumentationBlockFormatter(instrumentation_block))
730    return formatters
731
732  def _transition_instrumentation_block(
733      self,
734      instrumentation_block,
735      new_state=_InstrumentationBlockStates.UNKNOWN):
736    """Transitions and finishes the current instrumentation block.
737
738    Args:
739      instrumentation_block: _InstrumentationBlock, the current
740        instrumentation block to finish.
741      new_state: _InstrumentationBlockState, the next state for the
742        parser to transition to.
743
744    Returns:
745      The new instrumentation block to use for storing parsed
746      instrumentation output.
747    """
748    formatters = self._create_formatters(instrumentation_block, new_state)
749    for formatter in formatters:
750      test_record = formatter.create_test_record(self.TAG)
751      if test_record:
752        self.results.add_record(test_record)
753        self.summary_writer.dump(test_record.to_dict(),
754                                 records.TestSummaryEntryType.RECORD)
755    return instrumentation_block.transition_state(new_state=new_state)
756
757  def _parse_method_block_line(self, instrumentation_block, line):
758    """Parses the instrumnetation method block's line.
759
760    Args:
761      instrumentation_block: _InstrumentationBlock, the current
762        instrumentation method block.
763      line: string, the raw instrumentation output line to parse.
764
765    Returns:
766      The next instrumentation block, which should be used to continue
767      parsing instrumentation output.
768    """
769    if line.startswith(_InstrumentationStructurePrefixes.STATUS):
770      instrumentation_block.set_key(_InstrumentationStructurePrefixes.STATUS,
771                                    line)
772      return instrumentation_block
773    elif line.startswith(_InstrumentationStructurePrefixes.STATUS_CODE):
774      instrumentation_block.set_status_code(line)
775      return self._transition_instrumentation_block(instrumentation_block)
776    elif line.startswith(_InstrumentationStructurePrefixes.RESULT):
777      # Unexpected transition from method block -> result block
778      instrumentation_block.set_key(_InstrumentationStructurePrefixes.RESULT,
779                                    line)
780      return self._parse_result_line(
781          self._transition_instrumentation_block(
782              instrumentation_block,
783              new_state=_InstrumentationBlockStates.RESULT,
784          ),
785          line,
786      )
787    else:
788      instrumentation_block.add_value(line)
789      return instrumentation_block
790
791  def _parse_result_block_line(self, instrumentation_block, line):
792    """Parses the instrumentation result block's line.
793
794    Args:
795      instrumentation_block: _InstrumentationBlock, the instrumentation
796        result block for the instrumentation run.
797      line: string, the raw instrumentation output to add to the
798        instrumenation result block's _InstrumentationResultBlocki
799        object.
800
801    Returns:
802      The instrumentation result block for the instrumentation run.
803    """
804    instrumentation_block.add_value(line)
805    return instrumentation_block
806
807  def _parse_unknown_block_line(self, instrumentation_block, line):
808    """Parses a line from the instrumentation output from the UNKNOWN
809    parser state.
810
811    Args:
812      instrumentation_block: _InstrumentationBlock, the current
813        instrumenation block, where the correct categorization it noti
814        yet known.
815      line: string, the raw instrumenation output line to be used to
816        deteremine the correct categorization.
817
818    Returns:
819      The next instrumentation block to continue parsing with. Usually,
820      this is the same instrumentation block but with the state
821      transitioned appropriately.
822    """
823    if line.startswith(_InstrumentationStructurePrefixes.STATUS):
824      return self._parse_method_block_line(
825          self._transition_instrumentation_block(
826              instrumentation_block,
827              new_state=_InstrumentationBlockStates.METHOD,
828          ),
829          line,
830      )
831    elif (line.startswith(_InstrumentationStructurePrefixes.RESULT) or
832          _InstrumentationStructurePrefixes.FAILED in line):
833      return self._parse_result_block_line(
834          self._transition_instrumentation_block(
835              instrumentation_block,
836              new_state=_InstrumentationBlockStates.RESULT,
837          ),
838          line,
839      )
840    else:
841      # This would only really execute if instrumentation failed to start.
842      instrumentation_block.add_value(line)
843      return instrumentation_block
844
845  def _parse_line(self, instrumentation_block, line):
846    """Parses an arbitrary line from the instrumentation output based upon
847    the current parser state.
848
849    Args:
850      instrumentation_block: _InstrumentationBlock, an instrumentation
851        block with any of the possible parser states.
852      line: string, the raw instrumentation output line to parse
853        appropriately.
854
855    Returns:
856      The next instrumenation block to continue parsing with.
857    """
858    if instrumentation_block.state == _InstrumentationBlockStates.METHOD:
859      return self._parse_method_block_line(instrumentation_block, line)
860    elif instrumentation_block.state == _InstrumentationBlockStates.RESULT:
861      return self._parse_result_block_line(instrumentation_block, line)
862    else:
863      return self._parse_unknown_block_line(instrumentation_block, line)
864
865  def _finish_parsing(self, instrumentation_block):
866    """Finishes parsing the instrumentation result block for the final
867    instrumentation run status.
868
869    Args:
870      instrumentation_block: _InstrumentationBlock, the instrumentation
871        result block for the instrumenation run. Potentially, thisi
872        could actually be method block if the instrumentation outputi
873        is malformed.
874
875    Returns:
876      A boolean indicating whether the instrumentation run completed
877        with all the tests passing.
878
879    Raises:
880      signals.TestError: Error raised if the instrumentation failed to
881        complete with either a pass or fail status.
882    """
883    formatter = _InstrumentationBlockFormatter(instrumentation_block)
884    return formatter.has_completed_result_block_format(
885        self.DEFAULT_INSTRUMENTATION_ERROR_MESSAGE)
886
887  def parse_instrumentation_options(self, parameters=None):
888    """Returns the options for the instrumentation test from user_params.
889
890    By default, this method assume that the correct instrumentation options
891    all start with DEFAULT_INSTRUMENTATION_OPTION_PREFIX.
892
893    Args:
894      parameters: dict, the key value pairs representing an assortment
895        of parameters including instrumentation options. Usually,
896        this argument will be from self.user_params.
897
898    Returns:
899      A dictionary of options/parameters for the instrumentation tst.
900    """
901    if parameters is None:
902      return {}
903
904    filtered_parameters = {}
905    for parameter_key, parameter_value in parameters.items():
906      if parameter_key.startswith(self.DEFAULT_INSTRUMENTATION_OPTION_PREFIX):
907        option_key = parameter_key[len(self.
908                                       DEFAULT_INSTRUMENTATION_OPTION_PREFIX):]
909        filtered_parameters[option_key] = parameter_value
910    return filtered_parameters
911
912  def run_instrumentation_test(self,
913                               device,
914                               package,
915                               options=None,
916                               prefix=None,
917                               runner=None):
918    """Runs instrumentation tests on a device and creates test records.
919
920    Args:
921      device: AndroidDevice, the device to run instrumentation tests on.
922      package: string, the package name of the instrumentation tests.
923      options: dict, Instrumentation options for the instrumentation
924        tests.
925      prefix: string, an optional prefix for parser output for
926        distinguishing between instrumentation test runs.
927      runner: string, the runner to use for the instrumentation package,
928        default to DEFAULT_INSTRUMENTATION_RUNNER.
929
930    Returns:
931      A boolean indicating whether or not all the instrumentation test
932        methods passed.
933
934    Raises:
935      TestError if the instrumentation run crashed or if parsing the
936        output failed.
937    """
938    # Dictionary hack to allow overwriting the instrumentation_block in the
939    # parse_instrumentation closure
940    instrumentation_block = [_InstrumentationBlock(prefix=prefix)]
941
942    def parse_instrumentation(raw_line):
943      line = raw_line.rstrip().decode('utf-8')
944      logging.info(line)
945      instrumentation_block[0] = self._parse_line(instrumentation_block[0],
946                                                  line)
947
948    device.adb.instrument(package=package,
949                          options=options,
950                          runner=runner,
951                          handler=parse_instrumentation)
952
953    return self._finish_parsing(instrumentation_block[0])
954
955
956class BaseInstrumentationTestClass(InstrumentationTestMixin,
957                                   base_test.BaseTestClass):
958  """Base class for all instrumentation test classes to inherit from.
959
960  This class extends the BaseTestClass to add functionality to run and parse
961  the output of instrumentation runs.
962
963  Attributes:
964    DEFAULT_INSTRUMENTATION_OPTION_PREFIX: string, the default prefix for
965      instrumentation params contained within user params.
966    DEFAULT_INSTRUMENTATION_ERROR_MESSAGE: string, the default error
967      message to set if something has prevented something in the
968      instrumentation test run from completing properly.
969  """
970