Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH v1 1/1] tap: fix is_deeply box.NULL corner cases
@ 2019-04-23 12:55 Kirill Shcherbatov
  2019-04-23 14:12 ` [tarantool-patches] " Kirill Shcherbatov
                   ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Kirill Shcherbatov @ 2019-04-23 12:55 UTC (permalink / raw)
  To: tarantool-patches, v.shpilevoy; +Cc: Kirill Shcherbatov

The tap:is_deeply call used to return inconsistent result
processing an empty tuple and tuple that has no values:
is_deeply({a = box.NULL}, {})  == true
is_deeply({}, {a = box.NULL}) == false
Fixed to return true in such case.

We shouldn't return false in such case because of marshaling
features covered by tests:
is_deeply(decode(encode(t)), t) must be true for all t,
including t = nil; decode(encode(nil)) == box.NULL.

Closes #4125
---
Branch: http://github.com/tarantool/tarantool/tree/kshch/gh-4125-inconsistent-isdeeply-results
Issue: https://github.com/tarantool/tarantool/issues/4125

 src/lua/tap.lua           |  4 ++--
 test/app-tap/tap.result   |  8 ++++++--
 test/app-tap/tap.test.lua | 10 +++++++++-
 3 files changed, 17 insertions(+), 5 deletions(-)

diff --git a/src/lua/tap.lua b/src/lua/tap.lua
index 546d15392..4f214ba52 100644
--- a/src/lua/tap.lua
+++ b/src/lua/tap.lua
@@ -117,8 +117,8 @@ local function cmpdeeply(got, expected, extra)
     end
 
     -- check if expected contains more keys then got
-    for i, _ in pairs(expected) do
-        if visited_keys[i] ~= true then
+    for i, v in pairs(expected) do
+        if visited_keys[i] ~= true and v ~= nil then
             extra.expected = 'key ' .. tostring(i)
             extra.got = 'nil'
             return false
diff --git a/test/app-tap/tap.result b/test/app-tap/tap.result
index 3e7882331..dced9a5a2 100644
--- a/test/app-tap/tap.result
+++ b/test/app-tap/tap.result
@@ -119,7 +119,7 @@ not ok - failed subtests
   failed: 1
   ...
     # is_deeply
-    1..6
+    1..10
     ok - 1 and 1
     ok - abc and abc
     ok - empty tables
@@ -136,10 +136,14 @@ not ok - failed subtests
       expected: 5
       got: 4
       ...
+    ok - {} and {a = nil}
+    ok - {a = nil} and {}
+    ok - {a = nil} and {b = nil}
+    ok - {a = nil} and {b = nil, c = nil}
     # is_deeply: end
 not ok - failed subtests
   ---
-  planned: 6
+  planned: 10
   failed: 2
   ...
     # like
diff --git a/test/app-tap/tap.test.lua b/test/app-tap/tap.test.lua
index 0e1de7f1c..c0593db46 100755
--- a/test/app-tap/tap.test.lua
+++ b/test/app-tap/tap.test.lua
@@ -118,7 +118,7 @@ end)
 
 
 test:test('is_deeply', function(t)
-    t:plan(6)
+    t:plan(10)
 
     t:is_deeply(1, 1, '1 and 1')
     t:is_deeply('abc', 'abc', 'abc and abc')
@@ -127,6 +127,14 @@ test:test('is_deeply', function(t)
     t:is_deeply({1}, {2}, '{1} and {2}')
     t:is_deeply({1, 2, { 3, 4 }}, {1, 2, { 3, 5 }}, '{1,2,{3,4}} and {1,2,{3,5}}')
 
+    --
+    -- gh-4125: is_deeply inconsistently works with box.NULL.
+    --
+    t:is_deeply({}, {a = box.NULL}, '{} and {a = nil}')
+    t:is_deeply({a = box.NULL}, {}, '{a = nil} and {}')
+    t:is_deeply({a = box.NULL}, {b = box.NULL}, '{a = nil} and {b = nil}')
+    t:is_deeply({a = box.NULL}, {b = box.NULL, c = box.NULL},
+                '{a = nil} and {b = nil, c = nil}')
 end)
 
 
-- 
2.21.0

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

* [tarantool-patches] Re: [PATCH v1 1/1] tap: fix is_deeply box.NULL corner cases
  2019-04-23 12:55 [tarantool-patches] [PATCH v1 1/1] tap: fix is_deeply box.NULL corner cases Kirill Shcherbatov
@ 2019-04-23 14:12 ` Kirill Shcherbatov
  2019-04-23 14:14   ` Vladislav Shpilevoy
  2019-04-24  9:37 ` [tarantool-patches] [PATCH v2 " Kirill Shcherbatov
  2019-04-25 10:29 ` [tarantool-patches] Re: [PATCH v1 " Kirill Yukhin
  2 siblings, 1 reply; 11+ messages in thread
From: Kirill Shcherbatov @ 2019-04-23 14:12 UTC (permalink / raw)
  To: tarantool-patches, v.shpilevoy

explicit is better than implicit, yep

- if visited_keys[i] ~= true and v ~= nil then
+ if visited_keys[i] ~= true and v ~= box.NULL then

===========================================

The tap:is_deeply call used to return inconsistent result
processing an empty tuple and tuple that has no values:
is_deeply({a = box.NULL}, {})  == true
is_deeply({}, {a = box.NULL}) == false
Fixed to return true in such case.

We shouldn't return false in such case because of marshaling
features covered by tests:
is_deeply(decode(encode(t)), t) must be true for all t,
including t = nil; decode(encode(nil)) == box.NULL.

Closes #4125
---
 src/lua/tap.lua           |  4 ++--
 test/app-tap/tap.result   |  8 ++++++--
 test/app-tap/tap.test.lua | 10 +++++++++-
 3 files changed, 17 insertions(+), 5 deletions(-)

diff --git a/src/lua/tap.lua b/src/lua/tap.lua
index 546d15392..e48adbbab 100644
--- a/src/lua/tap.lua
+++ b/src/lua/tap.lua
@@ -117,8 +117,8 @@ local function cmpdeeply(got, expected, extra)
     end
 
     -- check if expected contains more keys then got
-    for i, _ in pairs(expected) do
-        if visited_keys[i] ~= true then
+    for i, v in pairs(expected) do
+        if visited_keys[i] ~= true and v ~= box.NULL then
             extra.expected = 'key ' .. tostring(i)
             extra.got = 'nil'
             return false
diff --git a/test/app-tap/tap.result b/test/app-tap/tap.result
index 3e7882331..dced9a5a2 100644
--- a/test/app-tap/tap.result
+++ b/test/app-tap/tap.result
@@ -119,7 +119,7 @@ not ok - failed subtests
   failed: 1
   ...
     # is_deeply
-    1..6
+    1..10
     ok - 1 and 1
     ok - abc and abc
     ok - empty tables
@@ -136,10 +136,14 @@ not ok - failed subtests
       expected: 5
       got: 4
       ...
+    ok - {} and {a = nil}
+    ok - {a = nil} and {}
+    ok - {a = nil} and {b = nil}
+    ok - {a = nil} and {b = nil, c = nil}
     # is_deeply: end
 not ok - failed subtests
   ---
-  planned: 6
+  planned: 10
   failed: 2
   ...
     # like
diff --git a/test/app-tap/tap.test.lua b/test/app-tap/tap.test.lua
index 0e1de7f1c..c0593db46 100755
--- a/test/app-tap/tap.test.lua
+++ b/test/app-tap/tap.test.lua
@@ -118,7 +118,7 @@ end)
 
 
 test:test('is_deeply', function(t)
-    t:plan(6)
+    t:plan(10)
 
     t:is_deeply(1, 1, '1 and 1')
     t:is_deeply('abc', 'abc', 'abc and abc')
@@ -127,6 +127,14 @@ test:test('is_deeply', function(t)
     t:is_deeply({1}, {2}, '{1} and {2}')
     t:is_deeply({1, 2, { 3, 4 }}, {1, 2, { 3, 5 }}, '{1,2,{3,4}} and {1,2,{3,5}}')
 
+    --
+    -- gh-4125: is_deeply inconsistently works with box.NULL.
+    --
+    t:is_deeply({}, {a = box.NULL}, '{} and {a = nil}')
+    t:is_deeply({a = box.NULL}, {}, '{a = nil} and {}')
+    t:is_deeply({a = box.NULL}, {b = box.NULL}, '{a = nil} and {b = nil}')
+    t:is_deeply({a = box.NULL}, {b = box.NULL, c = box.NULL},
+                '{a = nil} and {b = nil, c = nil}')
 end)
 
 
-- 
2.21.0

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

* [tarantool-patches] Re: [PATCH v1 1/1] tap: fix is_deeply box.NULL corner cases
  2019-04-23 14:12 ` [tarantool-patches] " Kirill Shcherbatov
@ 2019-04-23 14:14   ` Vladislav Shpilevoy
  0 siblings, 0 replies; 11+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-23 14:14 UTC (permalink / raw)
  To: Kirill Shcherbatov, tarantool-patches, Kirill Yukhin

LGTM.

On 23/04/2019 17:12, Kirill Shcherbatov wrote:
> explicit is better than implicit, yep
> 
> - if visited_keys[i] ~= true and v ~= nil then
> + if visited_keys[i] ~= true and v ~= box.NULL then
> 
> ===========================================
> 
> The tap:is_deeply call used to return inconsistent result
> processing an empty tuple and tuple that has no values:
> is_deeply({a = box.NULL}, {})  == true
> is_deeply({}, {a = box.NULL}) == false
> Fixed to return true in such case.
> 
> We shouldn't return false in such case because of marshaling
> features covered by tests:
> is_deeply(decode(encode(t)), t) must be true for all t,
> including t = nil; decode(encode(nil)) == box.NULL.
> 
> Closes #4125
> ---
>  src/lua/tap.lua           |  4 ++--
>  test/app-tap/tap.result   |  8 ++++++--
>  test/app-tap/tap.test.lua | 10 +++++++++-
>  3 files changed, 17 insertions(+), 5 deletions(-)
> 
> diff --git a/src/lua/tap.lua b/src/lua/tap.lua
> index 546d15392..e48adbbab 100644
> --- a/src/lua/tap.lua
> +++ b/src/lua/tap.lua
> @@ -117,8 +117,8 @@ local function cmpdeeply(got, expected, extra)
>      end
>  
>      -- check if expected contains more keys then got
> -    for i, _ in pairs(expected) do
> -        if visited_keys[i] ~= true then
> +    for i, v in pairs(expected) do
> +        if visited_keys[i] ~= true and v ~= box.NULL then
>              extra.expected = 'key ' .. tostring(i)
>              extra.got = 'nil'
>              return false
> diff --git a/test/app-tap/tap.result b/test/app-tap/tap.result
> index 3e7882331..dced9a5a2 100644
> --- a/test/app-tap/tap.result
> +++ b/test/app-tap/tap.result
> @@ -119,7 +119,7 @@ not ok - failed subtests
>    failed: 1
>    ...
>      # is_deeply
> -    1..6
> +    1..10
>      ok - 1 and 1
>      ok - abc and abc
>      ok - empty tables
> @@ -136,10 +136,14 @@ not ok - failed subtests
>        expected: 5
>        got: 4
>        ...
> +    ok - {} and {a = nil}
> +    ok - {a = nil} and {}
> +    ok - {a = nil} and {b = nil}
> +    ok - {a = nil} and {b = nil, c = nil}
>      # is_deeply: end
>  not ok - failed subtests
>    ---
> -  planned: 6
> +  planned: 10
>    failed: 2
>    ...
>      # like
> diff --git a/test/app-tap/tap.test.lua b/test/app-tap/tap.test.lua
> index 0e1de7f1c..c0593db46 100755
> --- a/test/app-tap/tap.test.lua
> +++ b/test/app-tap/tap.test.lua
> @@ -118,7 +118,7 @@ end)
>  
>  
>  test:test('is_deeply', function(t)
> -    t:plan(6)
> +    t:plan(10)
>  
>      t:is_deeply(1, 1, '1 and 1')
>      t:is_deeply('abc', 'abc', 'abc and abc')
> @@ -127,6 +127,14 @@ test:test('is_deeply', function(t)
>      t:is_deeply({1}, {2}, '{1} and {2}')
>      t:is_deeply({1, 2, { 3, 4 }}, {1, 2, { 3, 5 }}, '{1,2,{3,4}} and {1,2,{3,5}}')
>  
> +    --
> +    -- gh-4125: is_deeply inconsistently works with box.NULL.
> +    --
> +    t:is_deeply({}, {a = box.NULL}, '{} and {a = nil}')
> +    t:is_deeply({a = box.NULL}, {}, '{a = nil} and {}')
> +    t:is_deeply({a = box.NULL}, {b = box.NULL}, '{a = nil} and {b = nil}')
> +    t:is_deeply({a = box.NULL}, {b = box.NULL, c = box.NULL},
> +                '{a = nil} and {b = nil, c = nil}')
>  end)
>  
>  
> -- 
> 2.21.0
> 

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

* [tarantool-patches] [PATCH v2 1/1] tap: fix is_deeply box.NULL corner cases
  2019-04-23 12:55 [tarantool-patches] [PATCH v1 1/1] tap: fix is_deeply box.NULL corner cases Kirill Shcherbatov
  2019-04-23 14:12 ` [tarantool-patches] " Kirill Shcherbatov
@ 2019-04-24  9:37 ` Kirill Shcherbatov
  2019-04-24 11:23   ` [tarantool-patches] " Vladislav Shpilevoy
  2019-04-24 11:57   ` Alexander Turenko
  2019-04-25 10:29 ` [tarantool-patches] Re: [PATCH v1 " Kirill Yukhin
  2 siblings, 2 replies; 11+ messages in thread
From: Kirill Shcherbatov @ 2019-04-24  9:37 UTC (permalink / raw)
  To: tarantool-patches, v.shpilevoy

After a discussion that took place on github after I submitted this patch, it was decided to introduce
the ability to change the behavior when comparing box.NULL and nil in is, isnt, is_deeply tests using
tap object flag  'strict'.

=======================================================================

The tap:is_deeply call used to return inconsistent result
processing an empty tuple and tuple that has no values:
is_deeply({a = box.NULL}, {})  == true
is_deeply({}, {a = box.NULL}) == false
Fixed to return true by default in such case. You may also
set tap.strict = true to change this behaviour.

@TarantoolBot document
Title: tap test new flag 'strict'
In some scenarios it is convenient to distinguish box.NULL and
nil in tap:is(), tap:isnt(), tap:is_deeply() tests.
For example, {result='Success'} and {result='Success', error=None}
are different HTTP responses. You may set t.strict = true to
behave such way.
Example:
t = require('tap').test('123')
t.strict = true
t:is_deeply({a = box.NULL}, {}) -- false

Closes #4125
---
Branch: http://github.com/tarantool/tarantool/tree/kshch/gh-4125-inconsistent-isdeeply-results
Issue: https://github.com/tarantool/tarantool/issues/4125

 src/lua/tap.lua           | 22 +++++++++---
 test/app-tap/tap.result   | 75 ++++++++++++++++++++++++++++++++++++---
 test/app-tap/tap.test.lua | 39 ++++++++++++++++++--
 3 files changed, 124 insertions(+), 12 deletions(-)

diff --git a/src/lua/tap.lua b/src/lua/tap.lua
index 546d15392..866786e6f 100644
--- a/src/lua/tap.lua
+++ b/src/lua/tap.lua
@@ -91,13 +91,18 @@ local function cmpdeeply(got, expected, extra)
 
     if ffi.istype('bool', got) then got = (got == 1) end
     if ffi.istype('bool', expected) then expected = (expected == 1) end
-    if got == nil and expected == nil then return true end
+    if extra.strict == false then
+        -- Convert all box.NULLs to LUA nil representation.
+        if got == box.NULL then got = nil end
+        if expected == box.NULL then expected = nil end
+    end
 
     if type(got) ~= type(expected) then
         extra.got = type(got)
         extra.expected = type(expected)
         return false
     end
+    if got == nil and expected == nil then return true end
 
     if type(got) ~= 'table' then
         extra.got = got
@@ -117,8 +122,9 @@ local function cmpdeeply(got, expected, extra)
     end
 
     -- check if expected contains more keys then got
-    for i, _ in pairs(expected) do
-        if visited_keys[i] ~= true then
+    for i, v in pairs(expected) do
+        if visited_keys[i] ~= true and
+           (extra.strict == true or v ~= box.NULL) then
             extra.expected = 'key ' .. tostring(i)
             extra.got = 'nil'
             return false
@@ -148,14 +154,18 @@ local function is(test, got, expected, message, extra)
     extra = extra or {}
     extra.got = got
     extra.expected = expected
-    return ok(test, got == expected, message, extra)
+    local rc = (test.strict == false or type(got) == type(expected)) and
+               got == expected
+    return ok(test, rc, message, extra)
 end
 
 local function isnt(test, got, unexpected, message, extra)
     extra = extra or {}
     extra.got = got
     extra.unexpected = unexpected
-    return ok(test, got ~= unexpected, message, extra)
+    local rc = (test.strict == true and type(got) ~= type(unexpected)) or
+               got ~= unexpected
+    return ok(test, rc, message, extra)
 end
 
 
@@ -163,6 +173,7 @@ local function is_deeply(test, got, expected, message, extra)
     extra = extra or {}
     extra.got = got
     extra.expected = expected
+    extra.strict = test.strict
     return ok(test, cmpdeeply(got, expected, extra), message, extra)
 end
 
@@ -225,6 +236,7 @@ local function test(parent, name, fun, ...)
         failed  = 0;
         planned = 0;
         trace   = parent == nil and true or parent.trace;
+        strict = false;
     }, test_mt)
     if fun ~= nil then
         test:diag('%s', test.name)
