Description
Problem
Current implementation of assert
in stdlib_experimental_error always prints the same message on failure:
subroutine assert(condition, code)
logical, intent(in) :: condition
integer, intent(in), optional :: code
if (.not. condition) call error_stop("Assert failed.", code)
end subroutine
As is, if asserts fail, they tell you that they failed, but nothing more.
It's quite useful to be able to pass a custom message to assert. For example:
call assert(x < 3, "Error: x must be < 3")
would tell you why the assertion failed. This approach is especially useful during development, but could also be useful in production, for example to validate user inputs.
This is equivalent to Python's:
assert x < 3, "Error: x must be < 3"
Solution
This implementation would allow the user to optionally pass a custom message:
use stdlib_experimental_optval, only: optval
subroutine assert(condition, msg, code)
logical, intent(in) :: condition
character(*), intent(in), optional :: msg
integer, intent(in), optional :: code
if (.not. condition) call error_stop(optval(msg, "Assert failed."), code)
end subroutine
The solution is straightforward and backward compatible, assuming any existing code passed the optional code
as a keyword argument.
I'd like to get support here before writing up a spec and a PR (both will be quite straightforward).
@certik @nncarlson @jvdp1 @ivan-pi @rweed What do you think? Anything I missed?
This issue is related to #72.
Activity
jvdp1 commentedon Jan 20, 2020
I think it is a good idea. I was developping a bit and got struggles to find which
assert
retunrs.false.
.Maybe
msg
should be trimmed inoptval
. How is it assumed that the user would take care of it?Currently, there is only
test_always_skip.f90
that is usingcode
throughcall assert(.false., 77)
.milancurcic commentedon Jan 20, 2020
Hmm, why would you want to trim it?
msg
is acharacter(*)
, so it will be always be of length of the input string. If the user (for whatever strange reason) passes amsg
padded with spaces, I don't see why stdlib should trim that.Good to know, thanks for scoping it out. If we implement this, we should fix this test to state
code=77
as keyword argument. This may be a good item for #3: pass optional arguments as keyword arguments to prevent breakage when the API changes.ivan-pi commentedon Jan 20, 2020
I looked through the functions and see no reason for trimming either, both
optval
anderror_stop
acceptcharacter(*), intent(in)
dummy variables.We should at least exempt
optval
from such a rule. But otherwise I agree it is a good practice to follow.nncarlson commentedon Jan 20, 2020
I'm unclear on the intended scope of this assertion code. Perhaps someone could orient me. Is this something meant for the testing framework only as @certik may have suggested or is meant for general use? Is there any relationship to a standards proposal for an
ASSERT
statement? Sorry, I haven't been following this thread as closely as I would have liked. I do use assertions in my own code, and have formed some very definite ideas about how they should work.jvdp1 commentedon Jan 20, 2020
I was thinking to the scenario you described. But I agree with your point of view.
Indeed, a good practice to follow (except for
optval
IMO).milancurcic commentedon Jan 21, 2020
@nncarlson I don't think there's a defined scope for stdlib's assert yet, or at least we haven't discussed it yet. I think we should. I also don't think we discussed any further the testing framework mentioned in #72.
Here's my wishlist for
assert
:.false.
;warn
argument;And that's it. I used asserts similar to this in both datetime-fortran and functional-fortran. I'd use them in other projects.
Other optional capabilities that I don't desire but wouldn't oppose are:
__FILE__
and__LINE__
cpp macros);Great! Can you share them?
nncarlson commentedon Jan 21, 2020
I think what is being imagined in the discussion here is more what I think of as a general "error handling" capability that is similar in spirit to Fortran namelists, which provide an input parsing capability that is good enough for a great many use cases. In the same vein, the error handling functions being discussed here provide value and are mostly good enough. I do believe there is a place in stdlib for such a thing.
I would not call these assertions, though. What I'd like to see as part of standard Fortran is an assertion capability with a much more limited scope similar to what is in C. Assertions are for catching programmer errors -- things like mismatched dummy array sizes, pre-conditions, post-conditions, etc. -- and not for catching errors that are ultimately due to program input. Those require proper error handling like what might be provided by the above. Assertions should be:
ASSERT(scalar-logical-expression)
; no need to use a module.ASSERT
would be a statement and not a procedure call.In the meantime, one can come close to this using the preprocessor and an
ASSERT
macro, which is what I currently do. This does require putting a#include
statement at the top of files to include a header file that defines the macro (this is a nuisance). Something likef90_assert
would necessarily be an external subroutine.I wouldn't object to this form of an assertion taking optional arguments like an error message, but I think that would require an explicit interface (unless
ASSERT
were part of the language), and seems like scope creep toward a more general error handling system.Maybe we could split things into a low-level assertion similar to what I describe and a separate higher-level error handling capability?
certik commentedon Jan 21, 2020
@nncarlson I think what you are proposing is a solution that works today, until j3-fortran/fortran_proposals#70 gets implemented in the language itself.
milancurcic commentedon Jan 22, 2020
I don't object to a minimal C-like assert like this. Should the current implementation of
assert
then be expanded to print file name and line?Yes, I'd personally find such subroutine more useful than a C-like assert. Basically what I'm looking for is the current implementation of
assert
instdlib_experimental_error
+ emitting a custom message + optionally warn-only. How should this be called?test_condition
?@certik where are you at on this?
jvdp1 commentedon Jan 22, 2020
That would be something nice to have at least file name and line in the output of
assert
. When implementing #119 , I added severealassert
in once in the test file, and tool me some times to find which one returns.false.
.Is it (in some way) related to #95 ? Or could both idea be merged?
milancurcic commentedon Jan 22, 2020
I think the ideas are related and could have similar use cases. However I am personally not in favor of #95 (for me it's overkill). What I described in the previous message is exactly what I wish for.
This problem could be easily fixed with this proposal :). It's also how I write tests, but I'm not married to the idea of it being called
assert
.nncarlson commentedon Jan 22, 2020
Exactly. In fact #72 started as just that, but quickly got diverted into a discussion about more elaborate error handling facilities. Again, I'm in no way opposed to such things, but they solve a different problem. Traditional assertions (and I'd rather the more elaborate error handling not co-opt that terminology) are strictly about catching unexpected programmer errors and should never be tripped (implemented by the compiler they can also declare conditions used by the optimizer). Other "expected" errors need a different solution. Perhaps one way to help distinguish the two types is to ask whether it is appropriate for the error check to be omitted in a non-debug build.
milancurcic commentedon Jan 22, 2020
Yes, I think this is the key distinction. I'd like to be able to test conditions with meaningful messages in both debug and release builds.
Okay, it seems from this thread that there is now a more-or-less clearly defined scope for
assert
based on input from @nncarlson and @certik. This proposal shouldn't touchassert
then.I will close this issue tomorrow and open a new proposal for
test_condition
to elicit feedback.certik commentedon Jan 22, 2020
Yes, indeed, I currently use the
assert
function for both tests and inside code. Rather, it should be split, and there should be a version used for tests, which will be executed in Release mode also. And then there should be a version to be used inside code in Debug mode only (no overhead in Release mode), implemented using a macro.urbanjost commentedon Jan 23, 2020
I have my own preprocessor, which has a debug mode than only includes $ASSERT lines and blocks of lines starting with "DEBUGVERSION: block" to "endblock DEBUGVERSION" only if debug mode is enabled and activated (that is, they are either both in the code or excluded from the code). Code that tests values is just part of the normal code (no way to turn if off or exclude it). So I use something similar to what you are describing a lot,. The discussion got be looking because the (very simplistic) macro $ASSERT allows up to nine values as a message. I found that over eighty percent of the $ASSERT lines
added a message (file name and line number gets added automatically) so I would give a thumbs up on a standard ASSERT directive, but vote for the optional message being available. But that seems like it should be a request for enhancement in the standard more than somthing in stdlib? As a footnote, the nine message values are class(*), which makes it really easy to build a useful message
(like the tostring example on the Fortran WIki).
I find that method extremely useful for building relatively arbitrary messages, albeit it requires unlimited polymorphic variables, which would probably exclude a lot of compilers from using a similar procedure. But if an ASSERT directive were to become part of the Fortran standard I would really like it to have a message that was similar, and think it would be useful if the message actually showed the logical test (which is something I keep meaning to add to my own $ASSERT directive and never seen to get around to :>).
milancurcic commentedon Jan 23, 2020
Closing in favor of #121.