1# DExTer : Debugging Experience Tester 2# ~~~~~~ ~ ~~ ~ ~~ 3# 4# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 5# See https://llvm.org/LICENSE.txt for license information. 6# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 7 8"""DexExpectWatch base class, holds logic for how to build and process expected 9 watch commands. 10""" 11 12import abc 13import difflib 14import os 15 16from dex.command.CommandBase import CommandBase 17from dex.command.StepValueInfo import StepValueInfo 18 19 20class DexExpectWatchBase(CommandBase): 21 def __init__(self, *args, **kwargs): 22 if len(args) < 2: 23 raise TypeError('expected at least two args') 24 25 self.expression = args[0] 26 self.values = [str(arg) for arg in args[1:]] 27 try: 28 on_line = kwargs.pop('on_line') 29 self._from_line = on_line 30 self._to_line = on_line 31 except KeyError: 32 self._from_line = kwargs.pop('from_line', 1) 33 self._to_line = kwargs.pop('to_line', 999999) 34 self._require_in_order = kwargs.pop('require_in_order', True) 35 if kwargs: 36 raise TypeError('unexpected named args: {}'.format( 37 ', '.join(kwargs))) 38 39 # Number of times that this watch has been encountered. 40 self.times_encountered = 0 41 42 # We'll pop from this set as we encounter values so anything left at 43 # the end can be considered as not having been seen. 44 self._missing_values = set(self.values) 45 46 self.misordered_watches = [] 47 48 # List of StepValueInfos for any watch that is encountered as invalid. 49 self.invalid_watches = [] 50 51 # List of StepValueInfo any any watch where we couldn't retrieve its 52 # data. 53 self.irretrievable_watches = [] 54 55 # List of StepValueInfos for any watch that is encountered as having 56 # been optimized out. 57 self.optimized_out_watches = [] 58 59 # List of StepValueInfos for any watch that is encountered that has an 60 # expected value. 61 self.expected_watches = [] 62 63 # List of StepValueInfos for any watch that is encountered that has an 64 # unexpected value. 65 self.unexpected_watches = [] 66 67 super(DexExpectWatchBase, self).__init__() 68 69 70 def get_watches(self): 71 return [self.expression] 72 73 @property 74 def line_range(self): 75 return list(range(self._from_line, self._to_line + 1)) 76 77 @property 78 def missing_values(self): 79 return sorted(list(self._missing_values)) 80 81 @property 82 def encountered_values(self): 83 return sorted(list(set(self.values) - self._missing_values)) 84 85 @abc.abstractmethod 86 def _get_expected_field(self, watch): 87 """Return a field from watch that this ExpectWatch command is checking. 88 """ 89 90 def _handle_watch(self, step_info): 91 self.times_encountered += 1 92 93 if not step_info.watch_info.could_evaluate: 94 self.invalid_watches.append(step_info) 95 return 96 97 if step_info.watch_info.is_optimized_away: 98 self.optimized_out_watches.append(step_info) 99 return 100 101 if step_info.watch_info.is_irretrievable: 102 self.irretrievable_watches.append(step_info) 103 return 104 105 if step_info.expected_value not in self.values: 106 self.unexpected_watches.append(step_info) 107 return 108 109 self.expected_watches.append(step_info) 110 try: 111 self._missing_values.remove(step_info.expected_value) 112 except KeyError: 113 pass 114 115 def _check_watch_order(self, actual_watches, expected_values): 116 """Use difflib to figure out whether the values are in the expected order 117 or not. 118 """ 119 differences = [] 120 actual_values = [w.expected_value for w in actual_watches] 121 value_differences = list(difflib.Differ().compare(actual_values, 122 expected_values)) 123 124 missing_value = False 125 index = 0 126 for vd in value_differences: 127 kind = vd[0] 128 if kind == '+': 129 # A value that is encountered in the expected list but not in the 130 # actual list. We'll keep a note that something is wrong and flag 131 # the next value that matches as misordered. 132 missing_value = True 133 elif kind == ' ': 134 # This value is as expected. It might still be wrong if we've 135 # previously encountered a value that is in the expected list but 136 # not the actual list. 137 if missing_value: 138 missing_value = False 139 differences.append(actual_watches[index]) 140 index += 1 141 elif kind == '-': 142 # A value that is encountered in the actual list but not the 143 # expected list. 144 differences.append(actual_watches[index]) 145 index += 1 146 else: 147 assert False, 'unexpected diff:{}'.format(vd) 148 149 return differences 150 151 def eval(self, step_collection): 152 for step in step_collection.steps: 153 loc = step.current_location 154 155 if (loc.path and os.path.exists(loc.path) and 156 os.path.exists(self.path) and 157 os.path.samefile(loc.path, self.path) and 158 loc.lineno in self.line_range): 159 try: 160 watch = step.program_state.frames[0].watches[self.expression] 161 except KeyError: 162 pass 163 else: 164 expected_field = self._get_expected_field(watch) 165 step_info = StepValueInfo(step.step_index, watch, 166 expected_field) 167 self._handle_watch(step_info) 168 169 if self._require_in_order: 170 # A list of all watches where the value has changed. 171 value_change_watches = [] 172 prev_value = None 173 for watch in self.expected_watches: 174 if watch.expected_value != prev_value: 175 value_change_watches.append(watch) 176 prev_value = watch.expected_value 177 178 self.misordered_watches = self._check_watch_order( 179 value_change_watches, [ 180 v for v in self.values if v in 181 [w.expected_value for w in self.expected_watches] 182 ]) 183