diff --git a/test/app-tap/tap.result b/test/app-tap/tap.result
index 3e7882331..13fa4fbcd 100644
--- a/test/app-tap/tap.result
+++ b/test/app-tap/tap.result
@@ -1,5 +1,5 @@
 TAP version 13
-1..32
+1..40
 ok - true
 ok - extra information is not printed on success
 not ok - extra printed using yaml only on failure
@@ -69,6 +69,24 @@ not ok - cdata type
   expected: ctype<int>
   got: ctype<unsigned int>
   ...
+not ok - box.NULL != nil
+  ---
+  got: null
+  ...
+not ok - nil != box.NULL
+  ---
+  expected: null
+  ...
+ok - box.NULL == box.NULL
+ok - nil == nil
+ok - box.NULL != nil
+ok - nil != box.NULL
+not ok - box.NULL == box.NULL
+  ---
+  unexpected: null
+  got: null
+  ...
+not ok - nil == nil
     # subtest 1
     1..2
     ok - true
@@ -119,7 +137,7 @@ not ok - failed subtests
   failed: 1
   ...
     # is_deeply
-    1..6
+    1..18
     ok - 1 and 1
     ok - abc and abc
     ok - empty tables
@@ -127,20 +145,67 @@ not ok - failed subtests
     not ok - {1} and {2}
       ---
       path: //1
+      strict: false
       expected: 2
       got: 1
       ...
     not ok - {1,2,{3,4}} and {1,2,{3,5}}
       ---
       path: //3/2
+      strict: false
       expected: 5
       got: 4
       ...
+    ok - {} and {a = nil} strict = false
+    ok - {a = nil} and {} strict = false
+    ok - {a = nil} and {b = nil} strict = false
+    ok - {a = nil} and {b = nil, c = nil} strict = false
+    ok - nil and box.NULL strict = false
+    ok - box.NULL and nil strict = false
+    not ok - {} and {a = nil} strict = true
+      ---
+      strict: true
+      expected: key a
+      got: nil
+      ...
+    not ok - {a = nil} and {} strict = true
+      ---
+      path: //a
+      strict: true
+      expected: nil
+      got: cdata
+      ...
+    not ok - {a = nil} and {b = nil} strict = true
+      ---
+      path: //a
+      strict: true
+      expected: nil
+      got: cdata
+      ...
+    not ok - {a = nil} and {b = nil, c = nil} strict = true
+      ---
+      path: //a
+      strict: true
+      expected: nil
+      got: cdata
+      ...
+    not ok - nil and box.NULL strict = true
+      ---
+      got: nil
+      expected: cdata
+      strict: true
+      ...
+    not ok - box.NULL and nil strict = true
+      ---
+      got: cdata
+      expected: nil
+      strict: true
+      ...
     # is_deeply: end
 not ok - failed subtests
   ---
-  planned: 6
-  failed: 2
+  planned: 18
+  failed: 8
   ...
     # like
     1..2
@@ -148,4 +213,4 @@ not ok - failed subtests
     ok - unlike(abcde, acd)
     # like: end
 ok - like
-# failed subtest: 15
+# failed subtest: 19
diff --git a/test/app-tap/tap.test.lua b/test/app-tap/tap.test.lua
index 0e1de7f1c..8a70e1c5d 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(40) -- plan to run 40 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()' }
@@ -60,6 +60,19 @@ test:iscdata(10, 'int', 'cdata type')
 test:iscdata(ffi.new('int', 10), 'int', 'cdata type')
 test:iscdata(ffi.new('unsigned int', 10), 'int', 'cdata type')
 
+--
+-- gh-4125: Strict nulls comparisons.
+--
+test.strict = true
+test:is(box.NULL, nil, "box.NULL != nil")
+test:is(nil, box.NULL, "nil != box.NULL")
+test:is(box.NULL, box.NULL, "box.NULL == box.NULL")
+test:is(nil, nil, "nil == nil")
+test:isnt(box.NULL, nil, "box.NULL != nil")
+test:isnt(nil, box.NULL, "nil != box.NULL")
+test:isnt(box.NULL, box.NULL, "box.NULL == box.NULL")
+test:isnt(nil, nil, "nil == nil")
+test.strict = false
 --
 -- Any test also can create unlimited number of sub tests.
 -- Subtest with callbacks (preferred).
@@ -118,7 +131,7 @@ end)
 
 
 test:test('is_deeply', function(t)
-    t:plan(6)
+    t:plan(18)
 
     t:is_deeply(1, 1, '1 and 1')
     t:is_deeply('abc', 'abc', 'abc and abc')
@@ -127,6 +140,28 @@ test:test('is_deeply', function(t)
     t:is_deeply({1}, {2}, '{1} and {2}')
     t:is_deeply({1, 2, { 3, 4 }}, {1, 2, { 3, 5 }}, '{1,2,{3,4}} and {1,2,{3,5}}')
 
+    --
+    -- gh-4125: is_deeply inconsistently works with box.NULL.
+    --
+    t:is_deeply({}, {a = box.NULL}, '{} and {a = nil} strict = false')
+    t:is_deeply({a = box.NULL}, {}, '{a = nil} and {} strict = false')
+    t:is_deeply({a = box.NULL}, {b = box.NULL},
+                '{a = nil} and {b = nil} strict = false')
+    t:is_deeply({a = box.NULL}, {b = box.NULL, c = box.NULL},
+                '{a = nil} and {b = nil, c = nil} strict = false')
+    t:is_deeply(nil, box.NULL, 'nil and box.NULL strict = false')
+    t:is_deeply(box.NULL, nil, 'box.NULL and nil strict = false')
+
+    t.strict = true
+    t:is_deeply({}, {a = box.NULL}, '{} and {a = nil} strict = true')
+    t:is_deeply({a = box.NULL}, {}, '{a = nil} and {} strict = true')
+    t:is_deeply({a = box.NULL}, {b = box.NULL},
+                '{a = nil} and {b = nil} strict = true')
+    t:is_deeply({a = box.NULL}, {b = box.NULL, c = box.NULL},
+                '{a = nil} and {b = nil, c = nil} strict = true')
+    t:is_deeply(nil, box.NULL, 'nil and box.NULL strict = true')
+    t:is_deeply(box.NULL, nil, 'box.NULL and nil strict = true')
+    t.strict = false
 end)
 
 
-- 
2.21.0

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

* [tarantool-patches] Re: [PATCH v2 1/1] tap: fix is_deeply box.NULL corner cases
  2019-04-24  9:37 ` [tarantool-patches] [PATCH v2 " Kirill Shcherbatov
@ 2019-04-24 11:23   ` Vladislav Shpilevoy
  2019-04-24 11:56     ` Kirill Shcherbatov
  2019-04-24 11:57   ` Alexander Turenko
  1 sibling, 1 reply; 11+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-24 11:23 UTC (permalink / raw)
  To: Kirill Shcherbatov, tarantool-patches

Hi! Thanks for the patch! See 6 comments below
and some fixes on the branch.

> diff --git a/src/lua/tap.lua b/src/lua/tap.lua
> index 546d15392..866786e6f 100644
> --- a/src/lua/tap.lua
> +++ b/src/lua/tap.lua
> @@ -91,13 +91,18 @@ local function cmpdeeply(got, expected, extra)
>  
>      if ffi.istype('bool', got) then got = (got == 1) end
>      if ffi.istype('bool', expected) then expected = (expected == 1) end
> -    if got == nil and expected == nil then return true end
> +    if extra.strict == false then

1. I would rather use 'not extra.strict' instead of explicit
comparison with true/false here and in other places, but up to
you, we do not have such strict rules for Lua.

> +        -- Convert all box.NULLs to LUA nil representation.
> +        if got == box.NULL then got = nil end
> +        if expected == box.NULL then expected = nil end

2. These conversions would not be needed, if you just added
'and extra.strict' to the check below. Just like you did
for 'is' and 'isnt' - compare types only when strict is set.

> +    end
>  
>      if type(got) ~= type(expected) then
>          extra.got = type(got)
>          extra.expected = type(expected)
>          return false
>      end
> +    if got == nil and expected == nil then return true end

3. This line is excessive. Next 'if' will detect that
type(got) ~= 'table' and will return 'got == expected'.

>  
>      if type(got) ~= 'table' then
>          extra.got = got
> @@ -117,8 +122,9 @@ local function cmpdeeply(got, expected, extra)
>      end
>  
>      -- check if expected contains more keys then got
> -    for i, _ in pairs(expected) do
> -        if visited_keys[i] ~= true then
> +    for i, v in pairs(expected) do
> +        if visited_keys[i] ~= true and
> +           (extra.strict == true or v ~= box.NULL) then

4. 'extra.strict == true' -> 'extra.strict' and it would fit
one line, but up to you as I said.

>              extra.expected = 'key ' .. tostring(i)
>              extra.got = 'nil'
>              return false
> @@ -148,14 +154,18 @@ local function is(test, got, expected, message, extra)
>      extra = extra or {}
>      extra.got = got
>      extra.expected = expected
> -    return ok(test, got == expected, message, extra)
> +    local rc = (test.strict == false or type(got) == type(expected)) and
> +               got == expected
> +    return ok(test, rc, message, extra)
>  end
>  
>  local function isnt(test, got, unexpected, message, extra)
>      extra = extra or {}
>      extra.got = got
>      extra.unexpected = unexpected
> -    return ok(test, got ~= unexpected, message, extra)
> +    local rc = (test.strict == true and type(got) ~= type(unexpected)) or
> +               got ~= unexpected
> +    return ok(test, rc, message, extra)
>  end
> diff --git a/test/app-tap/tap.result b/test/app-tap/tap.result
> index 3e7882331..13fa4fbcd 100644
> --- a/test/app-tap/tap.result
> +++ b/test/app-tap/tap.result
> @@ -69,6 +69,24 @@ not ok - cdata type
>    expected: ctype<int>
>    got: ctype<unsigned int>
>    ...
> +not ok - box.NULL != nil
> +  ---
> +  got: null

5. The result confuses. Looks like you checked box.NULL != nil
with strict = true and got 'not ok' on that -> box.NULL == nil
in strict mode. Lets better for all 'test:is' use '==' in comments
instead of '!='. And vice versa for 'test:isnt'. In such a case
you will see the output

    not ok - box.NULL == nil

Then it will be obvious that you tried to compare box.NULL and nil,
and failed at it.

> +  ...
> +not ok - nil != box.NULL
> +  ---
> +  expected: null
> +  ...
> +ok - box.NULL == box.NULL
> +ok - nil == nil
> +ok - box.NULL != nil
> +ok - nil != box.NULL
> +not ok - box.NULL == box.NULL
> +  ---
> +  unexpected: null
> +  got: null
> +  ...
> +not ok - nil == nil
>      # subtest 1
>      1..2
>      ok - true
> diff --git a/test/app-tap/tap.test.lua b/test/app-tap/tap.test.lua
> index 0e1de7f1c..8a70e1c5d 100755
> --- a/test/app-tap/tap.test.lua
> +++ b/test/app-tap/tap.test.lua
> @@ -127,6 +140,28 @@ test:test('is_deeply', function(t)
>      t:is_deeply({1}, {2}, '{1} and {2}')
>      t:is_deeply({1, 2, { 3, 4 }}, {1, 2, { 3, 5 }}, '{1,2,{3,4}} and {1,2,{3,5}}')
>  
> +    --
> +    -- gh-4125: is_deeply inconsistently works with box.NULL.
> +    --
> +    t:is_deeply({}, {a = box.NULL}, '{} and {a = nil} strict = false')
> +    t:is_deeply({a = box.NULL}, {}, '{a = nil} and {} strict = false')
> +    t:is_deeply({a = box.NULL}, {b = box.NULL},
> +                '{a = nil} and {b = nil} strict = false')
> +    t:is_deeply({a = box.NULL}, {b = box.NULL, c = box.NULL},
> +                '{a = nil} and {b = nil, c = nil} strict = false')
> +    t:is_deeply(nil, box.NULL, 'nil and box.NULL strict = false')
> +    t:is_deeply(box.NULL, nil, 'box.NULL and nil strict = false')
> +
> +    t.strict = true
> +    t:is_deeply({}, {a = box.NULL}, '{} and {a = nil} strict = true')
> +    t:is_deeply({a = box.NULL}, {}, '{a = nil} and {} strict = true')
> +    t:is_deeply({a = box.NULL}, {b = box.NULL},
> +                '{a = nil} and {b = nil} strict = true')
> +    t:is_deeply({a = box.NULL}, {b = box.NULL, c = box.NULL},
> +                '{a = nil} and {b = nil, c = nil} strict = true')
> +    t:is_deeply(nil, box.NULL, 'nil and box.NULL strict = true')
> +    t:is_deeply(box.NULL, nil, 'box.NULL and nil strict = true')
> +    t.strict = false

6. Great, I've just added one new test that
{a = box.NULL} equals {a = box.NULL}.

>  end)
>  
>  
> -- 
> 2.21.0
> 

Please, find my review fixes pasted below and on the branch:

=========================================================================
diff --git a/src/lua/tap.lua b/src/lua/tap.lua
index 866786e6f..94b080d5a 100644
--- a/src/lua/tap.lua
+++ b/src/lua/tap.lua
@@ -91,20 +91,14 @@ local function cmpdeeply(got, expected, extra)
 
     if ffi.istype('bool', got) then got = (got == 1) end
     if ffi.istype('bool', expected) then expected = (expected == 1) end
-    if extra.strict == false then
-        -- Convert all box.NULLs to LUA nil representation.
-        if got == box.NULL then got = nil end
-        if expected == box.NULL then expected = nil end
-    end
 
-    if type(got) ~= type(expected) then
+    if extra.strict and type(got) ~= type(expected) then
         extra.got = type(got)
         extra.expected = type(expected)
         return false
     end
-    if got == nil and expected == nil then return true end
 
-    if type(got) ~= 'table' then
+    if type(got) ~= 'table' or type(expected) ~= 'table' then
         extra.got = got
         extra.expected = expected
         return got == expected
@@ -123,8 +117,7 @@ local function cmpdeeply(got, expected, extra)
 
     -- check if expected contains more keys then got
     for i, v in pairs(expected) do
-        if visited_keys[i] ~= true and
-           (extra.strict == true or v ~= box.NULL) then
+        if visited_keys[i] ~= true and (extra.strict or v ~= box.NULL) then
             extra.expected = 'key ' .. tostring(i)
             extra.got = 'nil'
             return false
diff --git a/test/app-tap/tap.result b/test/app-tap/tap.result
index 13fa4fbcd..db56fd720 100644
--- a/test/app-tap/tap.result
+++ b/test/app-tap/tap.result
@@ -1,5 +1,5 @@
 TAP version 13
-1..40
+1..42
 ok - true
 ok - extra information is not printed on success
 not ok - extra printed using yaml only on failure
@@ -137,7 +137,7 @@ not ok - failed subtests
   failed: 1
   ...
     # is_deeply
-    1..18
+    1..20
     ok - 1 and 1
     ok - abc and abc
     ok - empty tables
@@ -162,6 +162,7 @@ not ok - failed subtests
     ok - {a = nil} and {b = nil, c = nil} strict = false
     ok - nil and box.NULL strict = false
     ok - box.NULL and nil strict = false
+    ok - {a = nil} and {a = nil} strict false
     not ok - {} and {a = nil} strict = true
       ---
       strict: true
@@ -201,10 +202,11 @@ not ok - failed subtests
       expected: nil
       strict: true
       ...
+    ok - {a = nil} and {a = nil} strict true
     # is_deeply: end
 not ok - failed subtests
   ---
-  planned: 18
+  planned: 20
   failed: 8
   ...
     # like
@@ -213,4 +215,22 @@ not ok - failed subtests
     ok - unlike(abcde, acd)
     # like: end
 ok - like
-# failed subtest: 19
+not ok - compare {1, 2, 3} and '200'
+  ---
+  strict: false
+  expected: '200'
+  got:
+  - 1
+  - 2
+  - 3
+  ...
+not ok - compare '200' and {1, 2, 3}
+  ---
+  strict: false
+  expected:
+  - 1
+  - 2
+  - 3
+  got: '200'
+  ...
+# failed subtest: 21
diff --git a/test/app-tap/tap.test.lua b/test/app-tap/tap.test.lua
index 8a70e1c5d..7c8948f4d 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(40) -- plan to run 40 test
+test:plan(42)
 test:ok(true, 'true') -- basic function
 local extra = { state = 'some userful information to debug on failure',
         details = 'a table argument formatted using yaml.encode()' }
@@ -131,7 +131,7 @@ end)
 
 
 test:test('is_deeply', function(t)
-    t:plan(18)
+    t:plan(20)
 
     t:is_deeply(1, 1, '1 and 1')
     t:is_deeply('abc', 'abc', 'abc and abc')
@@ -151,6 +151,8 @@ test:test('is_deeply', function(t)
                 '{a = nil} and {b = nil, c = nil} strict = false')
     t:is_deeply(nil, box.NULL, 'nil and box.NULL strict = false')
     t:is_deeply(box.NULL, nil, 'box.NULL and nil strict = false')
+    t:is_deeply({a = box.NULL}, {a = box.NULL},
+                '{a = nil} and {a = nil} strict false')
 
     t.strict = true
     t:is_deeply({}, {a = box.NULL}, '{} and {a = nil} strict = true')
@@ -161,6 +163,8 @@ test:test('is_deeply', function(t)
                 '{a = nil} and {b = nil, c = nil} strict = true')
     t:is_deeply(nil, box.NULL, 'nil and box.NULL strict = true')
     t:is_deeply(box.NULL, nil, 'box.NULL and nil strict = true')
+    t:is_deeply({a = box.NULL}, {a = box.NULL},
+                '{a = nil} and {a = nil} strict true')
     t.strict = false
 end)
 
