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