Jump to page: 1 2
Thread overview
[Issue 4644] New: assertExceptionThrown to assert that a particular exception was thrown
Aug 15, 2010
Jonathan M Davis
Aug 15, 2010
Jonathan M Davis
Aug 15, 2010
Andrej Mitrovic
Aug 15, 2010
Andrej Mitrovic
Aug 15, 2010
Andrej Mitrovic
Aug 15, 2010
Andrej Mitrovic
Aug 16, 2010
Jonathan M Davis
Aug 16, 2010
Andrej Mitrovic
Aug 16, 2010
Andrej Mitrovic
Aug 16, 2010
Andrej Mitrovic
Aug 17, 2010
Andrej Mitrovic
Aug 17, 2010
Jonathan M Davis
Aug 17, 2010
Jonathan M Davis
Aug 17, 2010
Andrej Mitrovic
Aug 17, 2010
Jonathan M Davis
Aug 17, 2010
Jonathan M Davis
Aug 17, 2010
Andrej Mitrovic
Mar 22, 2011
Jonathan M Davis
August 15, 2010
http://d.puremagic.com/issues/show_bug.cgi?id=4644

           Summary: assertExceptionThrown to assert that a particular
                    exception was thrown
           Product: D
           Version: D2
          Platform: Other
        OS/Version: Linux
            Status: NEW
          Severity: enhancement
          Priority: P2
         Component: Phobos
        AssignedTo: nobody@puremagic.com
        ReportedBy: jmdavisProg@gmail.com


--- Comment #0 from Jonathan M Davis <jmdavisProg@gmail.com> 2010-08-14 21:11:36 PDT ---
I would love a function in std.exception which could be used with unit tests to verify that a particular exception was thrown rather than verify the return value of a function. So, instead of writing

try
{
    func(/*...*/);
    assert(0, "Exception not thrown");
}
catch(Exception e)
{
}


you'd write something like

assertExceptionThrown(func(/*...*/), Exception);


where you give it the function call and the exception type to catch, which would then translate to the code above. Now, I'm not sure that you can get quite that syntax, since you'd be passing a function call with arguments and all rather a function pointer or delegate with arguments, but there might be a way to do it more like

assertExceptionThrown(Exception, &func, /*...*/);


and give it a function pointer or delegate with arguments. I'm not enough of a template wiz to know the best way to build assertExceptionThrown() (I'm not even sure that I know enough yet to build it all), so I don't know the best way to do it, but it seems like it should be possible one way or another. And it would certainly be useful.

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
August 15, 2010
http://d.puremagic.com/issues/show_bug.cgi?id=4644



--- Comment #1 from Jonathan M Davis <jmdavisProg@gmail.com> 2010-08-15 01:49:07 PDT ---
A useful counterpart might be assertExceptionNotThrown, though I think that an exception escaping the unittest block equates to test failure, so I'm not sure that it would be necessary. It would make tests clearer, however, since instead of having seemingly pointless statements, you would be clearly indicating that you were making sure that that statement didn't result in a thrown exception.

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
August 15, 2010
http://d.puremagic.com/issues/show_bug.cgi?id=4644


Andrej Mitrovic <andrej.mitrovich@gmail.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |andrej.mitrovich@gmail.com


--- Comment #2 from Andrej Mitrovic <andrej.mitrovich@gmail.com> 2010-08-15 14:00:01 PDT ---
Here, I made something similar to your request:

import std.stream, std.stdio;

auto assertExceptionThrown(alias func, alias exc, T...)(T args)
{
    auto funcName = __traits(identifier, func);

    try
    {
        func(args);
    }
    catch (exc e)
    {
    }
    catch (Exception e)
    {
        writefln("Error: Call to %s did not throw %s", funcName, typeid(exc));
        writeln(e);
    }
}

unittest
{
    assertExceptionThrown!(willThrow, core.exception.AssertError)(4);
}

