pschanely / CrossHair

An analysis tool for Python that blurs the line between testing and type systems.
Other
1.03k stars 49 forks source link

Pretty printing object instances (counter examples) #164

Open PiotrZakrzewski opened 2 years ago

PiotrZakrzewski commented 2 years ago

Is your feature request related to a problem? Please describe. When working with class instances as function parameters counter examples provided on stdout of crosshair show str(object) which does not contain info about the value of fields of this counter example. It looks like this:

/home/piotrzakrzewski/code/project/package/module/modulepy:29: error: false when calling function() (which returns <package.module.ClassName object at 0x7f30ffb49c10>)

Describe the solution you'd like Using rich.inspect could allow for pretty printing objects even without custom __repr__ image

see https://rich.readthedocs.io/en/stable/introduction.html Describe alternatives you've considered Add msg / or at least docs to inform user that implementing __repr__ will improve their user experience?

pschanely commented 2 years ago

What a neat idea!

I'm also musing about a hybrid approach: if a good repr exists, it's nice to have something that's trivially copy+pasteable into an repl / debugger / unit test. Issue #48 is also a counterexample repr problem that might be influenced by our choices.

I will need to perform some experiments; depending on how rich.inspect is implemented, some of the CrossHair smoke and mirrors for symbolic values might leak through. (either way, it's a good test) I'll have time in the next week to dig in.

pschanely commented 2 years ago

Some exploration (and interesting CrossHair fixes related to rich's dynamic imports) has happened. But there's a bit of an onion to unravel here: I'd like to make a few improvements along the way, so it'll take a while. More updates in a few weeks!

pschanely commented 2 years ago

So this took me a few months to get to ... instead of a few weeks. But better late than never. As of 98ec6da735af699e6a729e2572baf56ac101473d, I've got something working: we output an eval()-able string that approximates the way we constructed the instance within CrossHair. This should remove the counterexample dependency on repr() for custom class instances.

This is an ~aggressively breaking change, and I'll want to poke at a few things before I cut a release ... but will update this issue again when that happens.

azewiusz commented 2 years ago

I just want to mention that I came across this "issue" when started playing with CrossHair 0.0.31, classes annotated with @dataclasses.dataclass are represented nicely when I output pytest tests, but once I get to custom classes I get something like mentioned here - object reference.

def test_myfunction():
    assert myfunction(<concolic.intro.Counter object at 0x0000021928B99DD8>) == False
pschanely commented 2 years ago

Oh, great - glad that multiple people are interested in this. Would y'all give 0.0.32 a try? It contains my changes to make this issue better.

Here is a trivial example that reports the counterexample even though the class doesn't define repr: https://crosshair-web.org/?crosshair=0.1&python=3.8&gist=ea119ca6c87f9d92a6d75f58fad24898

azewiusz commented 2 years ago

The one thing I can say about provided counterexample is that object attribute values are not mentioned by their names, but generally good direction.

/tmp/main.py:12: error: false when calling process(Foo(0, 0)) (which returns (0, 0))
pschanely commented 2 years ago

The one thing I can say about provided counterexample is that object attribute values are not mentioned by their names, but generally good direction.

/tmp/main.py:12: error: false when calling process(Foo(0, 0)) (which returns (0, 0))

Ah, indeed. I'd point out that keyword-only arguments (those after the optional *, marker) will be reported with a keyword. (like the "z" parameter in this example)

But the current behavior suppresses keywords when it can. This is something a matter of taste; I've made a poll in #180 - please vote!