assert with formatting

classic Classic list List threaded Threaded
15 messages Options
Reply | Threaded
Open this post in threaded view
|

assert with formatting

Tobias Kieslich

Hi there,

working on unittests made me aware that having an assert function with  
formatting would be very helpful.

> a=4
> assert( a==1, '`a` should be `%d`, but was `%s`', 1, a )

Of course I could write a wrapper function which takes varargs  
compiles the string and run assert on that.
However, when hooking up to the debugger it will always report the  
assert in the helper function as a
location instead of the location in the unittest which is broken. It's  
obviously still available in the
callstack, but it would be nicer the other way.

Just a thought, not sure how hard it would be.

-tobbik


Reply | Threaded
Open this post in threaded view
|

Re: assert with formatting

Luiz Henrique de Figueiredo
> Of course I could write a wrapper function which takes varargs compiles the
> string and run assert on that.

See http://lua-users.org/lists/lua-l/2010-05/msg00494.html

> However, when hooking up to the debugger it will always report the assert in
> the helper function as a
> location instead of the location in the unittest which is broken. It's
> obviously still available in the
> callstack, but it would be nicer the other way.

For that, you'll have to redefine assert to call error with the
correct stack level.

Reply | Threaded
Open this post in threaded view
|

Re: assert with formatting

Tobias Kieslich

Quoting Luiz Henrique de Figueiredo <[hidden email]>:

>> Of course I could write a wrapper function which takes varargs compiles the
>> string and run assert on that.
>
> See http://lua-users.org/lists/lua-l/2010-05/msg00494.html
>
>> However, when hooking up to the debugger it will always report the assert in
>> the helper function as a
>> location instead of the location in the unittest which is broken. It's
>> obviously still available in the
>> callstack, but it would be nicer the other way.
>
> For that, you'll have to redefine assert to call error with the
> correct stack level.

Thank you, that's something I can work with. I looked at the source  
code and it
seems dirty to patch it into luaB_assert()

-tobbik



Reply | Threaded
Open this post in threaded view
|

Re: assert with formatting

Daurnimator
On 13 July 2018 at 10:11,  <[hidden email]> wrote:
> Quoting Luiz Henrique de Figueiredo <[hidden email]>:
>> For that, you'll have to redefine assert to call error with the
>> correct stack level.
>
>
> Thank you, that's something I can work with. I looked at the source code and
> it
> seems dirty to patch it into luaB_assert()


Writing your own assert function is quite easy:

function assert(cond, ...)
    if cond then
        return cond, ...
    else
        local err = select("#", ...) == 0 and "assertion failed!" or ...
        error(err, 2)
    end
end

The second parameter to 'error' tells it to ignore the 'assert'
function when generating a stack trace.

Reply | Threaded
Open this post in threaded view
|

Re: assert with formatting

Dirk Laurie-2
In reply to this post by Tobias Kieslich
2018-07-13 0:52 GMT+02:00  <[hidden email]>:

>
> Hi there,
>
> working on unittests made me aware that having an assert function with
> formatting would be very helpful.
>
>> a=4
>> assert( a==1, '`a` should be `%d`, but was `%s`', 1, a )
>
>
> Of course I could write a wrapper function which takes varargs compiles the
> string and run assert on that.

assert( a==1, ('`a` should be `%d`, but was `%s`'):format(1,a) )

Reply | Threaded
Open this post in threaded view
|

Re: assert with formatting

Javier Guerra Giraldez
On 13 July 2018 at 09:00, Dirk Laurie <[hidden email]> wrote:
> assert( a==1, ('`a` should be `%d`, but was `%s`'):format(1,a) )
>

slightly suboptimal, since the message is composed even if the assert succeeds.


--
Javier

Reply | Threaded
Open this post in threaded view
|

Re: assert with formatting

Dirk Laurie-2
In reply to this post by Daurnimator
2018-07-13 2:32 GMT+02:00 Daurnimator <[hidden email]>:

> On 13 July 2018 at 10:11,  <[hidden email]> wrote:
>> Quoting Luiz Henrique de Figueiredo <[hidden email]>:
>>> For that, you'll have to redefine assert to call error with the
>>> correct stack level.
>>
>>
>> Thank you, that's something I can work with. I looked at the source code and
>> it
>> seems dirty to patch it into luaB_assert()
>
>
> Writing your own assert function is quite easy:
>
> function assert(cond, ...)
>     if cond then
>         return cond, ...
>     else
>         local err = select("#", ...) == 0 and "assertion failed!" or ...
>         error(err, 2)
>     end
> end
>
> The second parameter to 'error' tells it to ignore the 'assert'
> function when generating a stack trace.

