slagyr / speclj

pronounced "speckle": a TDD/BDD framework for Clojure.
MIT License
459 stars 58 forks source link

use pprint #45

Closed thesoftwarephilosopher closed 10 years ago

thesoftwarephilosopher commented 11 years ago

When big-ish hashes turn out to not be the same, it's hard to see the difference. I find myself doing (pprint result) before should= in these cases. How about just use pprint instead within should=?

edtsech commented 11 years ago

@sdegutis take a look https://github.com/slagyr/speclj/pull/53

thesoftwarephilosopher commented 11 years ago

@slagyr I've tried both pprint and @brandonbloom's pretty-printing lib, adjusting the available options for both. Both get close but neither work well enough 100% of the time. I've also tried Joodo's pretty-map, but that doesn't work on sets.

@edtsech that won't work for deeper-nested structures.

Sounds like there's need for a new pretty-printing lib in order. Not sure how it should work, I just know we need something better than viewing data structures on one line :)

Right now I'm settling with wrapping my assertions with pprint using should-fail.

brandonbloom commented 11 years ago

@sdegutis Part of the point of Fipp is that it's incredibly easy to write new pretty printers. If there is something that is broken, then open an issue and I can probably implement it super quickly. If you've got custom structures you want to print, you should be able to extend the "pretty" protocol to your types easily. See the edn.clj file.

thesoftwarephilosopher commented 11 years ago

@brandonbloom hmm that might work. I'll look into it. The problem is basically that when I make an assertion that (= A B) and it fails, I want it to pretty-print both A and B in a way where I can easily see what's different about them, especially when they're nested structures, like a set containing maps containing vectors, etc.

brandonbloom commented 11 years ago

Fipp is great for pretty printing Clojure data / EDN, but it can also be used as a library to add your own pretty printing to whatever you want. If pretty printing normal data is good enough, then you're already all set with Fipp. Let me know if you find any bugs. If you need to pretty print something special, like the result of a diff, for example, you can use Fipp to help you automatically format the diff results horizontally vs vertically with indentation and the like. For that, however, you're on your own with the code, but I can provide guidance on making Fipp do what you want (if it can). The cited literature gets the ideas across too, if you're willing to read them.

thesoftwarephilosopher commented 11 years ago

@brandonbloom my initial experiment using Fipp was to just do (fip/pprint A {:width 1}) which mostly did what I want. It printed every map with each key-value pair on its own line. But when A was a set of maps, it seemed to print them all on the same line.

user=> (fipp.edn/pprint {:a 1 :b {:c 3 :d {:e {:f 5}}}} {:width 1})
{:a 1,
 :b {:c 3,
     :d {:e {:f 5}}}}
nil
user=> (fipp.edn/pprint #{{:a 1 :b {:c 3 :d {:e {:f 5}}}}} {:width 1})
#{{:a 1, :b {:c 3, :d {:e {:f 5}}}}}
nil
brandonbloom commented 11 years ago

I fixed printing of sets a while ago, but forgot to publish it. I just deployed version 0.4.1 to Clojars with set support.

thesoftwarephilosopher commented 11 years ago

@brandonbloom sweet, that might be just the ticket, thanks!

thesoftwarephilosopher commented 11 years ago

@brandonbloom The EDN printer is almost exactly what we need. We're basically printing a plain EDN data structure on assertion failure. It looks like the {:width N} argument is the number of characters relative to the column of the current indentation, right? I think the behavior we want is to have maps always print one key/value pair per line, and vectors/lists/sets print on a single line (all elements on the same line) separated by commas, unless either it contains a collection element or the character width of the one-line collection exceeds N, in which case it prints every element on its own line (at least I think, /cc @slagyr @trptcolin). Would this be hard to do @brandonbloom?

thesoftwarephilosopher commented 11 years ago

@slagyr @trptcolin

I have an implementation that indents properly that's close but needs some feedback: https://github.com/sdegutis/speclj/commit/8af1d84a85575a79763dd6bc9ae16372af917cfe

Right now it prints results like this:

 Expected: 1
      got: [2
  3
  4] (using =)

But I want to change -to-s to take an indentation-chars argument that specifies just how long each line after the first one should be indented. (A function that indents a string thusly is easy to imagine.) This way you get results like this:

 Expected: 1
      got: [2
            3
            4] (using =)

It's too fragile to just change -to-s to take a number arg, because if you change the string, you have to change the number too, and it has to be correct:

(-fail (str "Expected: " (-to-s 10 expected#) speclj.platform/endl "     got: " (-to-s 10 actual#) " (using =)"))

An alternative is to change -fail to take strings directly, and it can calculate the length of the first string arg to get the indentation-char-count:

(-fail "Expected: " (eventually-to-s expected#) speclj.platform/endl "     got: " (eventually-to-s actual#) " (using =)")

Then we only need to return something from eventually-to-s that -fail can recognize as something that needs to be converted with (-to-s indent-char-count the-string). It probably suffices to just return {:-to-s true, :str the-string}

Kind of ugly, but it requires the fewest changes to the codebase to get the above indentation working.

Thoughts? Better ideas?

brandonbloom commented 11 years ago

It looks like the {:width N} argument is the number of characters relative to the column of the current indentation, right?

Not quite. The width is where the right margin is. If some output goes past the right margin (or some search limit is hit) then the enclosing groups are formatted vertically instead of horizontally. No effort is made to prevent code that is greater than width levels nested from pushing past the right margin.

unless either it contains a collection element or the character width of the one-line collection exceeds N

Fipp's algorithm intentionally does not account for the type of elements because it could only analyze the prefix while still preserving it's bounded space & linear time requirements.

You could forgo those performance guarantees by performing such analysis during the conversion to a "pretty document". See the edn.clj file for a starting point.