[ANN] Wrong: an introspecting assert library

A

Alex Chaffee

For WhyDay, I took the time to clean up and document Wrong, a project
Steve Conover and I have been working on. It's inspired by assert{2.0}
(http://assert2.rubyforge.org/) but rewritten from scratch. Major
kudos to Phlip for starting us down this path with assert{2.0}, and
to Ryan Davis for his RubyParser and Ruby2Ruby libraries which made
it technically possible.

Before I say more, let me first say that this is totally not ready
yet! We would love to get some users, but there are some big caveats,
and there are plenty of things left to be done to make the results
look uniformly clean and beautiful. We want your feedback, and
especially to give us cases where either it blows up or the output is
ugly or uninformative.

Wrong provides a general assert method that takes any Ruby block.
Assertion failure messages are rich in detail. The Wrong idea is to
replace all those countless assert_this, assert_that library methods
which only exist to give a more useful failure message than "assertion
failed". Wrong replaces all of them in one fell swoop, since if you
can write it in Ruby, Wrong can make a sensible failure message out of
it.

Examples:

=A0 =A0require "wrong"
=A0 =A0include Wrong::Assert

=A0 =A0assert {1=3D=3D1}
=A0 =A0 =3D=3D> nil

=A0 =A0assert {2=3D=3D1}
=A0 =A0 =3D=3D> Expected (2 =3D=3D 1), but 2 is not equal to 1

=A0 =A0x =3D 7; y =3D 10; assert { x =3D=3D 7 && y =3D=3D 11 }
=A0 =A0 =3D=3D>
=A0 =A0Expected ((x =3D=3D 7) and (y =3D=3D 11)), but
=A0 =A0 =A0 =A0(x =3D=3D 7) is true
=A0 =A0 =A0 =A0x is 7
=A0 =A0 =A0 =A0(y =3D=3D 11) is false
=A0 =A0 =A0 =A0y is 10

=A0 =A0assert { 'hand'.include?('bird') }
=A0 =A0 =3D=3D>
=A0 =A0Expected "hand".include?("bird"), but "hand" does not include "bird"

=A0 =A0age =3D 24
=A0 =A0name =3D "Gaga"
=A0 =A0assert { age >=3D 18 && ["Britney", "Snooki"].include?(name) }
=A0 =A0 =3D=3D>
=A0 =A0Expected ((age >=3D 18) and ["Britney", "Snooki"].include?(name)), b=
ut
=A0 =A0 =A0 =A0(age >=3D 18) is true
=A0 =A0 =A0 =A0age is 24
=A0 =A0 =A0 =A0["Britney", "Snooki"].include?(name) is false
=A0 =A0 =A0 =A0name is "Gaga"

So how do we do it? Doesn't Ruby have poor support for AST
introspection (see
http://blog.zenspider.com/2009/04/parsetree-eol.html)? Well, yes, it
does, so we cheat: we figure out what file and line the assert block
is defined in, then open the file, read the code, and parse it
directly using Ryan Davis' amazing RubyParser and Ruby2Ruby. You can
bask in the kludge by examining `chunk.rb` and `assert.rb`. If you
find some code it can't parse, please send it our way.

Before you get your knickers in a twist about how this is totally
unacceptable because it doesn't support this or that use case, here
are our caveats and excuses:

* It works! Tested in 1.8.6, 1.8.7, 1.9.1, and 1.9.2-rc2. (Thank you, rvm!)
* Your code needs to be in a file. That means it doesn't work in IRB.
(If you're developing Ruby code without saving it to a mounted disk,
then sorry, Wrong is not right for you.)
* It's a development-time testing library, not a production runtime
library, so there are no security or filesystem issues.
* eval isn't evil, it's just misunderstood.
* It makes a few assumptions about the structure of your code, leading
to some restrictions:
=A0* You can't have more than one call to `assert` per line. (This
should not be a problem since even if you're nesting asserts for some
bizarre reason, we assume you know where your Return key is. And
actually, technically you can put two asserts on a line, but it always
describes the first one it sees, which means that if the second one
executes, its failure message will be incorrect or broken.)
=A0* You can't use metaprogramming to write your assert blocks.
=A0* All variables and methods must be available in the binding of the
assertion block.

Please feel free to challenge any of the above claims in a reply :).
I'd love to know if Ryan and the rest of the ruby-talk gang think this
is an acceptable or an outrageous use of a parsing library. In any
event, I think it constitutes a pretty compelling use case for adding
Proc#to_sexp or equivalent to the MRI runtime.

The gem's been released on RubyGems, so "gem install wrong" should
work, but you might be better off following my github project.

http://github.com/alexch/wrong
http://www.slideshare.net/alexchaffee/wrong-5069976

=A0- Alex
 
R

Ryan Davis

So how do we do it? Doesn't Ruby have poor support for AST
introspection (see
http://blog.zenspider.com/2009/04/parsetree-eol.html)? Well, yes, it
does, so we cheat: we figure out what file and line the assert block
is defined in, then open the file, read the code, and parse it
directly using Ryan Davis' amazing RubyParser and Ruby2Ruby. You can
bask in the kludge by examining `chunk.rb` and `assert.rb`. If you
find some code it can't parse, please send it our way.

HAHAHA!... you SICK SICK BASTARDS.... HAHAHAHA!

good work!
Please feel free to challenge any of the above claims in a reply :).
I'd love to know if Ryan and the rest of the ruby-talk gang think this
is an acceptable or an outrageous use of a parsing library. In any
event, I think it constitutes a pretty compelling use case for adding
Proc#to_sexp or equivalent to the MRI runtime.

Are you kidding? that output is freakin' beautiful!
 
B

Benoit Daloze

For WhyDay, I took the time to clean up and document Wrong, a project
Steve Conover and I have been working on. It's inspired by assert{2.0}
(http://assert2.rubyforge.org/) but rewritten from scratch. Major
kudos to Phlip for starting us down this path with assert{2.0}, and
to Ryan Davis for his RubyParser and Ruby2Ruby libraries which made
it technically possible.

=A0 =A0require "wrong"
=A0 =A0include Wrong::Assert

=A0 =A0assert {1=3D=3D1}
=A0 =A0 =3D=3D> nil

=A0 =A0assert {2=3D=3D1}
=A0 =A0 =3D=3D> Expected (2 =3D=3D 1), but 2 is not equal to 1

=A0 =A0x =3D 7; y =3D 10; assert { x =3D=3D 7 && y =3D=3D 11 }
=A0 =A0 =3D=3D>
=A0 =A0Expected ((x =3D=3D 7) and (y =3D=3D 11)), but
=A0 =A0 =A0 =A0(x =3D=3D 7) is true
=A0 =A0 =A0 =A0x is 7
=A0 =A0 =A0 =A0(y =3D=3D 11) is false
=A0 =A0 =A0 =A0y is 10

=A0- Alex

Neat ! I am indeed surprised you made possible to parse 1.9.

The output is still a bit looking like a stack trace of an error, but
the idea is excellent.

Thanks for this cool whyday code, you played well with a limit of the langu=
age.

B.D.
 
R

Ryan Davis

Neat ! I am indeed surprised you made possible to parse 1.9.

He prolly didn't... It still prolly won't parse 1.9isms, but luckily =
those usually aren't used in tests.=
 
B

Benoit Daloze

He prolly didn't... It still prolly won't parse 1.9isms, but luckily those usually aren't used in tests.

Ah, indeed.
But as it needs only to parse the body of the assert/deny block, it
will likely be rarely a big problem.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,534
Members
45,007
Latest member
obedient dusk

Latest Threads

Top