← Back to index

lib/BDD/Behave/Expectation.rakumod

356 / 390 lines covered (91.3%)

1unit module BDD::Behave::Expectation;23use BDD::Behave::Failure;4use BDD::Behave::Failures;56sub throw-unless-aggregating(Str $file, Int $line --> Nil) {7  my $aggregating = False;8  try { $aggregating = ?$*BEHAVE-AGGREGATING }9  return if $aggregating;10  die X::BDD::Behave::ExpectationFailed.new(:$file, :$line);11}12use BDD::Behave::Matcher;13use BDD::Behave::Matcher::Core;14use BDD::Behave::Matcher::Collection;15use BDD::Behave::Matcher::Type;16use BDD::Behave::Matcher::Numeric;17use BDD::Behave::Matcher::Boolean;18use BDD::Behave::Matcher::String;19use BDD::Behave::Matcher::Exception;20use BDD::Behave::Matcher::Change;21use BDD::Behave::Matcher::Async;22use BDD::Behave::Matcher::Custom;23use BDD::Behave::Mock::HaveReceived;2425class BetweenExpectation is export {26  has Mu   $.given;27  has Bool $.negated = False;28  has Str  $.file;29  has Int  $.line;30  has      $.min;31  has      $.max;32  has Bool $!exclusive = False;33  has      $!recorded-failure;3435  submethod BUILD(36    Mu :$given is raw, :$!negated, :$!file, :$!line, :$!min, :$!max,37  ) {38    $!given := $given;39  }4041  method validate(--> Nil) {42    my $matcher = BeBetweenMatcher.new(43      :min($!min), :max($!max), :exclusive($!exclusive),44    );45    my $matched = ?$matcher.matches($!given);46    my $passed  = $!negated ?? !$matched !! $matched;4748    if $passed {49      self.clear-failure;50      return;51    }5253    my $message = $!negated54      ?? $matcher.failure-message-negated($!given)55      !! $matcher.failure-message($!given);5657    self.replace-failure($message, $matcher);58  }5960  method replace-failure(Str $msg, $matcher --> Nil) {61    self.clear-failure;62    my $f = Failure.new(63      :file($!file),64      :line($!line),65      :given($!given),66      :expected($matcher.expected-value),67      :negated($!negated),68      :message($msg),69    );70    Failures.list.push($f);71    $!recorded-failure = $f;72  }7374  method clear-failure(--> Nil) {75    return unless $!recorded-failure.defined;76    my $list = Failures.list;77    my $idx  = $list.first({ $_ === $!recorded-failure }, :k);78    $list.splice($idx, 1) if $idx.defined;79    $!recorded-failure = Nil;80  }8182  method inclusive {83    $!exclusive = False;84    self.validate;85    self;86  }8788  method exclusive {89    $!exclusive = True;90    self.validate;91    self;92  }93}9495class WithinExpectation is export {96  has Mu   $.given;97  has Bool $.negated = False;98  has Str  $.file;99  has Int  $.line;100  has      $.delta;101102  submethod BUILD(103    Mu :$given is raw, :$!negated, :$!file, :$!line, :$!delta,104  ) {105    $!given := $given;106  }107108  method of($expected --> Nil) {109    my $matcher = BeWithinMatcher.new(:delta($!delta), :$expected);110    my $matched = ?$matcher.matches($!given);111    my $passed  = $!negated ?? !$matched !! $matched;112    return if $passed;113114    my $message = $!negated115      ?? $matcher.failure-message-negated($!given)116      !! $matcher.failure-message($!given);117118    my $f = Failure.new(119      :file($!file),120      :line($!line),121      :given($!given),122      :expected($matcher.expected-value),123      :negated($!negated),124      :message($message),125    );126    Failures.list.push($f);127    throw-unless-aggregating($!file, $!line);128  }129}130131class ChangeExpectation is export {132  has Mu   $.given;133  has      &.observable;134  has Bool $.negated = False;135  has Str  $.file;136  has Int  $.line;137  has      $!expected-from;138  has Bool $!has-from = False;139  has      $!expected-to;140  has Bool $!has-to = False;141  has      $!expected-by;142  has Bool $!has-by = False;143  has      $!expected-by-at-least;144  has Bool $!has-by-at-least = False;145  has      $!expected-by-at-most;146  has Bool $!has-by-at-most = False;147  has      $!before-value;148  has      $!after-value;149  has Bool $!callable-given = True;150  has Bool $!ran = False;151  has Bool $!passed = False;152  has      $!recorded-failure;153154  submethod BUILD(155    Mu :$given is raw, :&observable, :$!negated, :$!file, :$!line,156  ) {157    $!given      := $given;158    &!observable  = &observable;159  }160161  method !run-block(--> Nil) {162    return if $!ran;163    $!ran = True;164    $!callable-given = ?($!given ~~ Callable);165    return unless $!callable-given;166    $!before-value = &!observable.();167    $!given.();168    $!after-value  = &!observable.();169  }170171  method !build-matcher() {172    my $m = ChangeMatcher.new(:&!observable);173    $m.callable-given      = $!callable-given;174    $m.action-ran          = $!callable-given;175    $m.before-value        = $!before-value;176    $m.after-value         = $!after-value;177    $m.expected-from       = $!expected-from;178    $m.has-from            = $!has-from;179    $m.expected-to         = $!expected-to;180    $m.has-to              = $!has-to;181    $m.expected-by         = $!expected-by;182    $m.has-by              = $!has-by;183    $m.expected-by-at-least = $!expected-by-at-least;184    $m.has-by-at-least     = $!has-by-at-least;185    $m.expected-by-at-most = $!expected-by-at-most;186    $m.has-by-at-most      = $!has-by-at-most;187    $m;188  }189190  method validate(--> Nil) {191    self!run-block;192    my $matcher = self!build-matcher;193    my $matched = ?$matcher.check-captured;194    $!passed = $!negated ?? !$matched !! $matched;195196    try {197      if $*BEHAVE-AUTO-MATCHERS.defined {198        $*BEHAVE-AUTO-MATCHERS.push(%(:$matcher, :negated($!negated)));199      }200    }201202    if $!passed {203      self.clear-failure;204      return;205    }206207    my $message = $!negated208      ?? $matcher.failure-message-negated($!given)209      !! $matcher.failure-message($!given);210211    self.replace-failure($message, $matcher);212  }213214  method replace-failure(Str $msg, $matcher --> Nil) {215    self.clear-failure;216    my $f = Failure.new(217      :file($!file),218      :line($!line),219      :given($!given),220      :expected($matcher.expected-value),221      :negated($!negated),222      :message($msg),223    );224    Failures.list.push($f);225    $!recorded-failure = $f;226  }227228  method clear-failure(--> Nil) {229    return unless $!recorded-failure.defined;230    my $list = Failures.list;231    my $idx  = $list.first({ $_ === $!recorded-failure }, :k);232    $list.splice($idx, 1) if $idx.defined;233    $!recorded-failure = Nil;234  }235236  method from(Mu \value) {237    $!expected-from := value;238    $!has-from = True;239    self.validate;240    self;241  }242243  method to(Mu \value) {244    $!expected-to := value;245    $!has-to = True;246    self.validate;247    self;248  }249250  method by(Mu \value) {251    $!expected-by := value;252    $!has-by = True;253    self.validate;254    self;255  }256257  method by-at-least(Mu \value) {258    $!expected-by-at-least := value;259    $!has-by-at-least = True;260    self.validate;261    self;262  }263264  method by-at-most(Mu \value) {265    $!expected-by-at-most := value;266    $!has-by-at-most = True;267    self.validate;268    self;269  }270271  method Bool(--> Bool) { $!passed }272}273274class RaiseErrorExpectation is export {275  has Mu   $.given;276  has Bool $.negated = False;277  has Str  $.file;278  has Int  $.line;279  has Mu   $.expected-type;280  has Bool $.has-type = False;281  has      $!expected-message;282  has Mu   $!raised-exception;283  has Bool $!callable-given = True;284  has Bool $!ran = False;285  has Bool $!passed = False;286  has      $!recorded-failure;287288  submethod BUILD(289    Mu :$given is raw, :$!negated, :$!file, :$!line,290    Mu :$expected-type, Bool :$has-type, :$expected-message,291  ) {292    $!given             := $given;293    $!expected-type     := $expected-type;294    $!has-type           = ?$has-type;295    $!expected-message   = $expected-message;296  }297298  method !run-block(--> Nil) {299    return if $!ran;300    $!ran = True;301    $!callable-given = ?($!given ~~ Callable);302    return unless $!callable-given;303    try {304      $!given.();305      CATCH { default { $!raised-exception = $_; } }306    }307  }308309  method !build-matcher() {310    my $m = RaiseErrorMatcher.new(311      :expected-type($!expected-type),312      :$!has-type,313      :expected-message($!expected-message),314    );315    $m.callable-given   = $!callable-given;316    $m.raised-exception = $!raised-exception;317    $m;318  }319320  method validate(--> Nil) {321    self!run-block;322    my $matcher = self!build-matcher;323    my $matched = ?$matcher.check-captured;324    $!passed = $!negated ?? !$matched !! $matched;325326    try {327      if $*BEHAVE-AUTO-MATCHERS.defined {328        $*BEHAVE-AUTO-MATCHERS.push(%(:$matcher, :negated($!negated)));329      }330    }331332    if $!passed {333      self.clear-failure;334      return;335    }336337    my $message = $!negated338      ?? $matcher.failure-message-negated($!given)339      !! $matcher.failure-message($!given);340341    self.replace-failure($message, $matcher);342  }343344  method replace-failure(Str $msg, $matcher --> Nil) {345    self.clear-failure;346    my $f = Failure.new(347      :file($!file),348      :line($!line),349      :given($!given),350      :expected($matcher.expected-value),351      :negated($!negated),352      :message($msg),353    );354    Failures.list.push($f);355    $!recorded-failure = $f;356  }357358  method clear-failure(--> Nil) {359    return unless $!recorded-failure.defined;360    my $list = Failures.list;361    my $idx  = $list.first({ $_ === $!recorded-failure }, :k);362    $list.splice($idx, 1) if $idx.defined;363    $!recorded-failure = Nil;364  }365366  proto method with-message(|) {*}367368  multi method with-message(Regex $message) {369    $!expected-message = $message;370    self.validate;371    self;372  }373374  multi method with-message(Str:D $message) {375    $!expected-message = $message;376    self.validate;377    self;378  }379380  method Bool(--> Bool) { $!passed }381}382383multi sub build-be-matcher(Junction:D $expected) {384  BeMatcher.new(:$expected);385}386multi sub build-be-matcher(Matcher:D $expected) {387  $expected;388}389multi sub build-be-matcher($expected) {390  BeMatcher.new(:$expected);391}392393class EventuallyExpectation is export {394  has Mu   $.given;395  has Bool $.negated = False;396  has Str  $.file;397  has Int  $.line;398  has Real $.timeout  = 2;399  has Real $.interval = 0.05;400401  submethod BUILD(402    Mu :$given is raw, :$!negated, :$!file, :$!line, :$!timeout, :$!interval,403  ) {404    $!given := $given;405  }406407  method to { self }408409  method not {410    EventuallyExpectation.new(411      :given($!given),412      :negated(True),413      :$!file,414      :$!line,415      :$!timeout,416      :$!interval,417    );418  }419420  method !apply-matcher(Matcher $inner --> Bool) {421    my $matcher = EventuallyMatcher.new(422      :$inner, :$!timeout, :$!interval,423    );424425    try {426      if $*BEHAVE-AUTO-MATCHERS.defined {427        $*BEHAVE-AUTO-MATCHERS.push(%(:$matcher, :negated($!negated)));428      }429    }430431    my $matched = ?$matcher.matches($!given);432    my $passed  = $!negated ?? !$matched !! $matched;433434    if !$passed {435      my $message = $!negated436        ?? $matcher.failure-message-negated($!given)437        !! $matcher.failure-message($!given);438439      my $failure = Failure.new(440        :file($!file),441        :line($!line),442        :given($!given),443        :expected($matcher.expected-value),444        :negated($!negated),445        :message($message),446      );447      Failures.list.push($failure);448      throw-unless-aggregating($!file, $!line);449    }450451    $passed;452  }453454  method matches-with(Matcher $matcher) {455    self!apply-matcher($matcher);456  }457458  method be($expected) {459    self!apply-matcher(build-be-matcher($expected));460  }461462  method eq($expected) {463    self!apply-matcher(EqMatcher.new(:$expected));464  }465466  method be-truthy() { self!apply-matcher(BeTruthyMatcher.new) }467  method be-falsy()  { self!apply-matcher(BeFalsyMatcher.new) }468  method be-nil()    { self!apply-matcher(BeNilMatcher.new) }469470  method match(Regex $expected) {471    self!apply-matcher(MatchMatcher.new(:$expected));472  }473474  method include(**@items, *%pairs) {475    my @expected = @items;476    @expected.append: %pairs.pairs;477    if @expected.elems == 0 {478      die "include requires at least one item";479    }480    self!apply-matcher(IncludeMatcher.new(:expected(@expected)));481  }482483  method contain-exactly(**@items) {484    self!apply-matcher(ContainExactlyMatcher.new(:expected(@items)));485  }486487  method match-array($expected) {488    unless $expected ~~ Positional | Iterable {489      die "match-array requires an array argument";490    }491    self!apply-matcher(ContainExactlyMatcher.new(:expected($expected.list)));492  }493494  method start-with(**@items) {495    if @items.elems == 0 {496      die "start-with requires at least one item";497    }498    self!apply-matcher(StartWithMatcher.new(:expected(@items)));499  }500501  method end-with(**@items) {502    if @items.elems == 0 {503      die "end-with requires at least one item";504    }505    self!apply-matcher(EndWithMatcher.new(:expected(@items)));506  }507508  method be-a(Mu \type)  { self!apply-matcher(BeAMatcher.new(:type(type))) }509  method be-an(Mu \type) { self!apply-matcher(BeAMatcher.new(:type(type))) }510511  method be-greater-than($expected) {512    self!apply-matcher(BeGreaterThanMatcher.new(:$expected));513  }514  method be-gt($expected) {515    self!apply-matcher(BeGreaterThanMatcher.new(:$expected));516  }517  method be-greater-than-or-equal-to($expected) {518    self!apply-matcher(BeGreaterThanOrEqualMatcher.new(:$expected));519  }520  method be-gte($expected) {521    self!apply-matcher(BeGreaterThanOrEqualMatcher.new(:$expected));522  }523  method be-less-than($expected) {524    self!apply-matcher(BeLessThanMatcher.new(:$expected));525  }526  method be-lt($expected) {527    self!apply-matcher(BeLessThanMatcher.new(:$expected));528  }529  method be-less-than-or-equal-to($expected) {530    self!apply-matcher(BeLessThanOrEqualMatcher.new(:$expected));531  }532  method be-lte($expected) {533    self!apply-matcher(BeLessThanOrEqualMatcher.new(:$expected));534  }535536  method FALLBACK($name, |c) {537    my $registry = BDD::Behave::Matcher::Custom::registry();538    if $registry.exists($name) {539      my $matcher = $registry.build($name, |c);540      return self!apply-matcher($matcher);541    }542    die X::Method::NotFound.new(543      method   => $name,544      typename => self.^name,545    );546  }547}548549class ExpectationBuilder is export {550  has $.given;551  has Bool $.negated is rw = False;552  has Int $.line;553  has Str $.file;554555  method to { self }556557  method not {558    my $new = ExpectationBuilder.new(559      :given($!given),560      :negated(True),561      :line($!line),562      :file($!file)563    );564    $new;565  }566567  method !apply-matcher($matcher) {568    try {569      if $*BEHAVE-AUTO-MATCHERS.defined {570        $*BEHAVE-AUTO-MATCHERS.push(%(:$matcher, :negated($!negated)));571      }572    }573574    my $matched = ?$matcher.matches($!given);575    my $passed  = $!negated ?? !$matched !! $matched;576577    if !$passed {578      my $message = $!negated579        ?? $matcher.failure-message-negated($!given)580        !! $matcher.failure-message($!given);581582      my $expected-for-failure = $matcher ~~ BeMatcher583        ?? $matcher.expected584        !! $matcher.expected-value;585586      my $failure = Failure.new(587        :file($!file),588        :line($!line),589        :given($!given),590        :expected($expected-for-failure),591        :negated($!negated),592        :message($message),593      );594      Failures.list.push($failure);595      throw-unless-aggregating($!file, $!line);596    }597598    $passed;599  }600601  method be(|c) {602    my @pos = c.list;603    my %named = c.hash;604605    my $resolved-expected;606    if %named.elems == 1 && @pos.elems == 0 {607      my $key = %named.keys[0];608      try {609        $resolved-expected = $*LET-RUNTIME.value($key) if $*LET-RUNTIME.defined;610        CATCH {611          default {612            die "Unknown let(:$key)";613          }614        }615      }616    } elsif @pos.elems == 1 {617      my $expected = @pos[0];618619      $resolved-expected = $expected;620      if $expected ~~ Pair {621        try {622          $resolved-expected = $*LET-RUNTIME.value($expected.key) if $*LET-RUNTIME.defined;623        }624      }625    } else {626      die "be requires either a positional argument or a single named argument";627    }628629    self!apply-matcher(build-be-matcher($resolved-expected));630  }631632  method include(**@items, *%pairs) {633    my @expected = @items;634    @expected.append: %pairs.pairs;635    if @expected.elems == 0 {636      die "include requires at least one item";637    }638    self!apply-matcher(IncludeMatcher.new(:expected(@expected)));639  }640641  method eq($expected) {642    self!apply-matcher(EqMatcher.new(:expected($expected)));643  }644645  method contain-exactly(**@items) {646    self!apply-matcher(ContainExactlyMatcher.new(:expected(@items)));647  }648649  method match-array($expected) {650    unless $expected ~~ Positional | Iterable {651      die "match-array requires an array argument";652    }653    self!apply-matcher(ContainExactlyMatcher.new(:expected($expected.list)));654  }655656  method start-with(**@items) {657    if @items.elems == 0 {658      die "start-with requires at least one item";659    }660    self!apply-matcher(StartWithMatcher.new(:expected(@items)));661  }662663  method end-with(**@items) {664    if @items.elems == 0 {665      die "end-with requires at least one item";666    }667    self!apply-matcher(EndWithMatcher.new(:expected(@items)));668  }669670  method all(Mu \expected) {671    my $inner = expected ~~ Matcher672      ?? expected673      !! BeMatcher.new(:expected(expected));674    self!apply-matcher(AllMatcher.new(:inner($inner)));675  }676677  method be-a(Mu \type) {678    self!apply-matcher(BeAMatcher.new(:type(type)));679  }680681  method be-an(Mu \type) {682    self!apply-matcher(BeAMatcher.new(:type(type)));683  }684685  method be-an-instance-of(Mu \type) {686    self!apply-matcher(BeAnInstanceOfMatcher.new(:type(type)));687  }688689  method respond-to(**@names) {690    if @names.elems == 0 {691      die "respond-to requires at least one method name";692    }693    self!apply-matcher(RespondToMatcher.new(:expected(@names)));694  }695696  method have-attributes(*%attrs) {697    if %attrs.elems == 0 {698      die "have-attributes requires at least one attribute name => value pair";699    }700    self!apply-matcher(HaveAttributesMatcher.new(:expected(%attrs)));701  }702703  method be-greater-than($expected) {704    self!apply-matcher(BeGreaterThanMatcher.new(:$expected));705  }706707  method be-gt($expected) {708    self!apply-matcher(BeGreaterThanMatcher.new(:$expected));709  }710711  method be-greater-than-or-equal-to($expected) {712    self!apply-matcher(BeGreaterThanOrEqualMatcher.new(:$expected));713  }714715  method be-gte($expected) {716    self!apply-matcher(BeGreaterThanOrEqualMatcher.new(:$expected));717  }718719  method be-less-than($expected) {720    self!apply-matcher(BeLessThanMatcher.new(:$expected));721  }722723  method be-lt($expected) {724    self!apply-matcher(BeLessThanMatcher.new(:$expected));725  }726727  method be-less-than-or-equal-to($expected) {728    self!apply-matcher(BeLessThanOrEqualMatcher.new(:$expected));729  }730731  method be-lte($expected) {732    self!apply-matcher(BeLessThanOrEqualMatcher.new(:$expected));733  }734735  method be-between($min, $max) {736    my $expectation = BetweenExpectation.new(737      :given($!given),738      :negated($!negated),739      :file($!file),740      :line($!line),741      :$min,742      :$max,743    );744    $expectation.validate;745    $expectation;746  }747748  method be-truthy() {749    self!apply-matcher(BeTruthyMatcher.new);750  }751752  method be-falsy() {753    self!apply-matcher(BeFalsyMatcher.new);754  }755756  method be-nil() {757    self!apply-matcher(BeNilMatcher.new);758  }759760  method match(Regex $expected) {761    self!apply-matcher(MatchMatcher.new(:$expected));762  }763764  method !build-raise-expectation(765    Mu :$expected-type, Bool :$has-type, :$expected-message,766  ) {767    my $expectation = RaiseErrorExpectation.new(768      :given($!given),769      :negated($!negated),770      :file($!file),771      :line($!line),772      :$expected-type,773      :$has-type,774      :$expected-message,775    );776    $expectation.validate;777    $expectation;778  }779780  proto method raise-error(|) {*}781782  multi method raise-error() {783    self!build-raise-expectation;784  }785786  multi method raise-error(Regex $message) {787    self!build-raise-expectation(:expected-message($message));788  }789790  multi method raise-error(Mu \type) {791    self!build-raise-expectation(:expected-type(type), :has-type);792  }793794  multi method raise-error(Mu \type, Regex $message) {795    self!build-raise-expectation(796      :expected-type(type),797      :has-type,798      :expected-message($message),799    );800  }801802  proto method throw(|) {*}803804  multi method throw() {805    self!build-raise-expectation;806  }807808  multi method throw(Regex $message) {809    self!build-raise-expectation(:expected-message($message));810  }811812  multi method throw(Mu \type) {813    self!build-raise-expectation(:expected-type(type), :has-type);814  }815816  multi method throw(Mu \type, Regex $message) {817    self!build-raise-expectation(818      :expected-type(type),819      :has-type,820      :expected-message($message),821    );822  }823824  proto method raise(|) {*}825826  multi method raise() {827    self!build-raise-expectation;828  }829830  multi method raise(Regex $message) {831    self!build-raise-expectation(:expected-message($message));832  }833834  multi method raise(Mu \type) {835    self!build-raise-expectation(:expected-type(type), :has-type);836  }837838  multi method raise(Mu \type, Regex $message) {839    self!build-raise-expectation(840      :expected-type(type),841      :has-type,842      :expected-message($message),843    );844  }845846  method change(&observable) {847    my $expectation = ChangeExpectation.new(848      :given($!given),849      :&observable,850      :negated($!negated),851      :file($!file),852      :line($!line),853    );854    $expectation.validate;855    $expectation;856  }857858  method be-within($delta) {859    WithinExpectation.new(860      :given($!given),861      :negated($!negated),862      :file($!file),863      :line($!line),864      :$delta,865    );866  }867868  proto method be-kept(|) {*}869870  multi method be-kept() {871    self!apply-matcher(BeKeptMatcher.new);872  }873874  multi method be-kept(Real:D $timeout) {875    self!apply-matcher(BeKeptMatcher.new(:$timeout));876  }877878  proto method be-broken(|) {*}879880  multi method be-broken() {881    self!apply-matcher(BeBrokenMatcher.new);882  }883884  multi method be-broken(Real:D $timeout) {885    self!apply-matcher(BeBrokenMatcher.new(:$timeout));886  }887888  method complete-within(Real:D $duration) {889    self!apply-matcher(CompleteWithinMatcher.new(:$duration));890  }891892  method emit(**@values, Real :$within = 1) {893    self!apply-matcher(EmitMatcher.new(:expected(@values), :window($within)));894  }895896  method emit-at-least(Int:D $minimum, Real :$within = 1) {897    self!apply-matcher(898      EmitAtLeastMatcher.new(:$minimum, :window($within)),899    );900  }901902  method complete(Real :$within = 1) {903    self!apply-matcher(CompleteMatcher.new(:window($within)));904  }905906  method eventually(Real :$timeout = 2, Real :$interval = 0.05) {907    EventuallyExpectation.new(908      :given($!given),909      :negated($!negated),910      :file($!file),911      :line($!line),912      :$timeout,913      :$interval,914    );915  }916917  method have-received(Str:D $method-name) {918    my $expectation = HaveReceivedExpectation.new(919      :target($!given),920      :$method-name,921      :negated($!negated),922      :file($!file),923      :line($!line),924    );925    $expectation.validate;926    $expectation;927  }928929  method FALLBACK($name, |c) {930    my $registry = BDD::Behave::Matcher::Custom::registry();931    if $registry.exists($name) {932      my $matcher = $registry.build($name, |c);933      return self!apply-matcher($matcher);934    }935    die X::Method::NotFound.new(936      method   => $name,937      typename => self.^name,938    );939  }940}