From 64a70cf5941c3fa418587bd1852a4fd42cce1e27 Mon Sep 17 00:00:00 2001 From: Vadim Belman Date: Mon, 27 Jul 2020 14:05:00 -0400 Subject: [PATCH] Add a canary test The main tests are fudged with `skip` with which it is easy to miss when dispatching gets fixed. The canary test is fudged with `todo` and will result in a notification when it's about time to unfudge everything. --- S06-advanced/dispatching.t | 457 +++++++++++++++++++------------------ spectest.data | 1 + 2 files changed, 241 insertions(+), 217 deletions(-) diff --git a/S06-advanced/dispatching.t b/S06-advanced/dispatching.t index 52523d06f2..7c1765a3f1 100644 --- a/S06-advanced/dispatching.t +++ b/S06-advanced/dispatching.t @@ -1,259 +1,282 @@ use v6; use Test; use soft; -plan 4; - -#?rakudo todo 'Until 2020 dispatcher proposal is implemented' -subtest "Dispatcher Chain" => { - plan 13; - my @order; - my class C1 { - method foo(|) { @order.push: ::?CLASS.^name } - } - - my class C2 is C1 { - proto method foo(|) {*} - multi method foo(Str $s) { - @order.push: ::?CLASS.^name ~ "(Str)"; - nextsame; - } - multi method foo(Int $s) { - @order.push: ::?CLASS.^name ~ "(Int)"; - nextsame; - } - multi method foo(Num) { - @order.push: ::?CLASS.^name ~ "(Num)"; - nextsame +plan 6; + +#?rakudo todo "This is canary test. If this TODO passes then it's probably the time to unfudge the main tests" +# This block is to be removed when time comes. +{ + my class Foo { + method foo($v) { + $v * 2 } } - my class C3 is C2 { - method foo(|) { - @order.push: ::?CLASS.^name; + my class Bar is Foo { + multi method foo(Int $v) { nextsame } - } - - my class C4 is C3 { - proto method foo(|) {*} - multi method foo(Int:D $v) { - @order.push: ::?CLASS.^name ~ "(Int:D)"; - nextwith ~$v + multi method foo(Str $v) { + nextwith $v.Int } - multi method foo(Any) { - @order.push: ::?CLASS.^name ~ "(Any)"; - callsame - } - } - - my $inst; - - $inst = C3.new; - $inst.foo("bar"); - is-deeply @order.List, , "a multi-method doesn't break MRO dispatching"; - @order = []; - $inst.foo(42); - is-deeply @order.List, , "a multi-method dispatching works correctly"; - - $inst = C4.new; - @order = []; - $inst.foo("baz"); - is-deeply @order.List, , "multi being the first method in MRO still works"; - @order = []; - $inst.foo(13); - is-deeply @order.List, , "nextwith does what's expected"; - - my \proto := C2.^find_method('foo', :local, :no_fallback); - - nok proto.is_wrapped, "proto is not wrapped yet"; - my $wh1 = proto.wrap(my method foo-wrap(|) { @order.push: "foo-proto"; nextsame }); - ok proto.is_wrapped, "proto is wrapped now"; - - @order = []; - $inst.foo(""); - is-deeply @order.List, , "proto can be wrapped"; - - proto.unwrap($wh1); - @order = []; - $inst.foo(""); - is-deeply @order.List, , "proto can be unwrapped"; - - # This should be foo(Num) candidate - my \cand = proto.candidates[2]; - # Note that next* can't be used with blocks. - $wh1 = cand.wrap(-> *@ { @order.push('foo-num-wrap'); callsame }); - @order = []; - $inst.foo(pi); - is-deeply @order.List, , "we can wrap a candidate"; - - # We can even wrap a candidate with another multi. It works! - proto multi-wrap(|) {*} - multi multi-wrap(\SELF, Num) { - @order.push: "multi-wrap(Num)"; - nextsame - } - multi multi-wrap(\SELF, Any) { - @order.push: "multi-wrap(Any)"; - nextsame } - my $wh2 = cand.wrap(&multi-wrap); - @order = []; - $inst.foo(pi); - is-deeply @order.List, , "we can use a multi as a wrapper of a candidate"; - - cand.unwrap($wh1); - @order = []; - $inst.foo(pi); - is-deeply @order.List, , "we can unwrap a multi"; - - # Even nastier thing: wrap a candidate of our wrapper! - my $wwh = &multi-wrap.candidates[1].wrap(sub wrap-wrapper(|) { @order.push: 'cand-wrap'; nextsame }); - @order = []; - $inst.foo(pi); - is-deeply @order.List, , "we can use a multi as a wrapper of a candidate"; - - # Unwrap the method candidate from the second wrapper. We then get the original behavior. - cand.unwrap($wh2); - @order = []; - $inst.foo(pi); - is-deeply @order.List, , "we can use a multi as a wrapper of a candidate"; + my $obj = Bar.new; + is $obj.foo(21), 42, "Int is dispatched"; + is $obj.foo("11"), 22, "Str is dispatched"; } -#?rakudo todo 'Until 2020 dispatcher proposal is implemented' -subtest "Regression: nextcallee" => { - plan 2; - my @order; - my class C1 { - method foo(|) { - @order.push: ::?CLASS.^name +#?rakudo skip 'Until 2020 dispatcher proposal is implemented' +#?DOES 4 +{ + subtest "Dispatcher Chain" => { + plan 13; + my @order; + my class C1 { + method foo(|) { @order.push: ::?CLASS.^name } } - } - my class C2 is C1 { - method foo(|args) { - @order.push: ::?CLASS.^name; - my &callee = nextcallee; - self.&callee(|args) + + my class C2 is C1 { + proto method foo(|) {*} + multi method foo(Str $s) { + @order.push: ::?CLASS.^name ~ "(Str)"; + nextsame; + } + multi method foo(Int $s) { + @order.push: ::?CLASS.^name ~ "(Int)"; + nextsame; + } + multi method foo(Num) { + @order.push: ::?CLASS.^name ~ "(Num)"; + nextsame + } } - } - my class C3 is C2 { - method foo(|args) { - @order.push: ::?CLASS.^name; - nextsame + + my class C3 is C2 { + method foo(|) { + @order.push: ::?CLASS.^name; + nextsame + } } - } - my $inst = C3.new; - @order = []; - $inst.foo; - is-deeply @order.List, , "checkpoint"; - - C2.^find_method('foo', :no_fallback, :local) - .wrap( - sub (|args) { - @order.push: 'C2::foo::wrapper'; - my &callee = nextcallee; - &callee(|args) - }); - @order = []; - $inst.foo; - is-deeply @order.List, , "nextcallee doesn't break the dispatcher chain"; -} -#?rakudo todo 'Until 2020 dispatcher proposal is implemented' -subtest "Regression: broken chain" => { - plan 2; - # A stray $*NEXT-DISPATCHER could wrongfully be picked up by a dispatcher vivified by a nested routine invocation. - my @order; - my class C1 { - multi method foo { - @order.push: "C1::foo"; - $.bar; + my class C4 is C3 { + proto method foo(|) {*} + multi method foo(Int:D $v) { + @order.push: ::?CLASS.^name ~ "(Int:D)"; + nextwith ~$v + } + multi method foo(Any) { + @order.push: ::?CLASS.^name ~ "(Any)"; + callsame + } } - proto method bar(|) {*} - multi method bar { - @order.push: "C1::bar"; + my $inst; + + $inst = C3.new; + $inst.foo("bar"); + is-deeply @order.List, , "a multi-method doesn't break MRO dispatching"; + @order = []; + $inst.foo(42); + is-deeply @order.List, , "a multi-method dispatching works correctly"; + + $inst = C4.new; + @order = []; + $inst.foo("baz"); + is-deeply @order.List, , "multi being the first method in MRO still works"; + @order = []; + $inst.foo(13); + is-deeply @order.List, , "nextwith does what's expected"; + + my \proto := C2.^find_method('foo', :local, :no_fallback); + + nok proto.is_wrapped, "proto is not wrapped yet"; + my $wh1 = proto.wrap(my method foo-wrap(|) { @order.push: "foo-proto"; nextsame }); + ok proto.is_wrapped, "proto is wrapped now"; + + @order = []; + $inst.foo(""); + is-deeply @order.List, , "proto can be wrapped"; + + proto.unwrap($wh1); + @order = []; + $inst.foo(""); + is-deeply @order.List, , "proto can be unwrapped"; + + # This should be foo(Num) candidate + my \cand = proto.candidates[2]; + # Note that next* can't be used with blocks. + $wh1 = cand.wrap(-> *@ { @order.push('foo-num-wrap'); callsame }); + @order = []; + $inst.foo(pi); + is-deeply @order.List, , "we can wrap a candidate"; + + # We can even wrap a candidate with another multi. It works! + proto multi-wrap(|) {*} + multi multi-wrap(\SELF, Num) { + @order.push: "multi-wrap(Num)"; nextsame } - } - - my class C2 is C1 { - proto method bar(|) {*} - multi method bar { - @order.push: "C2::bar"; + multi multi-wrap(\SELF, Any) { + @order.push: "multi-wrap(Any)"; nextsame } - method foo { - @order.push: "C2::foo"; - nextsame; - } + my $wh2 = cand.wrap(&multi-wrap); + @order = []; + $inst.foo(pi); + is-deeply @order.List, , "we can use a multi as a wrapper of a candidate"; + + cand.unwrap($wh1); + @order = []; + $inst.foo(pi); + is-deeply @order.List, , "we can unwrap a multi"; + + # Even nastier thing: wrap a candidate of our wrapper! + my $wwh = &multi-wrap.candidates[1].wrap(sub wrap-wrapper(|) { @order.push: 'cand-wrap'; nextsame }); + @order = []; + $inst.foo(pi); + is-deeply @order.List, , "we can use a multi as a wrapper of a candidate"; + + # Unwrap the method candidate from the second wrapper. We then get the original behavior. + cand.unwrap($wh2); + @order = []; + $inst.foo(pi); + is-deeply @order.List, , "we can use a multi as a wrapper of a candidate"; } - my $inst = C2.new; - $inst.bar; - is-deeply @order.List, , "control: multi dispatches as expected"; - @order = []; - $inst.foo; - is-deeply @order.List, , "multi-dispatch is not broken"; -} - -# GH Raku/problem-solving#170 -#?rakudo todo 'Until 2020 dispatcher proposal is implemented' -subtest "Wrap parent's first multi-candidate" => { - plan 3; - my @order; - my $inst; - - my class C1 { - method foo(|) { - @order.push: 'C1::foo' + subtest "Regression: nextcallee" => { + plan 2; + my @order; + my class C1 { + method foo(|) { + @order.push: ::?CLASS.^name + } } - } - - my class C2 is C1 { - proto method foo(|) {*} - multi method foo(Int) { - @order.push: 'C2::foo(Int)'; - nextsame; + my class C2 is C1 { + method foo(|args) { + @order.push: ::?CLASS.^name; + my &callee = nextcallee; + self.&callee(|args) + } } - multi method foo(Any) { - @order.push: 'C2::foo(Any)'; - nextsame; + my class C3 is C2 { + method foo(|args) { + @order.push: ::?CLASS.^name; + nextsame + } } + my $inst = C3.new; + @order = []; + $inst.foo; + is-deeply @order.List, , "checkpoint"; + + C2.^find_method('foo', :no_fallback, :local) + .wrap( + sub (|args) { + @order.push: 'C2::foo::wrapper'; + my &callee = nextcallee; + &callee(|args) + }); + @order = []; + $inst.foo; + is-deeply @order.List, , "nextcallee doesn't break the dispatcher chain"; } - my class C3 is C2 { - method foo(|) { - @order.push: 'C3::foo'; - nextsame + subtest "Regression: broken chain" => { + plan 2; + # A stray $*NEXT-DISPATCHER could wrongfully be picked up by a dispatcher vivified by a nested routine invocation. + my @order; + my class C1 { + multi method foo { + @order.push: "C1::foo"; + $.bar; + } + + proto method bar(|) {*} + multi method bar { + @order.push: "C1::bar"; + nextsame + } + } + + my class C2 is C1 { + proto method bar(|) {*} + multi method bar { + @order.push: "C2::bar"; + nextsame + } + + method foo { + @order.push: "C2::foo"; + nextsame; + } } + + my $inst = C2.new; + $inst.bar; + is-deeply @order.List, , "control: multi dispatches as expected"; + @order = []; + $inst.foo; + is-deeply @order.List, , "multi-dispatch is not broken"; } - my @orig-order = ; - $inst = C3.new; - $inst.foo(42); - is-deeply @order, @orig-order, "control: multi-dispatch as expected"; + # GH Raku/problem-solving#170 + subtest "Wrap parent's first multi-candidate" => { + plan 3; + my @order; + my $inst; - my $wh = C2.^lookup('foo').candidates[0].wrap( - -> | { - @order.push: "C2::foo::wrapper"; - callsame + my class C1 { + method foo(|) { + @order.push: 'C1::foo' + } } - ); - @order = []; - $inst.foo(42); - is-deeply - @order.List, - , - "wrapping of the first candidate doesn't break the chain"; + my class C2 is C1 { + proto method foo(|) {*} + multi method foo(Int) { + @order.push: 'C2::foo(Int)'; + nextsame; + } + multi method foo(Any) { + @order.push: 'C2::foo(Any)'; + nextsame; + } + } - $wh.restore; + my class C3 is C2 { + method foo(|) { + @order.push: 'C3::foo'; + nextsame + } + } - @order = []; - $inst.foo(42); - is-deeply @order, @orig-order, "unwrapping of the candidate restores the order"; + my @orig-order = ; + $inst = C3.new; + $inst.foo(42); + is-deeply @order, @orig-order, "control: multi-dispatch as expected"; + + my $wh = C2.^lookup('foo').candidates[0].wrap( + -> | { + @order.push: "C2::foo::wrapper"; + callsame + } + ); + + @order = []; + $inst.foo(42); + is-deeply + @order.List, + , + "wrapping of the first candidate doesn't break the chain"; + + $wh.restore; + + @order = []; + $inst.foo(42); + is-deeply @order, @orig-order, "unwrapping of the candidate restores the order"; + } } done-testing; diff --git a/spectest.data b/spectest.data index 583f15608a..536a8ab548 100644 --- a/spectest.data +++ b/spectest.data @@ -484,6 +484,7 @@ S05-transliteration/trans-TR-operator.t S05-transliteration/trans-tr-lowercase-operator.t S05-transliteration/trans.t S05-transliteration/with-closure.t +S06-advanced/dispatching.t S06-advanced/callframe.t S06-advanced/callsame.t # TODO S06-advanced/dispatching.t