Use logging module for better control and configurability
print(f"blessing var {var_name}")
1"""Contains the logic that compares variables to `INPUT_DATA` via the entrypoint2`check(var_name, breakpoint_idx, frame)`. These comparisons report errors to stdout, and then return3a `Result` indicating whether or not the variable matched.45Checks *do not* stop after the first encountered error. Some redundant information may be ommitted6(e.g. checking pretty printed type name if the synthetic isn't properly attached to the type).7"""89import sys10import traceback11from typing import Any, Callable1213import lldb1415from .common import (16 BLESS,17 INPUT_DATA,18 ArrayChild,19 ArrayLikeChildren,20 Child,21 Result,22 Variable,23 print_error,24 print_mismatch,25)26from .from_lldb import (27 BasicType,28 TypeClass,29 bless_variable,30 get_generics,31 type_from_lldb,32 variable_from_lldb,33)3435VARS_TESTED: list[dict[str, Result]] = []36"""Used to help ensure all expected variables were tested. Each element of the list corresponds to a37breakpoint, and contains a set of all of the variable names tested for that breakpoint."""383940def check(var_name: str, breakpoint_idx: int, frame: lldb.SBFrame) -> Result:41 """`lldb-repr` pseudo-command entrypoint. Checks the variable against `INPUT_DATA` for the given42 frame at the given breakpoint.43 """4445 if BLESS:46 print(f"blessing var {var_name}")47 bless_variable(INPUT_DATA, var_name, breakpoint_idx, frame)4849 # Even if we're blessing, we still want to run the variable through the test to make sure we're50 # not somehow saving invalid information5152 valobj: lldb.SBValue = frame.var(var_name)53 if not valobj.IsValid():54 print_error(var_name, "Unable to find variable")55 return Result.Mismatch5657 var = variable_from_lldb(valobj)5859 try:60 expected = INPUT_DATA.breakpoints[breakpoint_idx][var_name]61 except IndexError:62 print_error("INPUT_DATA", f"No data found for breakpoint #{breakpoint_idx}")63 return Result.Mismatch64 except KeyError:65 print_error(66 "INPUT_DATA",67 f"No data found for var '{var_name}' at breakpoint #{breakpoint_idx}",68 )69 return Result.Mismatch7071 result = var_matches(var, expected, valobj)72 # --bless outputs blank breakpoints for any breakpoints with no variables, so we need to account73 # for that here74 if len(VARS_TESTED) <= breakpoint_idx:75 VARS_TESTED.extend({} for _ in range(1 + breakpoint_idx - len(VARS_TESTED)))7677 VARS_TESTED[breakpoint_idx][var_name] = result7879 if result == Result.Ok:80 print(f"{var_name}: Ok")8182 return result838485TYPES_TESTED: dict[str, Result] = {}86"""Since types are unique and unchanging, we only need to test each type once. This also helps87ensure we have tested all types in `INPUT_DATA`88"""899091def type_matches(92 sbtype: lldb.SBType, sbtarget: lldb.SBTarget, provider_ok: bool = False93) -> Result:94 """Checks a type and all field/generic types (recursively) against the data contained in95 `INPUT_DATA`."""96 name: str = sbtype.GetName()97 error_source = f"type '{name}'"9899 if (r := TYPES_TESTED.get(name)) is not None:100 # The proper result was returned the first time the type was tested, so we can just pretend101 # everything we've already seen has succeeded.102 if not r:103 print_error(104 f"type '{name}'", f"mismatch (see prior output for type '{name}')"105 )106 return r107108 ty = type_from_lldb(sbtype, sbtarget)109110 expected = INPUT_DATA.types.get(name)111112 if expected is None:113 result = Result.Mismatch114 print_error(f"type '{name}'", "type not found in input data")115 else:116 basic_type_result = (117 Result.Ok if ty.basic_type == expected.basic_type else Result.Mismatch118 )119 if basic_type_result == Result.Mismatch:120 print_mismatch(121 error_source,122 "basic_type (lldb.eBasicType)",123 f"{ty.basic_type} ({BasicType(ty.basic_type)})",124 f"{expected.basic_type} ({BasicType(expected.basic_type)})",125 )126127 type_class_result = (128 Result.Ok if ty.type_class == expected.type_class else Result.Mismatch129 )130131 if type_class_result == Result.Mismatch:132 print_mismatch(133 error_source,134 "type_class (lldb.eTypeClass)",135 f"{ty.type_class} ({TypeClass(ty.type_class).name})",136 f"{expected.type_class} ({TypeClass(expected.type_class).name})",137 )138139 ty_result = ty.matches(expected, name, provider_ok)140141 result = type_class_result and ty_result142143 TYPES_TESTED[name] = result144145 fields: list[lldb.SBTypeMember] = sbtype.fields146 inner_types = [f.GetType() for f in fields]147 inner_types.extend(get_generics(sbtype, sbtarget))148149 for t in inner_types:150 result = type_matches(t, sbtarget) and result151152 return result153154155def tested_all_types() -> bool:156 """Returns true if all types in INPUT_DATA were tested this run."""157158 expected_types = set(INPUT_DATA.types)159 untested_types = expected_types.difference(TYPES_TESTED.keys())160161 if len(untested_types) != 0:162 print(163 f"[repr error] The following types were expected, but were not tested:\n\164 {untested_types}"165 )166167 return len(untested_types) == 0168169170def tested_all_variables() -> bool:171 expected_vars = [set(vars) for vars in INPUT_DATA.breakpoints]172 untested_vars = [173 expected.difference(tested.keys())174 for expected, tested in zip(expected_vars, VARS_TESTED)175 ]176177 tested_not_expected = [178 set(tested.keys()).difference(expected)179 for expected, tested in zip(expected_vars, VARS_TESTED)180 ]181182 result = True183184 for i, v in enumerate(untested_vars):185 if len(v) == 0:186 continue187188 result = False189 print(190 f"[repr error] The following variables were expected at breakpoint#{i}, but were not \191tested:\n {v}"192 )193194 for i, v in enumerate(tested_not_expected):195 if len(v) == 0:196 continue197198 result = False199 print(200 f"[repr error] The following variables were tested, but do not exist in the input data \201at breakpoint#{i}:\n {v}"202 )203204 return result205206207def var_matches(var: Variable, expected: Variable, valobj: lldb.SBValue) -> Result:208 # Happy path requires very little intercession from us. We keep these values on the stack209 # so we don't have to recalculate them if we need to do error handling210 summary_ok = var.summary == expected.summary211 synthetic_ok = var.synthetic == expected.synthetic212 pretty_type_name_ok = var.pretty_type_name == expected.pretty_type_name213 pretty_print_ok = var.pretty_print == expected.pretty_print214 format_ok = var.format == expected.format215216 type_ok = var.type == expected.type217218 if var.has_visualizer() or expected.has_visualizer():219 type_match_ok = type_matches(220 valobj.GetType(),221 valobj.GetTarget(),222 summary_ok223 & synthetic_ok224 & format_ok225 & pretty_type_name_ok226 & pretty_print_ok,227 )228 else:229 type_match_ok = Result.Ok230231 value_ok = var.value == expected.value232233 work_list = [valobj.GetChildAtIndex(i) for i in range(valobj.GetNumChildren())]234 target = valobj.GetTarget()235 child_types_ok = True236237 while len(work_list) != 0:238 obj = work_list.pop()239240 for i in range(obj.GetNumChildren()):241 child = obj.GetChildAtIndex(i)242 # We don't need to report an error for invalid children here. Invalid objects can't be243 # blessed, thus should never exist in INPUT_DATA. That means they will always report244 # as a mismatch in `children_match`245 if child.IsValid():246 work_list.append(child)247 else:248 child_types_ok = False249250 if var.has_visualizer() or expected.has_visualizer():251 child_types_ok &= type_matches(obj.GetType(), target) == Result.Ok252 else:253 type_match_ok = Result.Ok254255 children_ok = children_match(256 var.children, expected.children, valobj.GetName(), valobj257 )258259 if (260 type_ok261 and type_match_ok262 and pretty_type_name_ok263 and pretty_print_ok264 and value_ok265 and synthetic_ok266 and summary_ok267 and format_ok268 and children_ok269 and child_types_ok270 ):271 return Result.Ok272273 error_source = f"var '{valobj.GetName()}'"274275 # otherwise, we want to output exactly what doesn't match276 # and any additional helpful information277278 # We check the type first. If this has changed, it's relatively likely nothing else will work279 # properly280 if not type_ok:281 print_mismatch(282 error_source,283 "type (Type Name)",284 var.type,285 expected.type,286 )287288 # We check the summary next since it's the most user-visible output. We don't need to check289 # `pretty_print` if the summary provider doesn't match.290 if not summary_ok:291 print_mismatch(292 error_source, "summary (Summary Provider)", var.summary, expected.summary293 )294 elif not pretty_print_ok:295 print_mismatch(296 error_source,297 "pretty_print (Summary Output)",298 var.pretty_print,299 expected.pretty_print,300 )301302 # try the summary provider directly to see if it's throwing an exception303 if var.summary is not None:304 try:305 provider = get_provider(var.summary)306 _ = provider(valobj, {})307 except Exception as e:308 print_error(309 error_source + " Summary",310 "Error while running Summary \311provider:",312 )313 traceback.print_exception(type(e), e, e.__traceback__, file=sys.stdout)314315 # Next we check the value and formatter. These mostly affect primitives.316 if not value_ok:317 print_mismatch(error_source, "value", var.value, expected.value)318 if not format_ok:319 print_mismatch(error_source, "format", var.format, expected.format)320321 # Synthetic is checked next since children, pretty type name, and pretty print rely on it. If322 # the synthetic doesn't match, we can assume those won't match either.323 if not synthetic_ok:324 print_mismatch(325 error_source,326 "synthetic (Synthetic Provider)",327 var.synthetic,328 expected.synthetic,329 )330 else:331 if not pretty_type_name_ok:332 print_mismatch(333 error_source,334 f"pretty_type_name ({var.synthetic}.get_type_name)",335 var.pretty_type_name,336 expected.pretty_type_name,337 )338339 if not children_ok and var.synthetic is not None:340 # If the children don't match, we can check for more catastrophic failures using the341 # synthetic provider. All the per-children errors will have been printed in the342 # `children_match` check above.343 try:344 synth_provider = get_provider(var.synthetic)345346 # First we check for exceptions in the constructor and initialization347 synth: lldb.SBSyntheticValueProvider = synth_provider(348 valobj.GetNonSyntheticValue(), {}349 )350 synth.update()351352 # If the `get_child_at_index` function doesn't exist, there's not much more we353 # can do354 if getattr(synth, "get_child_at_index", None) is not None:355 # If all the children are invalid (e.g. because a template arg isn't356 # resolving correctly, incorrect enum discriminant), we should dump the357 # internal state of the synthetic358 if not all(359 synth.get_child_at_index(i).IsValid()360 for i in range(synth.num_children())361 ):362 dump_synthetic_state(synth)363364 except Exception as e:365 print_error(366 error_source + " Synthetic",367 "Error while running Synthetic\368Provider:",369 )370 traceback.print_exception(type(e), e, e.__traceback__, file=sys.stdout)371372 return Result.Mismatch373374375def dump_synthetic_state(synth: Any):376 """Prints an object via builtin `vars()`. If `obj.__dict__` does not exist because the object is377 using `__slots__` intsead, the `__slots__` are converted into a dict and printed."""378 if (getattr(synth, "__dict__", None)) is not None:379 fields = vars(synth)380 elif (slots := getattr(synth, "__slots__", None)) is not None:381 fields = {name: getattr(synth, name, None) for name in slots}382 else:383 # Shouldn't be possible, but better safe than sorry384 print("Unable to print Synthetic Provider state")385 return386387 print(f"Synthetic Provider state:\n {fields})")388389390def children_match(391 children: list[Child],392 expected: list[Child],393 path: str,394 valobj: lldb.SBValue,395) -> Result:396 """Recursively checks children against an expected value and prints errors for mismatches."""397398 result = Result.Ok if len(children) == len(expected) else Result.Mismatch399400 mismatches = []401 missing = []402 invalid_count = 0403404 for i in range(len(expected)):405 exp = expected[i]406407 if i >= len(children):408 missing.append(exp.name)409 continue410411 got = children[i]412413 if isinstance(children, ArrayLikeChildren):414 if isinstance(got, ArrayChild):415 got = Child(f"[{i}]", children.type, got.value, got.children)416 else:417 got = Child(f"[{i}]", children.type, got, [])418419 if isinstance(expected, ArrayLikeChildren):420 if isinstance(exp, ArrayChild):421 exp = Child(f"[{i}]", expected.type, exp.value, exp.children)422 else:423 exp = Child(f"[{i}]", expected.type, exp, [])424425 if got.name is None:426 result = Result.Mismatch427 invalid_count += 1428 mismatches.append(429 f"{exp.name}: {exp.type} = {exp.value} -> <Invalid SBValue>"430 )431 elif got.name != exp.name or got.type != exp.type or got.value != exp.value:432 result = Result.Mismatch433 mismatches.append(434 f"{exp.name}: {exp.type} = {exp.value} -> {got.name}: {got.type} = {got.value}"435 )436 # no point recursing into children if we've already mismatched437 elif exp.children is not None and len(exp.children) > 0:438 result &= children_match(439 got.children,440 exp.children,441 f"{path}.{exp.name}",442 valobj.GetChildAtIndex(i),443 )444445 if result == Result.Ok:446 return result447448 # If every single child is invalid, we can condense the output a lot by pointing to the449 # synthetic instead of printing a bunch of identical mismatches450 if invalid_count == len(children):451 print_error(452 path,453 f"All children of this object are invalid SBValue objects.\n This is \454almost always caused by invalid state or logic in the SyntheticProvider.\n This object's \455synthetic appears to be '{valobj.GetTypeSynthetic().GetData()}'",456 )457 elif len(mismatches) != 0:458 error_str = "\n ".join(mismatches)459 print_error(460 path,461 f"The following children do not match (expected -> got):\n {error_str}",462 )463 elif len(missing) != 0:464 error_str = ", ".join(missing)465 print_error(466 path,467 f"The following children were expected, but were not found:\n {error_str}",468 )469 elif len(children) > len(expected):470 error_str = "\n ".join(471 f"{got.name}: {got.type} = {got.value}" for got in children[len(expected) :]472 )473 print_error(474 path,475 f"The following children were found, but were not expected:\n {error_str}",476 )477478 return result479480481def get_provider(provider_str: str) -> Callable[[lldb.SBValue, dict[Any, Any]], Any]:482 """Given a Varible.summary or Variable.Synthetic, imports the appropriate module and returns the483 matching Class/Function"""484 import importlib485486 [module, summary_name] = provider_str.split(".", 1)487 provider_module = importlib.import_module(module)488489 return getattr(provider_module, summary_name)
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.