Skip to content
This repository has been archived by the owner on Feb 8, 2020. It is now read-only.

Add T.TODO and T.Skip directives #6

Merged
merged 2 commits into from
Jan 13, 2017
Merged

Add T.TODO and T.Skip directives #6

merged 2 commits into from
Jan 13, 2017

Conversation

wking
Copy link
Collaborator

@wking wking commented Jan 11, 2017

The spec wants these without hyphens, so we can't generate them without a new API. You could have T.Directive(test bool, directive, description string), but it seems more convenient to have a separate method for each case.

@wking
Copy link
Collaborator Author

wking commented Jan 11, 2017

We could also drop the leading hyphen from the T.Ok output, as long as the description didn't start with a digit or hash. I still like the separate-methods approach I have in f5c62d7 best, but wanted to put the alternative on the table.

@mndrix
Copy link
Owner

mndrix commented Jan 12, 2017

it seems more convenient to have a separate method for each case

I agree. I like the separate methods.

When I skip tests, I usually skip more than one. So I'd like a signature like

func (t *T) Skip(count int, description string)

An example of my most common use case:

if ConditionIsMet() {
  t.Ok(Foo(), "foo")
  t.Ok(Bar(), "bar")
} else {
  t.Skip(2, "condition not met")
}

For TODO tests, it'd be nice if users could apply the TODO status to any test function. Right now TODO only makes sense for T.Ok but I'd eventually like to add methods for comparing numbers, strings, etc. I'd rather not make TODO variants of each of those.

The first thing that comes to mind is something like this:

t.TODO = true
t.Ok(Foo(), "foo")
t.EqOk(Bar(), Baz(), "bar == baz")
t.TODO = false  // could be done via defer too

or maybe do the same thing internally but hide it behind a callback:

t.TODO( func () {
   t.Ok(Foo(), "foo")
   t.EqOk(Bar(), Baz(), "bar == baz")
})

Having a plain field T.TODO bool would let users execute arbitrary logic to determine whether a group of tests should be marked TODO or not. Maybe the tests don't pass on Linux yet, but pass on other platforms, etc.

Preferences?

The spec [1] suggests "ok 23 # SKIP ...", but if you attempt this with:

  t.Pass("# SKIP foo")

you get an extra hyphen ("ok 23 - # SKIP foo") which is parsed as a
successful test (and not a skipped test).

Michael gives the following example to motivate 'count' [2]:

  if ConditionIsMet() {
    t.Ok(Foo(), "foo")
    t.Ok(Bar(), "bar")
  } else {
    t.Skip(2, "condition not met")
  }

The spec example of this is in [3], showing no special syntax for
skipping multiple tests (although there is a special syntax for
skipping *all* the tests).

Also alphabetize TESTS in the Makefile.

[1]: http://testanything.org/tap-version-13-specification.html#skipping-tests
[2]: mndrix#6 (comment)
[3]: http://testanything.org/tap-version-13-specification.html#skipping-a-few
wking added a commit to wking/tap.go that referenced this pull request Jan 12, 2017
The spec [1] suggests "not ok 13 # TODO ...", but if you attempt this
with:

  t.Fail("# TODO foo")

you get an extra hyphen ("not ok 13 - # TODO foo") which is parsed as
a failed test (and not a TODO test).

I'd initially written up an alternative to Ok:

  T.TODO(test bool, description string)

but Michael pointed out that future work may add additional test
methods (e.g. EqOk) and wanted to support those as well with something
like [2]:

  t.TODO = true
  t.Ok(Foo(), "foo")
  t.EqOk(Bar(), Baz(), "bar == baz")
  t.TODO = false  // could be done via defer too

or [2]:

  t.TODO( func () {
    t.Ok(Foo(), "foo")
    t.EqOk(Bar(), Baz(), "bar == baz")
  })

The child-state approach taken in this commit is closer to the latter,
but allows for method chaining in the single-test case:

  t.TODO().Ok(Foo(), "foo")