void willThrow(int i)
{
    //~ assert(0);
    writefln("willThrow using its passed argument %s..", i);
    throw new SeekException("Oops a file exploded!");
}

void main()
{
}

If you uncomment the assert(0) statement in the willThrow function, the unittest will run fine, since it catches an AssertError exception, the one you passed with the call to assertExceptionThrown.

It's just a prototype, but try it out and let me know if it works (I'm an exception newbie myself tbh..)

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
August 15, 2010
http://d.puremagic.com/issues/show_bug.cgi?id=4644



--- Comment #3 from Andrej Mitrovic <andrej.mitrovich@gmail.com> 2010-08-15 14:14:28 PDT ---
Sorry, that still won't work. I haven't made sure that the function *must* throw an exception. I'll work on it some more, stand by.. :)

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
August 15, 2010
http://d.puremagic.com/issues/show_bug.cgi?id=4644



--- Comment #4 from Andrej Mitrovic <andrej.mitrovich@gmail.com> 2010-08-15 14:34:21 PDT ---
There we go, this should now work:


import std.stream, std.stdio;

void assertExceptionThrown(alias func, alias exc, T...)(T args)
{
    auto funcName = __traits(identifier, func);

    try
    {
        func(args);
    }
    catch (exc e)
    {
        return;     // Expected exception was thrown
    }
    catch (Exception e)
    {
        writeln(e);
    }

    writefln("Error: Call to %s did not throw %s", funcName, typeid(exc));
    assert(0);
}

unittest
{
    assertExceptionThrown!(willThrow, core.exception.AssertError)(4);
}

void willThrow(int i)
{
    //~ assert(0);
    //~ throw new SeekException("Oops a file exploded!");
}

void main()
{
}


So, if you leave it like this it will complain about the expected exception not being thrown.

If you uncomment the first line, it will run silently and return.

I did add another gerenal Exception handler in the templated function, so when a completely different exception is thrown (in this case just uncomment the second line here) you will still be notified that the *expected* exception did not throw even though another one did.

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
August 15, 2010
http://d.puremagic.com/issues/show_bug.cgi?id=4644



--- Comment #5 from Andrej Mitrovic <andrej.mitrovich@gmail.com> 2010-08-15 14:36:13 PDT ---
I meant "general", not "gerenal". And when I talk about "first line" and "second line", I mean the ones in the willThrow function.

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
August 16, 2010
http://d.puremagic.com/issues/show_bug.cgi?id=4644



