Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH] lua: show locals when a tap test fails
@ 2018-08-18 12:36 Alexander Turenko
  2018-08-19 15:35 ` [tarantool-patches] " Alexander Turenko
  2018-08-24 12:25 ` Vladislav Shpilevoy
  0 siblings, 2 replies; 9+ messages in thread
From: Alexander Turenko @ 2018-08-18 12:36 UTC (permalink / raw)
  To: Vladislav Shpilevoy, Eugine Blikh; +Cc: Alexander Turenko, tarantool-patches

Fixes #3627.
---

branch: Totktonada/gh-3627-tap-show-local-variables
travis-ci: https://travis-ci.org/tarantool/tarantool/builds/417616160
issue: https://github.com/tarantool/tarantool/issues/3627

 src/lua/tap.lua           | 45 ++++++++++++++++++++++++++++++++++-----
 test/app-tap/tap.result   | 26 ++++++++++++++++++++--
 test/app-tap/tap.test.lua | 21 +++++++++++++++---
 3 files changed, 82 insertions(+), 10 deletions(-)

diff --git a/src/lua/tap.lua b/src/lua/tap.lua
index edc9f2211..9dd531422 100644
--- a/src/lua/tap.lua
+++ b/src/lua/tap.lua
@@ -1,6 +1,6 @@
 --- tap.lua internal file
 ---
---- The Test Anything Protocol vesion 13 producer
+--- The Test Anything Protocol version 13 producer
 ---
 
 -- yaml formatter must be able to encode any Lua variable
@@ -36,12 +36,43 @@ local function traceback(level)
     return trace
 end
 
+local function locals(test, level)
+    level = level or 3
+    local variables = {}
+    local idx = 1
+    while true do
+      local name, value = debug.getlocal(level, idx)
+      if name ~= nil then
+          -- compare a table with a tuple raises an error, so we check types
+          -- first
+          local eq = type(value) == type(test) and value == test
+          -- temporary values start with '('
+          if not name:startswith('(') and not eq then
+              variables[name] = value
+          end
+      else
+          break
+      end
+      idx = 1 + idx
+    end
+    return variables
+end
+
 local function diag(test, fmt, ...)
     io.write(string.rep(' ', 4 * test.level), "# ", string.format(fmt, ...),
         "\n")
 end
 
-local function ok(test, cond, message, extra)
+local function ok(test, cond, message, extra, opts)
+    opts = opts or {}
+    local show_locals
+    if opts.locals ~= nil then
+        show_locals = opts.locals
+    elseif test.locals ~= nil then
+        show_locals = test.locals
+    else
+        show_locals = true
+    end
     test.total = test.total + 1
     io.write(string.rep(' ', 4 * test.level))
     if cond then
@@ -58,6 +89,9 @@ local function ok(test, cond, message, extra)
         extra.filename = extra.trace[#extra.trace].filename
         extra.line = extra.trace[#extra.trace].line
     end
+    if show_locals then
+        extra.locals = locals(test)
+    end
     if next(extra) == nil then
         return false -- don't have extra information
     end
@@ -221,6 +255,7 @@ local function test(parent, name, fun, ...)
         failed  = 0;
         planned = 0;
         trace   = parent == nil and true or parent.trace;
+        locals  = parent == nil and true or parent.locals;
     }, test_mt)
     if fun ~= nil then
         test:diag('%s', test.name)
@@ -245,7 +280,7 @@ local function check(test)
     if test.planned ~= test.total then
         if test.parent ~= nil then
             ok(test.parent, false, "bad plan", { planned = test.planned;
-                run = test.total})
+                run = test.total}, {locals = false})
         else
             diag(test, string.format("bad plan: planned %d run %d",
                 test.planned, test.total))
@@ -255,13 +290,13 @@ local function check(test)
             ok(test.parent, false, "failed subtests", {
                 failed = test.failed;
                 planned = test.planned;
-            })
+            }, {locals = false})
         else
             diag(test, "failed subtest: %d", test.failed)
         end
     else
         if test.parent ~= nil then
-            ok(test.parent, true, test.name)
+            ok(test.parent, true, test.name, {locals = false})
         end
     end
     return test.planned == test.total and test.failed == 0
diff --git a/test/app-tap/tap.result b/test/app-tap/tap.result
index 3e7882331..36caa3023 100644
--- a/test/app-tap/tap.result
+++ b/test/app-tap/tap.result
@@ -1,5 +1,5 @@
 TAP version 13
-1..32
+1..33
 ok - true
 ok - extra information is not printed on success
 not ok - extra printed using yaml only on failure
@@ -148,4 +148,26 @@ not ok - failed subtests
     ok - unlike(abcde, acd)
     # like: end
 ok - like
-# failed subtest: 15
+    # locals
+    1..1
+        # locals nested
+        1..2
+        ok - locals are not printed in case of success
+        not ok - locals are printed in case of fail
+          ---
+          locals:
+            a: 42
+          ...
+        # locals nested: end
+    not ok - failed subtests
+      ---
+      planned: 2
+      failed: 1
+      ...
+    # locals: end
+not ok - failed subtests
+  ---
+  planned: 1
+  failed: 1
+  ...
+# failed subtest: 16
diff --git a/test/app-tap/tap.test.lua b/test/app-tap/tap.test.lua
index 0e1de7f1c..29dc0b100 100755
--- a/test/app-tap/tap.test.lua
+++ b/test/app-tap/tap.test.lua
@@ -12,15 +12,16 @@ local tap = require "tap"
 -- Create a root test
 --
 test = tap.test("root test")
--- Disable stack traces for this test because Tarantool test system also
--- checks test output.
+-- Disable stack traces and locals for this test because Tarantool test system
+-- also checks test output.
 test.trace = false
+test.locals = false
 
 --
 -- ok, fail and skip predicates
 --
 
-test:plan(32) -- plan to run 3 test
+test:plan(33) -- plan to run 3 test
 test:ok(true, 'true') -- basic function
 local extra = { state = 'some userful information to debug on failure',
         details = 'a table argument formatted using yaml.encode()' }
@@ -136,6 +137,20 @@ test:test('like', function(t)
     t:unlike('abcde', 'acd', 'unlike(abcde, acd)')
 end)
 
+-- The test case implicitly checks that locals will not be printed while
+-- checking the plan.
+test:test('locals', function(t)
+    t.locals = true
+    t:plan(1)
+    t:test('locals nested', function(t)
+        local a = 42
+        local tuple = box.tuple.new({})
+        t:plan(2)
+        t:ok(true, 'locals are not printed in case of success')
+        t:fail('locals are printed in case of fail')
+    end)
+end)
+
 --
 -- Finish root test. Since we used non-callback variant, we have to
 -- call check explicitly.
-- 
2.17.1

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [tarantool-patches] Re: [PATCH] lua: show locals when a tap test fails
  2018-08-18 12:36 [tarantool-patches] [PATCH] lua: show locals when a tap test fails Alexander Turenko
@ 2018-08-19 15:35 ` Alexander Turenko
  2018-08-19 15:36   ` Alexander Turenko
  2018-08-24 12:25 ` Vladislav Shpilevoy
  1 sibling, 1 reply; 9+ messages in thread
From: Alexander Turenko @ 2018-08-19 15:35 UTC (permalink / raw)
  To: Vladislav Shpilevoy, Eugine Blikh; +Cc: tarantool-patches

Sorry, forgotten to update result file. Updated the branch now.

diff --git a/test/app-tap/tap.result b/test/app-tap/tap.result
index 36caa3023..ea6160191 100644
--- a/test/app-tap/tap.result
+++ b/test/app-tap/tap.result
@@ -157,6 +157,7 @@ ok - like
           ---
           locals:
             a: 42
+            tuple: []
           ...
         # locals nested: end
     not ok - failed subtests

WBR, Alexander Turenko.

On Sat, Aug 18, 2018 at 03:36:24PM +0300, Alexander Turenko wrote:
> Fixes #3627.
> ---
> 
> branch: Totktonada/gh-3627-tap-show-local-variables
> travis-ci: https://travis-ci.org/tarantool/tarantool/builds/417616160
> issue: https://github.com/tarantool/tarantool/issues/3627

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [tarantool-patches] Re: [PATCH] lua: show locals when a tap test fails
  2018-08-19 15:35 ` [tarantool-patches] " Alexander Turenko
@ 2018-08-19 15:36   ` Alexander Turenko
  0 siblings, 0 replies; 9+ messages in thread
From: Alexander Turenko @ 2018-08-19 15:36 UTC (permalink / raw)
  To: Vladislav Shpilevoy, Eugine Blikh; +Cc: tarantool-patches

travis-ci: https://travis-ci.org/tarantool/tarantool/builds/417913593

WBR, Alexander Turenko.

On Sun, Aug 19, 2018 at 06:35:00PM +0300, Alexander Turenko wrote:
> Sorry, forgotten to update result file. Updated the branch now.
> 
> diff --git a/test/app-tap/tap.result b/test/app-tap/tap.result
> index 36caa3023..ea6160191 100644
> --- a/test/app-tap/tap.result
> +++ b/test/app-tap/tap.result
> @@ -157,6 +157,7 @@ ok - like
>            ---
>            locals:
>              a: 42
> +            tuple: []
>            ...
>          # locals nested: end
>      not ok - failed subtests
> 
> WBR, Alexander Turenko.
> 
> On Sat, Aug 18, 2018 at 03:36:24PM +0300, Alexander Turenko wrote:
> > Fixes #3627.
> > ---
> > 
> > branch: Totktonada/gh-3627-tap-show-local-variables
> > travis-ci: https://travis-ci.org/tarantool/tarantool/builds/417616160
> > issue: https://github.com/tarantool/tarantool/issues/3627
> 

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [tarantool-patches] Re: [PATCH] lua: show locals when a tap test fails
  2018-08-18 12:36 [tarantool-patches] [PATCH] lua: show locals when a tap test fails Alexander Turenko
  2018-08-19 15:35 ` [tarantool-patches] " Alexander Turenko
@ 2018-08-24 12:25 ` Vladislav Shpilevoy
  2018-08-24 12:51   ` Vladislav Shpilevoy
  2018-09-23 17:59   ` Alexander Turenko
  1 sibling, 2 replies; 9+ messages in thread
From: Vladislav Shpilevoy @ 2018-08-24 12:25 UTC (permalink / raw)
  To: Alexander Turenko, Eugine Blikh; +Cc: tarantool-patches

Hi! Thanks for the patch! See 7 comments below and the patch
on the branch.

On 18/08/2018 15:36, Alexander Turenko wrote:
> Fixes #3627.
> ---
> 
> branch: Totktonada/gh-3627-tap-show-local-variables
> travis-ci: https://travis-ci.org/tarantool/tarantool/builds/417616160
> issue: https://github.com/tarantool/tarantool/issues/3627
> 
>   src/lua/tap.lua           | 45 ++++++++++++++++++++++++++++++++++-----
>   test/app-tap/tap.result   | 26 ++++++++++++++++++++--
>   test/app-tap/tap.test.lua | 21 +++++++++++++++---
>   3 files changed, 82 insertions(+), 10 deletions(-)
> 
> diff --git a/src/lua/tap.lua b/src/lua/tap.lua
> index edc9f2211..9dd531422 100644
> --- a/src/lua/tap.lua
> +++ b/src/lua/tap.lua
> @@ -36,12 +36,43 @@ local function traceback(level)
>       return trace
>   end
>   
> +local function locals(test, level)
> +    level = level or 3
> +    local variables = {}
> +    local idx = 1
> +    while true do
> +      local name, value = debug.getlocal(level, idx)

1. Broken indentation: use 4 spaces, not 2.

> +      if name ~= nil then
> +          -- compare a table with a tuple raises an error, so we check types
> +          -- first

2. Please, start sentence with a capital letter and finish with
the dot.

3. Out of 66 symbols.

> +          local eq = type(value) == type(test) and value == test
> +          -- temporary values start with '('
> +          if not name:startswith('(') and not eq then
> +              variables[name] = value

4. So the variables, having nil value, are not showed?

> +          end
> +      else
> +          break
> +      end
> +      idx = 1 + idx
> +    end
> +    return variables
> +end
> @@ -221,6 +255,7 @@ local function test(parent, name, fun, ...)
>           failed  = 0;
>           planned = 0;
>           trace   = parent == nil and true or parent.trace;
> +        locals  = parent == nil and true or parent.locals;

5. If parent == nil, then this turns into 'true and true or ..',
so what is a point of 'and true'?

>       }, test_mt)
>       if fun ~= nil then
>           test:diag('%s', test.name)
> @@ -245,7 +280,7 @@ local function check(test)
>       if test.planned ~= test.total then
>           if test.parent ~= nil then
>               ok(test.parent, false, "bad plan", { planned = test.planned;
> -                run = test.total})
> +                run = test.total}, {locals = false})

6. Are we sure, that locals should be printed by default? Maybe
it would be better to print them on demand only via a global
option and remove per-test-case option? As I imagine a typical
test fail, when a test did not pass, you turn locals printing
on and run the test again.

>           else
>               diag(test, string.format("bad plan: planned %d run %d",
>                   test.planned, test.total))
> diff --git a/test/app-tap/tap.test.lua b/test/app-tap/tap.test.lua
> index 0e1de7f1c..29dc0b100 100755
> --- a/test/app-tap/tap.test.lua
> +++ b/test/app-tap/tap.test.lua
> @@ -12,15 +12,16 @@ local tap = require "tap"
>   -- Create a root test
>   --
>   test = tap.test("root test")
> --- Disable stack traces for this test because Tarantool test system also
> --- checks test output.
> +-- Disable stack traces and locals for this test because Tarantool test system
> +-- also checks test output.
>   test.trace = false
> +test.locals = false

7. In the comment 6 I meant exactly this: it is not
useful to reset locals, when I do not need them.

>   
>   --
>   -- ok, fail and skip predicates
>   --
>   
> -test:plan(32) -- plan to run 3 test
> +test:plan(33) -- plan to run 3 test
>   test:ok(true, 'true') -- basic function
>   local extra = { state = 'some userful information to debug on failure',
>           details = 'a table argument formatted using yaml.encode()' }

My diff (so far it is requested by SOP to paste it here):

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [tarantool-patches] Re: [PATCH] lua: show locals when a tap test fails
  2018-08-24 12:25 ` Vladislav Shpilevoy
@ 2018-08-24 12:51   ` Vladislav Shpilevoy
  2018-09-23 17:59   ` Alexander Turenko
  1 sibling, 0 replies; 9+ messages in thread
From: Vladislav Shpilevoy @ 2018-08-24 12:51 UTC (permalink / raw)
  To: Alexander Turenko, Eugine Blikh; +Cc: tarantool-patches


Sorry, forgot diff.

> 
> My diff (so far it is requested by SOP to paste it here):
> 

diff --git a/src/lua/tap.lua b/src/lua/tap.lua
index 9dd531422..625326065 100644
--- a/src/lua/tap.lua
+++ b/src/lua/tap.lua
@@ -41,21 +41,19 @@ local function locals(test, level)
      local variables = {}
      local idx = 1
      while true do
-      local name, value = debug.getlocal(level, idx)
-      if name ~= nil then
-          -- compare a table with a tuple raises an error, so we check types
-          -- first
-          local eq = type(value) == type(test) and value == test
-          -- temporary values start with '('
-          if not name:startswith('(') and not eq then
-              variables[name] = value
-          end
-      else
-          break
-      end
-      idx = 1 + idx
+        local name, value = debug.getlocal(level, idx)
+        if not name then
+            return variables
+        end
+        -- Compare a table with a tuple raises an error, so we
+        -- check types first.
+        local is_test = type(value) == type(test) and value == test
+        -- Temporary values start with '('.
+        if not name:startswith('(') and not is_test then
+            variables[name] = value
+        end
+        idx = idx + 1
      end
-    return variables
  end
  
  local function diag(test, fmt, ...)

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [tarantool-patches] Re: [PATCH] lua: show locals when a tap test fails
  2018-08-24 12:25 ` Vladislav Shpilevoy
  2018-08-24 12:51   ` Vladislav Shpilevoy
@ 2018-09-23 17:59   ` Alexander Turenko
  2018-09-24 10:47     ` Vladislav Shpilevoy
  1 sibling, 1 reply; 9+ messages in thread
From: Alexander Turenko @ 2018-09-23 17:59 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: Eugine Blikh, tarantool-patches

Hi!

Thanks for the comments. Answered inline.

I squashed all changes into the one commit and rebased on top of fresh
1.10.

branch: https://github.com/tarantool/tarantool/tree/Totktonada/gh-3627-tap-show-local-variables
issue: https://github.com/tarantool/tarantool/issues/3627

WBR, Alexander Turenko.

> > +local function locals(test, level)
> > +    level = level or 3
> > +    local variables = {}
> > +    local idx = 1
> > +    while true do
> > +      local name, value = debug.getlocal(level, idx)
> 
> 1. Broken indentation: use 4 spaces, not 2.
> 

Fixed by your patch.

> > +      if name ~= nil then
> > +          -- compare a table with a tuple raises an error, so we check types
> > +          -- first
> 
> 2. Please, start sentence with a capital letter and finish with
> the dot.
> 
> 3. Out of 66 symbols.
> 

Fixed by your patch.

> > +          local eq = type(value) == type(test) and value == test
> > +          -- temporary values start with '('
> > +          if not name:startswith('(') and not eq then
> > +              variables[name] = value
> 
> 4. So the variables, having nil value, are not showed?
> 

Yep. We can write it as '[nil]' or like so, but don't sure it worth to
do.

> >           trace   = parent == nil and true or parent.trace;
> > +        locals  = parent == nil and true or parent.locals;
> 
> 5. If parent == nil, then this turns into 'true and true or ..',
> so what is a point of 'and true'?
> 

Fixed.

> > @@ -245,7 +280,7 @@ local function check(test)
> >       if test.planned ~= test.total then
> >           if test.parent ~= nil then
> >               ok(test.parent, false, "bad plan", { planned = test.planned;
> > -                run = test.total})
> > +                run = test.total}, {locals = false})
> 
> 6. Are we sure, that locals should be printed by default? Maybe
> it would be better to print them on demand only via a global
> option and remove per-test-case option? As I imagine a typical
> test fail, when a test did not pass, you turn locals printing
> on and run the test again.
> 

The primary reason of the option is to omit the second run. It is more
useful when the fail is flaky or when it occurs in CI. If you going to
do second run you can print anything you want manually.

So I think it worth to enable it by default or discard the patch
entirely. Or maybe control it from an environment variable.

I personally use the patch around month locally. It sometimes gives very
large output (graphql schemas), but saves time to write something like
`print(require('yaml').encode(res))` each time a test fails.

If you dislike such extra output for failing tests, we should skip the
patch entirely. It is useful or not useful, not something in the middle.

> > --- Disable stack traces for this test because Tarantool test system also
> > --- checks test output.
> > +-- Disable stack traces and locals for this test because Tarantool test system
> > +-- also checks test output.
> >   test.trace = false
> > +test.locals = false
> 
> 7. In the comment 6 I meant exactly this: it is not
> useful to reset locals, when I do not need them.
> 

The reason here is just same as for `test.trace = false`: to compare
output of the module 'tap' easily in the test.

My diff:

diff --git a/src/lua/tap.lua b/src/lua/tap.lua
index 625326065..765c62c77 100644
--- a/src/lua/tap.lua
+++ b/src/lua/tap.lua
@@ -252,8 +252,8 @@ local function test(parent, name, fun, ...)
         total   = 0;
         failed  = 0;
         planned = 0;
-        trace   = parent == nil and true or parent.trace;
-        locals  = parent == nil and true or parent.locals;
+        trace   = parent == nil or parent.trace;
+        locals  = parent == nil or parent.locals;
     }, test_mt)
     if fun ~= nil then
         test:diag('%s', test.name)

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [tarantool-patches] Re: [PATCH] lua: show locals when a tap test fails
  2018-09-23 17:59   ` Alexander Turenko
@ 2018-09-24 10:47     ` Vladislav Shpilevoy
  2018-09-27  0:45       ` Alexander Turenko
  0 siblings, 1 reply; 9+ messages in thread
From: Vladislav Shpilevoy @ 2018-09-24 10:47 UTC (permalink / raw)
  To: Alexander Turenko; +Cc: Eugine Blikh, tarantool-patches, Kirill Yukhin

Hi! Thanks for the fixes!

>>> @@ -245,7 +280,7 @@ local function check(test)
>>>        if test.planned ~= test.total then
>>>            if test.parent ~= nil then
>>>                ok(test.parent, false, "bad plan", { planned = test.planned;
>>> -                run = test.total})
>>> +                run = test.total}, {locals = false})
>>
>> 6. Are we sure, that locals should be printed by default? Maybe
>> it would be better to print them on demand only via a global
>> option and remove per-test-case option? As I imagine a typical
>> test fail, when a test did not pass, you turn locals printing
>> on and run the test again.
>>
> 
> The primary reason of the option is to omit the second run. It is more
> useful when the fail is flaky or when it occurs in CI. If you going to
> do second run you can print anything you want manually.
> 
> So I think it worth to enable it by default or discard the patch
> entirely. Or maybe control it from an environment variable.
> 
> I personally use the patch around month locally. It sometimes gives very
> large output (graphql schemas), but saves time to write something like
> `print(require('yaml').encode(res))` each time a test fails.
> 
> If you dislike such extra output for failing tests, we should skip the
> patch entirely. It is useful or not useful, not something in the middle.

Understandable. Then LGTM, looks useful.

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [tarantool-patches] Re: [PATCH] lua: show locals when a tap test fails
  2018-09-24 10:47     ` Vladislav Shpilevoy
@ 2018-09-27  0:45       ` Alexander Turenko
  2018-10-20 18:36         ` Alexander Turenko
  0 siblings, 1 reply; 9+ messages in thread
From: Alexander Turenko @ 2018-09-27  0:45 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: Eugine Blikh, tarantool-patches, Kirill Yukhin

On Mon, Sep 24, 2018 at 01:47:45PM +0300, Vladislav Shpilevoy wrote:
> Hi! Thanks for the fixes!
> 
> > > > @@ -245,7 +280,7 @@ local function check(test)
> > > >        if test.planned ~= test.total then
> > > >            if test.parent ~= nil then
> > > >                ok(test.parent, false, "bad plan", { planned = test.planned;
> > > > -                run = test.total})
> > > > +                run = test.total}, {locals = false})
> > > 
> > > 6. Are we sure, that locals should be printed by default? Maybe
> > > it would be better to print them on demand only via a global
> > > option and remove per-test-case option? As I imagine a typical
> > > test fail, when a test did not pass, you turn locals printing
> > > on and run the test again.
> > > 
> > 
> > The primary reason of the option is to omit the second run. It is more
> > useful when the fail is flaky or when it occurs in CI. If you going to
> > do second run you can print anything you want manually.
> > 
> > So I think it worth to enable it by default or discard the patch
> > entirely. Or maybe control it from an environment variable.
> > 
> > I personally use the patch around month locally. It sometimes gives very
> > large output (graphql schemas), but saves time to write something like
> > `print(require('yaml').encode(res))` each time a test fails.
> > 
> > If you dislike such extra output for failing tests, we should skip the
> > patch entirely. It is useful or not useful, not something in the middle.
> 
> Understandable. Then LGTM, looks useful.
> 

To be honest, now I don't so sure in the thought that we should enable
it by default or discard completely. Sometimes it is useful to have
clean picture which cases are passed and which are not w/o extra
information (w/o locals and w/o traces). But sometimes it is useful to
know exact reason of a certain fail and have as much info as possible
(so locals are helpful).

When work on a patch locally both situations occur. Don't sure about CI.

Now I thinking about the following implementation: save locals for a
first failing case, print it after the whole test in the uppermost
test:check() and then print cases list (with ok/not ok, name and so on)
again to bring it to the focus. It gives both benefits: a developer does
not have to scroll for pass/fail list and have detailed info available
after some scrolling.

We could simplify that to 'print traces and locals during the test, then
give a short list in the uppermost test:check()', but there is a
pitfall. When something (say, a test case preparation) lead to premature
exit (say, because of a raised error) we still want to see the whole
picture (what is passed and what is failed) at the first glance and
then, maybe, detailed information about the fail. With the proposed
implementation we'll get the cases list, but not details: I think it is
better then vice versa.

So, to sum it up, I propose to keep a during-test output short, then, if
the test reach the uppermost test:check(), give details and repeat the
short cases list.

Now I think how better to handle the raised error case. Maybe it worth
to do xpcall around the whole test and then traverse over a stack to
find a frame with the uppermost test and print locals for that frame.
I'm thinking whether it is implementable w/o extra handle from a test
writer, say, as a global option (_G.print_test_locals_before_exit) of
tarantool that can be set by test-run.

Ugh, tricky thing...

WBR, Alexander Turenko.

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [tarantool-patches] Re: [PATCH] lua: show locals when a tap test fails
  2018-09-27  0:45       ` Alexander Turenko
@ 2018-10-20 18:36         ` Alexander Turenko
  0 siblings, 0 replies; 9+ messages in thread
From: Alexander Turenko @ 2018-10-20 18:36 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: Eugine Blikh, tarantool-patches, Kirill Yukhin

On Thu, Sep 27, 2018 at 03:45:16AM +0300, Alexander Turenko wrote:
> On Mon, Sep 24, 2018 at 01:47:45PM +0300, Vladislav Shpilevoy wrote:
> > Hi! Thanks for the fixes!
> > 
> > > > > @@ -245,7 +280,7 @@ local function check(test)
> > > > >        if test.planned ~= test.total then
> > > > >            if test.parent ~= nil then
> > > > >                ok(test.parent, false, "bad plan", { planned = test.planned;
> > > > > -                run = test.total})
> > > > > +                run = test.total}, {locals = false})
> > > > 
> > > > 6. Are we sure, that locals should be printed by default? Maybe
> > > > it would be better to print them on demand only via a global
> > > > option and remove per-test-case option? As I imagine a typical
> > > > test fail, when a test did not pass, you turn locals printing
> > > > on and run the test again.
> > > > 
> > > 
> > > The primary reason of the option is to omit the second run. It is more
> > > useful when the fail is flaky or when it occurs in CI. If you going to
> > > do second run you can print anything you want manually.
> > > 
> > > So I think it worth to enable it by default or discard the patch
> > > entirely. Or maybe control it from an environment variable.
> > > 
> > > I personally use the patch around month locally. It sometimes gives very
> > > large output (graphql schemas), but saves time to write something like
> > > `print(require('yaml').encode(res))` each time a test fails.
> > > 
> > > If you dislike such extra output for failing tests, we should skip the
> > > patch entirely. It is useful or not useful, not something in the middle.
> > 
> > Understandable. Then LGTM, looks useful.
> > 
> 
> To be honest, now I don't so sure in the thought that we should enable
> it by default or discard completely. Sometimes it is useful to have
> clean picture which cases are passed and which are not w/o extra
> information (w/o locals and w/o traces). But sometimes it is useful to
> know exact reason of a certain fail and have as much info as possible
> (so locals are helpful).
> 
> When work on a patch locally both situations occur. Don't sure about CI.
> 
> Now I thinking about the following implementation: save locals for a
> first failing case, print it after the whole test in the uppermost
> test:check() and then print cases list (with ok/not ok, name and so on)
> again to bring it to the focus. It gives both benefits: a developer does
> not have to scroll for pass/fail list and have detailed info available
> after some scrolling.
> 
> We could simplify that to 'print traces and locals during the test, then
> give a short list in the uppermost test:check()', but there is a
> pitfall. When something (say, a test case preparation) lead to premature
> exit (say, because of a raised error) we still want to see the whole
> picture (what is passed and what is failed) at the first glance and
> then, maybe, detailed information about the fail. With the proposed
> implementation we'll get the cases list, but not details: I think it is
> better then vice versa.
> 
> So, to sum it up, I propose to keep a during-test output short, then, if
> the test reach the uppermost test:check(), give details and repeat the
> short cases list.
> 
> Now I think how better to handle the raised error case. Maybe it worth
> to do xpcall around the whole test and then traverse over a stack to
> find a frame with the uppermost test and print locals for that frame.
> I'm thinking whether it is implementable w/o extra handle from a test
> writer, say, as a global option (_G.print_test_locals_before_exit) of
> tarantool that can be set by test-run.
> 
> Ugh, tricky thing...
> 
> WBR, Alexander Turenko.

I retained myself from implementing the complex and unobvious behaviour.

Now it is disabled by default.

Rebased on top of 2.1.

The diff from the previous version and the full diff are below.

https://github.com/tarantool/tarantool/issues/3627
https://github.com/tarantool/tarantool/tree/Totktonada/gh-3627-tap-show-local-variables
https://travis-ci.org/tarantool/tarantool/builds/444149450

WBR, Alexander Turenko.

Diff from the previous version
------------------------------

diff --git a/src/lua/tap.lua b/src/lua/tap.lua
index 5074305d3..d0f8b08c6 100644
--- a/src/lua/tap.lua
+++ b/src/lua/tap.lua
@@ -69,7 +69,7 @@ local function ok(test, cond, message, extra, opts)
     elseif test.locals ~= nil then
         show_locals = test.locals
     else
-        show_locals = true
+        show_locals = false
     end
     test.total = test.total + 1
     io.write(string.rep(' ', 4 * test.level))
@@ -257,7 +257,7 @@ local function test(parent, name, fun, ...)
         failed  = 0;
         planned = 0;
         trace   = parent == nil or parent.trace;
-        locals  = parent == nil or parent.locals;
+        locals  = parent ~= nil and parent.locals;
     }, test_mt)
     if fun ~= nil then
         test:diag('%s', test.name)
@@ -281,6 +281,7 @@ local function check(test)
     test.checked = true
     if test.planned ~= test.total then
         if test.parent ~= nil then
+            -- Do not show locals with the 'bad plan' message.
             ok(test.parent, false, "bad plan", { planned = test.planned;
                 run = test.total}, {locals = false})
         else
@@ -289,6 +290,8 @@ local function check(test)
         end
     elseif test.failed > 0 then
         if test.parent ~= nil then
+            -- Do not show locals with the 'failed subtests'
+            -- message.
             ok(test.parent, false, "failed subtests", {
                 failed = test.failed;
                 planned = test.planned;
@@ -298,7 +301,7 @@ local function check(test)
         end
     else
         if test.parent ~= nil then
-            ok(test.parent, true, test.name, {locals = false})
+            ok(test.parent, true, test.name)
         end
     end
     return test.planned == test.total and test.failed == 0
diff --git a/test/app-tap/tap.test.lua b/test/app-tap/tap.test.lua
index 29dc0b100..a810be3f6 100755
--- a/test/app-tap/tap.test.lua
+++ b/test/app-tap/tap.test.lua
@@ -12,10 +12,9 @@ local tap = require "tap"
 -- Create a root test
 --
 test = tap.test("root test")
--- Disable stack traces and locals for this test because Tarantool test system
--- also checks test output.
+-- Disable stack traces for this test because Tarantool test system also
+-- checks test output.
 test.trace = false
-test.locals = false
 
 --
 -- ok, fail and skip predicates

Full diff
---------

diff --git a/src/lua/tap.lua b/src/lua/tap.lua
index 546d15392..d0f8b08c6 100644
--- a/src/lua/tap.lua
+++ b/src/lua/tap.lua
@@ -1,6 +1,6 @@
 --- tap.lua internal file
 ---
---- The Test Anything Protocol vesion 13 producer
+--- The Test Anything Protocol version 13 producer
 ---
 
 -- yaml formatter must be able to encode any Lua variable
@@ -36,12 +36,41 @@ local function traceback(level)
     return trace
 end
 
+local function locals(test, level)
+    level = level or 3
+    local variables = {}
+    local idx = 1
+    while true do
+        local name, value = debug.getlocal(level, idx)
+        if not name then
+            return variables
+        end
+        -- Compare a table with a tuple raises an error, so we
+        -- check types first.
+        local is_test = type(value) == type(test) and value == test
+        -- Temporary values start with '('.
+        if not name:startswith('(') and not is_test then
+            variables[name] = value
+        end
+        idx = idx + 1
+    end
+end
+
 local function diag(test, fmt, ...)
     io.write(string.rep(' ', 4 * test.level), "# ", string.format(fmt, ...),
         "\n")
 end
 
-local function ok(test, cond, message, extra)
+local function ok(test, cond, message, extra, opts)
+    opts = opts or {}
+    local show_locals
+    if opts.locals ~= nil then
+        show_locals = opts.locals
+    elseif test.locals ~= nil then
+        show_locals = test.locals
+    else
+        show_locals = false
+    end
     test.total = test.total + 1
     io.write(string.rep(' ', 4 * test.level))
     if cond then
@@ -58,6 +87,9 @@ local function ok(test, cond, message, extra)
         extra.filename = extra.trace[#extra.trace].filename
         extra.line = extra.trace[#extra.trace].line
     end
+    if show_locals then
+        extra.locals = locals(test)
+    end
     if next(extra) == nil then
         return false -- don't have extra information
     end
@@ -224,7 +256,8 @@ local function test(parent, name, fun, ...)
         total   = 0;
         failed  = 0;
         planned = 0;
-        trace   = parent == nil and true or parent.trace;
+        trace   = parent == nil or parent.trace;
+        locals  = parent ~= nil and parent.locals;
     }, test_mt)
     if fun ~= nil then
         test:diag('%s', test.name)
@@ -248,18 +281,21 @@ local function check(test)
     test.checked = true
     if test.planned ~= test.total then
         if test.parent ~= nil then
+            -- Do not show locals with the 'bad plan' message.
             ok(test.parent, false, "bad plan", { planned = test.planned;
-                run = test.total})
+                run = test.total}, {locals = false})
         else
             diag(test, string.format("bad plan: planned %d run %d",
                 test.planned, test.total))
         end
     elseif test.failed > 0 then
         if test.parent ~= nil then