which I find easier to read.

The root state is the only one which holds nextTestNumber.  It might
make more sense if it was *also* the only the state that held Writer
(so internally t.root().printf(...) instead of t.printf(...)).
However, Writer is public, and we can't keep folks from fiddling with
it.  Respecting local overrides makes as much sense as anything,
although I doubt folks will want to override Writer in their TODO
child state.

[1]: http://testanything.org/tap-version-13-specification.html#todo-tests
[2]: mndrix#6 (comment)
@wking
Copy link
Collaborator Author

wking commented Jan 12, 2017 via email

@mndrix
Copy link
Owner

mndrix commented Jan 12, 2017

I've gone with a child-state approach where:

func (t *T) TODO() *T

gives a child T that has a private .todo flag set

I like this API. It works well for both one-off and multiple TODO tests, as you've outlined.

I often "comment out" a block of tests. In that case, I like the diff to show just the new "comment" lines. Like this:

+t = t.TODO()
 t.Ok(Foo(), "foo")
 t.EqOk(Bar(), Baz(), "bar == baz")
+t = t.Done()
 t.Ok(…)  // continue on with your non-TODO t

That leaves "git blame" output pointing at the commits which originally created Foo and Bar tests. It also makes it clear on code review that I haven't changed the tests, just their expected pass/fail status. So I think we should have Done() or EndTODO() or something.

As far as implementation goes, the parent-child semantics complicate it more than I'd like. I think we can retain the nice API you've created but use a copy semantics instead. Namely: "TODO() returns a derivative T in todo mode, sharing all other state". If T.nextTestNumber were *int instead of int, then the new methods become very short.

func (t *T) TODO() *T {
    newT := *t
    newT.todo = true
    return &newT
}

func (t *T) Done() *T {
    newT := *t
    newT.todo = false
    return &newT
}

expect folks to not mess with Writer unless they've thought it over ;)

I'm OK with this :-). If we get to the point where a T interface becomes obviously necessary, I think we can revisit it at that point.

The spec [1] suggests "not ok 13 # TODO ...", but if you attempt this
with:

  t.Fail("# TODO foo")

you get an extra hyphen ("not ok 13 - # TODO foo") which is parsed as
a failed test (and not a TODO test).

I'd initially written up an alternative to Ok:

  T.TODO(test bool, description string)

but Michael pointed out that future work may add additional test
methods (e.g. EqOk) and wanted to support those as well with something
like [2]:

  t.TODO = true
  t.Ok(Foo(), "foo")
  t.EqOk(Bar(), Baz(), "bar == baz")
  t.TODO = false  // could be done via defer too

or [2]:

  t.TODO( func () {
    t.Ok(Foo(), "foo")
    t.EqOk(Bar(), Baz(), "bar == baz")
  })

This commit adds a .TODO boolean following Michael's first proposal.

It also adds a Todo() method returning a copy of the test-state (but
setting TODO), which you can use to avoid altering the original
test-state's TODO value.  This can be useful when you don't want to
bother with a defer, or your TODO tests all live in an existing branch
of a function, or you want to chain methods for a one-off TODO test:

  t.TODO().Ok(Foo(), "foo")

To keep the count synchronized between sibling test states,
nextTestNumber is now a pointer.  We don't do anything to keep Writer
synchronized, because it's already part of the public API as a
non-pointer, and Michael is ok leaving this up to the caller [3].

[1]: http://testanything.org/tap-version-13-specification.html#todo-tests
[2]: mndrix#6 (comment)
[3]: mndrix#6 (comment)
@wking
Copy link
Collaborator Author

wking commented Jan 13, 2017 via email

@mndrix mndrix merged commit 967e479 into mndrix:master Jan 13, 2017
@mndrix
Copy link
Owner

mndrix commented Jan 13, 2017

Nicely done. Thanks.

@wking wking deleted the directives branch January 14, 2017 14:46
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants