Re: skip test interface

2001-07-19 Thread barries

On Thu, Jul 19, 2001 at 06:04:18PM -0400, [EMAIL PROTECTED] wrote:
> On Thu, Jul 19, 2001 at 10:17:07AM -0400, barries wrote:
> > The only pain I see there is the hardcoded test numbers in both places
> 
> Yes, that's just an artifact of how HiRes.t is written.  It rolls its
> own test functions.  Ignore that bit.

Actually, they make a good analog for test names, so the comment becomes:
that syntax is nice except that test names must be repeatedly entered
and kept in sync when skip()ing.

> > Of course, if you're brave/knave enough to not plan or use test numbers:
> > 
> >use Test::More 'noplan' ;
> > 
> >if ( $have_ualarm ) {
> >   ...do tests...
> >}
> > 
> > makes for short, simple tests.
> 
> Problem there is there's no external indication that anything got
> skipped. :( That and using no_plan requires you to upgrade
> Test::Harness.

Yup.  I could live with it for "casual" tests in a CPAN module, and I'd
call skip() for tests I cared about.  It also decouples skip() from
having a one-to-one correspondence with ok()s, making it easy to skip a
bunch of tests.

I think what you and I have both been beating our heads against, from
different directions, is that Perl just doesn't provide the syntax and
flow of control directives to do skip/ok/todo gracefully, where
gracefully means:

   1) Procedural control flow (ie non-declarative syntax, no "test
  executive")
   2) No need to duplicate test metadata (numbers, names, whatever)
   3) Ability to skip easily without and a call to skip() and an else
  block
   4) Ability to todoify/untodoify easily, without typing in the test
  name or number in the plan.  Hmmm, see todo_because below, though

The closure-as-first-arg-no-sub-keyword syntax is seductive, but
something like skip just won't feel right until you can give the \&
prototype in later positions.  And even then, I think it'll look goofy.

So.

How about a syntax like:

   use Test::Named ;

   =test foo

  skip "no foo!" unless $have_foo ;
  is foo, 10 ;  # Borrowed from Test::More, IIRC
  reset_foo ;

   =test bar

  todo_because "bar not low enough" ;
  ok bar ;

   =endtests

Obviously, a source filter.  The output might look like:

   Test::Named::begin('foo',__FILE__,__LINE__); eval{ #=test foo

  skip "no foo!" unless $have_foo ;
  is foo, 10 ;  # Borrowed from Test::More, IIRC

   } ; Test::Named::end(); Test::Named::begin( 'bar' ) ; eval { # =test bar

  todo_because "bar not low enough" ;
  ok bar ;

   } ; Test::Named::finish() # =end tests

Details:

   1) skip dies, Test::Named::end() "catches" it, emits message
   2) todo_because sets a flag, is(), etc. check flag, T::N::begin clears it
   3) T::N emits the plan line upon input EOF
   4) test messages all would have name, file, and line number, perhaps
  test number (especially if no name given)
   5) Multiple tests can have same name, differentiate by line number
   6) Perl's line numbering is maintained one-to-one by filter
   7) Result primitives:
 ok ;
 ok $cond ;
 notok ;
 notok $cond ;
 is $got, $expected ;
 isnt $got, $expected ;
 like $got, $regexp ;
 unlike $got, $regexp ;
   8) Non-result primitives:
 =test 
 =endtest
 =endtests
 skip $why ; # exits test immediately, others don't
 todo_because $why ; # alters behavior of result primitives
   9) A test must call a result primitive or it fails

Benefits:

   1) it's POD-like and readable, people will grok most of it readily
   2) Flow is obvious
   3) tests are counted and planned for you
   4) skip is natural Perl syntax
   5) todo_because is natural perl syntax (could be just "todo")
   6) metadata (test names) look like metadata
   7) a test name is only ever mentioned once
   8) all primitives bone simple APIs
   9) can put Perl code between/around =test...=endtest blocks
   10) Each test is in a block, so lexicals that are used in multiple
   tests myst be placed between tests (=endtest...my $foo;...=test),
   clearly differentiating intratest from intertest lexicals.

Cons:

   1) it's a source filter (though a simple, robust one)
   2) the eval blocks are non-obvious. Alternatives:
  - Could use SIG{__DIE__} instead of eval {}, but that's Eevil.
  - Could attempt to find occurences of
