[Dexter] Add support for aggregate expects in the debugger (#202545)
Allows aggregate expects to be written in Dexter scripts, in the form:
```
!value expect:
member1: 0
member2:
submember1: 1
submember2: 2
```
This uses the "variables" feature provided in the DAP interface to
recursively fetch sub-variables from each evaluated variable, and
modifies the DebuggerExpectMatch class to also be recursive, containing
submatches for each expected aggregate member value.
Some key concepts that are affected by this are the "match_result",
which has changed from a bool to a 3-way result ("FALSE", "TRUE",
"PARTIAL"), where "PARTIAL" means that at least one submatch was TRUE
and at least one was FALSE. We also add a `match_distance`, which is
used as a measure of correctness. The match distance ranges from 0.0 to
1.0: a total match has a distance of 0.0, a total mismatch has 1.0, and
a partial match's distance is equal to the average distance of its
immediate children.
This commit is contained in:
parent
b6f0fb6c24
commit
4cb6eee15d
9 changed files with 472 additions and 25 deletions
|
|
@ -18,7 +18,7 @@ import sys
|
|||
import threading
|
||||
import time
|
||||
from enum import Enum
|
||||
from typing import List, Optional
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
from dex.debugger.DebuggerBase import DebuggerBase, watch_is_active
|
||||
from dex.dextIR import FrameIR, LocIR, StepIR, StopReason, ValueIR
|
||||
|
|
@ -551,6 +551,22 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta):
|
|||
time.sleep(0.001)
|
||||
return self._debugger_state.get_response(seq)
|
||||
|
||||
# Helper method that sends the request defined by "command" + "arguments", awaits the response, and returns the
|
||||
# response when it arrives. An optional timeout for the response may be passed.
|
||||
# If allow_failure is passed, then the result may instead be a str containing the fail reason if the request failed.
|
||||
def _communicate_request(
|
||||
self, command: str, arguments=None, timeout: float = 60.0, allow_failure=False
|
||||
) -> Union[Dict, str]:
|
||||
req_id = self.send_message(self.make_request(command, arguments))
|
||||
response = self._await_response(req_id, timeout)
|
||||
if not response["success"]:
|
||||
if not allow_failure:
|
||||
raise DebuggerException(
|
||||
f"received failure response for command {command}"
|
||||
)
|
||||
return response["message"]
|
||||
return response["body"]
|
||||
|
||||
## End of DAP communication methods
|
||||
############################################################################
|
||||
|
||||
|
|
@ -1018,6 +1034,36 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta):
|
|||
def _evaluate_result_value(expression: str, result_string: str) -> ValueIR:
|
||||
"""For the result of an "evaluate" message, return a ValueIR. Implementation must be debugger-specific."""
|
||||
|
||||
# For the given `value` and associated `variables_reference`, recursively requests "variables" information for all
|
||||
# child variables and adds them as sub_values to `value`.
|
||||
def _evaluate_subvariables(self, value: ValueIR, variables_reference: int):
|
||||
if variables_reference == 0:
|
||||
return
|
||||
# DFS subvariables recursively, adding them as sub_values to their parent ValueIRs.
|
||||
variables_irs = {variables_reference: value}
|
||||
search_vars = [variables_reference]
|
||||
while search_vars:
|
||||
next_var = search_vars[0]
|
||||
search_vars = search_vars[1:]
|
||||
# The ValueIR for the variable/subvariable whose children we are examining.
|
||||
variable_ir: ValueIR = variables_irs[next_var]
|
||||
result_vars: Dict = self._communicate_request(
|
||||
"variables", {"variablesReference": next_var, "filter": "named"}
|
||||
)
|
||||
for var in result_vars["variables"]:
|
||||
new_ir = self._evaluate_result_value(
|
||||
var["name"], var["value"], var.get("type")
|
||||
)
|
||||
variable_ir.sub_values.append(new_ir)
|
||||
new_ref = var.get("variablesReference", 0)
|
||||
if (
|
||||
new_ir.could_evaluate
|
||||
and not new_ir.is_irretrievable
|
||||
and new_ref != 0
|
||||
):
|
||||
variables_irs[new_ref] = new_ir
|
||||
search_vars.append(new_ref)
|
||||
|
||||
def evaluate_expression(self, expression, frame_idx=0) -> ValueIR:
|
||||
# The frame_idx passed in here needs to be translated to the debug adapter's internal frame ID.
|
||||
dap_frame_id = self._debugger_state.frame_map[frame_idx]
|
||||
|
|
@ -1040,8 +1086,13 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta):
|
|||
result = eval_response["message"]
|
||||
else:
|
||||
result = "<unable to evaluate expression>"
|
||||
variables_ref = 0
|
||||
else:
|
||||
result = eval_response["body"]["result"]
|
||||
variables_ref = eval_response["body"].get("variablesReference", 0)
|
||||
type_str = eval_response["body"].get("type")
|
||||
|
||||
return self._evaluate_result_value(expression, result, type_str)
|
||||
value_ir = self._evaluate_result_value(expression, result, type_str)
|
||||
self._evaluate_subvariables(value_ir, variables_ref)
|
||||
|
||||
return value_ir
|
||||
|
|
|
|||
|
|
@ -128,5 +128,8 @@ class StepIR:
|
|||
if self.watches:
|
||||
lines.append(f"Variables:")
|
||||
for value in sorted(self.watches.values(), key=lambda v: v.expression):
|
||||
lines.append(f" {value}")
|
||||
if value.sub_values:
|
||||
value.dump_nested(lines, 1)
|
||||
else:
|
||||
lines.append(f" {value}")
|
||||
return lines
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@
|
|||
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class ValueIR:
|
||||
"""Data class to store the result of an expression evaluation."""
|
||||
|
||||
|
|
@ -26,6 +29,7 @@ class ValueIR:
|
|||
self.error_string = error_string
|
||||
self.is_optimized_away = is_optimized_away
|
||||
self.is_irretrievable = is_irretrievable
|
||||
self.sub_values: list[ValueIR] = []
|
||||
|
||||
def __str__(self):
|
||||
prefix = '"{}": '.format(self.expression)
|
||||
|
|
@ -39,3 +43,9 @@ class ValueIR:
|
|||
self.could_evaluate, self.is_irretrievable, self.is_optimized_away
|
||||
)
|
||||
)
|
||||
|
||||
def dump_nested(self, lines: List[str], indent: int = 0):
|
||||
indent_str = " " * indent
|
||||
lines.append(f"{indent_str}{self}")
|
||||
for v in self.sub_values:
|
||||
v.dump_nested(lines, indent + 1)
|
||||
|
|
|
|||
|
|
@ -6,33 +6,207 @@
|
|||
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
"""Utilities for matching debugger output to script expected values."""
|
||||
|
||||
from typing import Any, Dict, List, Union
|
||||
from collections import Counter, OrderedDict
|
||||
from enum import Enum, IntEnum
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
from dex.dextIR import ValueIR
|
||||
from dex.test_script.Nodes import Expect, Value
|
||||
|
||||
|
||||
def get_expected_value_set(
|
||||
expected, prepend_tuple: Tuple = ()
|
||||
) -> Dict[Tuple[str], int]:
|
||||
"""For the given "expected" taken directly from the script YAML, returns the set of all actual expected
|
||||
values, using tuples to represent nested expected values, and mapping each result to the number of times that it
|
||||
appears in the set (giving a count). For example, the expected values of:
|
||||
```
|
||||
!value foo:
|
||||
- 4
|
||||
- 6
|
||||
- x: 5
|
||||
y: 10
|
||||
- x: 5
|
||||
y: 20
|
||||
```
|
||||
Would be represented by the dict:
|
||||
```
|
||||
{
|
||||
(4,): 1,
|
||||
(6,): 1,
|
||||
("x", 5): 2,
|
||||
("y", 10): 1,
|
||||
("y", 20): 1,
|
||||
}
|
||||
```
|
||||
"""
|
||||
result: Dict[Tuple, int] = Counter()
|
||||
if isinstance(expected, list):
|
||||
for ev in expected:
|
||||
result.update(get_expected_value_set(ev, prepend_tuple))
|
||||
elif isinstance(expected, dict):
|
||||
for sub_expect, sub_expected in expected.items():
|
||||
next_prepend = prepend_tuple + (str(sub_expect),)
|
||||
result.update(get_expected_value_set(sub_expected, next_prepend))
|
||||
else:
|
||||
result[prepend_tuple + (str(expected),)] += 1
|
||||
return result
|
||||
|
||||
|
||||
class MatchResult(IntEnum):
|
||||
FALSE = 0
|
||||
PARTIAL = 1
|
||||
TRUE = 2
|
||||
|
||||
|
||||
class DebuggerExpectMatch:
|
||||
"""Class that represents the match between a particular expected value for an Expect node and the actual debugger
|
||||
output corresponding to the watched value for that node."""
|
||||
output corresponding to the watched value for that node.
|
||||
The value of `expected` may be either a string-convertible scalar (non-collection) value, representing a
|
||||
non-aggregate expected value, or it may be a dict of strings to other expected values.
|
||||
`actual_result` is None if either `actual` or `expect.get_variable_result(actual)` is None,
|
||||
Otherwise, if `expected` is a dict, then `actual_result` is a dict[str, DebuggerExpectMatch],
|
||||
Otherwise, `actual_result` is a str.
|
||||
"""
|
||||
|
||||
def __init__(self, expect: Expect, expected, actual: ValueIR):
|
||||
def __init__(self, expect: Expect, expected, actual: Optional[ValueIR]):
|
||||
self.expect = expect
|
||||
self.expected = expected
|
||||
self.actual = actual
|
||||
self.actual_result = self.expect.get_variable_result(self.actual)
|
||||
self.match_result = (
|
||||
self.expected is not None and str(self.expected) == self.actual_result
|
||||
self.actual_result, self.match_result = self._get_actual_result()
|
||||
self.match_distance = self._get_match_distance()
|
||||
|
||||
def _get_actual_result(
|
||||
self,
|
||||
) -> Tuple[Union[str, Dict[str, "DebuggerExpectMatch"], None], MatchResult]:
|
||||
if isinstance(self.expected, dict):
|
||||
sub_expect_results: Dict[str, DebuggerExpectMatch] = OrderedDict()
|
||||
for sub_expect, sub_expected in self.expected.items():
|
||||
# If the value of `actual` is None, we still want this match to reflect the structure of the expected
|
||||
# value, so if we have an expected value: `!value foo: {a: 0, b: 1}`, and `actual == None`, then we
|
||||
# should produce a match `foo: {'a': None, 'b': None}`, rather than `foo: None`, so we unconditionally
|
||||
# traverse the expected value here tree even if we have a None result.
|
||||
value = (
|
||||
None
|
||||
if self.actual is None
|
||||
else next(
|
||||
(
|
||||
sub_value
|
||||
for sub_value in self.actual.sub_values
|
||||
if sub_value.expression == sub_expect
|
||||
),
|
||||
None,
|
||||
)
|
||||
)
|
||||
sub_expect_results[sub_expect] = DebuggerExpectMatch(
|
||||
self.expect, sub_expected, value
|
||||
)
|
||||
if all(
|
||||
result.match_result == MatchResult.TRUE
|
||||
for result in sub_expect_results.values()
|
||||
):
|
||||
match_result = MatchResult.TRUE
|
||||
elif all(
|
||||
result.match_result == MatchResult.FALSE
|
||||
for result in sub_expect_results.values()
|
||||
):
|
||||
match_result = MatchResult.FALSE
|
||||
else:
|
||||
match_result = MatchResult.PARTIAL
|
||||
return sub_expect_results, match_result
|
||||
|
||||
actual_result = (
|
||||
self.expect.get_variable_result(self.actual)
|
||||
if self.actual is not None
|
||||
else None
|
||||
)
|
||||
match_result = (
|
||||
MatchResult.TRUE
|
||||
if (self.expected is not None and str(self.expected) == actual_result)
|
||||
else MatchResult.FALSE
|
||||
)
|
||||
return actual_result, match_result
|
||||
|
||||
def _get_match_distance(self) -> float:
|
||||
if self.match_result == MatchResult.TRUE:
|
||||
return 0.0
|
||||
if self.match_result == MatchResult.FALSE:
|
||||
return 1.0
|
||||
assert (
|
||||
isinstance(self.actual_result, Dict) and self.actual_result
|
||||
), "Partial match without submatches."
|
||||
dists = [m.match_distance for m in self.actual_result.values()]
|
||||
return sum(dists) / len(dists)
|
||||
|
||||
def get_expression(self) -> Optional[str]:
|
||||
return self.actual.expression if self.actual else None
|
||||
|
||||
def get_all_matched_values(self, prepend_tuple: Tuple = ()) -> Set[Tuple]:
|
||||
"""Similar to `get_expected_value_set` above, but returns the set of expected values that were successfully
|
||||
matched in this DebuggerExpectMatch, and returns just a set (no counts)."""
|
||||
if self.match_result == MatchResult.FALSE:
|
||||
return set()
|
||||
assert (
|
||||
self.actual_result is not None and self.actual is not None
|
||||
), "Non-false match with no actual result."
|
||||
if isinstance(self.actual_result, str):
|
||||
assert (
|
||||
self.match_result == MatchResult.TRUE
|
||||
), "Partial match without submatches."
|
||||
return {prepend_tuple + (str(self.expected),)}
|
||||
result = set()
|
||||
for sub_expr, sub_result in self.actual_result.items():
|
||||
result = result.union(
|
||||
sub_result.get_all_matched_values(prepend_tuple + (sub_expr,))
|
||||
)
|
||||
return result
|
||||
|
||||
def short_str(self, use_color=True) -> str:
|
||||
def colorize(input: str, match_result: MatchResult) -> str:
|
||||
if not use_color:
|
||||
return input
|
||||
if match_result == MatchResult.TRUE:
|
||||
return f"<g>{input}</>"
|
||||
if match_result == MatchResult.PARTIAL:
|
||||
return f"<y>{input}</>"
|
||||
return f"<r>{input}</>"
|
||||
|
||||
if self.actual is None:
|
||||
return colorize("<Missing>", self.match_result)
|
||||
if self.actual_result is None:
|
||||
if self.actual.is_optimized_away:
|
||||
return colorize("<OptimizedOut>", self.match_result)
|
||||
if self.actual.is_irretrievable:
|
||||
return colorize("<Irretrievable>", self.match_result)
|
||||
return colorize("<EvaluateFailed>", self.match_result)
|
||||
if isinstance(self.actual_result, str):
|
||||
return colorize(self.actual_result, self.match_result)
|
||||
assert isinstance(self.expected, dict)
|
||||
sub_values = [
|
||||
colorize(f'"{sub_expr}": ', sub_result.match_result)
|
||||
+ sub_result.short_str()
|
||||
for sub_expr, sub_result in self.actual_result.items()
|
||||
]
|
||||
return f"{{ {', '.join(sub_values)} }}"
|
||||
|
||||
|
||||
def get_expect_match(expect: Expect, expected_values, actual: ValueIR):
|
||||
"""Given one or more expected values for an Expect node and an actual ValueIR, returns a match for the first
|
||||
matching expected values, or for None if there are no matching expected values."""
|
||||
"""Given one or more expected values for an Expect node and an actual ValueIR, returns a match for the best
|
||||
matching expected value, which is either the first exact match, or the match with the lowest distance (see
|
||||
`DebuggerExpectMatch._get_match_distance` above), or returns a match for None if there are no expected values with
|
||||
a match distance less than 1.0 (i.e. all expected values have no overlap with the actual value).
|
||||
"""
|
||||
if not isinstance(expected_values, list):
|
||||
expected_values = [expected_values]
|
||||
best_partial_match = DebuggerExpectMatch(expect, None, actual)
|
||||
best_partial_match_dist = 1.0
|
||||
for expected_value in expected_values:
|
||||
expect_match = DebuggerExpectMatch(expect, expected_value, actual)
|
||||
if expect_match.match_result:
|
||||
if expect_match.match_result == MatchResult.TRUE:
|
||||
return expect_match
|
||||
return DebuggerExpectMatch(expect, None, actual)
|
||||
# A "FALSE" match will have a match distance of 1.0, and therefore will never be considered a "best match".
|
||||
if expect_match.match_distance < best_partial_match_dist:
|
||||
best_partial_match = expect_match
|
||||
best_partial_match_dist = expect_match.match_distance
|
||||
|
||||
return best_partial_match
|
||||
|
|
|
|||
|
|
@ -9,7 +9,11 @@
|
|||
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
from dex.evaluation.ExpectMatch import DebuggerExpectMatch
|
||||
from dex.evaluation.ExpectMatch import (
|
||||
DebuggerExpectMatch,
|
||||
MatchResult,
|
||||
get_expected_value_set,
|
||||
)
|
||||
from dex.test_script.Nodes import Expect, Value
|
||||
|
||||
|
||||
|
|
@ -52,6 +56,8 @@ class ScalarMetric(Metric):
|
|||
return ScalarMetric(self.value + other.value, self.improves_asc)
|
||||
|
||||
def __repr__(self):
|
||||
if isinstance(self.value, float):
|
||||
return f"{self.value:.4}"
|
||||
return f"{self.value}"
|
||||
|
||||
|
||||
|
|
@ -98,19 +104,31 @@ def get_variable_metrics(
|
|||
if not isinstance(expected_values, list):
|
||||
expected_values = [expected_values]
|
||||
num_total_steps = len(matches)
|
||||
all_expected_values = get_expected_value_set(expected_values)
|
||||
seen_expected_values = set()
|
||||
num_correct_steps = 0
|
||||
num_missing_var_steps = 0
|
||||
num_unexpected_value_steps = 0
|
||||
partial_step_correctness = 0.0
|
||||
for match in matches:
|
||||
if match.match_result:
|
||||
seen_expected_values.add(match.expected)
|
||||
partial_step_correctness += 1.0 - match.match_distance
|
||||
seen_expected_values = seen_expected_values.union(
|
||||
match.get_all_matched_values()
|
||||
)
|
||||
if match.match_result == MatchResult.TRUE:
|
||||
num_correct_steps += 1
|
||||
elif match.actual_result is None:
|
||||
num_missing_var_steps += 1
|
||||
else:
|
||||
num_unexpected_value_steps += 1
|
||||
num_seen_values = sum(ev in seen_expected_values for ev in expected_values)
|
||||
assert all(
|
||||
ev in all_expected_values for ev in seen_expected_values
|
||||
), "Saw expected values that weren't expected?"
|
||||
num_seen_values = sum(all_expected_values[ev] for ev in seen_expected_values)
|
||||
num_missing_values = sum(
|
||||
0 if ev in seen_expected_values else count
|
||||
for ev, count in all_expected_values.items()
|
||||
)
|
||||
# And finally produce the metrics map and add the new result to the list.
|
||||
metrics = {
|
||||
# The number of steps. Though this is not a useful metric in itself, it may be useful to see in tandem with
|
||||
|
|
@ -122,6 +140,8 @@ def get_variable_metrics(
|
|||
"incorrect_steps": ScalarMetric(
|
||||
num_total_steps - num_correct_steps, improves_asc=False
|
||||
),
|
||||
# The sum of the 0.0-1.0 "correctness value" of matches across each step.
|
||||
"partial_step_correctness": ScalarMetric(partial_step_correctness),
|
||||
# The number of steps where the watched variable/expression was not available in the debugger.
|
||||
"missing_var_steps": ScalarMetric(num_missing_var_steps, improves_asc=False),
|
||||
# The number of steps where the watched variable/expression had a value not in the set of expected values.
|
||||
|
|
@ -133,8 +153,6 @@ def get_variable_metrics(
|
|||
# The number of expected values that were observed at least once.
|
||||
"seen_values": ScalarMetric(num_seen_values),
|
||||
# The number of expected values that were not observed.
|
||||
"missing_values": ScalarMetric(
|
||||
len(expected_values) - num_seen_values, improves_asc=False
|
||||
),
|
||||
"missing_values": ScalarMetric(num_missing_values, improves_asc=False),
|
||||
}
|
||||
return metrics
|
||||
|
|
|
|||
|
|
@ -14,7 +14,11 @@ from collections import defaultdict
|
|||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
from dex.dextIR import DextIR, StepIR
|
||||
from dex.evaluation.ExpectMatch import DebuggerExpectMatch, get_expect_match
|
||||
from dex.evaluation.ExpectMatch import (
|
||||
DebuggerExpectMatch,
|
||||
MatchResult,
|
||||
get_expect_match,
|
||||
)
|
||||
from dex.evaluation.Metrics import (
|
||||
Metric,
|
||||
get_variable_metrics,
|
||||
|
|
@ -132,17 +136,17 @@ class DebuggerRunMatch(object):
|
|||
matching_expects = [
|
||||
(expect, match)
|
||||
for expect, match in step_match.expect_matches.items()
|
||||
if match.match_result
|
||||
if match.match_result == MatchResult.TRUE
|
||||
]
|
||||
non_matching_expects = [
|
||||
(expect, match)
|
||||
for expect, match in step_match.expect_matches.items()
|
||||
if not match.match_result
|
||||
if match.match_result != MatchResult.TRUE
|
||||
]
|
||||
if matching_expects:
|
||||
result += f" Matching nodes: [{', '.join(f'{expect}={match.actual_result}' for expect, match in matching_expects)}]\n"
|
||||
result += f" Matching nodes: [{', '.join(f'{expect}={match.short_str()}' for expect, match in matching_expects)}]\n"
|
||||
if non_matching_expects:
|
||||
result += f" Non-matching nodes: [{', '.join(f'{expect}={match.actual_result}' for expect, match in non_matching_expects)}]\n"
|
||||
result += f" Non-matching nodes: [{', '.join(f'{expect}={match.short_str()}' for expect, match in non_matching_expects)}]\n"
|
||||
return result
|
||||
|
||||
def get_metric_output(self):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
// RUN: %dexter_regression_test_cxx_build %s -o %t
|
||||
// RUN: %dexter_regression_test_run --use-script --skip-evaluate --binary %t \
|
||||
// RUN: -- %s | FileCheck %s
|
||||
|
||||
/// Check that the debugger is able to fetch the components of aggregate values.
|
||||
|
||||
struct Point {
|
||||
int X;
|
||||
int Y;
|
||||
int Z;
|
||||
};
|
||||
|
||||
struct Rect {
|
||||
Point TopLeft;
|
||||
Point BottomRight;
|
||||
};
|
||||
|
||||
int main() {
|
||||
Point P{1, 2, 3};
|
||||
int *I = &P.X;
|
||||
Rect R{{1, 1, 1}, {2, 2, 2}};
|
||||
int L[] = {0, 1, 2, 3, 4};
|
||||
return 0; // !dex_label ret
|
||||
}
|
||||
|
||||
// CHECK: Frame 0:
|
||||
// CHECK-NEXT: main
|
||||
// CHECK: "I": (int *)
|
||||
// CHECK-NEXT: "*I": (int) 1
|
||||
// CHECK-NEXT: "L": (int[5])
|
||||
// CHECK-NEXT: "[0]": (int) 0
|
||||
// CHECK-NEXT: "[1]": (int) 1
|
||||
// CHECK-NEXT: "[2]": (int) 2
|
||||
// CHECK-NEXT: "[3]": (int) 3
|
||||
// CHECK-NEXT: "[4]": (int) 4
|
||||
// CHECK-NEXT: "P": (Point)
|
||||
// CHECK-NEXT: "X": (int) 1
|
||||
// CHECK-NEXT: "Y": (int) 2
|
||||
// CHECK-NEXT: "Z": (int) 3
|
||||
// CHECK-NEXT: "R": (Rect)
|
||||
// CHECK-NEXT: "TopLeft": (Point)
|
||||
// CHECK-NEXT: "X": (int) 1
|
||||
// CHECK-NEXT: "Y": (int) 1
|
||||
// CHECK-NEXT: "Z": (int) 1
|
||||
// CHECK-NEXT: "BottomRight": (Point)
|
||||
// CHECK-NEXT: "X": (int) 2
|
||||
// CHECK-NEXT: "Y": (int) 2
|
||||
// CHECK-NEXT: "Z": (int) 2
|
||||
|
||||
/*
|
||||
---
|
||||
!where {lines: !label ret}:
|
||||
!value P:
|
||||
X: 1
|
||||
Y: 2
|
||||
Z: 3
|
||||
!value I:
|
||||
"*I": 1
|
||||
!value R:
|
||||
TopLeft:
|
||||
X: 1
|
||||
Y: 1
|
||||
Z: 1
|
||||
BottomRight:
|
||||
X: 2
|
||||
Y: 2
|
||||
Z: 2
|
||||
!value L:
|
||||
"[0]": 0
|
||||
"[1]": 1
|
||||
"[2]": 2
|
||||
"[3]": 3
|
||||
"[4]": 4
|
||||
...
|
||||
*/
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// RUN: %dexter_regression_test_cxx_build %s -o %t
|
||||
// RUN: %dexter_regression_test_run --use-script --binary %t -- %s \
|
||||
// RUN: | FileCheck %s
|
||||
|
||||
/// Check that the debugger is able to evaluate the components of aggregate
|
||||
/// values.
|
||||
|
||||
struct Point {
|
||||
int X;
|
||||
int Y;
|
||||
int Z;
|
||||
};
|
||||
|
||||
struct Rect {
|
||||
Point TopLeft;
|
||||
Point BottomRight;
|
||||
};
|
||||
|
||||
int main() {
|
||||
Point P{1, 2, 3};
|
||||
int *I = &P.X;
|
||||
Rect R{{1, 1, 1}, {2, 2, 2}};
|
||||
int L[] = {0, 1, 2, 3, 4};
|
||||
return 0; // !dex_label ret
|
||||
}
|
||||
|
||||
// CHECK: total_watched_steps: 4
|
||||
// CHECK: correct_steps: 3
|
||||
// CHECK: incorrect_steps: 1
|
||||
// CHECK: partial_step_correctness: 3.333
|
||||
// CHECK: missing_var_steps: 0
|
||||
// CHECK: unexpected_value_steps: 1
|
||||
// CHECK: seen_values: 13
|
||||
// CHECK: missing_values: 2
|
||||
|
||||
/*
|
||||
---
|
||||
!where {lines: !label ret}:
|
||||
!value P:
|
||||
X: 1 # Correct
|
||||
Y: 0 # Incorrect
|
||||
# Missing "Z"
|
||||
W: 8 # Not present
|
||||
!value I:
|
||||
"*I": 1
|
||||
!value R:
|
||||
TopLeft:
|
||||
X: 1
|
||||
Y: 1
|
||||
Z: 1
|
||||
BottomRight:
|
||||
X: 2
|
||||
Y: 2
|
||||
Z: 2
|
||||
!value L:
|
||||
"[0]": 0
|
||||
"[1]": 1
|
||||
"[2]": 2
|
||||
"[3]": 3
|
||||
"[4]": 4
|
||||
...
|
||||
*/
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// RUN: %dexter_regression_test_cxx_build %s -o %t
|
||||
// RUN: %dexter_regression_test_run --use-script --binary %t -- %s \
|
||||
// RUN: | FileCheck %s
|
||||
|
||||
/// Check that the debugger is able to correctly evaluate a list of expected
|
||||
/// aggregate values.
|
||||
|
||||
struct Point {
|
||||
int X;
|
||||
int Y;
|
||||
};
|
||||
|
||||
int main() {
|
||||
Point P{1, 2};
|
||||
P.X = 3; // !dex_label start
|
||||
P.Y = 0;
|
||||
P.X = 1;
|
||||
P.Y = 2;
|
||||
P = {0, 0};
|
||||
return 0; // !dex_label end
|
||||
}
|
||||
|
||||
// CHECK: total_watched_steps: 6
|
||||
// CHECK: correct_steps: 6
|
||||
// CHECK: incorrect_steps: 0
|
||||
// CHECK: partial_step_correctness: 6.0
|
||||
// CHECK: missing_var_steps: 0
|
||||
// CHECK: unexpected_value_steps: 0
|
||||
// CHECK: correct_step_coverage: 100.0% (6/6)
|
||||
// CHECK: seen_values: 12
|
||||
// CHECK: missing_values: 0
|
||||
|
||||
/*
|
||||
---
|
||||
!where {lines: !range [!label start, !label end]}:
|
||||
!value P:
|
||||
- X: 1
|
||||
Y: 2
|
||||
- X: 3
|
||||
Y: 2
|
||||
- X: 3
|
||||
Y: 0
|
||||
- X: 1
|
||||
Y: 0
|
||||
- X: 1
|
||||
Y: 2
|
||||
- X: 0
|
||||
Y: 0
|
||||
...
|
||||
*/
|
||||
Loading…
Reference in a new issue