@@ -171,6 +175,13 @@ test:test('like', function(t)
     t:unlike('abcde', 'acd', 'unlike(abcde, acd)')
 end)
 
+--
+-- Test, that in case of not strict comparison the order of
+-- arguments does not matter.
+--
+test:is_deeply({1, 2, 3}, '200', "compare {1, 2, 3} and '200'")
+test:is_deeply('200', {1, 2, 3}, "compare '200' and {1, 2, 3}")
+
 --
 -- Finish root test. Since we used non-callback variant, we have to
 -- call check explicitly.

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

* [tarantool-patches] Re: [PATCH v2 1/1] tap: fix is_deeply box.NULL corner cases
  2019-04-24 11:23   ` [tarantool-patches] " Vladislav Shpilevoy
@ 2019-04-24 11:56     ` Kirill Shcherbatov
  2019-04-24 12:01       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 11+ messages in thread
From: Kirill Shcherbatov @ 2019-04-24 11:56 UTC (permalink / raw)
  To: tarantool-patches, Vladislav Shpilevoy

On 24.04.2019 14:23, Vladislav Shpilevoy wrote:
> Hi! Thanks for the patch! See 6 comments below
> and some fixes on the branch.
Hi! Thank you for review.

> 1. I would rather use 'not extra.strict' instead of explicit
> comparison with true/false here and in other places, but up to
> you, we do not have such strict rules for Lua.
> 2. These conversions would not be needed, if you just added
> 'and extra.strict' to the check below. Just like you did
> for 'is' and 'isnt' - compare types only when strict is set.
> 3. This line is excessive. Next 'if' will detect that
> type(got) ~= 'table' and will return 'got == expected'.
> 4. 'extra.strict == true' -> 'extra.strict' and it would fit
> one line, but up to you as I said.
Your changes are looking good for me.

> 5. The result confuses. Looks like you checked box.NULL != nil
> with strict = true and got 'not ok' on that -> box.NULL == nil
> in strict mode. Lets better for all 'test:is' use '==' in comments
> instead of '!='. And vice versa for 'test:isnt'. In such a case
> you will see the output
> 
>     not ok - box.NULL == nil
> 
> Then it will be obvious that you tried to compare box.NULL and nil,
> and failed at it.
Fixed.

> 6. Great, I've just added one new test that
> {a = box.NULL} equals {a = box.NULL}.
Tnx!

=======================================================

The tap:is_deeply call used to return inconsistent result
processing an empty tuple and tuple that has no values:
is_deeply({a = box.NULL}, {})  == true
is_deeply({}, {a = box.NULL}) == false
Fixed to return true by default in such case. You may also
set tap.strict = true to change this behaviour.

@TarantoolBot document
Title: tap test new flag 'strict'
In some scenarios it is convenient to distinguish box.NULL and
nil in tap:is(), tap:isnt(), tap:is_deeply() tests.
For example, {result='Success'} and {result='Success', error=None}
are different HTTP responses. You may set t.strict = true to
behave such way.
Example:
t = require('tap').test('123')
t.strict = true
t:is_deeply({a = box.NULL}, {}) -- false

Closes #4125
---
 src/lua/tap.lua           | 19 +++++---
 test/app-tap/tap.result   | 95 ++++++++++++++++++++++++++++++++++++---
 test/app-tap/tap.test.lua | 50 ++++++++++++++++++++-
 3 files changed, 150 insertions(+), 14 deletions(-)

diff --git a/src/lua/tap.lua b/src/lua/tap.lua
index 546d15392..94b080d5a 100644
--- a/src/lua/tap.lua
+++ b/src/lua/tap.lua
@@ -91,15 +91,14 @@ local function cmpdeeply(got, expected, extra)
 
     if ffi.istype('bool', got) then got = (got == 1) end
     if ffi.istype('bool', expected) then expected = (expected == 1) end
-    if got == nil and expected == nil then return true end
 
-    if type(got) ~= type(expected) then
+    if extra.strict and type(got) ~= type(expected) then
         extra.got = type(got)
         extra.expected = type(expected)
         return false
     end
 
-    if type(got) ~= 'table' then
+    if type(got) ~= 'table' or type(expected) ~= 'table' then
         extra.got = got
         extra.expected = expected
         return got == expected
@@ -117,8 +116,8 @@ local function cmpdeeply(got, expected, extra)
     end
 
     -- check if expected contains more keys then got
-    for i, _ in pairs(expected) do
-        if visited_keys[i] ~= true then
+    for i, v in pairs(expected) do
+        if visited_keys[i] ~= true and (extra.strict or v ~= box.NULL) then
             extra.expected = 'key ' .. tostring(i)
             extra.got = 'nil'
             return false
@@ -148,14 +147,18 @@ local function is(test, got, expected, message, extra)
     extra = extra or {}
     extra.got = got
     extra.expected = expected
-    return ok(test, got == expected, message, extra)
+    local rc = (test.strict == false or type(got) == type(expected)) and
+               got == expected
+    return ok(test, rc, message, extra)
 end
 
 local function isnt(test, got, unexpected, message, extra)
     extra = extra or {}
     extra.got = got
     extra.unexpected = unexpected
-    return ok(test, got ~= unexpected, message, extra)
+    local rc = (test.strict == true and type(got) ~= type(unexpected)) or
+               got ~= unexpected
+    return ok(test, rc, message, extra)
 end
 
 
@@ -163,6 +166,7 @@ local function is_deeply(test, got, expected, message, extra)
     extra = extra or {}
     extra.got = got
     extra.expected = expected
+    extra.strict = test.strict
     return ok(test, cmpdeeply(got, expected, extra), message, extra)
 end
 
@@ -225,6 +229,7 @@ local function test(parent, name, fun, ...)
         failed  = 0;
         planned = 0;
         trace   = parent == nil and true or parent.trace;
+        strict = false;
     }, test_mt)
     if fun ~= nil then
         test:diag('%s', test.name)
diff --git a/test/app-tap/tap.result b/test/app-tap/tap.result
index 3e7882331..12bf86ec2 100644
--- a/test/app-tap/tap.result
+++ b/test/app-tap/tap.result
@@ -1,5 +1,5 @@
 TAP version 13
-1..32
+1..42
 ok - true
 ok - extra information is not printed on success
 not ok - extra printed using yaml only on failure
@@ -69,6 +69,24 @@ not ok - cdata type
   expected: ctype<int>
   got: ctype<unsigned int>
   ...
+not ok - box.NULL == nil strict = true
+  ---
+  got: null
+  ...
+not ok - nil == box.NULL strict = true
+  ---
+  expected: null
+  ...
+ok - box.NULL == box.NULL strict = true
+ok - nil == nil strict = true
+ok - box.NULL != nil strict = true
+ok - nil != box.NULL strict = true
+not ok - box.NULL != box.NULL strict = true
+  ---
+  unexpected: null
+  got: null
+  ...
+not ok - nil != nil strict = true
     # subtest 1
     1..2
     ok - true
@@ -119,7 +137,7 @@ not ok - failed subtests
   failed: 1
   ...
     # is_deeply
-    1..6
+    1..20
     ok - 1 and 1
     ok - abc and abc
     ok - empty tables
@@ -127,20 +145,69 @@ not ok - failed subtests
     not ok - {1} and {2}
       ---
       path: //1
+      strict: false
       expected: 2
       got: 1
       ...
     not ok - {1,2,{3,4}} and {1,2,{3,5}}
       ---
       path: //3/2
+      strict: false
       expected: 5
       got: 4
       ...
+    ok - {} and {a = box.NULL} strict = false
+    ok - {a = box.NULL} and {} strict = false
+    ok - {a = box.NULL} and {b = box.NULL} strict = false
+    ok - {a = box.NULL} and {b = box.NULL, c = box.NULL} strict = false
+    ok - nil and box.NULL strict = false
+    ok - box.NULL and nil strict = false
+    ok - {a = box.NULL} and {a = box.NULL} strict false
+    not ok - {} and {a = box.NULL} strict = true
+      ---
+      strict: true
+      expected: key a
+      got: nil
+      ...
+    not ok - {a = box.NULL} and {} strict = true
+      ---
+      path: //a
+      strict: true
+      expected: nil
+      got: cdata
+      ...
+    not ok - {a = box.NULL} and {b = box.NULL} strict = true
+      ---
+      path: //a
+      strict: true
+      expected: nil
+      got: cdata
+      ...
+    not ok - {a = box.NULL} and {b = box.NULL, c = box.NULL} strict = true
+      ---
+      path: //a
+      strict: true
+      expected: nil
+      got: cdata
+      ...
+    not ok - nil and box.NULL strict = true
+      ---
+      got: nil
+      expected: cdata
+      strict: true
+      ...
+    not ok - box.NULL and nil strict = true
+      ---
+      got: cdata
+      expected: nil
+      strict: true
+      ...
+    ok - {a = box.NULL} and {a = box.NULL} strict true
     # is_deeply: end
 not ok - failed subtests
   ---
-  planned: 6
-  failed: 2
+  planned: 20
+  failed: 8
   ...
     # like
     1..2
@@ -148,4 +215,22 @@ not ok - failed subtests
     ok - unlike(abcde, acd)
     # like: end
 ok - like
-# failed subtest: 15
+not ok - compare {1, 2, 3} and '200'
+  ---
+  strict: false
+  expected: '200'
+  got:
+  - 1
+  - 2
+  - 3
+  ...
+not ok - compare '200' and {1, 2, 3}
+  ---
+  strict: false
+  expected:
+  - 1
+  - 2
+  - 3
+  got: '200'
+  ...
+# failed subtest: 21
diff --git a/test/app-tap/tap.test.lua b/test/app-tap/tap.test.lua
index 0e1de7f1c..e2a78f630 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(42)
 test:ok(true, 'true') -- basic function
 local extra = { state = 'some userful information to debug on failure',
         details = 'a table argument formatted using yaml.encode()' }
@@ -60,6 +60,19 @@ test:iscdata(10, 'int', 'cdata type')
 test:iscdata(ffi.new('int', 10), 'int', 'cdata type')
 test:iscdata(ffi.new('unsigned int', 10), 'int', 'cdata type')
 
+--
+-- gh-4125: Strict nulls comparisons.
+--
+test.strict = true
+test:is(box.NULL, nil, "box.NULL == nil strict = true")
+test:is(nil, box.NULL, "nil == box.NULL strict = true")
+test:is(box.NULL, box.NULL, "box.NULL == box.NULL strict = true")
+test:is(nil, nil, "nil == nil strict = true")
+test:isnt(box.NULL, nil, "box.NULL != nil strict = true")
+test:isnt(nil, box.NULL, "nil != box.NULL strict = true")
+test:isnt(box.NULL, box.NULL, "box.NULL != box.NULL strict = true")
+test:isnt(nil, nil, "nil != nil strict = true")
+test.strict = false
 --
 -- Any test also can create unlimited number of sub tests.
 -- Subtest with callbacks (preferred).
@@ -118,7 +131,7 @@ end)
 
 
 test:test('is_deeply', function(t)
-    t:plan(6)
+    t:plan(20)
 
     t:is_deeply(1, 1, '1 and 1')
     t:is_deeply('abc', 'abc', 'abc and abc')
@@ -127,6 +140,32 @@ test:test('is_deeply', function(t)
     t:is_deeply({1}, {2}, '{1} and {2}')
     t:is_deeply({1, 2, { 3, 4 }}, {1, 2, { 3, 5 }}, '{1,2,{3,4}} and {1,2,{3,5}}')
 
+    --
+    -- gh-4125: is_deeply inconsistently works with box.NULL.
+    --
+    t:is_deeply({}, {a = box.NULL}, '{} and {a = box.NULL} strict = false')
+    t:is_deeply({a = box.NULL}, {}, '{a = box.NULL} and {} strict = false')
+    t:is_deeply({a = box.NULL}, {b = box.NULL},
+                '{a = box.NULL} and {b = box.NULL} strict = false')
+    t:is_deeply({a = box.NULL}, {b = box.NULL, c = box.NULL},
+                '{a = box.NULL} and {b = box.NULL, c = box.NULL} strict = false')
+    t:is_deeply(nil, box.NULL, 'nil and box.NULL strict = false')
+    t:is_deeply(box.NULL, nil, 'box.NULL and nil strict = false')
+    t:is_deeply({a = box.NULL}, {a = box.NULL},
+                '{a = box.NULL} and {a = box.NULL} strict false')
+
+    t.strict = true
+    t:is_deeply({}, {a = box.NULL}, '{} and {a = box.NULL} strict = true')
+    t:is_deeply({a = box.NULL}, {}, '{a = box.NULL} and {} strict = true')
+    t:is_deeply({a = box.NULL}, {b = box.NULL},
+                '{a = box.NULL} and {b = box.NULL} strict = true')
+    t:is_deeply({a = box.NULL}, {b = box.NULL, c = box.NULL},
+                '{a = box.NULL} and {b = box.NULL, c = box.NULL} strict = true')
+    t:is_deeply(nil, box.NULL, 'nil and box.NULL strict = true')
+    t:is_deeply(box.NULL, nil, 'box.NULL and nil strict = true')
+    t:is_deeply({a = box.NULL}, {a = box.NULL},
+                '{a = box.NULL} and {a = box.NULL} strict true')
+    t.strict = false
 end)
 
 
@@ -136,6 +175,13 @@ test:test('like', function(t)
     t:unlike('abcde', 'acd', 'unlike(abcde, acd)')
 end)
 
+--
+-- Test, that in case of not strict comparison the order of
+-- arguments does not matter.
+--
+test:is_deeply({1, 2, 3}, '200', "compare {1, 2, 3} and '200'")
+test:is_deeply('200', {1, 2, 3}, "compare '200' and {1, 2, 3}")
+
 --
 -- Finish root test. Since we used non-callback variant, we have to
 -- call check explicitly.
-- 
2.21.0

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

* [tarantool-patches] Re: [PATCH v2 1/1] tap: fix is_deeply box.NULL corner cases
  2019-04-24  9:37 ` [tarantool-patches] [PATCH v2 " Kirill Shcherbatov
  2019-04-24 11:23   ` [tarantool-patches] " Vladislav Shpilevoy
@ 2019-04-24 11:57   ` Alexander Turenko
  2019-04-24 12:02     ` Vladislav Shpilevoy
  1 sibling, 1 reply; 11+ messages in thread
From: Alexander Turenko @ 2019-04-24 11:57 UTC (permalink / raw)
  To: tarantool-patches, Kirill Shcherbatov; +Cc: v.shpilevoy

> @@ -163,6 +173,7 @@ local function is_deeply(test, got, expected, message, extra)
>      extra = extra or {}
>      extra.got = got
>      extra.expected = expected
> +    extra.strict = test.strict

The 'extra' parameter is needed to provide a user more information in
case of a fail. Here we use it to push options for cmpdeeply(). I would
add 'opts' parameter instead.

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

* [tarantool-patches] Re: [PATCH v2 1/1] tap: fix is_deeply box.NULL corner cases
  2019-04-24 11:56     ` Kirill Shcherbatov
@ 2019-04-24 12:01       ` Vladislav Shpilevoy
  0 siblings, 0 replies; 11+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-24 12:01 UTC (permalink / raw)
  To: Kirill Shcherbatov, tarantool-patches, Kirill Yukhin

LGTM.

On 24/04/2019 14:56, Kirill Shcherbatov wrote:
> On 24.04.2019 14:23, Vladislav Shpilevoy wrote:
>> Hi! Thanks for the patch! See 6 comments below
>> and some fixes on the branch.
> Hi! Thank you for review.
> 
>> 1. I would rather use 'not extra.strict' instead of explicit
>> comparison with true/false here and in other places, but up to
>> you, we do not have such strict rules for Lua.
>> 2. These conversions would not be needed, if you just added
>> 'and extra.strict' to the check below. Just like you did
>> for 'is' and 'isnt' - compare types only when strict is set.
>> 3. This line is excessive. Next 'if' will detect that
>> type(got) ~= 'table' and will return 'got == expected'.
>> 4. 'extra.strict == true' -> 'extra.strict' and it would fit
>> one line, but up to you as I said.
> Your changes are looking good for me.
> 
>> 5. The result confuses. Looks like you checked box.NULL != nil
>> with strict = true and got 'not ok' on that -> box.NULL == nil
>> in strict mode. Lets better for all 'test:is' use '==' in comments
>> instead of '!='. And vice versa for 'test:isnt'. In such a case
>> you will see the output
>>
>>     not ok - box.NULL == nil
>>
>> Then it will be obvious that you tried to compare box.NULL and nil,
>> and failed at it.
> Fixed.
> 
>> 6. Great, I've just added one new test that
>> {a = box.NULL} equals {a = box.NULL}.
> Tnx!
> 
> =======================================================
> 
> The tap:is_deeply call used to return inconsistent result
> processing an empty tuple and tuple that has no values:
> is_deeply({a = box.NULL}, {})  == true
> is_deeply({}, {a = box.NULL}) == false
> Fixed to return true by default in such case. You may also
> set tap.strict = true to change this behaviour.
> 
> @TarantoolBot document
> Title: tap test new flag 'strict'
> In some scenarios it is convenient to distinguish box.NULL and
> nil in tap:is(), tap:isnt(), tap:is_deeply() tests.
> For example, {result='Success'} and {result='Success', error=None}
> are different HTTP responses. You may set t.strict = true to
> behave such way.
> Example:
> t = require('tap').test('123')
> t.strict = true
> t:is_deeply({a = box.NULL}, {}) -- false
> 
> Closes #4125
> ---
>  src/lua/tap.lua           | 19 +++++---
>  test/app-tap/tap.result   | 95 ++++++++++++++++++++++++++++++++++++---
>  test/app-tap/tap.test.lua | 50 ++++++++++++++++++++-
>  3 files changed, 150 insertions(+), 14 deletions(-)
> 
> diff --git a/src/lua/tap.lua b/src/lua/tap.lua
> index 546d15392..94b080d5a 100644
> --- a/src/lua/tap.lua
> +++ b/src/lua/tap.lua
> @@ -91,15 +91,14 @@ local function cmpdeeply(got, expected, extra)
>  
>      if ffi.istype('bool', got) then got = (got == 1) end
>      if ffi.istype('bool', expected) then expected = (expected == 1) end
> -    if got == nil and expected == nil then return true end
>  
> -    if type(got) ~= type(expected) then
> +    if extra.strict and type(got) ~= type(expected) then
>          extra.got = type(got)
>          extra.expected = type(expected)
>          return false
>      end
>  
> -    if type(got) ~= 'table' then
> +    if type(got) ~= 'table' or type(expected) ~= 'table' then
>          extra.got = got
>          extra.expected = expected
>          return got == expected
> @@ -117,8 +116,8 @@ local function cmpdeeply(got, expected, extra)
>      end
>  
>      -- check if expected contains more keys then got
> -    for i, _ in pairs(expected) do
> -        if visited_keys[i] ~= true then
> +    for i, v in pairs(expected) do
> +        if visited_keys[i] ~= true and (extra.strict or v ~= box.NULL) then
>              extra.expected = 'key ' .. tostring(i)
>              extra.got = 'nil'
>              return false
> @@ -148,14 +147,18 @@ local function is(test, got, expected, message, extra)
>      extra = extra or {}
>      extra.got = got
>      extra.expected = expected
> -    return ok(test, got == expected, message, extra)
> +    local rc = (test.strict == false or type(got) == type(expected)) and
> +               got == expected
> +    return ok(test, rc, message, extra)
>  end
>  
>  local function isnt(test, got, unexpected, message, extra)
>      extra = extra or {}
>      extra.got = got
>      extra.unexpected = unexpected
> -    return ok(test, got ~= unexpected, message, extra)
> +    local rc = (test.strict == true and type(got) ~= type(unexpected)) or
> +               got ~= unexpected
> +    return ok(test, rc, message, extra)
>  end
>  
>  
> @@ -163,6 +166,7 @@ local function is_deeply(test, got, expected, message, extra)
>      extra = extra or {}
>      extra.got = got
>      extra.expected = expected
> +    extra.strict = test.strict
>      return ok(test, cmpdeeply(got, expected, extra), message, extra)
>  end
>  
> @@ -225,6 +229,7 @@ local function test(parent, name, fun, ...)
>          failed  = 0;
>          planned = 0;
>          trace   = parent == nil and true or parent.trace;
> +        strict = false;
>      }, test_mt)
>      if fun ~= nil then
>          test:diag('%s', test.name)
> diff --git a/test/app-tap/tap.result b/test/app-tap/tap.result
> index 3e7882331..12bf86ec2 100644
> --- a/test/app-tap/tap.result
> +++ b/test/app-tap/tap.result
> @@ -1,5 +1,5 @@
>  TAP version 13
> -1..32
> +1..42
>  ok - true
>  ok - extra information is not printed on success
>  not ok - extra printed using yaml only on failure
> @@ -69,6 +69,24 @@ not ok - cdata type
>    expected: ctype<int>
>    got: ctype<unsigned int>
>    ...
> +not ok - box.NULL == nil strict = true
> +  ---
> +  got: null
> +  ...
> +not ok - nil == box.NULL strict = true
> +  ---
> +  expected: null
> +  ...
> +ok - box.NULL == box.NULL strict = true
> +ok - nil == nil strict = true
> +ok - box.NULL != nil strict = true
> +ok - nil != box.NULL strict = true
> +not ok - box.NULL != box.NULL strict = true
> +  ---
> +  unexpected: null
> +  got: null
> +  ...
> +not ok - nil != nil strict = true
>      # subtest 1
>      1..2
>      ok - true
> @@ -119,7 +137,7 @@ not ok - failed subtests
>    failed: 1
>    ...
>      # is_deeply
> -    1..6
> +    1..20
>      ok - 1 and 1
>      ok - abc and abc
>      ok - empty tables
> @@ -127,20 +145,69 @@ not ok - failed subtests
>      not ok - {1} and {2}
>        ---
>        path: //1
> +      strict: false
>        expected: 2
>        got: 1
>        ...
>      not ok - {1,2,{3,4}} and {1,2,{3,5}}
>        ---
>        path: //3/2
> +      strict: false
>        expected: 5
>        got: 4
>        ...
> +    ok - {} and {a = box.NULL} strict = false
> +    ok - {a = box.NULL} and {} strict = false
> +    ok - {a = box.NULL} and {b = box.NULL} strict = false
> +    ok - {a = box.NULL} and {b = box.NULL, c = box.NULL} strict = false
> +    ok - nil and box.NULL strict = false
> +    ok - box.NULL and nil strict = false
> +    ok - {a = box.NULL} and {a = box.NULL} strict false
> +    not ok - {} and {a = box.NULL} strict = true
> +      ---
> +      strict: true
> +      expected: key a
> +      got: nil
> +      ...
> +    not ok - {a = box.NULL} and {} strict = true
> +      ---
> +      path: //a
> +      strict: true
> +      expected: nil
> +      got: cdata
> +      ...
> +    not ok - {a = box.NULL} and {b = box.NULL} strict = true
> +      ---
> +      path: //a
> +      strict: true
> +      expected: nil
> +      got: cdata
> +      ...
> +    not ok - {a = box.NULL} and {b = box.NULL, c = box.NULL} strict = true
> +      ---
> +      path: //a
> +      strict: true
> +      expected: nil
> +      got: cdata
> +      ...
> +    not ok - nil and box.NULL strict = true
> +      ---
> +      got: nil
> +      expected: cdata
> +      strict: true
> +      ...
> +    not ok - box.NULL and nil strict = true
> +      ---
> +      got: cdata
> +      expected: nil
> +      strict: true
> +      ...
> +    ok - {a = box.NULL} and {a = box.NULL} strict true
>      # is_deeply: end
>  not ok - failed subtests
>    ---
> -  planned: 6
> -  failed: 2
> +  planned: 20
> +  failed: 8
>    ...
>      # like
>      1..2
> @@ -148,4 +215,22 @@ not ok - failed subtests
>      ok - unlike(abcde, acd)
>      # like: end
>  ok - like
> -# failed subtest: 15
> +not ok - compare {1, 2, 3} and '200'
> +  ---
> +  strict: false
> +  expected: '200'
> +  got:
> +  - 1
> +  - 2
> +  - 3
> +  ...
> +not ok - compare '200' and {1, 2, 3}
> +  ---
> +  strict: false
> +  expected:
> +  - 1
> +  - 2
> +  - 3
> +  got: '200'
> +  ...
> +# failed subtest: 21
> diff --git a/test/app-tap/tap.test.lua b/test/app-tap/tap.test.lua
> index 0e1de7f1c..e2a78f630 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(42)
>  test:ok(true, 'true') -- basic function
>  local extra = { state = 'some userful information to debug on failure',
>          details = 'a table argument formatted using yaml.encode()' }
> @@ -60,6 +60,19 @@ test:iscdata(10, 'int', 'cdata type')
>  test:iscdata(ffi.new('int', 10), 'int', 'cdata type')
>  test:iscdata(ffi.new('unsigned int', 10), 'int', 'cdata type')
>  
> +--
> +-- gh-4125: Strict nulls comparisons.
> +--
> +test.strict = true
> +test:is(box.NULL, nil, "box.NULL == nil strict = true")
> +test:is(nil, box.NULL, "nil == box.NULL strict = true")
> +test:is(box.NULL, box.NULL, "box.NULL == box.NULL strict = true")
> +test:is(nil, nil, "nil == nil strict = true")
> +test:isnt(box.NULL, nil, "box.NULL != nil strict = true")
> +test:isnt(nil, box.NULL, "nil != box.NULL strict = true")
> +test:isnt(box.NULL, box.NULL, "box.NULL != box.NULL strict = true")
> +test:isnt(nil, nil, "nil != nil strict = true")
> +test.strict = false
>  --
>  -- Any test also can create unlimited number of sub tests.
>  -- Subtest with callbacks (preferred).
> @@ -118,7 +131,7 @@ end)
>  
>  
>  test:test('is_deeply', function(t)
> -    t:plan(6)
> +    t:plan(20)
>  
>      t:is_deeply(1, 1, '1 and 1')
>      t:is_deeply('abc', 'abc', 'abc and abc')
> @@ -127,6 +140,32 @@ test:test('is_deeply', function(t)
>      t:is_deeply({1}, {2}, '{1} and {2}')
>      t:is_deeply({1, 2, { 3, 4 }}, {1, 2, { 3, 5 }}, '{1,2,{3,4}} and {1,2,{3,5}}')
>  
> +    --
> +    -- gh-4125: is_deeply inconsistently works with box.NULL.
> +    --
> +    t:is_deeply({}, {a = box.NULL}, '{} and {a = box.NULL} strict = false')
> +    t:is_deeply({a = box.NULL}, {}, '{a = box.NULL} and {} strict = false')
> +    t:is_deeply({a = box.NULL}, {b = box.NULL},
> +                '{a = box.NULL} and {b = box.NULL} strict = false')
> +    t:is_deeply({a = box.NULL}, {b = box.NULL, c = box.NULL},
> +                '{a = box.NULL} and {b = box.NULL, c = box.NULL} strict = false')
> +    t:is_deeply(nil, box.NULL, 'nil and box.NULL strict = false')
> +    t:is_deeply(box.NULL, nil, 'box.NULL and nil strict = false')
> +    t:is_deeply({a = box.NULL}, {a = box.NULL},
> +                '{a = box.NULL} and {a = box.NULL} strict false')
> +
> +    t.strict = true
> +    t:is_deeply({}, {a = box.NULL}, '{} and {a = box.NULL} strict = true')
> +    t:is_deeply({a = box.NULL}, {}, '{a = box.NULL} and {} strict = true')
> +    t:is_deeply({a = box.NULL}, {b = box.NULL},
> +                '{a = box.NULL} and {b = box.NULL} strict = true')
> +    t:is_deeply({a = box.NULL}, {b = box.NULL, c = box.NULL},
> +                '{a = box.NULL} and {b = box.NULL, c = box.NULL} strict = true')
> +    t:is_deeply(nil, box.NULL, 'nil and box.NULL strict = true')
> +    t:is_deeply(box.NULL, nil, 'box.NULL and nil strict = true')
> +    t:is_deeply({a = box.NULL}, {a = box.NULL},
> +                '{a = box.NULL} and {a = box.NULL} strict true')
> +    t.strict = false
>  end)
>  
>  
> @@ -136,6 +175,13 @@ test:test('like', function(t)
>      t:unlike('abcde', 'acd', 'unlike(abcde, acd)')
>  end)
>  
> +--
> +-- Test, that in case of not strict comparison the order of
> +-- arguments does not matter.
> +--
> +test:is_deeply({1, 2, 3}, '200', "compare {1, 2, 3} and '200'")
> +test:is_deeply('200', {1, 2, 3}, "compare '200' and {1, 2, 3}")
> +
>  --
>  -- Finish root test. Since we used non-callback variant, we have to
>  -- call check explicitly.
> -- 
> 2.21.0
> 

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

* [tarantool-patches] Re: [PATCH v2 1/1] tap: fix is_deeply box.NULL corner cases
  2019-04-24 11:57   ` Alexander Turenko
@ 2019-04-24 12:02     ` Vladislav Shpilevoy
  2019-04-24 12:14       ` Alexander Turenko
  0 siblings, 1 reply; 11+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-24 12:02 UTC (permalink / raw)
  To: Alexander Turenko, tarantool-patches, Kirill Shcherbatov



On 24/04/2019 14:57, Alexander Turenko wrote:
>> @@ -163,6 +173,7 @@ local function is_deeply(test, got, expected, message, extra)
>>      extra = extra or {}
>>      extra.got = got
>>      extra.expected = expected
>> +    extra.strict = test.strict
> 
> The 'extra' parameter is needed to provide a user more information in
> case of a fail. Here we use it to push options for cmpdeeply(). I would
> add 'opts' parameter instead.
> 

If you do not like extra output about 'strict' value, then we
could make it 'nil' by default, not 'false', and it will not
be printed until someone starts to use it.

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

* [tarantool-patches] Re: [PATCH v2 1/1] tap: fix is_deeply box.NULL corner cases
  2019-04-24 12:02     ` Vladislav Shpilevoy
@ 2019-04-24 12:14       ` Alexander Turenko
  0 siblings, 0 replies; 11+ messages in thread
From: Alexander Turenko @ 2019-04-24 12:14 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches, Kirill Shcherbatov

On Wed, Apr 24, 2019 at 03:02:32PM +0300, Vladislav Shpilevoy wrote:
> 
> 
> On 24/04/2019 14:57, Alexander Turenko wrote:
> >> @@ -163,6 +173,7 @@ local function is_deeply(test, got, expected, message, extra)
> >>      extra = extra or {}
> >>      extra.got = got
> >>      extra.expected = expected
> >> +    extra.strict = test.strict
> > 
> > The 'extra' parameter is needed to provide a user more information in
> > case of a fail. Here we use it to push options for cmpdeeply(). I would
> > add 'opts' parameter instead.
> > 
> 
> If you do not like extra output about 'strict' value, then we
> could make it 'nil' by default, not 'false', and it will not
> be printed until someone starts to use it.

I'm ok if it is consistent with other such parameters. Kirill said it
is, so ok.

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

* [tarantool-patches] Re: [PATCH v1 1/1] tap: fix is_deeply box.NULL corner cases
  2019-04-23 12:55 [tarantool-patches] [PATCH v1 1/1] tap: fix is_deeply box.NULL corner cases Kirill Shcherbatov
  2019-04-23 14:12 ` [tarantool-patches] " Kirill Shcherbatov
  2019-04-24  9:37 ` [tarantool-patches] [PATCH v2 " Kirill Shcherbatov
@ 2019-04-25 10:29 ` Kirill Yukhin
  2 siblings, 0 replies; 11+ messages in thread
From: Kirill Yukhin @ 2019-04-25 10:29 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, Kirill Shcherbatov

Hello,

On 23 Apr 15:55, Kirill Shcherbatov wrote:
> The tap:is_deeply call used to return inconsistent result
> processing an empty tuple and tuple that has no values:
> is_deeply({a = box.NULL}, {})  == true
> is_deeply({}, {a = box.NULL}) == false
> Fixed to return true in such case.
> 
> We shouldn't return false in such case because of marshaling
> features covered by tests:
> is_deeply(decode(encode(t)), t) must be true for all t,
> including t = nil; decode(encode(nil)) == box.NULL.
> 
> Closes #4125
> ---
> Branch: http://github.com/tarantool/tarantool/tree/kshch/gh-4125-inconsistent-isdeeply-results
> Issue: https://github.com/tarantool/tarantool/issues/4125

I've checked your patch into master and 2.1 branch.

--
Regards, Kirill Yukhin

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

end of thread, other threads:[~2019-04-25 10:29 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-04-23 12:55 [tarantool-patches] [PATCH v1 1/1] tap: fix is_deeply box.NULL corner cases Kirill Shcherbatov
2019-04-23 14:12 ` [tarantool-patches] " Kirill Shcherbatov
2019-04-23 14:14   ` Vladislav Shpilevoy
2019-04-24  9:37 ` [tarantool-patches] [PATCH v2 " Kirill Shcherbatov
2019-04-24 11:23   ` [tarantool-patches] " Vladislav Shpilevoy
2019-04-24 11:56     ` Kirill Shcherbatov
2019-04-24 12:01       ` Vladislav Shpilevoy
2019-04-24 11:57   ` Alexander Turenko
2019-04-24 12:02     ` Vladislav Shpilevoy
2019-04-24 12:14       ` Alexander Turenko
2019-04-25 10:29 ` [tarantool-patches] Re: [PATCH v1 " Kirill Yukhin

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