• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
86    def resolve_label(self, label_line_pair):
87        # from_line and to_line could have the same label.
88        label, lineno = label_line_pair
89        if self._to_line == label:
90            self._to_line = lineno
91        if self._from_line == label:
92            self._from_line = lineno
93
94    def has_labels(self):
95        return len(self.get_label_args()) > 0
96
97    def get_label_args(self):
98        return [label for label in (self._from_line, self._to_line)
99                      if isinstance(label, str)]
100
101    @abc.abstractmethod
102    def _get_expected_field(self, watch):
103        """Return a field from watch that this ExpectWatch command is checking.
104        """
105
106    def _handle_watch(self, step_info):
107        self.times_encountered += 1
108
109        if not step_info.watch_info.could_evaluate:
110            self.invalid_watches.append(step_info)
111            return
112
113        if step_info.watch_info.is_optimized_away:
114            self.optimized_out_watches.append(step_info)
115            return
116
117        if step_info.watch_info.is_irretrievable:
118            self.irretrievable_watches.append(step_info)
119            return
120
121        if step_info.expected_value not in self.values:
122            self.unexpected_watches.append(step_info)
123            return
124
125        self.expected_watches.append(step_info)
126        try:
127            self._missing_values.remove(step_info.expected_value)
128        except KeyError:
129            pass
130
131    def _check_watch_order(self, actual_watches, expected_values):
132        """Use difflib to figure out whether the values are in the expected order
133        or not.
134        """
135        differences = []
136        actual_values = [w.expected_value for w in actual_watches]
137        value_differences = list(difflib.Differ().compare(actual_values,
138                                                          expected_values))
139
140        missing_value = False
141        index = 0
142        for vd in value_differences:
143            kind = vd[0]
144            if kind == '+':
145                # A value that is encountered in the expected list but not in the
146                # actual list.  We'll keep a note that something is wrong and flag
147                # the next value that matches as misordered.
148                missing_value = True
149            elif kind == ' ':
150                # This value is as expected.  It might still be wrong if we've
151                # previously encountered a value that is in the expected list but
152                #  not the actual list.
153                if missing_value:
154                    missing_value = False
155                    differences.append(actual_watches[index])
156                index += 1
157            elif kind == '-':
158                # A value that is encountered in the actual list but not the
159                #  expected list.
160                differences.append(actual_watches[index])
161                index += 1
162            else:
163                assert False, 'unexpected diff:{}'.format(vd)
164
165        return differences
166
167    def eval(self, step_collection):
168        for step in step_collection.steps:
169            loc = step.current_location
170
171            if (loc.path and os.path.exists(loc.path) and
172                os.path.exists(self.path) and
173                os.path.samefile(loc.path, self.path) and
174                loc.lineno in self.line_range):
175                try:
176                    watch = step.program_state.frames[0].watches[self.expression]
177                except KeyError:
178                    pass
179                else:
180                    expected_field = self._get_expected_field(watch)
181                    step_info = StepValueInfo(step.step_index, watch,
182                                              expected_field)
183                    self._handle_watch(step_info)
184
185        if self._require_in_order:
186            # A list of all watches where the value has changed.
187            value_change_watches = []
188            prev_value = None
189            for watch in self.expected_watches:
190                if watch.expected_value != prev_value:
191                    value_change_watches.append(watch)
192                    prev_value = watch.expected_value
193
194            self.misordered_watches = self._check_watch_order(
195                value_change_watches, [
196                    v for v in self.values if v in
197                    [w.expected_value for w in self.expected_watches]
198                ])
199