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