+            -- Do not show locals with the 'failed subtests'
+            -- message.
             ok(test.parent, false, "failed subtests", {
                 failed = test.failed;
                 planned = test.planned;
-            })
+            }, {locals = false})
         else
             diag(test, "failed subtest: %d", test.failed)
         end
diff --git a/test/app-tap/tap.result b/test/app-tap/tap.result
index 3e7882331..ea6160191 100644
--- a/test/app-tap/tap.result
+++ b/test/app-tap/tap.result
@@ -1,5 +1,5 @@
 TAP version 13
-1..32
+1..33
 ok - true
 ok - extra information is not printed on success
 not ok - extra printed using yaml only on failure
@@ -148,4 +148,27 @@ not ok - failed subtests
     ok - unlike(abcde, acd)
     # like: end
 ok - like
-# failed subtest: 15
+    # locals
+    1..1
+        # locals nested
+        1..2
+        ok - locals are not printed in case of success
+        not ok - locals are printed in case of fail
+          ---
+          locals:
+            a: 42
+            tuple: []
+          ...
+        # locals nested: end
+    not ok - failed subtests
+      ---
+      planned: 2
+      failed: 1
+      ...
+    # locals: end
+not ok - failed subtests
+  ---
+  planned: 1
+  failed: 1
+  ...
+# failed subtest: 16
diff --git a/test/app-tap/tap.test.lua b/test/app-tap/tap.test.lua
index 0e1de7f1c..a810be3f6 100755
--- a/test/app-tap/tap.test.lua
+++ b/test/app-tap/tap.test.lua
@@ -20,7 +20,7 @@ test.trace = false
 -- ok, fail and skip predicates
 --
 
-test:plan(32) -- plan to run 3 test
+test:plan(33) -- plan to run 3 test
 test:ok(true, 'true') -- basic function
 local extra = { state = 'some userful information to debug on failure',
         details = 'a table argument formatted using yaml.encode()' }
@@ -136,6 +136,20 @@ test:test('like', function(t)
     t:unlike('abcde', 'acd', 'unlike(abcde, acd)')
 end)
 
+-- The test case implicitly checks that locals will not be printed while
+-- checking the plan.
+test:test('locals', function(t)
+    t.locals = true
+    t:plan(1)
+    t:test('locals nested', function(t)
+        local a = 42
+        local tuple = box.tuple.new({})
+        t:plan(2)
+        t:ok(true, 'locals are not printed in case of success')
+        t:fail('locals are printed in case of fail')
+    end)
+end)
+
 --
 -- Finish root test. Since we used non-callback variant, we have to
 -- call check explicitly.

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2018-10-20 18:36 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-08-18 12:36 [tarantool-patches] [PATCH] lua: show locals when a tap test fails Alexander Turenko
2018-08-19 15:35 ` [tarantool-patches] " Alexander Turenko
2018-08-19 15:36   ` Alexander Turenko
2018-08-24 12:25 ` Vladislav Shpilevoy
2018-08-24 12:51   ` Vladislav Shpilevoy
2018-09-23 17:59   ` Alexander Turenko
2018-09-24 10:47     ` Vladislav Shpilevoy
2018-09-27  0:45       ` Alexander Turenko
2018-10-20 18:36         ` Alexander Turenko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox