Re: Brittle exception tests Marc Nieper-Wißkirchen 16 Jun 2020 20:18 UTC

Am Di., 16. Juni 2020 um 21:19 Uhr schrieb Wolfgang Corcoran-Mathe
<xxxxxx@sigwinch.xyz>:
>
> On 2020-06-16 09:37 +0200, Marc Nieper-Wißkirchen wrote:
> > in your test suite, you use your macro `catch-exceptions` in a few places.
> > However, it catches every exception so it makes those tests a bit
> > meaningless ...
>
> Agreed, it's very brittle.  I'm not sure how to do better, though.
>
> I've reviewed the tests of some other SRFIs for hints.  Those which
> use SRFI 64 (e.g. SRFI 146) generally use `test-error' without an
> `error-type' argument (the meaning of which is implementation-defined,
> anyway), effectively giving a success result if any exception is
> raised.  Others (like 135) use `guard' with a single #t clause,
> which is another catch-all.

With respect to SRFI 146, I have to blame myself. But I couldn't do
much because of the error type argument being
implementation-dependent. Maybe a post-finalization note can be added
to SRFI 64 saying that in R7RS systems the error type argument can be
a predicate.

> Perhaps `error-object?' could be used meaningfully here:
>
>     (define-syntax catch-errors
>       (syntax-rules ()
>         ((_ expr)
>          (guard (exn ((error-object? exn) 'error)) expr))))
>
> A few tests in Chibi suggest that this doesn't narrow the net much.

> I'd appreciate any suggestions for a better way to do this.

I'm not at all sure whether I have a good answer. But I think we have
to look at the tests individually.

In the error test for `maybe-join', there is not much to test because
"it is an error" does not have to be signaled. Your test only tests
your specific implementation, not any portable implementation. In my
opinion, portable test suites are most helpful (because they encourage
alternative implementations), so I would drop that particular test.
And I would use `assume' of SRFI 145 instead of `error' in the sample
implementation because `assume' is exactly for these "it is an error"
cases.

The same comment holds for all other occurrences of `catch-errors'
except for the test for `maybe-if', where the specification says that
an error is signaled. Here, you cannot use `error-object?' either in a
portable test because a portable implementation may not use `error' to
signal the error but use `raise' on some implementation-defined
object. Thus, you cannot guard against such an error portably (except
with a catch-all guard, which is brittle for tests and which should be
a no-go for programs).

But you cannot cure this in your implementation or in your tests. The
problem lies in the specification. As I have already written to John,
an error that has to be signaled but which I cannot test against, is
not only no more helpful than the condition "it is an error" (as you
can see by not being able to write a proper portable test), but even
worse than "it is an error" because it disallows the kinds of
optimizations underlying the idea of SRFI 145.

I have asked John (whom I included in CC) to remove the requirement
that the error has to be signaled. While I think this makes the most
sense and is most helpful to implementers and programmers, he has
disagreed. I haven't yet understood his argument. But in any case, if
an error MUST be signaled, SRFI 189 really needs to export a
predicate, say, `maybe-if-error?` that can be used to test again. The
reason why a sensible test cannot be written at the moment is exactly
that such a predicate is missing. As soon as you have `maybe-if-error`
you can write such a test and all issues with the tests have been
resolved.

But, if I could decide, I would change the meaning of

(maybe-if maybe-expr just-expr nothing-expr)

to

(let ((%maybe-expr maybe-expr))
  (cond
    ((just? %maybe-expr) just-expr)
    ((nothing? %maybe-expr) nothing-expr)
    (else (assume #f "maybe-if: maybe expected" %maybe-expr))))

I think this captures the underlying intent completely and it reduces
the question of how and whether the error is reported to a more
primitive form, namely `assume` from SRFI 145, which is always good.
The sample implementation of SRFI 145 even has a feature identifier
for this.

Marc