It does not ignore the 'assert' function, it just changes the source
code reference at the start of the message. The line number in
'assert' still appears in the traceback.

Reply | Threaded
Open this post in threaded view
|

Re: assert with formatting

Sean Conner
In reply to this post by Javier Guerra Giraldez
It was thus said that the Great Javier Guerra Giraldez once stated:
> On 13 July 2018 at 09:00, Dirk Laurie <[hidden email]> wrote:
> > assert( a==1, ('`a` should be `%d`, but was `%s`'):format(1,a) )
> >
>
> slightly suboptimal, since the message is composed even if the assert succeeds.

  Um, but wouldn't

  format_assert(a == 1 , "'a' should be '%d', but was '%s'",1,tostring(a))

*also* compose the message even if a equals 1?  Lua doesn't do lazy
evaluation.

  -spc


Reply | Threaded
Open this post in threaded view
|

Re: assert with formatting

Pierre Chapuis
>   Um, but wouldn't
>
>   format_assert(a == 1 , "'a' should be '%d', but was '%s'",1,tostring(a))
>
> *also* compose the message even if a equals 1?  Lua doesn't do lazy
> evaluation.

It would call `tostring(a)` in all cases, but it would not call `string.format` because that branch of the `if` would never be evaluated.

I am not entirely sure if there is a real performance gain, though.

Reply | Threaded
Open this post in threaded view
|

Re: assert with formatting

Dirk Laurie-2
2018-07-13 16:23 GMT+02:00 Pierre Chapuis <[hidden email]>:

>>   Um, but wouldn't
>>
>>   format_assert(a == 1 , "'a' should be '%d', but was '%s'",1,tostring(a))
>>
>> *also* compose the message even if a equals 1?  Lua doesn't do lazy
>> evaluation.
>
> It would call `tostring(a)` in all cases, but it would not call `string.format` because that branch of the `if` would never be evaluated.
>
> I am not entirely sure if there is a real performance gain, though.

'assert' is not about performance. It is about catching blunders at a
point where you hope you can track them.

Reply | Threaded
Open this post in threaded view
|

Re: assert with formatting

Javier Guerra Giraldez
On 13 July 2018 at 17:17, Dirk Laurie <[hidden email]> wrote:
> 'assert' is not about performance. It is about catching blunders at a
> point where you hope you can track them.


very true; but at the same time, it should be as light as possible in
the common case, so you can put it everywhere without remorse and
leave them in production code.



--
Javier

Reply | Threaded
Open this post in threaded view
|

Re: assert with formatting

Hisham
In reply to this post by Pierre Chapuis
On 13 July 2018 at 11:23, Pierre Chapuis <[hidden email]> wrote:

>>   Um, but wouldn't
>>
>>   format_assert(a == 1 , "'a' should be '%d', but was '%s'",1,tostring(a))
>>
>> *also* compose the message even if a equals 1?  Lua doesn't do lazy
>> evaluation.
>
> It would call `tostring(a)` in all cases, but it would not call `string.format` because that branch of the `if` would never be evaluated.
>
> I am not entirely sure if there is a real performance gain, though.

Not only the cost of running string.format, but also memory:

for i = 1, N do
   local a = do_something(i)
   assert(a == 1, ("`a` should be `%d`, but was `%s`"):format(1,a))
end

This produces N strings in memory to be garbage collected (adding time
to GC runs), while this doesn't:

for i = 1, n do
   local a = do_something(i)
   format_assert(a == 1, "`a` should be `%d`, but was `%s`", 1, a)
end

(Note the removed tostring(), or else we'd still get N strings; so if
necessary the implementation of format_assert should do that instead.)

Plus, whether the above is true or not is also
implementation-dependent: Lua 5.3 stores short strings up to 40 bytes
in TValues, so in that version the above incurs or not in GC costs
depends on the length of the error message.

-- Hisham

Reply | Threaded
Open this post in threaded view
|

Re: assert with formatting

Sean Conner
It was thus said that the Great Hisham once stated:

> On 13 July 2018 at 11:23, Pierre Chapuis <[hidden email]> wrote:
> >>   Um, but wouldn't
> >>
> >>   format_assert(a == 1 , "'a' should be '%d', but was '%s'",1,tostring(a))
> >>
> >> *also* compose the message even if a equals 1?  Lua doesn't do lazy
> >> evaluation.
> >
> > It would call `tostring(a)` in all cases, but it would not call `string.format` because that branch of the `if` would never be evaluated.
> >
> > I am not entirely sure if there is a real performance gain, though.
>
> Not only the cost of running string.format, but also memory:
>
> for i = 1, N do
>    local a = do_something(i)
>    assert(a == 1, ("`a` should be `%d`, but was `%s`"):format(1,a))
> end
>
> This produces N strings in memory to be garbage collected (adding time
> to GC runs), while this doesn't:
>
> for i = 1, n do
>    local a = do_something(i)
>    format_assert(a == 1, "`a` should be `%d`, but was `%s`", 1, a)
> end
>
> (Note the removed tostring(), or else we'd still get N strings; so if
> necessary the implementation of format_assert should do that instead.)

  And then a isn't a string or a number and you get another error while
trying to log an error; calling tostring() is safer.

  But if you are really concerned about GC and performance, you'll need to
force lazy evaluation on this:

        format_assert(a == 1 , function()
          return ("'a' should be %d, but was '%s'"):format(1,tostring(a)))
        end)

This is assuming that format_assert() can check if the second parameter is a
function and call it.  Doing it this way, the string isn't constructed until
it's needed (that's what lazy evaluation is).  

  -spc

Reply | Threaded
Open this post in threaded view
|

Re: assert with formatting

Hisham
On 13 July 2018 at 17:17, Sean Conner <[hidden email]> wrote:

> It was thus said that the Great Hisham once stated:
>> On 13 July 2018 at 11:23, Pierre Chapuis <[hidden email]> wrote:
>> >>   Um, but wouldn't
>> >>
>> >>   format_assert(a == 1 , "'a' should be '%d', but was '%s'",1,tostring(a))
>> >>
>> >> *also* compose the message even if a equals 1?  Lua doesn't do lazy
>> >> evaluation.
>> >
>> > It would call `tostring(a)` in all cases, but it would not call `string.format` because that branch of the `if` would never be evaluated.
>> >
>> > I am not entirely sure if there is a real performance gain, though.
>>
>> Not only the cost of running string.format, but also memory:
>>
>> for i = 1, N do
>>    local a = do_something(i)
>>    assert(a == 1, ("`a` should be `%d`, but was `%s`"):format(1,a))
>> end
>>
>> This produces N strings in memory to be garbage collected (adding time
>> to GC runs), while this doesn't:
>>
>> for i = 1, n do
>>    local a = do_something(i)
>>    format_assert(a == 1, "`a` should be `%d`, but was `%s`", 1, a)
>> end
>>
>> (Note the removed tostring(), or else we'd still get N strings; so if
>> necessary the implementation of format_assert should do that instead.)
>
>   And then a isn't a string or a number and you get another error while
> trying to log an error; calling tostring() is safer.

I did explicitly say that tostring() should be called inside the implementation
of format_assert. Does it complicate the implementation of format_assert?
For sure. But the implementation of format_assert does not need to be simple
or fast. The overhead of non-failing asserts must be small.

>   But if you are really concerned about GC and performance, you'll need to
> force lazy evaluation on this:
>
>         format_assert(a == 1 , function()
>           return ("'a' should be %d, but was '%s'"):format(1,tostring(a)))
>         end)
>
> This is assuming that format_assert() can check if the second parameter is a
> function and call it.  Doing it this way, the string isn't constructed until
> it's needed (that's what lazy evaluation is).

And you'll be creating a closure each time this line runs, which is
even worse. (And yes, I know about closure caching in Lua 5.2+ — it
won't save you every time.)

Also, I happen to know what lazy evaluation is. There's plenty of
Haskell in my Github repo.

-- Hisham

Reply | Threaded
Open this post in threaded view
|

Re: assert with formatting

Dirk Laurie-2
2018-07-13 23:06 GMT+02:00 Hisham <[hidden email]>:
> On 13 July 2018 at 17:17, Sean Conner <[hidden email]> wrote:

> Also, I happen to know what lazy evaluation is. There's plenty of
> Haskell in my Github repo.

:-( </attitude>