/^\s*skip\b/ and make them gotos, but that's an error prone parsing
process.
   3) Lexical visibility is limited to one test (could be a benefit, see
  #10 above)

Odd thought: could do away with almost all of the result primitives and
use expect/got primitives:

   =test foo

  expect 10 ;  # Could be qr/.../, etc.
  got foo ;

   =test bar

  dont_expect 10 ; # must not be 10!
  got bar ;

   =endtests

I have a source filter started to do this, need to play with it some
more.  Feedback?

- Barrie



Re: skip test interface

2001-07-19 Thread schwern

On Thu, Jul 19, 2001 at 10:17:07AM -0400, barries wrote:
> The only pain I see there is the hardcoded test numbers in both places

Yes, that's just an artifact of how HiRes.t is written.  It rolls its
own test functions.  Ignore that bit.


> Of course, if you're brave/knave enough to not plan or use test numbers:
> 
>use Test::More 'noplan' ;
> 
>if ( $have_ualarm ) {
>   ...do tests...
>}
> 
> makes for short, simple tests.

Problem there is there's no external indication that anything got
skipped. :( That and using no_plan requires you to upgrade
Test::Harness.


-- 
Michael G Schwern   <[EMAIL PROTECTED]>   http://www.pobox.com/~schwern/
Perl6 Quality Assurance <[EMAIL PROTECTED]>   Kwalitee Is Job One



Re: skip test interface

2001-07-19 Thread Michael G Schwern

On Fri, Jul 20, 2001 at 01:30:39AM -0400, Michael G Schwern wrote:
> test_block {
> skip "Pigs can't fly" unless $pig->can('fly');
> 
> $pig->takeoff;
> ok( $pig->altitude > 0 );
> ok( $pig->airspeed > 0 );
> } 2;

Right, so here's how you basically implement that:

sub test_these (&;$) {
my($code, $how_many) = @_;

&$code;
TESTES: if( $Skipped ) {
$how_many ||= 1;
print _ok_skipped($Skip_Reason) for 1..$how_many;
}
$Skipped = $Todo = 0;
$Skip_Reason = '';
}


sub skip {
$Skipped = 1;
$Skip_Reason = shift;
goto TESTES;
}


sub todo {
my($reason) = shift;
$Todo = 1;
}


Okay, so what you're probably wondering is why skip() uses a goto
instead of just a simply dying and test_these() trapping it.  The nice
part about a goto is it doesn't interfere with any eval BLOCK or
__DIE__ handler that might be in use by the test or the code being
tested.

And if we wire up ok() to be sensitive to $Todo it will all work.

skip() and todo() need to do a little checking back through the call
stack to ensure they were called inside a test_these() block and puke
otherwise.


I'll see if I can have a new version of Test::More out tommorrow.


-- 

Michael G. Schwern   <[EMAIL PROTECTED]>http://www.pobox.com/~schwern/
Perl6 Quality Assurance <[EMAIL PROTECTED]>   Kwalitee Is Job One
Death follows me like a wee followey thing.
-- Quakeman



Re: skip test interface

2001-07-19 Thread Michael G Schwern

On Fri, Jul 20, 2001 at 12:51:19AM -0400, barries wrote:
>1) skip dies, Test::Named::end() "catches" it, emits message
>2) todo_because sets a flag, is(), etc. check flag, T::N::begin clears it

Thank you, you just gave me a wonderful idea:

use Test::More tests => 2;

test_block {
skip "Pigs can't fly" unless $pig->can('fly');

$pig->takeoff;
ok( $pig->altitude > 0 );
ok( $pig->airspeed > 0 );
} 2;

skip() will throw an exception.  test_block() will catch that
exception and print something like:

ok 1 # skipped Pigs can't fly
ok 2 # skipped Pigs can't fly

(since we told test_block how many tests are inside it).  If no_plan
is set, you don't need to tell test_block how many tests are in there.

todo works much the same:

use Test::More tests => 2;

test_block {
todo "Give pigs wings" unless $pig->can('fly');
  
$pig->takeoff;
ok( $pig->altitude > 0 );
ok( $pig->airspeed > 0 );
};

todo() sets a flag, ok() notes that flag and acts normally but puts in
the extra # TODO and reason.

not ok 1 # TODO Give pigs wings
not ok 2 # TODO Give pigs wings

Its not necessary to tell the test_block() how many tests there are
since it always runs them.  test_block() resets the todo flag at the
end of the block.

I think this will work.  I just need a better name than test_block().


-- 

Michael G. Schwern   <[EMAIL PROTECTED]>http://www.pobox.com/~schwern/
Perl6 Quality Assurance <[EMAIL PROTECTED]>   Kwalitee Is Job One
There is nothing here
But a lip of hardened paste
>From our night of joy.
-- ignatz