--- Comment #6 from Jonathan M Davis <jmdavisProg@gmail.com> 2010-08-16 01:03:07 PDT ---
Thanks for the help. One thing that I'm finding you need with these types of functions though is the file and line number. Otherwise, tracking down the problem can be quite hard. I suppose if the stack trace on linux actually printed out the functions rather than just their addresses, then it wouldn't be as big a problem, but as it is, the stack trace is pretty useless. Normally, I'd tack the file and line number on the end with the default arguments of __FILE__ and __LINE__, but with the variadic template, I don't think that you can do that. So, you'd have to be explicit here. In any case, here are my current versions. They're a bit more concise than yours and work better with unit tests (given that you shouldn't normally print from unit tests). They're a bit uglier to use due to the necessity to pass __FILE__ and __LINE__ yourself, but I think that they work quite well for unit tests at the moment. I see no need to catch the wrong exception, since it'll just wiz on by and get caught by the main exception handler like any other exception. The key here is to verify whether the correct one was thrown. For assertExceptionNotThrown, I would have printed out the Exception as part of the AssertError that get's thrown, but you end up with 2 stack traces, and it's ugly. Hopefully, the programmer would have a fair idea of what Exception would be thrown, and they can go and run the function without the wrapper if they need to. The function still serves nicely to indicate test failure however.

void assertExceptionThrown(string file, size_t line, alias E, alias func,
T...)(T args)
    if(__traits(compiles, {try{}catch(E e){}}))
{
    try
        func(args);
    catch(E e)
        return;     // Expected exception was thrown

    throw new AssertError(format("assertExceptionThrown() failed: No %s was
thrown from %s()", E.stringof, __traits(identifier, func)), file, line);
}

void assertExceptionThrown(string msg, string file, size_t line, alias E, alias
func, T...)(T args)
    if(__traits(compiles, {try{}catch(E e){}}))
{
    try
        func(args);
    catch(E e)
        return;     // Expected exception was thrown

    throw new AssertError(format("assertExceptionThrown() failed: No %s was
thrown from %s(): %s", E.stringof, __traits(identifier, func), msg), file,
line);
}

void assertExceptionNotThrown(string file, size_t line, alias E, alias func,
T...)(T args)
    if(__traits(compiles, {try{}catch(E e){}}))
{
    try
        func(args);
    catch(E e)
        throw new AssertError(format("assertExceptionNotThrown() failed: %s was
thrown from %s()", E.stringof, __traits(identifier, func)), file, line);
}

void assertExceptionNotThrown(string msg, string file, size_t line, alias E,
alias func, T...)(T args)
    if(__traits(compiles, {try{}catch(E e){}}))
{
    try
        func(args);
    catch(E e)
        throw new AssertError(format("assertExceptionNotThrown() failed: %s was
thrown from %s(): %s", E.stringof, __traits(identifier, func), msg), file,
line);
}


I really think that we should have more of these types of functions and have them added to std.exception (or wherever appropriate), so that we can have better unit testing facilities. assert does the job, but it's not a very precise instrument. And in this case, it's too verbose due to the necessary try-catch block.

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
August 16, 2010
http://d.puremagic.com/issues/show_bug.cgi?id=4644



--- Comment #7 from Andrej Mitrovic <andrej.mitrovich@gmail.com> 2010-08-16 06:55:46 PDT ---
Nice work!

Btw, don't templates already have __FILE__ and __LINE__ ?

See here:

http://www.digitalmars.com/d/2.0/template.html

Under "Template Value Parameters" it states "The __FILE__ and __LINE__ expand to the source file name and line number at the point of instantiation."

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
August 16, 2010
http://d.puremagic.com/issues/show_bug.cgi?id=4644



--- Comment #8 from Andrej Mitrovic <andrej.mitrovich@gmail.com> 2010-08-16 11:23:52 PDT ---
Ah I see the problem now. __FILE__ and __LINE__ can be used to initialize a parameter as a default value, but the only way they can be optional is if they're the last parameters of the template function. I'll see if there can be a way to work around this..

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
August 16, 2010
http://d.puremagic.com/issues/show_bug.cgi?id=4644



--- Comment #9 from Andrej Mitrovic <andrej.mitrovich@gmail.com> 2010-08-16 13:04:56 PDT ---
Good news, I've found a way to pass __LINE__ and __FILE__. It's a tad bit ugly and uses a struct, I haven't been able to call a struct's custom constructor without calling it with an extra parameter. I'm trying to call my struct constructor without passing any arguments, but it doesn't seem to work. So I'm using a workaround. Anyway, check it out:

void assertExceptionThrown(alias E, alias func, T...)(lineInfo info, T args)
    if(__traits(compiles, {try{}catch(E e){}}))
{
    try
        func(args);
    catch(E e)
        return;     // Expected exception was thrown

    throw new AssertError(format("assertExceptionThrown() failed: No %s was
thrown from %s()", E.stringof, __traits(identifier, func)), info._file,
info._line);
}


import std.stdio, std.stream, core.exception, std.string, std.typecons;

int myfunc(int i)
{
    return i;
}

struct lineInfo
{
    string _file;
    uint _line;

    this(int x, string file = __FILE__, uint line = __LINE__)
    {
        _file = file;
        _line = line;
    }
}


void main()
{
    assertExceptionThrown!(core.exception.AssertError, myfunc)(lineInfo(0) ,
5);
}

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
« First   ‹ Prev
1 2