forked from OSchip/llvm-project
[Dexter] Allow Dexter watch commands to specify a range of acceptable FP values
This patch adds an optional argument to DexExpectWatchBase, float_range, which defines a +- acceptance range for expected floating point values. If passed, this assumes every expected value to be a floating point value, and an exception will be thrown if this is not the case. Differential Revision: https://reviews.llvm.org/D124511
This commit is contained in:
parent
3f4a63e5f8
commit
30bb659c6f
|
@ -144,7 +144,7 @@ type checked against the list of `types`
|
|||
---
|
||||
## DexExpectWatchValue
|
||||
DexExpectWatchValue(expr, *values [,**from_line=1][,**to_line=Max]
|
||||
[,**on_line][,**require_in_order=True])
|
||||
[,**on_line][,**require_in_order=True][,**float_range])
|
||||
|
||||
Args:
|
||||
expr (str): expression to evaluate.
|
||||
|
@ -159,6 +159,9 @@ type checked against the list of `types`
|
|||
on_line (int): Only evaluate the expression on this line. If provided,
|
||||
this overrides from_line and to_line.
|
||||
require_in_order (bool): If False the values can appear in any order.
|
||||
float_range (float): If provided, `values` must be floats, and will
|
||||
match an actual value if they are within `float_range` of each other.
|
||||
|
||||
|
||||
### Description
|
||||
Expect the expression `expr` to evaluate to the list of `values`
|
||||
|
|
|
@ -15,7 +15,7 @@ from copy import copy
|
|||
from pathlib import PurePath
|
||||
from collections import defaultdict, OrderedDict
|
||||
|
||||
from dex.utils.Exceptions import CommandParseError
|
||||
from dex.utils.Exceptions import CommandParseError, NonFloatValueInCommand
|
||||
|
||||
from dex.command.CommandBase import CommandBase
|
||||
from dex.command.commands.DexCommandLine import DexCommandLine
|
||||
|
@ -310,6 +310,10 @@ def _find_all_commands_in_file(path, file_lines, valid_commands, source_root_dir
|
|||
err_point = copy(cmd_point)
|
||||
err_point.char += len(command_name)
|
||||
raise format_parse_err(str(e), path, file_lines, err_point)
|
||||
except NonFloatValueInCommand as e:
|
||||
err_point = copy(cmd_point)
|
||||
err_point.char += len(command_name)
|
||||
raise format_parse_err(str(e), path, file_lines, err_point)
|
||||
else:
|
||||
if type(command) is DexLabel:
|
||||
add_line_label(labels, command, path, cmd_point.get_lineno())
|
||||
|
|
|
@ -18,6 +18,7 @@ from pathlib import PurePath
|
|||
|
||||
from dex.command.CommandBase import CommandBase, StepExpectInfo
|
||||
from dex.command.StepValueInfo import StepValueInfo
|
||||
from dex.utils.Exceptions import NonFloatValueInCommand
|
||||
|
||||
class AddressExpression(object):
|
||||
def __init__(self, name, offset=0):
|
||||
|
@ -56,6 +57,13 @@ class DexExpectWatchBase(CommandBase):
|
|||
self._from_line = kwargs.pop('from_line', 1)
|
||||
self._to_line = kwargs.pop('to_line', 999999)
|
||||
self._require_in_order = kwargs.pop('require_in_order', True)
|
||||
self.float_range = kwargs.pop('float_range', None)
|
||||
if self.float_range is not None:
|
||||
for value in self.values:
|
||||
try:
|
||||
float(value)
|
||||
except ValueError:
|
||||
raise NonFloatValueInCommand(f'Non-float value \'{value}\' when float_range arg provided')
|
||||
if kwargs:
|
||||
raise TypeError('unexpected named args: {}'.format(
|
||||
', '.join(kwargs)))
|
||||
|
@ -135,6 +143,33 @@ class DexExpectWatchBase(CommandBase):
|
|||
"""Return a field from watch that this ExpectWatch command is checking.
|
||||
"""
|
||||
|
||||
def _match_expected_floating_point(self, value):
|
||||
"""Checks to see whether value is a float that falls within the
|
||||
acceptance range of one of this command's expected float values, and
|
||||
returns the expected value if so; otherwise returns the original
|
||||
value."""
|
||||
try:
|
||||
value_as_float = float(value)
|
||||
except ValueError:
|
||||
return value
|
||||
|
||||
possible_values = self.values
|
||||
for expected in possible_values:
|
||||
try:
|
||||
expected_as_float = float(expected)
|
||||
difference = abs(value_as_float - expected_as_float)
|
||||
if difference <= self.float_range:
|
||||
return expected
|
||||
except ValueError:
|
||||
pass
|
||||
return value
|
||||
|
||||
def _maybe_fix_float(self, value):
|
||||
if self.float_range is not None:
|
||||
return self._match_expected_floating_point(value)
|
||||
else:
|
||||
return value
|
||||
|
||||
def _handle_watch(self, step_info):
|
||||
self.times_encountered += 1
|
||||
|
||||
|
@ -150,23 +185,25 @@ class DexExpectWatchBase(CommandBase):
|
|||
self.irretrievable_watches.append(step_info)
|
||||
return
|
||||
|
||||
expected_value = self._maybe_fix_float(step_info.expected_value)
|
||||
|
||||
# Check to see if this value matches with a resolved address.
|
||||
matching_address = None
|
||||
for v in self.values:
|
||||
if (isinstance(v, AddressExpression) and
|
||||
v.name in self.address_resolutions and
|
||||
self.resolve_value(v) == step_info.expected_value):
|
||||
self.resolve_value(v) == expected_value):
|
||||
matching_address = v
|
||||
break
|
||||
|
||||
# If this is not an expected value, either a direct value or an address,
|
||||
# then this is an unexpected watch.
|
||||
if step_info.expected_value not in self.values and matching_address is None:
|
||||
if expected_value not in self.values and matching_address is None:
|
||||
self.unexpected_watches.append(step_info)
|
||||
return
|
||||
|
||||
self.expected_watches.append(step_info)
|
||||
value_to_remove = matching_address if matching_address is not None else step_info.expected_value
|
||||
value_to_remove = matching_address if matching_address is not None else expected_value
|
||||
try:
|
||||
self._missing_values.remove(value_to_remove)
|
||||
except KeyError:
|
||||
|
@ -177,7 +214,7 @@ class DexExpectWatchBase(CommandBase):
|
|||
or not.
|
||||
"""
|
||||
differences = []
|
||||
actual_values = [w.expected_value for w in actual_watches]
|
||||
actual_values = [self._maybe_fix_float(w.expected_value) for w in actual_watches]
|
||||
value_differences = list(difflib.Differ().compare(actual_values,
|
||||
expected_values))
|
||||
|
||||
|
@ -229,14 +266,16 @@ class DexExpectWatchBase(CommandBase):
|
|||
# A list of all watches where the value has changed.
|
||||
value_change_watches = []
|
||||
prev_value = None
|
||||
all_expected_values = []
|
||||
for watch in self.expected_watches:
|
||||
if watch.expected_value != prev_value:
|
||||
expected_value = self._maybe_fix_float(watch.expected_value)
|
||||
all_expected_values.append(expected_value)
|
||||
if expected_value != prev_value:
|
||||
value_change_watches.append(watch)
|
||||
prev_value = watch.expected_value
|
||||
prev_value = expected_value
|
||||
|
||||
resolved_values = [self.resolve_value(v) for v in self.values]
|
||||
self.misordered_watches = self._check_watch_order(
|
||||
value_change_watches, [
|
||||
v for v in resolved_values if v in
|
||||
[w.expected_value for w in self.expected_watches]
|
||||
v for v in resolved_values if v in all_expected_values
|
||||
])
|
||||
|
|
|
@ -54,6 +54,14 @@ class CommandParseError(Dexception):
|
|||
self.caret = None
|
||||
|
||||
|
||||
class NonFloatValueInCommand(CommandParseError):
|
||||
"""If a command has the float_range arg but at least one of its expected
|
||||
values cannot be converted to a float."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NonFloatValueInCommand, self).__init__(*args, **kwargs)
|
||||
self.value = None
|
||||
|
||||
class ToolArgumentError(Dexception):
|
||||
"""If a tool argument is invalid."""
|
||||
pass
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
// Purpose:
|
||||
// Check that a \DexExpectWatchValue float_range that is not large enough
|
||||
// detects unexpected watch values.
|
||||
//
|
||||
// UNSUPPORTED: system-darwin
|
||||
//
|
||||
// RUN: not %dexter_regression_test -- %s | FileCheck %s
|
||||
// CHECK: float_range_out_range.cpp:
|
||||
|
||||
int main() {
|
||||
float a = 1.0f;
|
||||
a = a - 0.5f;
|
||||
return a; //DexLabel('check')
|
||||
}
|
||||
|
||||
// DexExpectWatchValue('a', '1.00000', from_line=ref('check1'), to_line=ref('check2'), float_range=0.4)
|
|
@ -0,0 +1,15 @@
|
|||
// Purpose:
|
||||
// Check that \DexExpectWatchValue float_range=0.0 matches only exact
|
||||
// values.
|
||||
//
|
||||
// UNSUPPORTED: system-darwin
|
||||
//
|
||||
// RUN: not %dexter_regression_test -- %s | FileCheck %s
|
||||
// CHECK: float_range_zero_nonmatch.cpp:
|
||||
|
||||
int main() {
|
||||
float a = 1.0f;
|
||||
return a; //DexLabel('check')
|
||||
}
|
||||
|
||||
// DexExpectWatchValue('a', '1.0000001', on_line=ref('check'), float_range=0.0)
|
|
@ -0,0 +1,18 @@
|
|||
// Purpose:
|
||||
// Check that \DexExpectWatchValue float_range=0.5 considers a range
|
||||
// difference of 0.49999 to be an expected watch value for multple values.
|
||||
//
|
||||
// UNSUPPORTED: system-darwin
|
||||
//
|
||||
// RUN: %dexter_regression_test -- %s | FileCheck %s
|
||||
// CHECK: float_range_multiple.cpp:
|
||||
|
||||
int main() {
|
||||
float a = 1.0f;
|
||||
float b = 100.f;
|
||||
a = a + 0.4999f;
|
||||
a = a + b; // DexLabel('check1')
|
||||
return a; //DexLabel('check2')
|
||||
}
|
||||
|
||||
// DexExpectWatchValue('a', '1.0', '100.0', from_line=ref('check1'), to_line=ref('check2'), float_range=0.5)
|
|
@ -0,0 +1,16 @@
|
|||
// Purpose:
|
||||
// Check that omitted float_range from \DexExpectWatchValue turns off
|
||||
// the floating point range evalution and defaults back to
|
||||
// pre-float evalution.
|
||||
//
|
||||
// UNSUPPORTED: system-darwin
|
||||
//
|
||||
// RUN: %dexter_regression_test -- %s | FileCheck %s
|
||||
// CHECK: float_range_no_arg.cpp:
|
||||
|
||||
int main() {
|
||||
float a = 1.0f;
|
||||
return a; //DexLabel('check')
|
||||
}
|
||||
|
||||
// DexExpectWatchValue('a', '1.00000', on_line=ref('check'))
|
|
@ -0,0 +1,16 @@
|
|||
// Purpose:
|
||||
// Check that \DexExpectWatchValue float_range=0.5 considers a range
|
||||
// difference of 0.49999 to be an expected watch value.
|
||||
//
|
||||
// UNSUPPORTED: system-darwin
|
||||
//
|
||||
// RUN: %dexter_regression_test -- %s | FileCheck %s
|
||||
// CHECK: float_range_small.cpp:
|
||||
|
||||
int main() {
|
||||
float a = 1.0f;
|
||||
a = a - 0.49999f;
|
||||
return a; //DexLabel('check')
|
||||
}
|
||||
|
||||
// DexExpectWatchValue('a', '1.0', on_line=ref('check'), float_range=0.5)
|
|
@ -0,0 +1,14 @@
|
|||
// Purpose:
|
||||
// Check that \DexExpectWatchValue float_range=0.0 matches exact values.
|
||||
//
|
||||
// UNSUPPORTED: system-darwin
|
||||
//
|
||||
// RUN: %dexter_regression_test -- %s | FileCheck %s
|
||||
// CHECK: float_range_zero_match.cpp:
|
||||
|
||||
int main() {
|
||||
float a = 1.0f;
|
||||
return a; //DexLabel('check')
|
||||
}
|
||||
|
||||
// DexExpectWatchValue('a', '1.0000000', on_line=ref('check'), float_range=0.0)
|
Loading…
Reference in New Issue