pax_global_header00006660000000000000000000000064151413313740014514gustar00rootroot0000000000000052 comment=8e8e938e9f3bbc6050e2ba05c36177c543ad7138 cucumber-cucumber-ruby-core-08c81cd/000077500000000000000000000000001514133137400174455ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/.coveralls.yml000066400000000000000000000000301514133137400222310ustar00rootroot00000000000000service_name: travis-ci cucumber-cucumber-ruby-core-08c81cd/.github/000077500000000000000000000000001514133137400210055ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000021771514133137400246150ustar00rootroot00000000000000# Description Please include a summary of the change, mentioning any issues that are fixed (or partially fixed) by this change. Please also include relevant motivation and context. e.g. "Fixes issue with XYZ" ## Type of change Please delete options that are not relevant. - Refactoring (improvements to code design or tooling that don't change behaviour) - Bug fix (non-breaking change which fixes an issue) - New feature (non-breaking change which adds new behaviour) - Breaking change (will cause existing functionality to not work as expected) Please add an entry to the relevant section of CHANGELOG.md as part of this pull request. ## Note to other contributors If your change may impact future contributors, explain it here, and remember to update README.md and CONTRIBUTING.md accordingly. # Checklist: Your PR is ready for review once the following checklist is complete. You can also add some checks if you want to. - [ ] Tests have been added for any changes to behaviour of the code - [ ] New and existing tests are passing locally and on CI - [ ] `bundle exec rubocop` reports no offenses - [ ] CHANGELOG.md has been updated cucumber-cucumber-ruby-core-08c81cd/.github/img/000077500000000000000000000000001514133137400215615ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/.github/img/cucumber-open-logo.png000077500000000000000000003170601514133137400260030ustar00rootroot00000000000000PNG  IHDR Yg3bKGD IDATxyx]yYklְeY $̨,@DPNf nzҤ'ӓ=- Ig SpllcyƒƽZJk}Isے[k&km_ +4-l05r YB͐$3WoR/sBdrj4j&wc2 ??9m.,ʴG2c ד8Nߊ[7mg͒L{{:M?J͖ic%͖cs&5hK}܏IY6Iy3 R~Ez a+' =;QΎ3%-MdJ fZ/gB/|…ik(ִSU'{ O@ܩjVfv!zH{8F{_9t0@<0/=%NZUBwVK7Vm;fZ[I: `(%].soԘp"ߙ; egxeG񲚺c f.K:^褻$}'knۙt 䜖JJ'NQrs7]dCytX:Xf04$̿r=ăxS{!IQ'}+%&2ʣTsׅ9ϥ}@Ҍfv֮$th(N0='}IoI:pֆNپ o營q%IV<_eS|yu'Y֓|z_7s\> /Z9p 7⤑@n$ Pn_Qn8XL[׮EY`"ypwQnwQnO \Qnop{Qn[^ᶂ $?U7=tdG+TًZIIh^OI^SZYyGeҲ/^}jB|tP@An[An `kQ3c 7IyL<}+?eݭO'*M.zhwr&YuybS?*(ږtV֖e\PˤI+xjLnG!dq5g;rߴ]IJByB-.}\R]y"IYyϫ?J֘N:UEp{ 6)x|DQOJczC.-v~]M, ʣsqcϭ7/+^UIQ\SĨG<2*7$+ Nپhk=b/t^YJ<77+JR+DW4>9C Q~D*Lͤδ"UtٲnIgrEy 5w]3Nwi*i)y*ujjd~ґ IcNLJ7=r;I'*dJ: ʣedܾCNI9"&:V)5Vc ~;AǒssXƤ@9W&Z:Ηt6wMQN֐J:YTA*||DݏSpNh¦_TRSޫ }xV)UTW1tD(AګpS>8cPr8ZŤUv@R(&cIYO&椳)YTwQqkvUJԍS~UZ&F/^\n}QÌזQ^sJ5^qPBvPT2w/x޶nņ@(dYK Ie=]S~Ts w*sIG!sko$tz*g_4-,ƛR^ e-xlDVpϐ\P̟T|5L:DhftMM2wMY^זQgL|X4ܞ@_U=r; IyEN;g׮eY J#|Ƣ%ؤ,5J/ɵ|4 2.p Vps K:+ L?3o|ytuRN e3}Zt)J_0M<8t_{+6%Jh 51?dg&<)5E7ΐͤ4dT. JaijyΤ@)Q-˝Jt?bR:e4C^[64.+iQrXKIR($'AƟrUGEytr FASIG'EWf:뒎į蔿9KʗOV6tʣg9[.>,2)}Tehj U|`((^r=kV>tx5G_AK⫝̸oJJ'kN+f'$(+5i8L:V'-޶YKkܵLE7Ȳl,j(xp8(CW뤃ˡ< gvW9{GY2Eq٤I;(rn!sm[s /苴-응cK4'e.g=`τm6in4/٩3eY/UyJu)xbTng1A9zwwZ93!ˣK%`25(&7e<ϜpS^8'ׄ6ob `pѶE ഩꚜҋ:@|U'8SfMmp{CL2<̫npqNZ_٪iuq!3S:=',ڶڼ8<&Ty3&{oCk<|lձR 79mmCom(IÒP*ޫ%K3W'e nN~ s֔yW@ &Ѧf^LkLӳR&մͻcxc`Bp~H:5y^[F%8 LzӜR519wf\L\Yu,I\lfF՟8 ࿘ozjy pD*sWL٭1OɦTGvqL ϫV̘-'Y9wnTăTZ7GcVUfW1 dgRꜼtԓ߹+)ho~x)QJ_TyS/Z_Uhc>lյ7^E"/pi)YwCQ꬙9-F=@)ͣ3Zrf(*( G%Eb/{EL#J/3e.簖󝅗E9#Z/rD"*T9Ոt%iiT@r=HXs":@Lb,.HGdM$nPR컚5Ml%Ξ ֬= avѹb۵Ult#vnUంGn IaVꜩ1V,GϾt cr7JEӢARPpWȟ_5fW-{E 틨ԙ;sҁ|C4XʣصNj|_UDInk^}` e=y%-*yɷa60Ow!i<¡@C# Q;2J/W*>Yʙ7j߶E7$tSϴ|˴LTGs=:D9#}td(M *xhdYUXCJ)J;E}g w;sƾ]v)J]T/󸹼\ϯWW{<1Ո{6[Q P"_?>#)63̥3:rc?ڥ^UՃ*Jn?F~'F"Na bG_ 4%;%I+ݨlN;obHˣK;"`RR% ޫnT;<8ݤo=I=-cY)]1*u(G;=xwHˣeRQ:gQ]E|}c@s2:]4 -i߷Ʌ.9n,٪-#×}K*j3mP~"k$t.η QG5b IDATO)x|4sF?Y_lFԂGGU'}{ i էxz#Q' Nd[A%",/.oZQJC#fi,MB 4o=omהgeLW=r; p)uw_;hy]jof`jfLS$T+!I eppo[AC9R(oWwmZWo!X?\͞\Ѽ6I-MzSKV'mpJ_q[ߠsIGrl3ۓ>]Loڲ@IEҾl:4gᛢ8[2}nC F5-G_wkH)Y#4/v% osG(f){yF6Մ9CUo&};dж:;M69͔\̚UMr)q:Ir?WE09ً~x'RJ *(;*]2ӳ2=%gO9=dO-h (\EǙ'M|ɛ6_ϰ o^w^jϛ.{?{9Y9IL~\gNIIzԤCǜon_u@Y!ygu/K$F)(!7WȍuKت6s ثD3Ʈ߮hԙSߴ[Sco>%-,OV4͑i:%;IN ޣLP;URL'K۟&0Su>-}W sL]t.tIU2nq$?)ٻGuN:=ݽyM g@ɿn9yZ]sXOEuQn_yv?&^"bUFldkn^B5uwyC3%;S$uIjJ:W KzHu2_ Ydҡ\gՒ%7 enDgIia\/M|'/3E 9C^xSpǶշEv0%<P-7ϫVH$;J< hHlHaFRutdun+~e;)#7=o%.U_ ?͓yrv)\g&[nɧ-ϖ ,KwOU=(F'ےO:~&UM+D5OITtL<^8)} [G+ >Xv ϚweqT'FͩR5o@,{QFy9g(' ?{]"E@YjXxɬ\g6?(){ޕ-]PB%)/Ru0iQ  ʳ߰+p㨊& _Po bھ}͝sI'ע4I繮އzjFWԤCNkGQ>%9LɴI?uҹ_< (Iy4 WIj(Y/eYO%ѕPzScIGxE[v+($Cc?(߭ wϫbuTQġ}ick?I_33M:N'/]LsglXxɬ#(g-9&l}PȾq侟]rFO_aPJP]9s>s.7M6$|6b+*:Q %a\en2Έp?S?8Mu-z9 I~`죩dKgu3zNN:×;s<*ٟJ$ipݔ=pkt߼^d#V+ъU$6˽\#nUTިJmvwwUsx'IIgI;\gIpy͝W;k%EM u,FkƤ`h[G{륚DD|l,:~PT\/ه+w%";(k}pqI<9VvgI0~MNmX֤Ԓt]G}#j`9 ElDVeIG{z>uZ^RurNwxWelUdY>˞'M0(^j[_ҹx]t=&˝ӕK5/H$eKRA\%a gdz{)üҽJpgW*n(ޙɟRKuOj#jG8d-]9kœǙqܝtr]Rh Ia!t^;60(OQ2IM%haFԼy'7Jۼ#aGgqtԨoi.p`fwwWzc΃Qin,~9靪3zNN: t-]?ch%fWP0?۸f)ޏPg̏fU$qyE' gW΂Q5u=`N}IgAI(Vt.O:aGs'QW˟Wш7;{DbG|Vzd\rZ_$W{eH:%,8"lX OZ)9,Dϖ$E*~_LMXȲʹŢp\Uᦼܞ 9` )\[_qGvKop`ftMmI!&<82lT%U%2'wmy_@C~JDK+XÁi9OM[%^&69.B< 2e3s|wUytI (6gkT5\ْH9/u(/3 ?;,Gi3Jy MeoZ|$ʡ gv Cw;ASCrE9uV煋7mgiC.d%aʔ:wjE"9UzbiJc(5#c0ϝRl3O$}4w^朮S^S_>js.|*ӿsy!]fW\y6y%uB~9崾&2'~n̻Sy[ߦ1=v`Ƴ`I4Ѷnŝm>Coy?t9!BM&l;6Ҏe(ݏ٣Nc@̷Fln+P9T6)v(h]w;J&NZgu6.w;ZԘjkX0"ӭ/BB!B!B="upy UGDSmMNƛ曩?Ou+f\%p+B!pƀG3>oup*V_bƒ猂p;xzqn!l/*c-_Xp.qmRc-OL.Y[:Zj=mE]MN!B!Bp8"޸||tg2-_q>:@[y0㧡؜[B!t@y]AA߼ކ-Y״ i ߋ*ՠU3Ym"ojdxap_Q=曩GS-Ms@?ǷB!B!lb<^lTTke͝.s:Z*߅ԍ[z@&w!B!pAG63t;cYQڑE4q;2/n= jB4QF׃ٲKjp;k47mv;|jYn!B!B!D .\cjiy䶥KM .d0 DLT/7B!AGFYߺDAB1$QN7g_s7[A>04ʹR[F8yl~ Cp cjq۹!B!B!B]fL|{moG>TVOxpF{qqB!Z<@av4 /#JPeLw 4d*PJyGS\;%I~۝jwҎ֦/r.B!B!BaHb<2DU]#/mZ%qs:61]G;W!Bţxg% _dzv%! ۙ5.{.2I!B!Bﲊ~ `Kwhi˥aҥS 8莪HbqB!mXdL!Ba1-|'ڱAI^{ŝGQYॺ,Pz5gٴaH݅>V|~jm蚥KwB!B!B8gfn;710;|K.Rm7eIU B!xߒx1N<|Ӈau 6UYޠr@W@A/U 'iv+QYcfSagkX@!B!B!'?];[^p!C MvB!M޷t%VS"l=|Ӈ#0@)[\o4QkcRX{TUJX0f)g~a nr#6jmB!B!q 3" ͅb?Rٮ+@XTOE*VjP B >~jǗqupWƝ$,3tJkǁH;/Ll~aK6gŽ`/;[t\!B!B!pKE<>{pqtO6㊃ҭHLh FcҮEɁXB!*/9`alrn'"Ӳ!=5H##< IPUԸ ݷfK|J 4Bwbh( 4R Gh?t]i"'e8҅w5}хB!BQLB!v] îI IDATis$ CYlYr$B!~˫edx{7b@̓.6ƿw.|M|u ҏn5kaFi(~|t80l)&-_}cSkIxATl \1vXy ߬.`mEB!BGpnB!k)gݩԋ;+QN;nk%B!,QSvM-cقd}'ikf̎ uaוk 0^|iP|LAΌSGP3FcA%vOCH0֎EnЌwr0e?z^ !B!Dac<*B7UeN$ۛ_u2a\:G>GfH,!BaW1ϰ~=׭EvvS甡J˛ᾇ۳Qv%(! mEnҪ 7DNkN!F]дsd B!BY: !.:J_t8;S H i78K!BXhţX_0Hy+͵|k-眝H&Qrx#lnOTG7?{-ҙlX-r٠/9S!B!H: !.\ `!w| 0_rLjE#`~X \ GB!r{**>hiGIѡ,܂LVӀnY][s]ڱT^kcA6uTd˺/;76Y_b`Hmx0LToo]9iu8C֦9S!B!D?fHQ!%&A%G2ߔjm~ј2&|_@(ځ8B!B)5 [M(@d_ډއNc/+Hea:mZ@(u@TCgu]U:O<[G| 2~p5?c !B!IQ!5t0÷h:OXlc8t-. ;K!BX÷XaP(G{g'˲YF.ߎBЎ/q$,)/ 0{kjG󔰌6Toiɥ|:ec"V<r{^%`F!ɼG;S!ľqGfFRj &E`j$cd6wi,vu[޴Bab/^B! ӗ g~a^'c Dl͗8PE/8B!">#F JFHwJ?ihv3S:9 S;,#?o13|Ӭ ?g@_N]Pg tE&~c0Y6ם GS-O4;S! R< N mVI '`,x;`s(A H}G06ajN&VgҥC&(<55 0Ցa3D(c``$= =@ 7a H-ktg)]vCMVࣘ 0!*`r1Ho62; h[_a"w0zZ2i}u]MD?/T64hc 9F0${.18 N2ixƼ%7lW܁ 7~Q񸯼Ex3qDM@pG{]x׸چ='0 0x<l6x[ o:Oo7 9O_(?Lv8'TNfǵZ߱3bo4 7JQVZvgbhWP1w}Vrɂ=&z7w6SE_C;Ҿed܆7)B͇6%oW x~4҇٣5Au0^:[tQIOR|ֹv6,v.B G1t0`5$JLC{b_ua n*3Bi` hcm?9Rf_1^.鳏aSEaa g,A=xvs™Ch3~kOZδi+;1^VLeESy c`+.e^2|ٗ-zv}]/xqU՜]'F`uB̑@PFzB-V/k`z4 8TkBU9caF&;Bf4 k`~3yNJ910$s)Xg˒ ٭rCa Ig.fM!vMn'vTo2vXD3B>.O)Ky44hf#I$q̋{3馭/vB9z)0!00ٵDKSmMg_xY0|SZ,֝G:%4݁8b39j'b?h VRXA&/`nZntn;:%L @/ZB+X;SxVElQ1 KioLLe5+U{]\ctAJx+Yrˮo47c8@AL ۾'!x}M0ʩNYFϷ\V"&Fm;O3N #i<4r'C$¹vj-C}oiթ֦Mkv2|'E`?Y3OxK>F¦{SM^(iHL|==k^HL6 sOu)Kƙq!r02x/ȧ1jf j\>_Թ`,7=-ְé֦lZ{/O4<01p"+=AȄZe29 Zh3q>@vJ&I&k_iwp4VeV53K?ėSnQ±̸͡p V6ˡx9G/Mf&' Mnj-6-j fdχ8 AN.bräIc:Q0ubښc|W(6lZD}GK5Vu*>dq0 x7rvbZdqBk]&uD*z|mÀ6w G l;5(b" weyz;awѦop GG(=ׯ2}75._la|1L @Gv ;wT_ԉC(H IM#Q T_ĸ <|!=)CKz4mO0ۦu I(5iY3ӨPM5뽿e`S̸G"qfđh&.4mLR 1!8NayM$U.<`˪Hbԅ`089 LG)B%TvNLWytdK ʅN{ 0S7M ߧښ00Lܪ w 21y뜗 CcEER+p4?1oԾ ?2X2HhuuCkض 0%@ˁ#>M63l#ґ{BaFȫދq$Mp7`pGE<>LŦNƶFQP4"e.ظtm'X@;Ȼ=xmw!4z[( bWrr?F0Si/xy5hq]^ Jܚ *Fjm|f&c\mCqc 64;O!@ɆA2׌-(hr-%BBXJ㩀}ţ}0_1p4+Z(ro]'|6\'tXh_LQgK}r)&OWsD8f\m(M.%;ZfU{#=8 DWYw-| *nzHLJw2^_<1>'7l0H>4iЩXy7imeBcjn]!j.7Kr0;UG|S Ed% 簽xkam2Ons;!0[^E<.t1_T,&aÊů8O!rMӣu z@xqDt;-=dnQDS-]0#u _{in ǒ$#J]n%ţ PcaÊ&1&>L(hj=4r,_ N] L,!-co2WTQ~ v B#!u4>G5Vp?ӣGjvp=LZ t|)p4"27v29i/鱺kͬ2sM*tje6wav>^@0gXF'gy4K~M~ ;ַ 2E>p*_z%ƅɿq JqjTC]fҤ`8 /g$+RQ==ÑN&_Shf3}T {z 4)>ؾf;X3wzrNFwMQM*R=h%*fBEb P;: O:T,!%z41/˾,87+S:?o֣÷|} s34mLCK *2^ǒgK C.9J <(!\eKz~P6ct)ܛLׅp$i}dmhuuC@&5ʵΡ1\b)ƽN8|d3큘α=꘡|8>laX`8|2Mj\mCv"D8R:_ | Gäh&tuyh⋦=Kw8afEng"7 #BU}_6!piQPUwfډдgjPMO9K!r„SǒhУ[_$ : `ӼK} $MJ9`BX֝v"9;#uRH $p/׷`$&}(,+5KZiīnPFGTWU^``an(QdAO Ed6'XR .+CѤxg2v:Vs:@0lQ>#ң9{F [& }P ojvTalI?Ѕ3v%xel}Q98V~\,!iic%绝KNc)7Q I^ PΖqz4(,r A/cNy`jjH6& f?0G=::V?drE0~ 6:PE.MJ:*%ţYtwkS$#^>g/a?9fDW&gYC&o& @`#`ts]EK|0pQVFK c!O7"K9a=O|].>5!X72_0l@I3{g kDuq,da~!vjxȡXByz4ejS" LWvB ơWz$S"{MWz4+-Ν1 YV40N%r;O@1!| p"Asl.v&^&O@ZEyna2]+_Tvb\<"6ӒQ(Z 0kRrP_w;BL2 ??P,!me$g o !Vov??F\ L#[(!M'B'NԔCPļ :EPlePhxذ@quH1,au̍3P-ka; G Lv$]<""AiȘ DC0 U( \ioFyڪz~m IDATلp[34u󶪚{0eH3WT5n'UX,Y@𩱱qn'Ex7cH,VX `)֬[@:gCqZybC0?jw"Pg9>f_96:k\@-UzO᪚KNŋBgLp; =z4-26<@ U U5g H۱8U}ΡY@w ng"DPvtLc`T,! 5J 63ysA<3fn<]Cqg8R'Hl. qg8Rۉxсև#jCSMs_&ӒhR p$>L"kei=2[6K*էם3)#]RISq$$6Q&78qĤI}X,|Ou"ߔ)7E}NC*߆ ̶lJ8ס8oB{'!7:ղ$7xQX$oC~f^U8 K3kT@PI(6h@?HrIΆgǾ s(TYEqbҤxܢ`D0)Y :4Plas1Cq/tvtGcfKX'nM|Q=Vw-?Da"zBCEa{ wQ% Vr{<*mȶp; ! ]IƆI9`$+}Kv.a=R;>I|^F['%D"!E?Bnnu  z̳دf<6gۉ-K~q;FGˢ%# 6 ,l_"/p(6K>Eû`(*<.~+IV([q;A#Hk3=X{?1ˏA`4g$Bn'4=~v `9@Xo+{yєt/<=ߪj.7Me6g -xN9kء&/q$4v(GWD_Qj{:y[ >Ph#Xݵ`<"`ٻL31f< `P<kU5c@kvC|ww_$eL[PTL ung0NS")7@ L2RXQ*a]`xn&N)b!2 3c<G82t>'Ek.w7c`EH<dޤ,m0R{fՋ޽21/2̰,M$Øp1b7{sb<^ǿ^&Ez; b ›&4a$6LnNQ>3D0f9N! `?@~n';eR.)_%Л`ao¤J36 Rֿ=Pk~|+9lh&ladҴ220&Lc&p ~CuҤY{P,!0nv;=^2#uQ5GNLpё ]+#\v"nR&h{pH :[$]>\k rYK#۩-%6ۅ;@0L#}43VDS8T`SG]B '6e9@- Z*]J}n"`r."zѶl-QE_D扭H6{o_77c8qyǦ([)Q :ՉW]Zo !DEyHH}N^WՕ~ 4jQ-3(C@$#녰bߏٺ1YĚ/Y`pe +BY)w9ԇii0& gTo VDIE Nw+3HݬT[spš؜5NWկNHjEs q _1p)"6ggOK8ceS %6vpy5?ñ}G+>4+r0,b]؎kh۷챹{(&N!t o9S^Aqluu[]9|~*ڽKfmX3)^s2.P`sj]=h›wRm.WW{?nYװmbo;=IS¾w8׃m?f\mr]ƀ]MM_l8w;>#jVOåI:D89[~Ag F||dP>i2y jboorN)Lgz۽yk E`( 5K!p̉ ACmr|e[;[\{?O8QLf3%>зs"O! *II,K] QAF;x熊_id:SIA|Xo,ZJ?[~ ?l:ƶFH#&fou x$Jڴ 9a}^ 6_$g++V@CL[ +%g:F0hkZt{ouuu_njo}]FWP&@'6ʧvòo}7ׯnѧ&JT W 具d}Z׀*"߲ۚ1GmJ{Lp @ioYY ׵L榫'ƍ}t'c#f> B?t:N?fx]n5pp_$_(XVw&w7PZ'?W9}s\I-44ܟo }c|/,b43g.tBFnGyNhQLSNB/4>h*a.?gjkW Ggӏohis鳩ڱ&ѩ 9КKwfN )dCUw6}>=ml[IRt&fa|*":0ηp..J6:[ѽt5=D~JxE4|:RM'u6ݛ+77RmMSQ}jRڅ ŒM1pQGkS GjՂtGKдf8[,B%/w4G=p7t 6D7oVZspT hwnm8GiTj1<0pT)Tk^-Ss6Z/Sʘ„E&CPή p ǒffp GfݩkyCWtL0 &ݯTK4ycÊůښ>@Dgt:>Gk58W.Eţ&]GD4;̮ws^GdI V,WdLW(p(-%@H^#PIE!,͌ Fsgr!.%ڧy"sY{ks!|l.(J -y%Dk.]ה:/ݾ5|o_C=W8|."tiR˟/ U|gdyIvPܚװ79X<:w%Ys3 ,@o>y.*vѳ'S QjPH5KI/(ѢU[9Z¬wU`W\Y&7YJ_뼵b}.N&J{]!:{H <8^ çKm@pr_盶dgW^`L?Ɂ3~rϚ=H,a68y [gd}a6@.yo(a6lXU-v^"K^kG}s҂{;?d9^91"ͣ"l=V A5,t}7鵜%:qdiۄcŊ} @#i \[|SQ6n\])tA_,/t\:yoҫi]CދVES]쌠YN ,<is(f&?2y1l%b1y`hV)Ar!"J WE_Ec~OߨY}kB&e]垮 hDMEpk5cX兓_{xqmׯq 9QC}]wB8j/"`^&[B#Dy{Ά7`/{u}oDO*voUy8L.:P7;FJ6$CM̝/߹u)T!7 R9>Du5Px}ܷgĞ{+/Kh#fIO 䞰.j! &hnƗ u(\wWUY Y9scK5}I/`o!PyKp:*'.*<ih<}h j<1)hhyl@^ Q>9u H*8yro+O{FAL%Sn'FڰaU %cRE7Hz)6sKB< :l X0/T񱡞}HL^(Iy`_jB Hwu{:j\Q5qj4?|cI/v%ul/ߢ!zW!r([ʽm0f3e32JG=rO珒^zyє)䛡Eqeǒe&7׿`B-r߭ js|!H.CrlUͷߴ;酄P#P: hNXU(Ü ?nR;s.zF;Z?] 3I/騢[yh_y2SZ^QoO(JƖ.-@qj$VDD'@cjy微#":FC:75ܥ+`DmT 7z|w (PV 塾PrRaՕE_`U@e@/5Ś)u}^kZ7+;,7|d66g/0 {򸙈a`:#X IDATj"ý% Vz/x_. 9~-/jr8; &PޔU? '_^85ͰVsW S9mN dvNM+7ҟĄ/Bߝ '^$h}dnp$ͣ0?ɈNoL/zŌ7!Z؂Qz o&~hB^33!|%fb4'fċ(G('tA([=? EDt\T;zo~0uHwM8{;oJz!{?@Դ9?7@NV՝uLz!I߹]{1Q(Oy]ti+u>W]a\j v&*_5iRGro׍I/AԅihzpɝοOzI|MX_`_T`֒ w:^6!>91u*do9cQ{2 ʲ GIYz+'Cz:jzhlQ %A&F>b(v,^\lCYUHqm&"jJ""7?o|nkZ4埂Ԝ"knX~g%~Y&?E$@E|^e96yTmb4Jܽ "fZy!^$ou\սND[~cv/x4D0wuiFvT+87P7lSmx¾3m3(˜4RDD$h_mch!RZK6Q.u >I?4Գ>"VO'$mذ ^FGG^eAwXOh~?[^DZ v 1ϘsA.AP߅_0ȄC> Ž?^vV,陸˽%EPf Rz,ʠ 'yY'>>=t𶀑לzꕍ9+VD}e t~2#T`~MerhwH!. 2z)y(K4[#6fO6~9DD9Sʽ}8h\5$΍I! vwnW`yP=<#CꏢT4 ղxtw 82CFCz/'3ȎrA+7n\]Iz!Y!ЂiOiuĹn7H1%UUF~7PKe9"u*Qn|.uUs,32A?oI/"z0=ɩ=)h{?&""ԽchtX0`ADM5IA>':^D .sC ESCSwMWҋH*mVd곗N58^NL=knȖ^^{RS@_g5bcZe=2N)!^9PkH=TCD!rN`ߚ>ی Pp.~qF >ג^GwL ζO1h}"L)ɌbX?ƪciG7 vޜ"N$MPN'`.$?HD9G2SAK/ CD{q (7^CmIN"7{V?hVN=lh(ySҋȊm? K!9N\ _dN#3|_AiN^lrǪ~W2k _BL0oY:}_Nz Y *յ9G w7o2Ȝ=tcf.^~qF*)_MGUn>È.i~SsaDDcqtd&$H-Qxs_|w^ ",ONzi5uwv&f 듍g;<ԣ;:X܎6V:0@E gyiп:t@4~'Q:b< 炼Sfp):b[z}0e.-n5vdX?.*iG^D&twK,V֧r0h:ƉF}߻]%3%qw;N@RKT}&'6w} N="VO K9א5;ݸ FD'?u`Jk{VҪ~ ~ S.^vqF*?z;uP0tb|,/w6cW7lX^9Z<.Z4dH sGCuv5{ikOCc ͣ׹)L&JQ"uo8=΋ rl=lG%BT6{o4xQNx_ +G_Gfכ鎉{^D67̶1}iy[ϼ:S|A.HX$ۤA9QATQ Cy4_h[I!knTȏDer™^3)lܸ"kMKN+I!SD~aZ_$Y6G*DD9G!S?$(-;[feDD'hpJM]C27MHz YU&L7Yeg/ }HV1k`r_VBuy~TQA90ͣQ:MƪulsMͅ!>H󙈾0DΑ<+J<1ྡ;oYkX?56)s?﷬l#2<`|5+x6f䈰yr.*YK3ODtBT&(OI.ʚ|GFM>!Lz YuݪQ7hİSyߟ0+߱.Zσ?"(?-H*G$DqM 1=3znQ8m{/0ͪw .}^j<8 ߺN:#-\T@GArGNd(\>#5v/.Ed 'DB*da_]n5w&LRl7-iV2ͤyTl%"5j(ã Qfoz\$QHf&Fn2*aGHbyTCdL&+uϚhW, }ڹc0Пm_IE%L#]@ʺM4yT!U8l ߺ}9u] @nU_xZ_3/\p g$N7!@w.5 fUAtQѵ[m P7kD"(jzCQ7DZNЛL*hQU[kq@)#MS@Hz zCd`U}S~7+:B4GspV=\P@]Ǿ@C&Dis@[7pQSĿZ=׶C^ޮu֊m(< HGM.A7EDuAD0Kf v 3)Df Hz발#c&=^^C(\fR]X/;颫z|U}QjU;6y"mU(%P>Cn*G{QGd,MPw?m>ɵIB!syt_qz mU_P K 6ͣY{:9c JHZ+X  #t`nw9Լ 5:>b̬mBZȝI _t=QweG#1ݬ(B㠂-cpVO*k'cvڔ *]Z:h5>0Q)G] ;wց_w+Iѳ'ɈlKn}9TQ[ugԗkA=ELDṓ旒^ZSЊQQC4nGb( [CR& lHz 1lRG(LE#.ynA"4_+^cC7y A7ΐMzBG=Z'HJX~)kU`nfW>vؾ Ny'X!IQqw',S11/ #*y=ɣiVQُo~>-h1<4g瞖znTFἉ(l<6ZEYNqRՃG(0\ؿ?nɗxF>cݖ@F> c t{57h A>呝DDlo$vADD lM|@^C yoM/ UWq' ɣ-IK4FqIttXPEQͣjU hq䙋dX,*f?5fuUhڦAD9v70[G?w o~]tf;`;>Wz4e4wnB_CkN;k3`~aM%qoPQ]|ɰcVa<koٞ"Y=Vz̦ j?ڜ X`U}kU;D" jN-umIz_*0o M GutKpb~ZS= ᱫ$̯E4j&,qu`+t_\縷Q0#N`$?]YI/!Sr@}}&T= 4gq g+KUƂyl0Uu3z yJx_3vÕ ' WuɃ+7>ZEAII"S(eXQ܃lBI/'tɱC/JNJ"0G7#(jM0DN(Ivte}5mX{1yU,"Gk!rBRͣdJ|=ȽY6MƪM0Fyb`^ ij{ͼ]s-pͬ$(x(u6|R5$=?ޅnAH{>)^Ns-[I/%tAy$5ܠTa(eBcuQ"J5UHz FRbNz ydMϱ(_OVGCĈw!rR/%PL'1uWɣkӽmtˣY3I2lktaG-icQ]3G?NtJ+3w)3gV0r {Ü+up(hsMGDMF`ԉy(EvO@=2մ5DN@q9!,DUZBMauoDBhQ[oF1=mwJ/2HB+cߎkf'M;V/,z5ьQbbx,2<b~󓈨!v&WJ]hcvLا&yT̳+XN!VGqv=25hǒ18jq}4 4m7QlEqalyhti#r>/F `*'ePM;{=t(>guB-7 \>'B36|͍!F4p@˟q8]#a$ tg0 sE)@euheW}swLӚV#XYOѽ6jIxD@(飍SfsMQt"^ITZ12CgH=[nQZ#@ !QظyG(DrMdB2rEZZ2Q.hI/#AcC/JAA:9Wr(e<D%L, CDdBmGƛD~>lHęF%X{Napͪ!r(TunM){kdU[^3SU^GQ?\Xҫ΁SLz%MADh}Iw_/5Y~YbN(ECYpzAyRρ53(bIMcΛ=p#1nN!}iN!r(q<ʩLwv׵kf!' ʨ?[/W@=ǫoҫf"z΄TܢV$kI~5D,~.:c-A>S s\@ôiw#(ģ`Yݫfw-zV"SAdXޣ;Ȉ(TlUov"]E"ˇěkSihOh) i&xcE9y4zn""Wl ""GgJ E,rщ_Q'OO#gH'53Vkf""JǴk|(d& 7VZNkCBZR@F>ކ@MkjnMT4Y/clɼRKH ccRHsR9!rOs݆بniz1e‰N"Q( CYL3R@m75 ݱkf""J\D8jt>pim w2%ה3kCÈ71B7ֺaSZ= 'Oٱx$)e;t$ֆhٽnŪV,gR v`(eXj"""J=UC$b[U~st(ADt""]a6!MBF9QV=o>n^TElwB+;mQU?btcj=[. QNhEwaC0ͨuId"itV;>z2$' nGෘ|ٹsݍ, S8*GϞ@YDDjaR>p'""T> 7$݃pf@<əa,`%cl O$78kf(=@~t}Fl iV^|{.P {?Ϯ;&@;'#T5-_c/' As-D'&T2'": 1@9 2b˧88fpg'azU 'ͣDD"Oh# e ]u,n9n^ nV~ROKguoQod5ƈ5,xGijް(` J/lzL7sQ.Tkg 'NPc6>~[[E)X?<s(Np!U -5)--(ɣdyi:yAOprʩĿHz DYwAt$>IGWcCVfizٞ|0I%xMujo $pEz |Pi: A<~]2(i"rQ|:A^~Ν e{[NZ`V/z֋Zxtx;GnUv.ֆ4@|'>=vǾcc_цpM# ?6 U<g>so  E]DMJJD =Ր7.8osc67SGdi}"P0IrAQ,1@v*hZ,^\đNm Qkff( D I*ıbYYiC;Q"-䙈L_;ADł  *m-o@s -a u觶vuCovM:1v *j_t@)(^kO7ҫg&Ԋ4i`ÆU+($pW\*r.r9eYY*GDOKz yz\`<QC"@^DB}qjWvfϿpEr R@Q[r/5 ɳ hFfzmZf͵DDOy8+@nt`/K'ҕSbc":3Z6j2[5VY[@hnQ Z^? -Di DLL=OQ@ʩhɩxmfIk!zIaJj#Qh>'R:nT`.1$"f.^iT'n*M%lX(5vh4#D%zEJ9{c*^8#-Hz 9vmyɠuqU61{ʮ-v*pmC{K4lIBQ_u;:N K(<{L@֧h-N*_*#ǿ[Tso zx:;9lp'P:muo CQ@I-hLJ 3{mG#0)L)P(is.yt#"kF-0kDo?Џ6ҥ _r9}`>e=ͶG:,< 'OD[uXEYgbC^^ ѣtiHju]sZp U1̻zVX|'61{QU] 6fhɔSQ\>1-U*PQh "D77K򃝨ߕZc<&*0!NPxքG!S"DSxT#eֳzEaJ^և;, #@Qo"lul֤aG6zUN874F}~n;P!N9σp7n~CHSFr ܈=P < 3/~ve PhiIMO f"5PޝU>LtoӴ"@:3-hg(hdST@DEԟ _PxUP Jf)iDҖ.$6mfGR(%i{g|ђ|͝;ybIJz&Vzcg*'B1v`\FIk.`"K,bl@Ms9Jֵ?ep$~iZ::i}!dݛtS3ss(2k)7 FڽE7'3ZM+_ԛA/rt^zt $ްɕ"bd- hqW(KA}P@oo'8stg&_!"6pd=^E{{Fw0_\c(gBA5PZdu,-X])_64A}g}/4_#IIʢaqMBOh>f}8|,hހHs}!!cǀ3pe Sy€n0u;9yB%'XӦvgf:*Sa !(nrtz3 Dm#xc2&fp9솷*uBl kYXpd@YL/Zo":/dDhס^u7:O"^TVI46 !>4ys'{]E!zxicK]:3ҹBMgEqR,,a@dՖQ_,Z='0b^P|7(zuwZ'Z4B6\hƌ"sC! {TzrBXu}`ƃCdbXnoXOkbqЋa 9Pc"A5=s{]G> ǫNxW>& &fBWk5is0:b8`Bؗ;e޲=k `= gV9C!|%z#'?m.Oɱ&BLdLgk}" bWna;ufX}UB!א;v[h<XZQB^W_a9[,s.w}}ea&Zj,,NJbq`~:E |ؔ5#4g5bV2ռ%{^ 4خ9C!|e`/Mf2pE&3Ł˲ YJow! C4|R3i^`+Z0uf!Ot(>FG2o|(ТGx7h%}~;x﯎飞ѱ?J,(F;~vM#lWm9FckBLo_J-mI1k9CuoЧ1h/(J:bXq-9:|g%OZCi]P%!dsٞ![= ω%E\hFielg2ٞo jSJu=e gH=3Yd}_sBq#ŞJmD_A\i6jzfk{JY$; rāM O'N֜,ru B1\1pR8u]Y@ڏ,%eݫ;c,ZNG !,6k^gV"n8W|kGo= f*Ԕo ߓ.&^ `4Ŋ>3Yֱ~%Gt0ysBn(Geh>.ug{TqAeh>qWAƢ,, AGKBxAEr^ԝagSG:f\'>;G ek䚴dBx@!wh}@?;tf 3j&7FfUM7%>Ad] tS -?AxgĚPc0T[_  :9%&"cSmYLe L3fBy452W䪘eNK *>41h|=Nu2i]`kh"qP8)Gg7@ .1HgF>ce .~m"dwt[z4,3.{,Ұ$^{8"lfIY9loIQ#N`BC-u OTx- Ǘ3Ⱥ`^A"໨g{[L4jEK e JGK/ӝCOFf&u!{xԔCۛu--<ʏf@\ܣ=eaQpRm wqEĒy\0Duc̘Q]DL?Yܰo(cNl6"W,x#-@^鮍-u\=X 6s8:dwCbIwe\'[Bq3q}Msw /9!@w@ ں7aeQDQorgfB *I7DV|DsuUg{ݿAP\pyŜ~} {'"|D`>!bO]3B`"~h"`1o3w^Wc(O!Ɣxgȸ[ͮ%>i-Ɍjm|x0p|n&&YjCg5:e'?`YBN.(gkKe+ܕ# 9<#?>7{iFIө WY\TvנksЍd#Se3.Fh@Fnz9dsJ"B64?~i0ؐe}BzLwFR~QVY9HMWOت/b"'xJ8Zu30cFRN4ۃzlҬ93)xb\ ]4˵Ѫ{WC2;9% `+/L %PpY|^bZY|ĖAedžu퍯qD[z4 !𒳋~jY`s2ZQkrFЮYyVbXhoX]^ Y.ĪQʪ3[ eJ8~,=@ Rh>c %-7)Hj~b(W&ǫdY=pkB1Lq≫.ôp4q  3W@Nn69I)H*j0 M}S3 ]q@ַ5>=.c,! r<%DUgg\~<*մ.˔,IdVtF Ga憊 Tl$3eV̪B!#a Gd_rAH6"." <^FEm@->kq1<_p3Nk(J_ &.FFh"O8MdE2c!Rໆc33qRt/`!4}6,r4q!'LGI ǪfQS v^n(+'ֵ?K] :LO~`f(WĒ;'{]bR- `d="Gt/&xԹXf~ mj&x7>8YD#L7){̃ҽdVR|L.6A5+e3։'rb/~u=B3622l$Ή%DߌĒ8ѪƝXiji\jژW.]ߟהhZ%5O| 54߯ K{ιj{4е>:t9̾'V8a)ib|X@*<59^=Bt)}G-SsN:3Id6gVy&sդܙN4ېmӯ 8M\Ēl?A_2CM ď:#dpywwX[Xۑyjuzt-/ G':"B<&@,Я26k[[)K9j3u{ge='FfUUz]M>*qbKX.'ʺyQG=Lp,aCypbKdl?ЉV9!1pʣC"uΪlB({QG 5bRϱp,ybz R!s[QYY\28 0M7L*IR%VoQkY,yVSccUH,yOȶׂV0,!jom|z] 0АĒcU**h];sD1muOzXpwLճ'Oa}ڣQ9v_px(m|Ŝ}"9ɉ'Ē6> BDN iLR3| I;őYU=VTvn2\&:T쯣&Yca9!%pomۦk"g:nd;coQ͊GhǏ=Ɵf(HNubU9+7E߉'.2iĔ5#X0uX=&_bj;Ӄ|cHX)h^ִLh49/(Wf0H'(!9t[dyEȉ%BL |c_c6Y7˹mĿrbɇ+L W"N,CgULׄB5 d́ƣ}9Kx3N41y]iexK]o0Zg[^W!PƊXۨ()?DÌE^_Xdzljl +q۬ Őmۉ'WaSf׌ĪjXaj0@u !)K^Kl=-Tk;/ZcyCkgk;[O6DlU%d/Df& {{{[r ] B`N L%)لЅ+`06$A Qf;,,;liL}!D=NW؄3TNtaX2'Z+'|Ŷj"vo 'G'VubZɏdܞZ IǴ1?ߧ`8}=!+:UNj1q`-NכMI-Et09%(?6(!(,6=Ē7#'U9UW3u6[ۙpk/GbU_/7|6h'H,O& :KBږ5.$|*8Kx SO;g%W%j~Tkz]xKmRuACDX212TKs5liMTȫF3fNۉ'~B7=8'$FgֹX3D?U !ruX2I@q^.2}Ohĸg⎱_y֋9W^14kg>BPbV=oJ5e=zbZVMk6z]̷±\`WU08֫\lk1:P̉W-!ƭF?ZP{shb lxBK^NϬiVEA|V酮j.mot7cɕ GmuO2bHβNb@O CMuy]:!hP84[Fy4D 'D[v'+ץZ0^OT0ӗ`ٝږ5آG T$|ߝCSS֣zirzJF$JLɫKSmďdz)ΊĒ7hݒ/UY^MBѓXiKM.h~fS<)ZMOuÕ(*ƂڀKGLho󰞃Rzy)wE80MXB:[^OQLf"&\=gzfTv:҉%%9 \"l?617x]ا-sN<orOT(^ nff|,Acϻ*TNuV?U>(]0Un/]m ww_L4~A`bXpb+ ֆ{"L"u+al+}c`oE/C+@ƻMUS,'GJ}H p({adLb-BCSS6Db͛ȷu39? M -O>E?/WÙ0D8 ȏ+JX7͛熣ZuGGKݫԱO 4>ƌkDM1ݤ2Xti;hiR\Fzv Q8vOb>@YV_Rn(:G{?nē71^\럣zh;b0]߹XDhB ;&[R"G`a%;k]?VN9C*6q9E.g˝Xr@*ƒ]-;o̷K>OtdNoʑoilDLXﯷ2Kpiȶ]'\`1*٭~kssٳЙk/3k-?1h#6\%o}ɅʢɤMb$yӮzȵ-u=eY.‹&=V'lb_w-𸮽p > .8̵KFǘ ՝CΠ#Ѫmwmw'PFp7Y=ĸG Ӆ7=r(:MQ#:5Cqp_wSYpEqS=Pu u퍯iZ\w:Z9'[^WsD8W*XٓĒo05+,E9TZQ8PJ3&1 PSiw 5xh87Wય1jDM61B/h:hB]G[+e\,0zF8՗‰%WhKOˢ&nBuy܉㊊CG8Nb `۟g.Z+ )8^NhJ)fj_Ēo0PuՔWcgM̰1Z0W>OF/ХaEdf\8^ ;d RhY邦WV *(`g0XuZ1HN,?L}FdwXkp|cf=_tD"FPi=e}N4ICvt,/ ; K6F|.abD74?oɠrB/) Soն{jmJg(i  ‘ '1 'L`3@[@L =a` 484H 4Ȍ JUY'XBeK=58ʦZ2++X P`6epFF2ζR?,˧Cyã6Z陠Qㅟ19dBEpbnWֿ Xp7v!`@{'ǁ-R0R01 DS3l\?K5>%q`"։%'B} 5k=ꬢ"XfM8" 7/Ϡw6<? A![Rm ;kbgL՜X >l ǒmnV.z`?!^=&H\ 9anߥ ݺ=DQ!yI_ ¥P=}N,Will[;wASӐvUVw"a'*&2: 0dv8!XWsÊ=fd93DSwqXX&DDybk+ߘ$VaVG6h&518\T&`,hz,rL30DN,GIU:J7\o8 , nfjgf8΅eeEg*.}XX%/d3D̘ \hrmīT(V ;zcDDs)MǔvbvTἼV !iqѾ>;v ?x`$2!w<ɉ%.p?w4`U}ҕ~`(XP W; u={," ߯*0׀7n2(4)&Ӽ*ѵ-T]Rms_# `hB1"eoZKV~7+P_3֙(A(ŽޢKro0jKY]+nD0Խ#=J3fT#VIlm#`[eZC8OExGfWz/^˸= ܶ+&īz"ޜ)Yo_dm_xpSʢAKM^`7'yV35:DoAk{C{[$N\ +fL!T&L0(~x=Ct_^KB6"35l)mB18 6ĒxUX +mū΄L:bGSeF&(`>#(|0ԘMR^bH1gVU  +hY^)OvzMN43o&9DXUE]ػښ* |{5IXSb_k>W8孇k\β-^R0mwEWپ8P!`\B@ lrILLxRjqdD`I%=uw-2bؒ@phvaE}`GcKV9%y]  FbU0W;|h.NRȸK@,@Ack1\g\S5Xyyv4?\աY-#P|$BWyxhAAB u,j,Y.T,ZV}u-{~YX]Mv H+6؀B][hרϞ촠>u]aMs'|^W"'UWqH=eHDzhuL|׵Es<& ˌlZo[r{5 @?z곿lgٸ.w ';C!W;[շرՉ%n<0 o@o"fF_: ?l)a>~Z:z/īgaQ9BBx$iBGF ǚo"^#isCå+?p[f'Y.iE*P Xrg@_: gUK2@6@Mf⏬[ׅZ_'?7ۘ E{f!EfUMc HYzzAk6:r׌L!sҭp۾O$_A09Ngp2|lhb#3a &&Ke۷Al ?Z\jnXї;y]( mwtubfR~a}{?ג':l x]Я"ѯ׵?ܫ/@|Tk |a8(n09x]GcTKo1H>_pbT /(tLTKQ& !`4@ t=HbN R!}&!$bhR-&Nu-"'Tks/YvvN-e]@qÓ 'Rm/u5Oj[E!LaFp8ى'D≯=|Ctx e&wA2<:?rS EQ!,ƈLEAKԚ^ _sƣi *>*NL#B)^>ƩO3 L4w]{@ӅJoz]Be+GU2Y"U͕BRwԲ @kYZu!~R{]B@ C4IgWL|;@P`=1.l>~#@.֧5Ό[}I*(xC>ޞQLB SaM Q0Jn;E546C6Br0}*C" IDAT 2T8h H?9um]ecDI_@H{]_WZPBvӯ;Zgaߟ#SeqJ^"J=Et>^Rpv6^!)B1DϣF_*xagvˆݤ7lzZЭlj Co9e~ v_;[6@*wv>jm!Z^T34{]BGW r \0!VVrξrvCOxN` }g웦FZ"YTd!`rxCٗzVZoBmsAyTM >J z& _WYH>EZ [[Z V]g&\ IgԙYnYҌ_͵5Ojz(ŇɮhjiyB˗_^:II:9 . ܛ$tq\[}QVݷC`sY~1-Z湃KzK1~o5ŝܹ-,QGn/K;Vy ,utECE4!=}׮8e諒ޞtK]q ^1{m{meHRIuSOjT}r酒v\UwtU3ݡJ?ȎQ湚okڗN?OGc0wZ3q-Ģ46IZCб>}oX mH:'ӫF&qtAʷ5},hƵ|i]&ݗt1j\rmM̍L48pa)~s07fW͒b4N׳as4sag~^cY~t22 ]z\y i"Z9xaF56I}MuêVW#8T8\yKAƊlkO,s@R̬U"[URYRm)+o4Y݇sJXB{( R朼"Ww~omz @vK2+ (ӿ2R{{lWW;[We;\dbR݊(IR,_`/I>t ȣl&}EU]IS<ñO9kK/޹ѓϙo>!ikBSR_y`$E>T7{sێG$}]C \% bI?N:8/[: Y[9u.zj1[k,eҗvޘt~ ͤ(zK-XfyDx NÌsdLAfA?_6#I&˦={/H-̣I"QI(uK;bJMqXx_r_v{1*@.~KgkD8@+\k:3$G ./eҟ8]#Z)iZ ϔ_$_\[ e[Օˤ C,u&ir̃E֦dV^h:`o߰zių\$XWnϝ0|3/wI:8R(ŹLŹ&(Ӌ_֔~Sz=/OԔ>,Is\!wHr}Xt#ʣ=~ܭhk110J7קc[HP>?I6ϕ%u'i+q4SlweO9?׺j.665˹WT㧚tRґFI7Jݘtչ~܉3/tه4ޖk]pU v80e+D#Rge ^(vvvc6/TƎ͵6ߜk]x{;.ͥ* L܆懒΄rH'u_9I/\$Ec̯E ZwΤÌ+|[3 _4yf}* rtC>u5 j[$i3{g]ÒpQ[[Z {cu\1;v=֣/$ոiU⇫$&HM׌(WV]򎺸F)%c츹(&--Iߖ=׃?tjƁ7;*֓?/wf%yg/V57iI窂.]_*7w=,x֞:lc/b;$t$}?膮I){>4g%A*)I窂Iׅӊ_ji6KZ0Dudj]睻jx7qj_=g`h"<ּRy-]䑿[IM8(⃒5:WH\pruێ_r䯑+~AF>nu-]bC䓗M~UuwW[IRS%Ӓ7BFVD6t{%+wKvFAh". Iߟt-%oP?`זCtnÒ3j?ϭ6,]}_5I붚pՒB%@ݞ#2ʯT~վ|VcT2O(ߦR&su F\ۚ?JIqLFfeL8XIj7SS*x?*&{ߪ.IZwko¤s4~ɮq^m7B}^猉qIW[٧T&Kh|0]f7*h_ٝt K>J魒"dSaiƮ,zXFy+蓲I3Ⳃ>$&93oٴO: ]"e*o^hd%.OF ',cλQRWJ֠Ovu?zDif4| ^|%Zy,5͎?y;nˊ&Lz~IjJJvƆ\&9 noILN]Ò0F2pKY}V ܣ%{?UgK<*I7~\K:u"l`)?>`̬ i;KN0]K%](+M&ZդnM!UHfn҅fV^'if&nɶ:zΆ $e R&U2{qFv~~4ډ=)鳒>7aK6sF>~j m[kIP3c`jm ^qfTJftÒ RMCO͞2PԚ@$,|W}gH*|SٲY[!=rl{cQܹM$}M˗پR-ލ.'!4RcJ޴kݛpf%_,鴤IiVF +| tShj iu/z,x,+>( TSvCM04+ԝ=6bnVu{M=^_HU{tuy(ZIT3!;55%}d%'.ui.ؾh$E kݽtbbUrA8S69-tEfHv\GB(i_Z%<Ҵ.}Iҗ_J֤#׹Vdj+]\yKҁpp)ˇ=w+O2KAꗹuw>$wF}q _1?Bw9H%v*vʵm'Ce7mM:XwKʋ#饊tN4)X%_'%gG+ oA2y4_s$t$eY=A&#Ǒc/:!LtKgh(֤vIZ'ngdxL[a^z8kxmUyt?ω`̗I/6P2_#Qyg]I{Sمr͗tv&? JnK:9/>2[z^$$ RQW`{rD?_čѦaI|ETܺ;m}w9Q/1B(%_rTkw= @-?ty46s6* =e/Q ͜lύ̧w)uIgKҨkO5,Y)ҸLZz&D8XPJ*qu.2Dri'0 (8'Hzy/Yf$kKK]f$mɅv=-ZTSĤLa%e%`&:64^\!tleà|+:dzDIIȲrm3vz,  MB9έB ";* vEq~e`Lz ZWm}$`_r[vʮ—;@ @2?sqT[g3,ıQIR+鱸HvHQܣ/*c=R?<{eWH}[P(iN߮T 7a;!ia[gԖG%)iZ+1+<·UD+2ˣ4}Y/1>~SQ&(WVgbaw2:4ˣX*U?VcX駻TNVcȥRUBUefcX(wcK_%`5ߥʺ`)ݧXgcXDyTPUER92=~ ׽fcژ)JRF[YoT*"?w{cۘ*JRMj;%WeXUr-0~WVpKV>Ӵ.!ƶ1Wݱq0IZR|0;(Vcq(^T+N*!ƾ1W{KJB |z;8xeWƬTx縮h_,JRj" ~CVv p?V\{a̖G%)-<"T;H_~W=.`|QIʝ4Ò<5 @ HśŖS <Ƙ IDAT82ˣjl %mHTfG){+1 kb`QIk,X,jHw*Z#;wr_ %%*qf\G%igI5Ӌ v= ` -Ը>[w`|7QImh~<\RΓUf^۫Y0JwCuInpHUyTm%URG*~K6hIrzc?Eԛ^{iܕG%)ך[t7gcuS. ?Wp$ۚot ՞[^W/vHlA L}*wݛ}qmܖG%)I_iҍ՞>4+r40E]e ޔpx zk\G%y6U% c %Wluv6@-)Aƻ^$eC߫HTU f.S(s4|sL<*iE;a[$#nk :J?QejzFTA& R7KE˿ڣ;{$A'TŮr߱.Sa&S$Ƈ5m Lb<m)psVF¾"|O5&8&VyTҦM.7O{B ~nJ?둢$8T[uRQ;wWa f•Gis' J?ߥ/q=0&\u{*՘;FW#nBG%Ia56_TpӠ>]ᣅ"8.U]uѾf{5x&nytZܾX] ~vJ+ws=0Jܭ'2_̵o0D/Jgۚդ+KPq~ܭO?(WN,V^GwWk]iUkl&}LIe+hU"x*~7_q[kTyZZ*`b<unZ.`R!C$%<9)ʸB`vUe _ȷJI2Gx>]LH֢ vJlDnlmn0}lkuI&*\ۡk;=B TC/k;l9*uUgP}Z]ҏըPfkׯ[# B)Vg3?Za03ؙkWRXےv;KI4xNEruI;^iS#Th_9;i$rY$)|dP߮/z'ŝ5Dzu|(0ido4t]TiCRG_km\<|椳<5-*~+% ~f|ʃ\&}Uu)|dP:5-*[]]Typ@)Uy̅oVT{0L4%s^5.AR]ҙ*߾[;{1MP9ӥIt4OkݼT毦N+V- ܩwILߘ ']Ѿ;Q0Qef/9&]zSYLӔjԙGR02|Y_w>]Ze]>< &:|/Ⱦ('e$Rgӧ*8G1*|dP;{bwcKoʣ`I:-ߺYZq܆uo4jIt8P{TOMSiJ6UveR_W{TK:陭-+wcK_a1`΢EӃWJAɦ&gYd'OQ *uTN,Q&S]U;ަM+]DB v1腗WS|Ro$'NZNS:uO'ꭨNJ@qo(t̂ZtjNV)J=g&+[t,,|z+7)]yu ʣ`I:^gksK6,]h ')n>)4pӠOiR*3EȎ.=^S;yk{lc5 6ǸVvElNF=v,X)_t$,)9&8\WT^'M+S--ly 1<:= ,>%oΒʣcwUϬG Ʌ0>/bEytwEE*2M?cʣc[m"D&?I5P X8Yp)=rCI1*[to)Jr"$C@5P_<*]^IG%'.GǞ([V [|$C@P@,Z4=v'9IiGǖڽ*};//&vL$u,Iߟd&ʣҊ`޹,)RIʣc#oR}0._4oI4T 腗 Ko&tAyt -xsVQh~SNzUGD@(b؊nO.TtEytUuJ=^ӊtHQc/~vdzw^tEytt/pcKIG ]|ڤ@([Er(yIy&GGH*URٓNW1鶤@(`q}$]!KtDyt*ޜS`Q$ٶh@y.2^)NH:DytT]*[*&ۨ$Fwfڕt-(͛4.u~_QMV -$Iߜ?nXJ:&G1(z˗IRIӫ5hBJҭ=*߱[FUCL Iш(|y{rt\J8ʣnTѲ_߰zmI`<;cd0%[7\Fr}ʣ}JRyޤkU޷+(0$G~IM,g΄RymJN$I3?000Qp@mEץ/\rT9҅I)vîuc˞8K+4ydWKn==1`EI8QM]ҏ%9w٩OcٺvıH;jܗTk^d(Bw̨tR`dP&]_EImk# DwѻBc$,mI𿒂PWTÒGdzĥ͞ w6mj,H\Xʤ ^1{m{Gb1SMFpEI[Gpؘg\4T3>#-PfK5f"Ka%6KZ+5u; -~dT^/釱,.vX 6cU+W]Ò^I|d%+iRL/3%`BkLTw]?)q=&Eѳj3$"RJHjXu&ִA#Ă(0DRe*[Qtg?Oi4BL$G/ERJ[ɒΔ̡Ka}'֪YRao$V)&g9Z[Ŷ3MRdzg*%[]iu5ƝM'Ynd00s]a0&Mww ^)v稈3.Q7w[ٴ!GqċͽVS;JR:I6wOs>0Dvi'K;wqW[ݒ,6xZ6L@dlZbK2godɐL2L& aF&a1 l@cY-+! 0Z2^d[[wCRT]^9P.זTuu,*QK-ozTn800| YOL,X(Ñ$PAZT]\B#)!{L#|tU5`ɣDEAGF>ÿM:(TMuZo׆u0DDDDDy:򿥹,`mYY5{4d}iF Z,moM\BY!@+>LߪYt\|M_hBL%*`?>34zuHRqp\CDDDDD@U8rEW=BId]$n<YH5ZX} [Ě/f$ؐ퍌&&yLJ`T7⽡pdc<\BG{Zu,)ώX?8""""""iɣDyNm$ԏM/&s=bz`]ޗF%""""n. Ҝ%FnV$7Ļ6=7\:SJc0Te'Bd'asa{v#Θl4a G ˲ΔS<ɳx؎p'D<t'AdH-@|?T߸)75Qk *ԧ^$|DĤuUt69efPHɣO*ļ'J ˞X=GWyNؗUNJg TpS_{!"""""""ɹ}$9C8zln9շ{"xLG=Nٺswl)3Cj67xuv+KuppeW Nv -NAg&p%ZzN3=iǢWSܚ"*WP׼Ȩ60J7 IDAT{:(Ah%ռcڝOVR~` {s e3."""""""G1{1g0|c/'s' T=XsMa ˽Qz;>Wb۹AϸܷOQMEp`u;V5E;$T9jW=X̀ސ2[EcxW׹*1x>޵i3jwÞΎx,qUW:g];."""""""JɣD9Hܾށ/<=ÅvV\3OEh:AS6EOtaO yn=9ͮwip=vz̑@U=f"`Df'2Q=8AI=\'9ȟ\#.vENrChFLDDDDDDDɣDS)Hn;o+* 7bYgcѫ: """""rODO8=oܕ`q)pl&k]?<|lđ+vlwuV,YQ}j8鬽=Y6QNq.+1QN(u^Xhʜtsv:W;窤Wu1og0瑈/Q&/)f>c(P&!Fi\z`Ue n^_mbAܷWL@D=1}6zCV2Z9Oy32bR84Tܷ|kӄaTẲ(<=vF}eחUW5{ODDDDDDDibQ"CHl>d!d˳:޹z`]!gQ:jjJkD0Y*rq(%P\e1:f)&""""""": G2P$6$A_,Qʗ{Lu`mȑvRE^RЗPX*$%yZ?rJλ9Dʽ 6 #;JJ&(E'j{ޯٿ5`{VSSrC3&+{i oVT 5Vl;2i/O*=sp.EAT"jO|Mx:UtªP8 ŊK6m:6NlY-oɣg)K!Qx#a?p~h:Tt8UM-V_{\CDSgQMiv jdTTA ‘y(?ɇ}x2[#L^ ~-i/]@r@VG{dv}v`DWLK6aKV(s H&N{*4!Y ,iW(u=vo|gs4,P7,NV #uG$Sz;_f(R]Q; z)<ܬuHK_g\|kQz:e9=[?w 4z=|;XPmU1ie:ս~}v,G>ehq^FK0ϼ вMOnE-2GY `Q"o~lHp"N@~?ԫ 7Btf bQ>PZopc)fjn506݉L\Ts#n>{23ߨ#{Xx3\ip.<u_qNlCB2z|p"vq=6a=G+|@%NDDDDDD4E2A熀**W ?FP]Pω us*1Q~mgKd;ݔ똈(<ظ6- Y;q[b*RMO‘G99ʺ>~JJ4n3"%w9vbpWuB>jj8"`a˓bpg+* @541:UUUi 0K6mz5y}0ffV """"""qOw o-v"""""")Q$Cyf3C@b$d)/cDTП"_lݝx؇?;Ԣ'`B(ުj@|v? D+P9dDT-TR;Ī‘7ƢBXEKZ/Ϻ :ָb?4}eStT6 EbQ9LW2** 3>nb8EFp_Z۵i/fHD&"HYɣTttĠeYM$ N#",^n+YG:d, WL~[3-z|0v""""""b:"$tO=T_E/g(9rs ĈDj3sFIPIeRk!G~YnZ[0˧B]%j)}D\O %2+8hٲ}6{^;U,6A`4=I\qO[bip[Uun)R~` {ctmV%""""""2L%*OB](v宮s/ Z~!\\@D#w}ZRw)@Y< 7}O]`F`]ZלtsLf{?о?}򙊜~l-2A܎gW =./p[ xg{j.]`ȫݍ-\rOe+A)˼$Q4&zSOgǀh#I)SQ %SWW^rՖt+ GR~`b;\D} F(DDDDDDD4GOH罝wsHqO*f$ ><w5y* :n/r7O@588f-I]g궼*WϒA`(h̗Ñ'Tx/JlTKu7cyFqJslޑTP8ATӵˏ' |ѩA ^ z9 f<Ư;Wu`Кj[ \] |*_BrYS : ##+9~u΄ւ 2H]gUvEE݆. @ȟ:`Yipu8{֫wue:*Nj|~[fh/e}u$eonSC0,|RL J0 EP8r\ݳ-_ =߄c|V@YD@;PIic2k!E﫪mTOŞ2$HCDݞX;WtGw"]P'>!||/U2܌mn-$@ЙhT7(w@'='D'QJu|rJ>UY{Qu.Y^STPus&յSiGU]>P/.VΪ\[W44_ ~Pw!P:qov+|1p B᭷UL8 eEݿin2\k d%\@zES_k ė{Qͥ3 ;BhEe @˕|hvuo1&-֣-䶪pHp儝 -k84lѪ} $[j`%1Zve4μ9P8՚Quadua_[K6{g1^ @YR`(QnuC {bѦ ?׽@"v:I+N:SѴP``+_tek;$c :Y*ԷA`DIJZ뚽hԪȻrFex{EG-*hV(L4`gO"x77L24 x@by՘;C |wQ9yYv`=YwUM` ЛK'&\X:\ޯp䃪In ad]U{3~9ՒA rPlV曬2YuT{s8+Tf /JT{A'wE驏!‘򽳬7+$ 2A{fCP. EzѲeG{P{RL,6eQ*ZRU\NaP85.Pf|>w]==:7> `3W?+*  lDDDDDDDDβڑ&@Z-n}*$_( %FM=Y"yTSoI@Nsj߳cOΔqկ EOc|٪K6}{D Pmd5c }6JRk) T.Akr~p2F* kN?"e-U5:.:T%@L!WARBYi&3ZV@O?fc| $ :G*&@?w0Nk^4^ܑű \IUuH\ӹ^Vk;xڬpT¸@=\ dg^ꆇD'[ʇ}|bڵЎ ^N4 &Ml1שJ 55W:ftRXm5qF4424PVޭj8?L  Gcѫh)"IUW֮9+ѬЅy!#+s."aYYᑤƛػ5VLns-1?BHT\`Pm |ƅix8q.D\]ch _oq32hao0uyJNIcP|kcſctw'zG2vT6^/"7:v9Tȍ}щ'U=2M#𻪺58al 7V9&X8)5Pj̩؆S<4XBuoQ >X ۨuy_ם=BgN_]T;;~8Tx{-E5ي9):*ؽ\Tsw|o[ ~&ɾvhi]Z^#NryI(xmmOE/^WPIesm5XX&i;KUc_GkoOƁ j" }SBM]7M ^T&Ki0iM.{\-(}7j]eLM _יFwup7S;6@8¿ ºƚPmOU_J /z-1+QC!T zKڻIumSm(AT8 XFFDDDDDDDbQ"  &:TVU nƜnL/յkF,{;[4"&]/w' Lct0l`.{33z:[ G`l57U5׹!VkEEoJgM`4Qw^]58a//^-1ƙcu_~>Je]ӹP'Sgsˡ$z:jѯ8>˚MFD7}k/L- ~1M ^xZr9"'|fub]))/V]+n_J r*[Qzս9 +F@puJ|.("עl{f"Q(6@Y5fq=pӍ)n)Ζgt]?08P.c@(rc+/‘I),V`Q,5#xhәY'볬_Ypz#yںҙ6F\W9WP8*?VoMfg'HuFvEsY Y eѰ֧3\QJ`n/zQn3țD]bP DͿꚮդ\?I'L0xug(߀O-kLnq,o6>2'Ϲ.lGuqK<'V#ghF;2Nڿp2&WONa-戥 >z)~G觝DXkFoomvb\cQ욽/L2{"X=ZC'vU}~̩ $呰*Ns8L1N{{o:C+tu@/F'˙aSEdQK ܜjm.$ ^_ft<^я\ &yϢ'MEEB@ e0vh]$ㄩct.906yeF=.%D'{29ʍk+k܆\'5s'(;7X?4)6Mnױkܰ#7؜oZ+W,ra_}ݬQj}^SkhXtJU}zkCWO,фX 'VhZcl9P?H(ՙ+ז]rjQF[:3q- 7Ƣ?u'=%l>i`Y|_,axPkY(wv8jPZޞOț,Sl u7HO׆.p:g2' #qNT-PpD| n+p pϹR 7{X^>R4U?{߸+`.ӃnbY^Z.ax0|uf?z ;stQl; }sYipA<=3޵ኞ؆}ݛUOWx,z Q[Ñ L"U{ byo*׏VPs րjT!G YNnCK6 c)ν,_@{s)%'mg^uҹuF pryUԤhY܀ѤW0{u1[f>*IP[#D!1$D'~0q&j[6S$iI8kQ |0N]CzDDDDDDD&R1ex,xg}6w>13s~@:Mȣȕ)(y:+cr*rm4A4wm>݊Ӄ pU9]yuD>HxC!X0Mπ$|~n\Ԁ& { X4YP|'TYYbyΎէ`ViA'}ZZZl#s/qnYl.#xJK%TO3-^MUAP-q:hT #ɂ陙DL8[mQvTΎP8CXUsuLoob|Ӧ5^>E]N_#~v r*n5IgBƬ&V(;/.%{{f er87e@QU{9U&J6F2׸p|ȉ*AaZ^PX^DDDDDDD37{LXݿ89u8T}NiE]ϵk4e:'E'Jvχ5S:\XNqq:kW-يZc8~mywVP/wN% 8%,8gvro; )ޮ,B"?"Cά<W4-$SSDGJWlBYTj8W,\ǐj.uDDDDDDDt,Vإx OA)b#7(tw'z_,]CsTyb #?ʺj{f‡ 2>V&ne__9P6A(^پu8$?jrHUDBs6pHn2WqOHMUS\PsmYypI D{TI;zEcpg9uFHg[tF"xqK,䍁+YJv$RzL<\Y?~yp!1n%<$N(x .YWphYcE==]2~,T~a<̵x䑖o)<%ӹ DDDDDDDym)'؆HO,ޮ7{cmķ=Q"µOjQ)3✸h%U%Xtv336t ',/GiqOE䣂xXMNq6Pª&+Cj՚οx{'Yg?#zcѿzI0@r$h+` 1-SR=kUN:}(ٌ'ē/ T}ȕв x%Q&õ]BG1|f7}A8STdDp^_W 94+;̤Q8U,T<;WD%;U "ޫ  Ix k'eg^ /Yp߲z($gq D@u4tRE۲~Qͥ xɨ7AFO ihQ< <`KNx/}s;2h^4'h;|PS]ԜxțDDDDw["6۠k<8^̕+9Q ;*ZnlhG1>YrCMMвƙHxNp&wmzzcI҃?C֢Kˏ$tJDgxjНO,%hUnT='}1KoTQmLJJWx-X8R`(d@/d*WFƙ.=ӸU28O<$ex^Gq/+* Y'̘9|@,t9e1,*mWE B1{) 'uN`f7#.r*>vΑ꣭rX"""""""+vm@:{Ù@VIsl%6ύ)ac]P=xI|("YZNTȲ+[ȼd"b[eYj٠YKVǪfb8&*V8mc_:q;dȔ1kwejK5k++o7`>IVŽ!iK-x.;P&ھ7sONyq%l^q(Q`(eB jc*&yԸo煨8UNe邓Z% =J?zkQ2k"IcRm#[2w<֢e"&ϋeĬl(Uw @║yb:kt M8ǖ*ʖY=MJ]ŴwZK,usoW:ӑfZ'e߂)B#7kFNv DDDDDDDG/*]U䷭ǽc '2zC6NNXg^u˶ݮ:nE]m|3piHMn)m3T蕓ɩĴ6kIM5,/b-q@sGFUM]yd/uuGL;W#F~[06ҩbE!)J߀G(Q1y|IjXH,CoxF1MJU^mxqC)Tg7&<9*a}eٕ3 v跭`۶a@Apݘ&ϛV"hBfxvdsYP W-aidnLEu~hsmV:8CիztOgb(jUVV_0Piy@WR!_彃XuW-D\i\,-b%tG8]PEx qQ*؄ִT}gioΖ+7kX}""""r,3dg+Oc畹ᥛ/ImŋYbAm!uWSaW;/yqƨ9g΃Dr12 D\/A2/ΙLYFUи^λwoZwr*Dc. WW礃7bҒ|r{j`0 l11p$|"i ༓30nB|ϟ EZ98B85UiYD7='\|J\c -f*ZQ#zҳMuu RGz;wWE(gdo7Ӿ1UOp(S}ǙhlHmΕE TBqoO@0EDDDDDDDY0yL\q۶pMymr#1"wWTnXJo[b{>T,/f~U y4 g~GտG'\X򟨮^ΑUYڍ)ݿP,1jKT8P~h\;eL$Ze'U]SPgɣ9MSpWa=h7g Uw忓Y,>08l*0Ő٠2`SO2a>mf&,N&9,w _*e|/g; rɂzwK_2p]WMIşW<|GH߬u\Iب 㓊=<.1M-?; bͲE- !@PDX !z(> ڗRVMw% ےziݲ^Tݴ6ҥU"wA-I D—3b ~jQ31R,ս}N߂9uSZ|Ѣ/ 7h]/)1y4bb@(Ծk{ `=jPjE%~9g3WZG^Dn#jg x v irJ|;;Z[-j4"T䜈J&vUE~IrrȒ;r0>Y=tʏMLC}X:8nCn~A겪x# 沾>\ׇ@ߊgܛ{t'޴@Sȡ/fC05:-O{[keYiU F$ UW,-" r(kTdŽ۶5NVVU5gioT{]KDDDDDDta( Y{dy0m2Z -}SSH*ʦ*nNx@:]H6"20bliknD;UTZ`>ZpN7$6/71eC=ɖr846tǫ"'k,`W1ţUw+׆sߨY2Rp$C-}jP U6CO ^?=+#IST%1ǭ/n( Gja1 'm}$<=H)_*><%i43ЩlXuķAQ1YKљxEիˀsn|Lg?](\'չaw8R{ \QW6#6>ni *viKECgiC"PY-=U͒P16 `X7Ñw[՛LzE#jvt6謾WuS_bcQGujӆ8qޒؼ ,ՋDTjA[ 0]ֳ9ʁеfBQ$+?*-z;"= a.0 SjKlىL"^<)7* 21L<2[V0nr6<5sh]h '\}^_:RT=ɖo:ɶ-q \qtӻe}xibg9bz5<-8+*1)Z |]7/'>jb:zm9~*;{dq:?ಢ@nq45bSz[GmW>@} NQ"""LQ^@PcN)_qO#\qXzzs&ޥ"(fA h61д<*TuDɭA1>H< 01N'>n| -b5p&O|XݵNjXSɖ'+j1DMkDb}c‘0cG`D1q-AJ=k>ҟe2x& 3JX́a~99y}e4~($ߌ*n p$!w9jq_+eym ,ޢӀp&VaYLQY7<|oB# |4@H`f~LRGm"gZ[ӈLKC2 BPӓl3T"ksF>STptY*q2WHYoDtI&GLu5M|l蓩dc.՜iX-끡Ьo8"O ,yT|ѢMC>UUѺDDDDDDDD'az"""K?DoMu$>M)$ F=xA_PtS!q\m1meG' hmehqs|oW}gk1?!3.>Kk| ȥCwn֌V-DDDDDDDGN#{}T/Mņ=jښ*z;-XeLP7ʋm翥H׀Xcu'ʧxP1EZȾyl}ـZ"G='@+^h9Z~tj@mw:}oJ SQӧe=cƀɖ:BlڑJ&:՝L:IpӉnk :6n 2OK--=Nh?v̒E~tf +&a ~onoz=7]֝iGN#=9|`j܀P@u'{iG^a?2;7oLm˳`mU%j ]/!pӭ"i-H4{7*s:0u 9@ޛJ6oʠ9\ݞ'[@?5qQe1TBnלMnܟH|R?Eɇb,K%s$:t'wUla I x[*0%8**-FmédjYWmOa~cD4yTpguKXֻr#D(PObSʮΖ)\M<n*H~xJ@\`Q:I(nʆM%_,tarfAuE*W(* H_5>JDDDDDD4y$""id!x%k\ ͣEw6vB):aIy+N8Ir"Eq&E,<ܭ7*L (,Tx̀n*t߽{Ѣo P7ZqB1G(6  vO[WXIGzW^?;čz- K,;xbQTrIv'\,ae'lAx2G+x~K`^'*oQ\`˷hGg7tl"BC(b,NgnL#JcWF0Xxޏs"y' Knc| ARu2&O򁹏N~ןbչa72U=~"x=tLCeoxA"mkfҁY}hmͫD‘TG'Z^,6cdpfy)~[]]ReU2BU4;Ր@5baCtem)|kd8pu ϋ_V;˘,F#"! %b?YI Gp*=ͅL.z5U Ð zadq+I:!,UAاn~W{ !"̏֟QbOfa;X,'&yɣdIȡɣ;Sɖ􀈈|pvdR FٚJ6L~DDDDDDDDDDDDDDa:""""""ɠ0n.&;""""""""""""Bb(M{ 5TcQ1y7MN~DDDDDDDDDDDDDDQ""""""D+ Q1yʚ\c3Ѳy"""""""""""""*0&t&? :tQ""""""5jli_GDDDDDDDDDDDDDTxL%"""""ihm5D3[`51&ѴSt KUEDDDDDDDDDDDDT+UW֝j}* `3eW(TɎ0yp$ ^ۡ `Zz1 Yl*>{Ӻ,Qb(M55uDr}OGbMYM/פ: Q1`(MW)>7lBCDDDDDDDDDDDDT,ض Pyf02KM*Ę=mI """^^1w }H!"rXl5su#xtpgkBDD G7;Xx4iVN5IENDB`cucumber-cucumber-ruby-core-08c81cd/.github/img/gherkin-example.png000077500000000000000000001442531514133137400253630ustar00rootroot00000000000000PNG  IHDRW6JsRGBgAMA a pHYs(J@IDATx^ %}֎zhYнq,lGtAY8$H||<ϑ[ָn㎄G7%%Q^ 8A˒erծ=gլ֣cUVYjUbxfeX][i3z&mPA۬Z+iF7p0i !@ "JX5 Ips !<LǤ8)̆Irsṡ>ù 1k [܅~ru=Ni霛o.uZMPֺIg]~\~@SG0>=߬T]L_H0yRofY f%#]n f1wA#gY;3KҴ>ʀCQ7а3L3|&߸A$f!u~nRM"țoa$Q`M-Vwװ}L¤3J7iLõ1~(6o!U2j^']fX6npU7ӡ$ëU7]`h&4}.gm{y2 4 3Qx՚P[D`;rvM7|v56ovMpm[f 8{gPfMֽ=|.#'('q>3~fl}Xm[7yr>]wLK!ר\t 3{6f#dgpu|GjqǛ2gڨAXsm{|6gͰr4\džg|^S<%"Hooqqt<厫i|nq@ lab3M35*j3ac5;3MQ+86a8Z~^4W*w+Q7i3] ;\RAոX{/Wy4Q>^m9ö.i?;\ԸTSõ/{aیͰ^N-^{4ur;8Ugju kzmbsIg T֑c#ٜ~l.sV39Dps?W7\MÎ9i:m@+..}Frսgr9Ȩ!Uڄi66|.5./g(* 9)_091mnEUjXa^K> F Li<^(wR;|jXa}^rr@k`ͿNuz91^0+ڄS^9>yI@rô~ni:|63L)7jpd Lq8${4*CnZ|n{gaRCY.q_7 m~9ä0}e!9aV0{m^tK8Ln?IrZ@_9æMΰھ5/9%k5iŸچdޯL{f~1up԰aR ߏ /RPL+ F R{&|-9Hv@SÅsKuLjX4V0l-<#~دi\Ç>åD~~ncr?;}KOi3|Son԰6K]ZaRbcäTi?T0f)hQr3usK fXIuzpa s^džI +a$6i`-dN~Յhm}? >cr1z48 (ٰl8/-:6LaM{nv6 kf~.I;5(6a0jX l0u I IWa&Mk[4Xc$m>kbr,jጺb_{Oocԅمi~7e2w1]"_ںc$|߿'pI}._K ˅ ^ 'uJ8iFZΨ9Gk<²k?_%ۄrɢBe>3J?ߓH8S^[#-}̏.62+ buSw,'u~a$?I H  ~Ƅr uDЍr6FV"uaY[uYFy#0WK I  Ywsgugھ5za<6FAd~6ݩg0{߄bH  5u=Z`F6&uه/]ׯiTسX$~6R2{~YX\\U7faaNga0uR7 #Qa) ӪgkN=(aVu`\ ~Ϣ%?FRb^< 1Ն|`mY\\'Ug,7jʔRMM (iԳԽ2 a26ݾ_(^_831~ Ce~{_RMuR5o/pRgX[2$iJ +k0mgΆC/J-=hT=0^hu_ذueԳ~WWVϵvVv[=;ױ簟&FRKKF $rr\)y a{5j3a~.5|Ӹ;y]u9R{g\ |(h C>ԩ˗r3uÆIT7FWsuX+ņS7Q$7kj*PS`Mj3~MsFr&$6gs(> ĄS,_ ߫vmE gBrƙ1]!9S0% aRÆsB!eNani__X7{apMM.wIMפͰ1})94nh'%KF;Nh3K=r7rvtLH3q`m#z\}eV+mSÄ19YS(Q'M$Zf6U,&OSne}gV9fǙ63{i?yuxN5N7P-iN ϓbRn\]/iVnQQy99 F 뾫Ͱ@|ɥf"f `& 3)Y.T}~LgR8)~YUg컅_lB(w^]UF5./l3cm x|)9|aS4͛v X/3u%M; ' 4@ByS8z˶fЧmlHg`_L]SH ڼUI ? .ퟫБY :I/CM:BԄ[?^[u9Ğeaqq;t}<7_WK峹?,̻#8ۯo'(u{mq ''Ore\kaa/=-.>/uIn _K c ars=/:Cs}//~GU _}lKٽcL$hŭrzk_[|+5Z[>|63峂I} M}'OgG-OS8a1rlqU_Y Ux3/r9{G@A`)Yg_Woh׼Fê"fNCdX'jvbM㡅"R{12hҶ7HM%^ٺſn\כv(h7еt^ C-Zn.嗭\裎z~}_~iq ')j 'v:W-bT}OW?M?~nӦl~GBn8?Mg]w=}_TeuRk (qC٭i}GW]6{lYiP[Wjɮ]KuhN<_ee(7O5.ӦCT|/%/6́ܛv| ߞs{?6J Ǻ|?Wl_}.kCQlӔs7`y@J!TlxVwwY\heӤ/O%/#Z>3 ,44}Ο¹kw숎͟^7>Kdlpou?-K:o}~x*8"!4r?y`|+o#(_n?Aϗ S/~+e? H8-6 T:]Kh床^ԪMK|+G #òRpuõSv{>կnׇ/ݡ=Y|WǪ,{u{n>nSUgy&,!=N< _+w/?TKeS:P5 D1^|}?']~5z?r;)yr8])S=]/7o}oxCȟ._;6m,9{: y#e/e)wW@?B?^TZ6>=[`wx(gg٭s?s6[ë35b(J.jzh<ԥ2ASOxU^+ce0Z \ r/o񞟉 =pJO,N\G /[oFkINK8y9ׇC󘷻-C7|d|?_w=<N?KBKK֢yw7aGlR(~+wj(Ϯ=x I''쮺S]DYsG@,'}mC_wv A gYx7TطkJa3gѮskN]cJs47tmXvTOԅ}eC~/` !lK?0K}өV($Wx֩JHw?']r-?R`>9 Jdk8G񽫾/W+ i/*Q%Sre\kaa3Uodfz~43u~%c~w~n Pd -!?`@!8bXw_ Ei[m-Eq߼<,ƢxzrwEꍊB?(\QybQ\zS&`yB -fٮq(a4^FSk? ߬{o+Z\S|u:Z4̫//߰kNY}冢8'YwZ~!ƫy3W,j h;f齘3Ǐ#Fӣk\ m\? *;WQkFCgw$I6):/ML{&_I),uˁٕ]yxrnE֢xEqZ;H28c4- iWh#F GyrTkGᚃ $oXp񾯺&,ߘC!l^k)} >@p{Fy ^xz[Ɯtqo?Npij޽<)8SY@si\6U t^i Y?Ї`jwGV_/4]zuG`*>zc;MB?l'~ M;f*k-]X+¾xcaޏܝ:W- oLbtZZ8}d!EM/B3]/;n14-jeONQ_Dp(*ة"b@r>XK 7 v']{k5y{h{lg:}IܺCOGE"C7V!;Msq>z tZ\w`*f|v;kO}w/,?kXVw^AsNjQ\w`f|v3k.{>ܒNZ]mj.{ᝃG" ԒObOᚅ|g\_(.{EYٺOEZ!]ck"izZ ~ T6ro/F~SkSS4^ ߬Sswo+ކzs #cwUӋDKhX& WJA KOYw]Q~RO/CWϳKa:W;-t^Ӎ3ڜ^m۫K4^b==^s[s6s cwxW 2}ه? .V\Y=ZXXLCus_43u~%c~LBShSZ*s)j}SC >]ѪG>|_N5cP3Fw>?GWYsW>U|M;u8 xBwki%?dyz,3>WPktjbxi[ ZߜH ?Є~Za~- p?Ln򡛇9ӌs9-tVߑW7+Q?Շ/t;P>ĢO7ѝ⸆_~xƫP/Fw՝z 5-0KXu4_~jŧh?`@!?`@!?`@u5~<,,..^XuЁ^zطo_ٽ~-oyK `|[*{{Æ aVvcXfev7=J==j ڸzszus_43u~%c~3j_}Izm;8o^;m aqƍg_׋+K/0_n[n)oSO-1,;{k_9h4oF-ܹʷW[z1;v[quוa [~Nj.zUvZGu650PkUfZ3nƵ㩿˪/0:;5@ Zr)l(0t~QZuYeDž^HAh7 ~qoMt:<һe'6Ul|{n&3<|8&vJƓO>J\ve`5?\vs=n- :D alWGydq}@hWXwe}455=} G=< NP٦Nec.vewt=(e:lM+ϺCP8?xz\!іZ%9Cu|{RbTg/c<GQ;. ?g릷+neƵwZ~~՜_䷝ _4Z4ɨ7pr-?-W- יmˆE]p˴4isƑ[zl!|C]ҫGar2Wz-v~lmKjK4m˩O;_6wQRFmkSGgn?SSqwմ+ ފjߌ:SkC ?YՕ{]OkU6>pJ+u6-pKyŲtg仟|mú.Γa;vpnɯԏ1mC#;ߚ^TX7f;;^y;4M*#vJƝ]4B@ӎʬO[Z:Uv(Sxmvg<͛`~T?~8.}eDlݺue ю=N]u6_MVWޢՍoZ𚦹S,\'c3-PZ?R6@\:7[~&u]Q6lٲ,g4ޜ=Ѳ{T6-{l_^Zgn>UvMO4mR4/}ӥzlq+=h[*零ꃂ!]d aSgPSMXQW׵LpPWv}ոϺ:uzkl,)kMZ)R>Q uW7yΆ9VƣgWԊMߗo|G>'I~WݺW:_ϪTvTYe7u1C i'F?8!Cя~qcRhڴk{! oSw*iW1 Z姲?4_u~je7ڦ|ڶ7Vvu/C*va4-{$-g͇I6RfT˦ꚖbOn[s3_V9/Z'ofY9izUM/dga$(еG4=VGV4_]P:~@MI9l~TVMN|RzHDǫǫ5uCz(P%zɓo?GT7ǏǛ7'3qڟ|3珬#_W}xx޻2նY5シza9xwR6\Sq3l}2 kU٪=}ΗOX|=.~ױ}N׾SN9|xU<+z_}\\QZ.6LOBw<̷>ǖ-[_|e•avڵ2Ci G8LϦ磌Zw~'yXT}IEM;wV,?V״iTj2m*XxeU}rV}훟XY̟zwp_2k.Wj euńopaRu?_y=OV._/ub,:?g?n_ BR4>V˲OG|zz3Ϭ->]ؾ_eWuƄH.H8SAיp{8g82_럆mZ|CTƶL5MwӸ᷉Mӝl[?j+,$(BCCiW7W˗7Ye2*U]Y\r.˽Y&f,Kl-2XW>f XHt8Q [uCv=q'j?.gY+R+qiY_UZgVV;De3(kI'>P+Z꺦iq#ՀuT'4v]ŗZm?CZ~~iM-zS. 痟։yY~Zw֙p;,oVlE)}Gm_7YZ:nUNzO(,<ĖOR/+2;$*OԲu=_mH%O냆ɺֵKr_97|ꪫ? v_>2}肶]m?GS: = :=V6=Rv;C O?ejϕXeX5t .6z3_X9Y++Nv:E#E?QѣnGkSpÇ6@{'n]8`|h‡XΈçlTR;eۡR=)DҎ3xbu*/שR}*c s3u۫.hY]6og~.7MjTlj痟_C[~)Eθ`Syۗm |_R9vޗ;F~p@Kei#7d'~Ho4-?[u`p_ O]uhh4ZvߵXcO*@ɆQ9yuT;_QY?̟?ɨQ~~?TN~S *e߇hglo?5go(X1je+]O3Ŧ]>|˾N~cn*+k{ةYR5{Եån u ?s3Gcf;Ώ(Q2}\AV:=Cvf_֗:V4xvhzYwiݦ)#?~2,T[.uQu|(߲(bćuAwL9?~pkՁp ƶ}ϸT~r 79g1=>8_kMsر:E';eʾw~:2Vc .hDK 38$ 8FUYq>; jwvb}^AM?YjetMmyfrX]6hi? Ѹ1Gෆc.G߻|qW7nFY?iS4vP4t:Zr[@X=ItP]VpOaw*C_e-f<gd;n@퓕 VuR%g2uݗAG_oиۜ`ꂭv+],gr.3KrXȩ"o)GwNS}ϔ f%7k` Z k;`Mhg_Ni?L<@-ʿ_ӤpW7״v>N6OwrfQ/,oieS?\o 8: WUrqhꮁ7]wM#oli'qRL{{Oc~':qDȏ+aw+ov\7~(k BK;:1Ʒ(+ۦp׳n-]bӟ6{_?L:M CST_GuJ@( e?ku6AV Z',$ﳲbӪy۾41%:~$VV> [߸7{aY<&Q~> ~;.:> o Щʽ I|tjmϷvƟo÷kYݰZ1 {t}es YuweэMVMʼm0qǕwErWT8[niW>Aǜ:<*,]wuewHavmi;RM;>[Ϯa j:0UIQSjYkw`؟bV'׏:# "+@,uߦj3+ fl㷟xmV,),@!Ʒ0өq7Zvut~[Qlf]j2n*@꫖>VV%l6OH~߬en Co~uNL_k:rMo~o PPkZmz2N۾qmqP<94h< n}~V>S ~rFM5})֊QgEҼ|Mҭae|;lt67X{{Fܣ/cut'~W1wXSn}lJk]e 9Xy; źW~Pks~߮}ӁlA~BiqPdG;'\ӏ~,FW`0K?vЧ; ׍׿ Ŵ!T[kv쯏1v!0rrvy0חp#\bMN=P9kSu]D,sY_rjzYW]랖v%͓zvΦ;\~~ۋQӁ(`OhN+o-u"murhTb-p0 (&NVQ]Ǘơד4w]mgSΫƫnQ0hun-+Q8j>LߧYo$gMgk̟_MDf֩Ѹ}8.ղNi$w'WVϵYmcϩ~Խgrh᷆(t{8r8 ~x D:kGPy<y -vܹ/я|`igH;1Am5VyI V;~uwՎNUBv4M;#zaceei4u0diYf}Q]OHfTS]O˴mݜ%VRuӖ$/-{yӴa^NTm}ӊRk8ZQa7J`eखq&!mƭ~v`pƝ><}!4X~5N@帧E=Co;/_(}Ȥn9 yl/QcySESl5Pǯ7ԅUj]jnWնjtZ(ABWf]mnY+Ѯk6o~Zn ئKMӬl77K~9zgU8~_zC}:eӠ:jיN)]U}`QV/cZ_1-v굶}hYz$!]_eVNڢ$pZk~Q6aŔ~?k٩F@7D5ˆv(惶?|mWN9VSO)^}vL}M7gxV)]j=ߦzh7&E߂o>1kZsl[Q8FsB;:z֢J&EaE]TZ!avZ^k@hY`EuLVlbbO¾V* `(SK* Z2bTu$ >kBm ԚJW>* 0*ܴ?`@!?`@!?`@!?`@!?`@!?`@!?`@!?`@!?`@!?`@!?`@!?`@!?`@!daQPS?7sOUw|_/~ղ-'\>;0׮k-,,|\E:/ML{&_I`$7tSq-7xcqꩧC\sMsU}V7\´ _w`fa;0~NE+۷o/.V{キʝk`mv0pGvZy^va{^z }Ё*`~zozaÆ=~he߾}C=o}|Moڥ~}g_[ctp9V)?т{2yA]zYSN9dXkNu:=9cg-Vj!0kXk>Zug/A~߱V8};yG/\9$3/|-ZsYgUuajO}ƍU7? 7%K_Z?]wqU7? Hu?CSbƝGN?"_ƹƣǴ딟pZZ`ӚƮ*ߴۗ.<ii]o?(Y2kӌ0enmv}e4+ص.ֿ.ME]ΣWa]_MuMן8l\NWݴæ?r5]֩pܳX~mt ~u9'udaqqª;t}t}h:53gݺSt`ϪqAI?MAf~We}д6m.|)'OSVjCe_?U9SfS)S7_l(rR`*2鲲pm5= d+?mbR?ͦ4͟wϞ=Uc_jorj[mݺuS~*ѶLe 26O~[(6:8Eٳ,4Ç$j-A C?:N'WM+cӑ!$%U;WCG[j;vvb|vfՕ~ 5M~'`;~mG_1C+]/uur_ljCc#+SиNT-o__}UЎ3h-C?3IP;Fé,bSUeߥykrh'u¬4iއ4 듘9Ou+6~zeޑGRFw}~V-:Ѝ>h8XS婲m-,Pl9jmgMe풽rX4Nwj`YW●~bRW|yuO艹?nzMSuRͯfTЭyT~DeReޥv=,mUWM}mF?u{>g߯ CR9A.MGV.mߚXOW9m{ʙ& -~ǏDKq{z=^S=^[=^W=^_=~mxC8z[zz{Q=?o^\\ܜx`Kťm|'WSvV۲eKշƮ4_V>=S7͕avqRer+K?+߭mCec]ځOΟ޳RT}WS5SMYe Y{_/Ty>쳫*QrQiNM(?mzW~bOzUS_i3Ϭtp2\~׏Up:e7ӭ7'~\5e7Z1rֿ._RX˖aXsTjaRgꗳ/}~v{?._&m)mb/U?*ukas.OZr޳ᴜfEQ(MCwǑKo/)o 2)eTʫ,,K\{)L22YfZ,{et,|?`$ OCI_6壮%z6WT+2Om.V^W5Q #诈_~Msa+:Q/} IMITϬTRSQe!-T+.N]_7Mi=mL_;EZuZZ#Zrr{}>`lguIkayjbujצ~oT[6Zh~*Y,~wʪiqiwu/ZWl}?Kݚo?S?}ퟅUjyS/\.Aߴ<qlʭZ~CGЏv6R2$uf. Y>0.۩ :HIoΖϸ<7};;hO-cSA8~6-?Z~@-uʌdvMg}OC١>NѲ^⃠T F'~6m{_SY G<:㷯}>qm+moMgz۪pu{ˮi/:6L~h3U]O0oQi@s턇kyw svr};~gZy)*sk9c]K|5+.5h5Nו0D~1m}oqӴ?i>4_ O܇NZZQxg,:mS9[XX( ۺ0꒾7u%??M9g~?r#?mֿX8eS]T@~Z?}h#ƿѴ1λ>l뱖Z IΎLsj[+eCqfqvղf0!Q^tZ[Xs?aTy~ݦۼY`nv k%ГX&}$m۶rDa2k\w̡Q:Oy6ӣrXtք-h]S˶OSta*g9ՏΝ;˃AEU?(Z#:P/6q8$=3SOMVf;W }6 x rlہ>V:yitv ZX?lW܇7}d JÜ>l[ު7oۚӶu϶lˀna*cm?-q~.Wԕ;a;9Ѯ2b/CkEEAL`zS;߳vNӵf_C}u}.By_?u:%gnyCGwkdG!]n-' OKjfU}*[6Zn-~)~;:>۶~00~a풅<ڙĬjs੝@ 4Ä;MR9ڵFrNUPSV uϿ/:>_gui2:rnזvP߉º\Cw,gn5M/6*޹5Ŷ *}FfXV~?!gi].oݧ:zh;췟Z3\!gmgxG/ ~X:u-P;r-Un馪k|K\ǿnCQwAFti2O9rBM/uaN:G,=J;+ZcY(]]w]u0; 7Vjeߦ}}v9 wfZ!p* B,~Zi$8NZ_s~sUGl\jӾhmCTFbʼEgkfk:}C6 zӣm'?LHkn]'6ua<;AA|8O٪/s-ہJ.v`VNPӡrS=k0a(YWqu?OeQ;:Οmc eX[}n?꫕N{?:jj8|pAUa3d9 4?@Z79i;>R[:7_o@,LrRRH3e>Ox=?͋7*/թIwg|+;l2et췟be# }걟7-omccDPNY76]H_V-?lm|ee7Pi ; 7YӲ9 T!x4O 6.k.kLry]'M .͟y4imwʲiSzaT'ufWh4mSYpC6>JȺjCo?ڙΊ8h'ҿuFӫMfjC}vi'ywhX_xՈ~c: 6?6OFMeoÖcjXY~h[g!ͣkZx4͗կil5?7-;*67BAƫҲsyuJfj[}?Clm4-.pEz- ;0|*̟"ii=W\70?~bqJoE0_]naB`.mڴzPkAì}*>zY0E{1 YO)1?`N/!5\_zբC<0 A:mTx62Х"qHv屖~7%V?uz$0keZu깮uYzQspM;!?`@!?`@!?`@!?`@!?`@!?`@!?`@!?`@!?`@!?`@!?`@!?`@!?`@x衇o}[Ukң.~]o簟 3;ݻwW}8S8>{-{3,򖷔ݣкW~⬳Ϊ^0hWVϵ>Su.VϢn{{NK~=ۯp#{dcŞzʋc^{wq/g:8qUߵ륗^*WʷaÆ^}W>_ps=/kƒm۶U `={ʀEA 0 ]vJHw7zj=n[W;䓋ozUʽ/j' eCP|LW\Q7۴F [n-1:{iy=Z^2޹s'1~5ڭOYluުm;8nzrX̏3Fej3oK/-bƍe?¾W]u _^]~hEA%_\v+S+⩳V?w__->WuU 2.r鼷놪/ZEbI#O9{}^jPjks\ l}l{Uw 9| 'fWW}ʽ[.*-G-sz_kw5הajf-:Sx pCEV|u?Y^/eqVO_@:%|75t t nځ,uvWvNٲ;|SܵڀPwܳ vZ(>hCө:q;njPΟMB'?><ַnL ŧL9]:Y8Ο>gNʝE7TM1-}.:h~qNNϵ tys3 n]B*K8@FNׯ/h|C;TG?.ͧN=|iu_oWwQ0K54B;q{uAߤ//VfVOxz tC4Yy}i8ύ[Q.غ)R\} Nϭ$pӎ1?d[s}E°~W *Eaݧ޵ tqmpiqI^Oa).Ak{ i> G4_s>#9}jR:@Z̥^S?oQϖuu$m߾;Wq: PS4/1@3F7~5ڵضm[y0JQw>VxG*# #3r$}u]k٥]RS+}X/zrߑ]?ͫ_*2д7ki0MaVmٲ< p-TGS*}_4{?- o8_S]PQl{5ZRXaTOo6B?8*?Y(^S?7sOOUݡvPR),=ڨN_ήZ)S3GRx/,}?vq_|8c׼6aOokWjIK{Å, {폼l]q{_o0_N:OaߕMUY o-N|;k#> b~mqxвu N#7(\~grQc?r&Z{HeR:Hg#<<:#8b+^|=Դ3z`>qV_W㴇}e?Ǖy|]_uy/k_W V6y'/}KD?4{ꋦGˣnu˺W+~|}ǖ<|ꧺ{ꩧezóA*-7M;-|pq_AMz(u(VTUՅTYuͯ>m?yUg@_}PY`ϛ4m?'?ɕҶ?lj _a=Pip6i\Mub!}2\vS]MnMoX{5Ѻr1Ǭl__=ڶm[eπ(鎻' ի[jm6Xqþ-wVv; Nm|ܕaRVe t#p8+/1Ds=-iM-| ?0RK oM8fݦ9_S8@2 tKj=J&k~gZ@enä?֢Ynl6oEӼy?~žWM>EiӦ{¯M;ЊB+oȡrː&) ti,W!ZY)BMA;c_d, ,ӑBՕGp.vgݾ[i,SL-?W?T/EeeydRk4=R^.vLTyXYhq8 ;h/eQtni@B -X ͳჵV|IܭZj?*X]P$BOuM>`}yS,Ѳ;Z})m(d& fUneSa'*;.:cb¯y{&uvW4+؎})5Z筅n cmZS)2 rv컥lSkZɚ˦l&(qC l-JQ O-B ?\Ac( r)pa)d5ue r#EjG)JYU73ZX8SˢI]~>X/ÂZR0Qgl"\O젾nYZ`3C8}gS0i-T>I~uc3EvחEHłHmSï_7L.aw+`<~3Y:U-ͬş&:UU-tZgɃiWGgl)kj[ihkԠNT&:P qa\|G)iQ MQ(ZU`w a YBP%[#[ 祅ꥵR&RPգMe: g]p(' xc?MualhVUj_~hKuZi ~:l Xt+OUg_, |?g]ݣ}Q9:YDy @{baXPuŇv<сZ94=MIiX tm8?er-,~C\~>Qpczؖ[,8{X? rBYE1ZOS+-g?2o =OSS꽌[N??v'M~Z)S?]OtTۗ鴳BAZ9SYUj 1EgSJc~C`E>)C-b:~ H(@A+?{}F-t82La` H- IN۬~~5x>Zi0;9@тݬA-ro1Jcp#7NkSXQwUKH#;5WW[Búн@]VN Gݵ0=ke)zHa[~ͻ=>ꪪO1ѻ؎/OĖ߬t_|t:+u[.jig=/5#C6ܰw40wvX̎}7"v 8 b9l։]Iz} ˎ6TŖ<ɽROj%d{=nsq=UWy=0zJ6]Pƥ`Bf3xX=O%u GkTWΦㆨYדNV6lMnk1.s}JՅ&M'?dSXc7P.y6E!ާL٭aS;F.|\ZN vmm][i~;CqS T8z=YӡmvG_ փ:htO4]>꺕k8~{/X ӕsEKճ^ǞSR{+)#C+ tGY') } 6XDa¼u^9/g5 3 ź+mtWu|J֦JF*P뇺Vi:Xҁu]W mE4vh xtqNUYZI姃ckn )PPb) TTSqub[6;w<(l!'^kNWp r?I)9ImWO!_t}۔YuFӤaRCMF rZkuIʏ,=|@Ϻ~SUwEʑysq?HL-ߖߨ(9Sj9.>{q5?j 4_:%L۾⟗j!//W:jԸ~{`';MZT1Z;ou_[),YZwUyM_gT.rH(S 'l;S7'}֖q#UoM1V󾾩^P@s߰ٹgOcSusm(wB6ǵPfhgҭ*@ռS95,W})uf̻i{O60hWZYLNՅY>u=kЎQ@j':Y,.i]6A j= 딻gהoCZ`T~0Ow5t=+]ɨ[{FF HZݻ.fw-vfN՝.1 u~im]xkYw]Q~ROK/) Rs>`ЩLܥp)B 0 ~B 0 ~ ~u9'udaqqª;t} ` o=\`zhqqUfNmذ8n3gYmcϩ~ԽgrapB?+?uǗݣzox'b!W}1kn[n^O.nh~q)T}K/+֯__-o_nSO-$ bP~Ŧ]oO8|t/,>䫛-}se\ZU)Xc֭Uۊ. i]vYYNg}v<Cؼc+RXI|Ǫy|H8J`v]unrYgGW|pkH{'acۋn٭Sx?ṙ]*z{wM*ן>O~_^ө2?@QlH۲eKqvM Z٩ vk]~j4}x놲E~۴iSj_\s5ի,~X[0W_~{jgnСJOqzlBLS>fj G ?-ݍW7萷⩳Vv:~yTߋ{``3!8ܳ:X7t. Z{'`{ >o-N\GQ ptaaatj򆲿ƥNW_]| .b۶Өw9򩡣S.U5Jk/]N>sņ ҩvX5TC j\zlܸ|= zwgt^=ЪFkVpj>XҝuzxNaX٥i-+͗/ݴF7M>/n֚>MNߟ3{q 9G 0|W[?S># 0Nضo\[:22\LJ ?|5ܶǮ-v[B tsz_` w/UV+B/ DOi tW{=i~Maޥ^Zޅ6Moz.O뮻 wbz>\qիªtwfK1mj\aiJM[SæӈiݰSYiPW o^ޡZL]ʀOu?vp=ҵ1-̼ꪫm@W m瞑e _Sz1i9-)#gQu(F]-TO7O*QݐDehXOaZ-jzhFA~z5> A܇pZB 6 a͟k ](Ӹ,;#VM~\ V47j͓OͫXY)X$i\T 'Ŗ>MMѴ}n)̭NYk(3oJӴt֎ZGP phwM-m~ex( u?{k9j?^ UAhaE?oqiz?wJX)WRuMb EС.ZNSCBRPC+[PIcj-(ԸL4'> IjZABQIU-$넾_!ZitruS-lԲ΂?QYꔕչ4}ENOVZR; \AȓR`j?+SaBMZ#5t;X72I5~MaW &tEᗝHJتI&F/vZFZ6i\e*TIΟ%-ӷS;) e:惼ߚVS|SoRi4k!.Z>eŰ  ˧)K[QBFԣ݌Ň | C>aۼ?]1Yӗm 7 GW9j KV]RzkRa֧/}\)NGV8l ;M|ox}i}j)VO;u:B^Yn< .N/Xu;x>ŚX(SCjhWa-)S٩*7WA]Kn>>Mzm)(j[[}0hd7.O}u7ZłK] Ԛ?|1o_e>fY ztsk-RtZg];j{/|7P]u"00N|;l6f $zֺOR7}nmpփP4Wkfz~ &j|+Y7}S5}OA>aδoޱ[;~koġ>kje7=<~mnN ~nf1t պϷZϬ;ڍ2MzGgdoqiuI!\75,H%o񅲥ߣ|s:}VVN᳛V:ݴb^N;*l tT<:Ӛm97݄A6SO떟pe| 7 TU{]@pFY+m]rl7 v~5,̫;/^R;wݭ f`ICSk '$ n]wuUTv]2ߪ(,CǶm7:uA txՅO>^ONt7X}Q7rr+im/rgT콦~]o簟[T:zƜ[Ë#_w]G^S |Kx.8񍫯stƞo?a-^yuZ#Eӧq_oe.]uӑGwӟ^9';pB7W7_ *6w𵭶}Y[׺PӪ Ww8t 8#VN|ի^(4SXbQX~.M7^ک }#b9|;?S 5͓–_~l[[M/}Kw⩧*[Gmo[zL?OaTN}#6a9h~}0YXeusL~Vk.F޷]/VV]yѺQasr>yQteZObIn6MƭF;MozSYz|_.~7~x駫O,/Vm,=ܳҲOUZ۶mF9X Z4~<,,Th3',.ղu `]V˺ѧ7P]j {̯ 2W:ߡXaf w/u_ !gF;(wܙuIj P$B:kԆ&dvunS-͓桮(րF!DaO+zOዝJwRlx|OłT=h g;8Rhr)U<]) 'SڔY. +غTk-,,|\E:/ML{&_Sz1j?.oat } 3T`Sn,7?jþ⩳6:֪O<+xSi7)STjͦVbW]uUYnƇ}0ﰯ t6GNUwsZ6 B/:O7 KuIuM!4ּ\hpGwUߺWanBɉo|Gy>o61N p\ otc*bϛN{.N _|KI V:w 3~c~ I.jo7jͩV0W* ӈɧn6"iѮ@?((kV.:WkD۷"=M!ZZا;[}@>([Ow5&yW۷}nd_E_Sz!?`@!?`@!?Ysqezj׿^K/=P *?̭Pl|bӮ[{yǪ>{[^04ַ[guViӦ//7nXǫ!`X0rxK~L=`wS+XmH- /,瞫._袋`h0/X>zߨֽ Uv)R7=Mnn۶ /n{)h8N04~kǼ?x|6Z6';0:]l߾kk-o)z˖-Eq-T]0 ~kBϷړGnO奅0,jϗ'|ry]c-YXzԅ~z?Ϻ~xazM7}ڲ{ӵ^9pW[N}Fy{vM@Q~+ s)tTKDu(Zu͋רL9xAA?lذ8S;|hs-K47w\^.}Vaξ}׹7O :=+(/4TݛQ>giZ.~Y.ƭu/gs ?uߥkFu5~~ivϞ=eMÙgY-ӳvi-2=UӠ2nLsWVϵ>Su.VϢn{{NK~=ۯ=s?̭n.ܻ|jޮMwoO>ṙO?G^瞽n t3?lA(7\Z\'Wi 4MB^Ҷ✣W9(laIiZwPK5I nn`t)u^UAkH:E3o/+iRWV7f޽k;)\M\daH>o,sΑBQ(hR=HHӖ \n\o|lT?]u=vum yөeǧm+ǐ> }U׵bM7gbߧPUTЧScv](mӤmLlitMU8s-BΟΫP >Z`wKZƱV¾9^وƭ䫛M@t/Di+/~qhZ#,_B 뀴Z4ׅ0:5:uSeLSWt]9_V*#ML4owx| `㉕`ꛕjIlU}^,z/.tEeSgaFS!I >QaսTHk\﫮7Eeyu;{\l3YY??5 > ,\;`pj_-[sY3:58.x);wW~gZ?.NX 5+WI_ꦩKjsw:T [BikSdo/g[zyU?  l_iYu}Ѳw+\:Ҵ깮Z]S_~^0~{Y= N|;gl.z;N|6ut9,M);NDw)RL>@{CD5C9aW&)A]B# L;@:aSD@MNTC?k~euS5OMLO6n,pgeTa_'l_To+ <-?OztaYf!mޥ}8W/]-–}!?w?+n{mQNEB=f`UHOZEyE[R!N}bOhOݼwMei˭EBՃdRTW}OeK4}eMӼnzlf Zm@=F}W! ?je~yl;< GAA@-/H]W/rj(,PIFR-ϯܦ 7= ZYHM<Dijq2kHPoY4)CkSH4TU@9Qzj ,u4-VdžA" u:\wÇPذaA_Ϫ;6 ܳ@X9%VZ7 3@ knL2NuM+  SG6mڴS>'Ec ?4}M+xg]$Ӳ-͏S-FY IQ]SЦWMc=@;*P1u?k`]<E 2efҫ~Mn 2t: Nj9VSv)Z05ɃM 21/,`P7;y8I:Q-4=. @QU-q]:̧YܾhS[TuBsso5k{>]vpڏ*?]{kEk׮)GzNN1 [=&Aa[ɺW*;4 6E82K7Y9ݸ T eSx764v:Z>[),֖f9U]wޝG Utju7<gʾ֞={u@E6*͟3Nd-si[] %}G_am_SC[)@w-?k<ަ.XL.첕eX㆔}e7͉F+)N]G7k|`:0Ha뻅9GQ8Dt'ަV~~lZnt.}+i4b}N+JP|yuU]q B&0Ef*4|(#.r-U8&S] pIaq_5w|ꂅ7jG}GS]n)LbGϴ;:op,/MOj"55@A{뎮[-@qIY*M/^RܰwK".4u]h !t K}е`Vj!zq]SPMʷ)vũ| o1}ZaH;6zȷ4KֲAu]xuQ9SXzz r =MX D Y*T8㩣zeӖ_ZӤ-Z5~'_V*sM ~p4UUjZ4;io6[f.hS?7sOUws=~VBw}}^ŮMwEO^s}`nӮao}⒯n^-&}~Rmr :oA>(45k9;̦N?lXSa-?] TtWdTV lEӤR(iֳIM4_\.UaMOJӨM[]мNNY[n_pz&ULXVuF夰(k>:B͛¬ Km뗿Zp@Lb(IXu܆ Z'֍ϯV)ŦU!E4MLsTճX}=KS?SW~}.C:iB>zXاS?i[4e:`ԁX@VjUB~'6M6] 44ݓ:RPaq4-jIF'OT*KkQR}2`D,E2S/s ~aU+o͇IcZ3Y-RuA4o XȦɖ7[1zd49?VF!g;}I֭NMOM`z~{ y@Oewpnx?O>oM}o N]OVY7lwMHtp&FZ4;>/:p;æB1O*PAG<4{PT|Z@k>Y2gh>º\Յ>^o,N=ԲT+./s饗f_zqWT&\4ĖZ)pWeӗn[WӴ(T_r@+~c~KKŦ]UFV|R!8[nXOyVZh*z衇\bem۶C-nZlܸ|mT.}._=d!`Oa)?$Ua.?^Zm+/m;8o-C;N-o< .~ydM |k/u⛵ȦM q9Խgr¾y? vǎ~(.~>`zlv[`YاЗ?X)9ê>XK6bGK?tN㩿˪o{tZƧv /=tqtQ) i YmcڨBj(/)ٟ(6>pnx},w?jz }ڕ8q7^ j||us}Mn`L8 @-t4]WKM6guM r)Lѩဍ.*_9eU71е>iR(4 ]oLe:RbǦTg!`}]PPK5\SmZ^{m'!w EGWSYp3` j=w?_>,RUqB{_y¿.{v ƥ4UB0D7а !XHߣat}U}ijϸN9唪k9pZ6lP>ct*KթX9~(,e8a޽sn0y֨&fg[麘MW~OsG?.<-t_sޏ'OUÂrf9YޖZ)kQAƵk]+6|esrN C-y=a֮]-[Gyd$jN!̙gYބB_ Qb,t1iCR?0z/EU4V9(:iV Q.SꦖO}ZM͋W]uUrZʜh$|3GO]~u7c^✣WЯl-|I(ܻ]7ur`s-UW^_7g0u%zW_]#D7P(awܑoΝehLѳBlZd={O>lu-ƥiO{NwqU2M9|:Sz) Ϟꬆeתvs y_?t>MZ[:{X;z;ND̅oZĮg7Ӑ~e т:ߎ~L Zp\>R`BRGS0 fbg"T0c!MH9*pz-SBƧP/U?Z+TűN ^-V DD%^,3ROkoaS.j4toȿQuZI]&uj=G*,1/ߘ$FA`x:%/ՂZ`Ũ3 ʷڳSvϦOusn)-@c|@X%wNkV,~\sw K/x|c˫o1rB뮻l5u.5|}z}V~M(Ou ea!WX‡;LӼP0b*M3%M>ij7K:ERw2UK(u\kEN׸=bC; ,Rh vZZ%V#ujK?M.v Q6Ns\Al{ u]BAPi~@tB:} vU;WZz{jr7>d}=kgZY(fn֛wfޠ=cEZUB>;e;~&8]7oO5֭ʇy'_ğ=Ku @Kǩ+}F5_˥SOλ Re ol%Y:Z)0(G| ]eg`=->wqa`k Ss?\~{?&1w?af-k-k{ewBITDӥV:,q~*Ձ={:=V:]nMݬ7tS #NzË.+bws/Jq07*k)Pb}\ꫫضm[r鮪:xogT*Cg0(dU]C?}o|OFףj:qy^rbWzזb u_,lgus=KٽxA`W Fiyew\]l=gt~u^~3իǼźCP{w?u?YGas=~޵bSvN(PH%ׯ/=hSHoB!^zi4V\m:Xr}t)|S˥[;n喲N+}k#V,]UT}肟&k]iAoͦ@/S~uNkXѸaa꺾oa_ԺQedX(yZ -ӨZ MᚨE\]8OaZ7 +>|lh!w=qU{3B0%wfAZ )LSKST"nZMasDMZX'⯮%aQᢤQԕŗq8}Y1i 6Z~lUG֟94-ߢO5ӱj4ϣP|Yՙwb3~=7n6a0+ %nY&Xf)_M/6̊QNIA ~XSiЏ.Ίm=?`|Ӽ*M8:33 d20Zaۤ?Z`-X>F ?`YN(gpO:kS }dU¯M.$D(GG<̻M5k`qsߘmZ_Xp* ')' -#0@<.g31gY|BZUP:7k$&ovҹUuu1Mt!`m˝Qu{Ohg0l/Lf&+Pb\\)/u;M% &=qJ`P rǝ39͛v=LcHRuy7s8$g&]_4fF]֔ʙMugT-L ?4^ kN(5|xr kM2, _,ORTΔ?M:Z>g3n&s9SjX5N~n:{=|JʛRÇri;*m[_6֗;4`R9R|nQ vU5[a`MeMa;6χ-?^N}Wl:m H,?JeJMSsSM;f(gfH]A֦XT9X_0Dߋ~u#S9ˍr6ySa;jW7A94}H.k~xbç6l檛{m>_[w\3)Mh2gj6#XYϤHN$>&4{^pap|y-9yRST7)R7^RSn30GpMb>35~~qXc煅+i U>OJeK'֯s)uÌ2QذϿ9'+vUi9(R ņT96$Өə9-_/-/_`Mz?6&gkNg CM5a_Wa$côuO~`miba?zz3)&|?N}UϋU*T Wlݒ?,uLk`>k6bݩgi&,9$`A>b`Mݓ~Xg~a$ ?hRÆc~֝\Y|Tس5/.6ln,nשg4\}6+6ź0^%6(}g] :^l_߿/a{9ƙmZXwsg=?MϻN*Ĵi|K=Ka$֝zz~L~ppϴy#u1k.* X+ڌ7'ۈ SϿ/gPӯiٺ $knۯ{-똜aɤ+oݱ~_7|{ipQ4Q?}n~}Frk'aL6tQY(TBTԳƋ} 3Ů? džRbSϦXw}iڤ ըyHۀ. Ku S/=/?pjX~f~ņtϦꖦ;$4`vb0Xns MHl8Iu SOVaᬻ0aޗ0&?5Խ0o:F ~ub$X?/6\(w03mbjذl8/-g?0sX if]k9c~vOmY~nYaG{ᳩĆ1uޗaֺT?[uM$6IgTPrT ?T {3|Ngbbr'm2kL{a +r?[;\W dž&罶Kpڤ̲ifmCpױq[I +vn7nhh*HPJsCyFZ_o泆3Xle}_՛ZըfCY7w7 hx۟GF3^UZ֙]~AWZQ-g]Q-_ퟋ#K_Ei#}VooGѫWZof/ L?>Gk33Eg3_ 2Xwߓy)2ELm|4תTV/: 3!yw3sfdzvE1dE=b{z.VljŨ2;6k6(׷ϽZ59)"VWElߟ֮}dU>WLRgl=/sΎdȅam\G^ĩTtG猪G{[E6c>'9w73[d"=]g¯VlvȞoEzh^yo׾vv_be@;Fg3wGY6gCb_hff}]+̮^ojHF;ѳUً VbhQ}vWX .g|d|hg "\j*[tkEho>[ۊ]D fkU#so6keϷfg#h_ʕ!UfWsf>?늝O:훭UW2)WOgUDd+C3;w޴OW¸ɻdlծOݳħ^?eXtEvd/nS><]zf+CA^ޙj̪]o":#2gkvW7ڿoʢ;}ŝݫ;?W"[33W^V%G+!;dೲmA_P{|2c6Hdhw{W<-t"4#{DW<1:NW|@cWd_UO ;?!!Fw_wu KO pϰ"d{ʎK!zjh'PG}՛B{@+Co V'w! >}]W1Zw@৹;[}k¾aMppkwwUkO&x+׆}_˟^6oUo~Wqz=IENDB`cucumber-cucumber-ruby-core-08c81cd/.github/img/logo.svg000066400000000000000000000032531514133137400232450ustar00rootroot00000000000000 cucumber-cucumber-ruby-core-08c81cd/.github/lock.yml000066400000000000000000000017331514133137400224640ustar00rootroot00000000000000# Configuration for lock-threads - https://github.com/dessant/lock-threads # Number of days of inactivity before a closed issue or pull request is locked daysUntilLock: 365 # Issues and pull requests with these labels will not be locked. Set to `[]` to disable exemptLabels: [] # Label to add before locking, such as `outdated`. Set to `false` to disable lockLabel: false # Comment to post before locking. Set to `false` to disable lockComment: > This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. # Assign `resolved` as the reason for locking. Set to `false` to disable setLockReason: true # Limit to only `issues` or `pulls` # only: issues # Optionally, specify configuration settings just for `issues` or `pulls` # issues: # exemptLabels: # - help-wanted # lockLabel: outdated # pulls: # daysUntilLock: 30 # Repository to extend settings from # _extends: repo cucumber-cucumber-ruby-core-08c81cd/.github/renovate.json000066400000000000000000000002001514133137400235130ustar00rootroot00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "github>cucumber/renovate-config" ] } cucumber-cucumber-ruby-core-08c81cd/.github/stale.yml000066400000000000000000000015071514133137400226430ustar00rootroot00000000000000# Number of days of inactivity before an issue becomes stale daysUntilStale: 300 # Number of days of inactivity before a stale issue is closed daysUntilClose: 60 # Issues with these labels will never be considered stale exemptLabels: - ":safety_pin: pinned" # Label to use when marking an issue as stale staleLabel: ":hourglass: stale" # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed in two months if no further activity occurs. # Comment to post when closing a stale issue. Set to `false` to disable closeComment: > This issue has been automatically closed because of inactivity. You can support the Cucumber core team on [opencollective](https://opencollective.com/cucumber). cucumber-cucumber-ruby-core-08c81cd/.github/workflows/000077500000000000000000000000001514133137400230425ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/.github/workflows/release.yml000066400000000000000000000021171514133137400252060ustar00rootroot00000000000000name: Release RubyGems on: push: branches: - release/* jobs: pre-release-check: uses: cucumber/.github/.github/workflows/prerelease-checks.yml@main test-ruby: uses: ./.github/workflows/test.yml publish-rubygem: name: Publish Ruby Gem needs: [pre-release-check, test-ruby] runs-on: ubuntu-latest environment: Release steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Publish ruby gem uses: cucumber/action-publish-rubygem@d8918cbdee789cfc78f346a96a59596b87795be1 # v1.0.0 with: rubygems_api_key: ${{ secrets.RUBYGEMS_API_KEY }} create-github-release: name: Create GitHub Release and Git tag needs: publish-rubygem runs-on: ubuntu-latest environment: Release permissions: contents: write steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: cucumber/action-create-github-release@cf2c6f77ba35d2424362e83393a1c4c004cf2ddb # v1.1.1 with: github-token: ${{ secrets.GITHUB_TOKEN }} cucumber-cucumber-ruby-core-08c81cd/.github/workflows/rubocop.yml000066400000000000000000000005631514133137400252420ustar00rootroot00000000000000name: Lint on: push: branches: - main pull_request: branches: - main jobs: rubocop: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ruby/setup-ruby@v1 with: ruby-version: 3.4.8 bundler-cache: true - run: bundle exec rubocop cucumber-cucumber-ruby-core-08c81cd/.github/workflows/test.yml000066400000000000000000000014561514133137400245520ustar00rootroot00000000000000name: Test cucumber-core on: push: branches: - main - renovate/** pull_request: branches: - main workflow_call: schedule: - cron: "0 5 * * *" jobs: test: strategy: fail-fast: false matrix: os: [ubuntu-latest] ruby: ['3.2', '3.3', '3.4', '3.5'] include: - os: ubuntu-latest ruby: jruby - os: macos-latest ruby: '3.4' - os: windows-latest ruby: '3.4' runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - name: Run tests run: bundle exec rake shell: bash cucumber-cucumber-ruby-core-08c81cd/.gitignore000066400000000000000000000002141514133137400214320ustar00rootroot00000000000000# Exuberant Ctags /tags Gemfile.local Gemfile.local.lock /Gemfile.lock /tmp/ /coverage /pkg *.swo .ruby-version /doc .bundle vendor/ .idea/ cucumber-cucumber-ruby-core-08c81cd/.rspec000066400000000000000000000000521514133137400205570ustar00rootroot00000000000000--require spec_helper --color --tag ~slow cucumber-cucumber-ruby-core-08c81cd/.rubocop.yml000066400000000000000000000023211514133137400217150ustar00rootroot00000000000000inherit_mode: merge: Exclude inherit_from: .rubocop_todo.yml plugins: - rubocop-packaging - rubocop-rake - rubocop-rspec AllCops: # Keep this inline with the gemspec TargetRubyVersion: 3.2 DisplayCopNames: true DisplayStyleGuide: true NewCops: enable # Stylistic preference to keep dev dependencies in gemspec Gemspec/DevelopmentDependencies: Enabled: false # We do not support MFA at the moment with the new automated release process Gemspec/RequireMFA: Enabled: false # A line length of 200 covers most violations in the repo while still being # a more up-to-date length given today's screen sizes Layout/LineLength: Max: 200 # We want to permit Exception rescuing in these two instances as they literally guard against the final fail chance Lint/RescueException: Exclude: - lib/cucumber/core/test/action/defined.rb - lib/cucumber/core/test/around_hook.rb RSpec/ExampleLength: CountAsOne: ['array', 'hash', 'heredoc'] # Stylistic preference for cucumber RSpec/MessageSpies: EnforcedStyle: receive # We don't verbosely document this gem Style/Documentation: Enabled: false # Stylistic preference for cucumber Style/RegexpLiteral: EnforcedStyle: slashes AllowInnerSlashes: true cucumber-cucumber-ruby-core-08c81cd/.rubocop_todo.yml000066400000000000000000000076151514133137400227550ustar00rootroot00000000000000# This configuration was generated by # `rubocop --auto-gen-config` # on 2026-01-06 12:52:49 UTC using RuboCop version 1.81.7. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # TODO: [LH] v14 Pre-release iteration -> 58 files inspected, 392 offenses detected, 100 offenses auto-correctable # TODO: [LH] v14 Pre-release iteration 2 (Rubocop upgrade) -> 60 files inspected, 324 offenses detected, 86 offenses autocorrectable # TODO: [LH] v14 Pre-release iteration 3 (Rubocop additional upgrade) -> 60 files inspected, 267 offenses detected, 26 offenses autocorrectable # TODO: [LH] v15 Release incoming (Minimum ruby bump) -> 60 files inspected, 234 offenses detected, 12 offenses autocorrectable # TODO: [LH] v15.1 (Minor fixes) -> 60 files inspected, 239 offenses detected, 15 offenses autocorrectable # TODO: [LH] v16 (Gherkin/Message bump) -> 61 files inspected, 272 offenses detected, 60 offenses autocorrectable # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). Lint/RedundantRequireStatement: Exclude: - 'lib/cucumber/core/test/location.rb' # Offense count: 2 # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: Max: 21 # Offense count: 4 # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: Max: 28 # Offense count: 4 # Configuration parameters: CountComments, CountAsOne. Metrics/ModuleLength: Max: 449 # Offense count: 1 # Configuration parameters: CountKeywordArgs, MaxOptionalParameters. Metrics/ParameterLists: Max: 8 # Offense count: 2 # This cop supports unsafe autocorrection (--autocorrect-all). RSpec/EmptyExampleGroup: Exclude: - 'spec/cucumber/core/compiler_spec.rb' # Offense count: 38 # Configuration parameters: CountAsOne. RSpec/ExampleLength: Max: 11 # Offense count: 24 RSpec/MissingExampleGroupArgument: Exclude: - 'spec/cucumber/core/compiler_spec.rb' - 'spec/cucumber/core/filter_spec.rb' - 'spec/cucumber/core/gherkin/parser_spec.rb' - 'spec/cucumber/core/gherkin/writer_spec.rb' - 'spec/cucumber/core/test/case_spec.rb' - 'spec/cucumber/core/test/locations_filter_spec.rb' - 'spec/cucumber/core_spec.rb' # Offense count: 61 RSpec/MultipleExpectations: Max: 5 # Offense count: 47 # Configuration parameters: AllowSubject. RSpec/MultipleMemoizedHelpers: Max: 11 # Offense count: 6 # Configuration parameters: AllowedGroups. RSpec/NestedGroups: Max: 4 # Offense count: 23 # Configuration parameters: AllowedPatterns. # AllowedPatterns: ^expect_, ^assert_ RSpec/NoExpectationExample: Exclude: - 'spec/cucumber/core/compiler_spec.rb' - 'spec/cucumber/core/filter_spec.rb' - 'spec/cucumber/core/gherkin/parser_spec.rb' - 'spec/cucumber/core/gherkin/writer_spec.rb' - 'spec/cucumber/core/test/case_spec.rb' - 'spec/cucumber/core/test/locations_filter_spec.rb' - 'spec/cucumber/core_spec.rb' # Offense count: 5 # This cop supports safe autocorrection (--autocorrect). RSpec/RedundantPredicateMatcher: Exclude: - 'spec/cucumber/core/test/location_spec.rb' # Offense count: 2 RSpec/RepeatedExample: Exclude: - 'spec/cucumber/core/gherkin/parser_spec.rb' # Offense count: 1 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle, AllowModifiersOnSymbols, AllowModifiersOnAttrs, AllowModifiersOnAliasMethod. # SupportedStyles: inline, group Style/AccessModifierDeclarations: Exclude: - 'lib/cucumber/core/filter.rb' # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowedMethods, AllowedPatterns. Style/MethodCallWithoutArgsParentheses: Exclude: - 'lib/cucumber/core/test/timer.rb' cucumber-cucumber-ruby-core-08c81cd/CHANGELOG.md000066400000000000000000000154431514133137400212650ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). This document is formatted according to the principles of [Keep A CHANGELOG](http://keepachangelog.com). Please visit [cucumber/CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CONTRIBUTING.md) for more info on how to contribute to Cucumber. ## [Unreleased] ## [16.2.0] - 2026-02-06 ### Changed - Added the test result type 'ambiguous' ([#311](https://github.com/cucumber/cucumber-ruby-core/pull/311)) ## [16.1.1] - 2025-12-24 ### Fixed - Added a fix that prevented the `Duration` class from not being able to calculate duration correctly ## [16.1.0] - 2025-12-22 ### Changed - Code re-organised into more sub-files. No user facing changes - Further bumped the lower bounds of messages and gherkin several more versions ## [16.0.0] - 2025-12-16 ### Changed - Bumped the lower bounds of messages to v28, gherkin to v33 and tag-expressions to v6 ### Removed - Remove support for ruby 3.1 and below. 3.2 or higher is required now ## [15.4.0] - 2025-12-10 ### Changed - Permit usage of gherkin up to v39, messages up to v32 ## [15.3.0] - 2025-10-14 ### Changed - Permit usage of gherkin up to v34, tag-expressions up to v8 ### Fixed - Further fix situations in which multiple extraneous arguments could be passed from a proc to `Location#new` (discarding these arguments) ## [15.2.1] - 2025-08-21 ### Fixed - (`Proc#source_location` returns [path, start_line, start_column, end_line, end_column] with Ruby 3.5.0dev+) ([#299](https://github.com/cucumber/cucumber-ruby-core/pull/299) [yahonda](https://github.com/yahonda) [luke-hill](https://github.com/luke-hill)) - Alter default location provided to `Cucumber::Core::Test::Action::Defined` to only pass file and line ## [15.2.0] - 2025-08-08 ### Changed - Permit usage of gherkin up to v32, messages up to v29 - Minor internal refactors ## [15.1.0] - 2025-02-28 ### Changed - Permit usage of gherkin up to v30 ## [15.0.0] - 2024-12-24 ### Changed - Permit usage of messages up to v28 ### Fixed - References to the Time Conversion and UUID helpers needed altering to use the `Helpers` namespace ### Removed - Remove support for ruby 2.7 and below. 3.0 or higher is required now (Owing to messages bump) ## [14.0.0] - 2024-08-08 ### Changed - Permit usage of gherkin up to v29 and messages up to v26 - **Internal Breaking Change**: Structure of `Action` classes have changed. See upgrading notes for [14.0.0.md](upgrading_notes/14.0.0.md#upgrading-to-cucumber-core-1400) ([#282](https://github.com/cucumber/cucumber-ruby-core/pull/282)) ### Removed - Remove support for ruby 2.6 and below. 2.7 or higher is required now (Autofixed to Ruby 2.7 styles) ## [13.0.3] - 2024-07-24 ### Changed - Fixed up all remaining Layout autocorrect cops in the codebase ## [13.0.2] - 2024-03-21 ### Changed - Added CI testing for Ruby 3.3 - Fixed up a few minor rubocop offenses in the codebase around Array structuring ## [13.0.1] - 2024-01-31 ### Changed - Fixed up a few styling / layout cops in the tests ### Fixed - The `Cucumber::Core::Test::Result::Passed` class was missing the strict keyword argument handling ## [13.0.0] - 2023-12-05 ### Changed - Now using a 2-tiered changelog to avoid any bugs when using polyglot-release - More refactoring of the repo by fixing up a bunch of manual rubocop offenses (See PR's for details) ([#259](https://github.com/cucumber/cucumber-ruby-core/pull/259) [#262](https://github.com/cucumber/cucumber-ruby-core/pull/262) [#268](https://github.com/cucumber/cucumber-ruby-core/pull/268) [#274](https://github.com/cucumber/cucumber-ruby-core/pull/274)) - In all `Summary` and `Result` classes, changed the `strict` argument into a keyword argument. See upgrading notes for [13.0.0.md](upgrading_notes/13.0.0.md#upgrading-to-cucumber-core-1300) ([#261](https://github.com/cucumber/cucumber-ruby-core/pull/261)) - Permit usage of gherkin v27 ### Fixed - Restore support for matching a scenario by its Feature, Background, and Rule line numbers ([#247](https://github.com/cucumber/cucumber-ruby-core/pull/247)) ### Removed - Remove legacy `unindent` gem (Now no longer required since Ruby 2.3 and Squiggly heredocs) ([#278](https://github.com/cucumber/cucumber-ruby-core/pull/278)) ## [12.0.0] - 2023-09-06 ### Changed - Update gherkin and messages minimum dependencies - Added in new rubocop sub-gems for testing, pinning versions where appropriate - Removed all redundant / incorrect rubocop config overrides (Placed in TODO file) - Began to refactor the repo by initially fixing up a bunch of rubocop auto-fix offenses (See PRs for details) ([#257](https://github.com/cucumber/cucumber-ruby-core/pull/257) [#258](https://github.com/cucumber/cucumber-ruby-core/pull/258)) ### Removed - Remove support for ruby 2.4 and below. 2.5 or higher is required now ## [11.1.0] - 2022-12-22 ### Changed - Update gherkin and messages dependencies ### Fixed - Restore support for matching a scenario by tag and step line numbers. ([#237](https://github.com/cucumber/cucumber-ruby-core/pull/237), [#238](https://github.com/cucumber/cucumber-ruby-core/pull/238), [#239](https://github.com/cucumber/cucumber-ruby-core/pull/239)) ## [11.0.0] - 2022-05-18 ### Changed - Updated `cucumber-gherkin` and `cucumber-messages` [Unreleased]: https://github.com/cucumber/cucumber-ruby-core/compare/v16.2.0...HEAD [16.2.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v16.1.1...v16.2.0 [16.1.1]: https://github.com/cucumber/cucumber-ruby-core/compare/v16.1.0...v16.1.1 [16.1.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v16.0.0...v16.1.0 [16.0.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v15.4.0...v16.0.0 [15.4.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v15.3.0...v15.4.0 [15.3.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v15.2.1...v15.3.0 [15.2.1]: https://github.com/cucumber/cucumber-ruby-core/compare/v15.2.0...v15.2.1 [15.2.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v15.1.0...v15.2.0 [15.1.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v15.0.0...v15.1.0 [15.0.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v14.0.0...v15.0.0 [14.0.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v13.0.3...v14.0.0 [13.0.3]: https://github.com/cucumber/cucumber-ruby-core/compare/v13.0.2...v13.0.3 [13.0.2]: https://github.com/cucumber/cucumber-ruby-core/compare/v13.0.1...v13.0.2 [13.0.1]: https://github.com/cucumber/cucumber-ruby-core/compare/v13.0.0...v13.0.1 [13.0.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v12.0.0...v13.0.0 [12.0.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v11.1.0...v12.0.0 [11.1.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v11.0.0...v11.1.0 [11.0.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v10.1.1...v11.0.0 cucumber-cucumber-ruby-core-08c81cd/CHANGELOG.old.md000066400000000000000000000274461514133137400220500ustar00rootroot00000000000000# Changelog **NB: This is the legacy changelog. Please consult the new [CHANGELOG.md](./CHANGELOG.md) for new updates** ## [10.1.1] ### Changed - Patched `cucumber-tag-expressions` ## [10.1.0] ### Changed - Patched `cucumber-gherkin`, `cucumber-messages` and `cucumber-tag-expressions` ## [10.0.1] ### Changed - Patched `cucumber-gherkin` and `cucumber-messages` ## [10.0.0] ### Changed - Upgraded to gherkin v20 and messages v17. ## [9.0.1] ### Fixed - Skipped scenarios do not affect anymore status of flaky scenarios on retry ([#218](https://github.com/cucumber/cucumber-ruby-core/pull/218) @eduardrudko) ## [9.0.0] ### Changed - Upgraded to gherkin v18 and messages v15 - Updated other dependencies (look at the diff for details) ## [8.0.1] ### Fixed - Make releases from docker using secrets from keybase ## [8.0.0] ### Changed - Updated dependencies (look at the diff for details) ## [7.1.0] ### Changed - `cucumber-gherkin` ~> 14.0.1 - `cucumber-messages` ~> 12.2.0 - Updated gems: ## [7.0.0] ### Changed - cucumber-gherkin ~> 13 - cucumber-messages ~> 12 - Updated monorepo libraries: ## [6.0.0] ### Added - Add `envelope` event, which are used when emitting `Cucumber::Messages` - Add `TestCaseCreated` and `TestStepCreated` events, emitted when compiling a `Pickle` - Add `Id` field to `TestCase` and `TestStep` - Added rubocop (with todo file), and removed backports gems ([#186](https://github.com/cucumber/cucumber-ruby-core/pull/186), [#182](https://github.com/cucumber/cucumber-ruby-core/issues/182) [tas50](https://github.com/tas50), [luke-hill](https://github.com/luke-hill)) ### Changed - Update to Gherkin 10 ### Removed - Remove location for MultiLine arguments ## [5.0.2] ### Changed - Update to use Gherkin v8 ## [5.0.1] ### Removed - Remove support for ruby 2.2 and below. 2.3 or higher is required now. ## [5.0.0] ### Changed - Update to use Gherkin v7 ## [4.0.0] ### Changed - Update to use Gherkin v6 ([#158](https://github.com/cucumber/cucumber-ruby-core/pull/158) @brasmusson) - Let Scenarios with no Steps get the result status Undefined ([#157](https://github.com/cucumber/cucumber-ruby-core/pull/157) @brasmusson) - Convert to use the Gherkin compiler and Pickles ([#156](https://github.com/cucumber/cucumber-ruby-core/pull/156) @brasmusson) ### Removed - Remove the support for old style tag expressions ([#159](https://github.com/cucumber/cucumber-ruby-core/pull/159) @brasmusson) ## [3.2.1] ### Fixed - Switched `gherkin` in Gemspec to use _pessimistic_ versioning. (These two commits aren't merged into `master`, as they already exist in newer commits. This is a 'backported' patch to resolve [#160](https://github.com/cucumber/cucumber-ruby-core/issues/160)). ## [3.2.0] ### Added - Add the GherkinSourceParsedEvent ([#155](https://github.com/cucumber/cucumber-ruby-core/pull/155) @brasmusson) - Add #original_location to Cucumber::Core::Ast::Step and Cucumber::Core::Test::Step ([#150](https://github.com/cucumber/cucumber-ruby-core/pull/150), [#149](https://github.com/cucumber/cucumber-ruby-core/issues/149) @brasmusson) ### Fixed - Set message and backtrace for undefined result also when skipping (@brasmusson) ## [3.1.0] ### Changed - Upgraded to `cucumber-tag_expressions` 1.1.0 - Upgraded to `gherkin` 5.0.0 ## [3.0.0] ### Added - Do not create test cases for scenarios with no steps ([#144](https://github.com/cucumber/cucumber-ruby-core/pull/144) @brasmusson) - Handle selective strict settings ([#143](https://github.com/cucumber/cucumber-ruby-core/pull/143) @brasmusson) ### Changed - Step#name renamed to #text ([#137](https://github.com/cucumber/cucumber-ruby-core/pull/137) [@olleolleolle](https://github.com/olleolleolle)) - Use past tense in event names (`xStarting` -> `xStarted`) (see [cucumber/cucumber-ruby#1166](https://github.com/cucumber/cucumber-ruby/issues/1166) @brasmusson). ### Fixed - Fix DataTable's Location to be aware of all of its lines ([#142](https://github.com/cucumber/cucumber-ruby-core/pull/142) @botandrose) - As per [#251](https://github.com/cucumber/cucumber/issues/251): renamed History.md to CHANGELOG.md, added contributing message at beginning, and misc formatting. ([#145](https://github.com/cucumber/cucumber-ruby-core/pull/145) [jaysonesmith](https://github.com/jaysonesmith)) ## [3.0.0.pre.2] ### Added - Add a flaky result type to be used for flaky scenarios ([#141](https://github.com/cucumber/cucumber-ruby-core/pull/141), [cucumber/cucumber-ruby#1044](https://github.com/cucumber/cucumber-ruby/issues/1044) @brasmusson) - Make the Summary report able to say if the total result is ok ([#140](https://github.com/cucumber/cucumber-ruby-core/pull/140) @brasmusson) - Replay previous events to new subscribers ([#136](https://github.com/cucumber/cucumber-ruby-core/pull/136) @mattwynne) - Ruby 2.4.0 compatibility ([#120](https://github.com/cucumber/cucumber-ruby-core/pull/120) @junaruga) - Use tag expressions ([#116](https://github.com/cucumber/cucumber-ruby-core/pull/116) @brasmusson) - Access example table row data by param name ([#118](https://github.com/cucumber/cucumber-ruby-core/pull/118) @enkessler) ### Fixed - Travis: jruby-9.1.10.0 ([#130](https://github.com/cucumber/cucumber-ruby-core/pull/130) @olleolleolle) - Travis: jruby-9.1.12.0 ([#133](https://github.com/cucumber/cucumber-ruby-core/pull/132) @olleolleolle) ## [2.0.0] ### Added - Implement equality for test cases ([#111](https://github.com/cucumber/cucumber-ruby-core/pull/111) @mattwynne) - Implement an event bus (moved from Cucumber-Ruby) ([#106](https://github.com/cucumber/cucumber-ruby-core/pull/106) @mattwynne) - Use frozen string literals ([#105](https://github.com/cucumber/cucumber-ruby-core/pull/105) @twalpole) ### Fixed - Handle incomplete examples to scenario outlines. ([109](https://github.com/cucumber/cucumber-ruby-core/pull/109) @brasmusson) - Add with_filtered_backtrace method to unknown result ([107](https://github.com/cucumber/cucumber-ruby-core/pull/107) @danascheider) ### Removed - Remove support for Ruby v1.9.3. ([112](https://github.com/cucumber/cucumber-ruby-core/pull/112) @brasmusson) ## [1.5.0] ### Added - Update to Gherkin v4.0 (@brasmusson) ### Fixed - Use monotonic time ([#103](https://github.com/cucumber/cucumber-ruby-core/pull/103) @mikz) ## [1.4.0] ### Added - Update to Gherkin v3.2.0 (@brasmusson) ### Fixed ## [1.3.1] ### Added ### Fixed - Speed up location filtering ([#99](https://github.com/cucumber/cucumber-ruby-core/issues/99) @mattwynne @akostadinov @brasmusson) ## [1.3.0] ### Added - Add factory method to Cucumber::Core::Ast::Location that uses the output from Proc#source_location (@brasmusson) - Integrate Gherkin3 parser (@brasmusson) ### Fixed - Make sure that `after_test_step` is sent also when a test step is interrupted by (a timeout in) an around hook ([cucumber/cucumber-ruby#909](https://github.com/cucumber/cucumber-ruby/issues/909) @brasmusson) - Improve the check that a test_step quacks like a Cucumber::Core::Test::Step ([95](https://github.com/cucumber/cucumber-ruby-core/issues/95) @brasmusson) ## [1.2.0] ### Added - Enable the location of actions to be the step or hook location (@brasmusson) - Add the comments to the Steps, Examples tables and Examples table rows Ast classes (@brasmusson) - Expose name, description and examples_rows attributes of `Ast::ExamplesTable` (@mattwynne) - Add #to_sym to Cucumber::Core::Test::Result classes ([#89](https://github.com/cucumber/cucumber-ruby-core/pull/89) @pdswan) - Add #ok? to Cucumber::Core::Test::Result classes ([#92](https://github.com/cucumber/cucumber-ruby-core/pull/92) @brasmusson) ### Fixed ## [1.1.3] ### Added - Added custom `inspect` methods for AST Nodes (@tooky) ## [1.1.2] ### Added - Make Test Case names for Scenario Outlines language neutral [83](https://github.com/cucumber/cucumber-ruby-core/pull/83) (@brasmusson) - Add predicate methods for Multline arguments (@mattwynne) - Expose `Test::Case#feature` (@mattwynne) - Fail test case if around hook fails (@mattwynne, @tooky) - Expose `Test::Case#around_hooks` (@tooky) ## [1.1.1] ### Added - Calculate actual keyword for snippets (@brasmusson) ### Fixed - Remove keyword from `Test::Case#name` [82](https://github.com/cucumber/cucumber-ruby-core/pull/82) (@richarda) ## [1.1.0] ### Added - LocationsFilter now sorts test cases as well as filtering them (@mattwynne) ## [1.0.0] ### Added - Added dynamic filter class constructor (@mattwynne) ## [1.0.0.beta.4] ### Added - Introduce a Duration object (#[71](https://github.com/cucumber/cucumber-ruby-core/pull/71) [@brasmusson](https://github.com/brasmusson)) - BeforeStep hooks (#[70](https://github.com/cucumber/cucumber-ruby-core/pull/70) [@almostwhitehat](https://github.com/almostwhitehat)) - Expose `Test::Case#test_steps` (@mattwynne) ### Fixed - Handle empty feature files (#[77](https://github.com/cucumber/cucumber-ruby-core/pull/77), [cucumber/cucumber-ruby#771](https://github.com/cucumber/cucumber-ruby/issues/771) [@brasmusson](https://github.com/brasmusson)) - Run after hooks in reverse order (#[69](https://github.com/cucumber/cucumber-ruby-core/pull/69) [@erran](https://github.com/erran)) ## [1.0.0.beta.3] - Initial internal test version ## [1.0.0.beta.2] - First version [10.1.1]: https://github.com/cucumber/cucumber-ruby-core/compare/v10.1.0...v10.1.1 [10.1.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v10.0.1...v10.1.0 [10.0.1]: https://github.com/cucumber/cucumber-ruby-core/compare/v10.0.0...v10.0.1 [10.0.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v9.0.1...v10.0.0 [9.0.1]: https://github.com/cucumber/cucumber-ruby-core/compare/v9.0.0...v9.0.1 [9.0.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v8.0.1...v9.0.0 [8.0.1]: https://github.com/cucumber/cucumber-ruby-core/compare/v8.0.0...v8.0.1 [8.0.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v7.1.0...v8.0.0 [7.1.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v7.0.0...v7.1.0 [7.0.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v6.0.0...v7.0.0 [6.0.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v5.0.2...v6.0.0 [5.0.2]: https://github.com/cucumber/cucumber-ruby-core/compare/v5.0.1...v5.0.2 [5.0.1]: https://github.com/cucumber/cucumber-ruby-core/compare/v5.0.0...v5.0.1 [5.0.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v4.0.0...v5.0.0 [4.0.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v3.2.0...v4.0.0 [3.2.1]: https://github.com/cucumber/cucumber-ruby-core/compare/v3.2.0...v3.2.1 [3.2.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v3.1.0...v3.2.0 [3.1.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v3.0.0...3.1.0 [3.0.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v3.0.0.pre.2...v3.0.0 [3.0.0.pre.2]: https://github.com/cucumber/cucumber-ruby-core/compare/v2.0.0...3.0.0.pre.2 [2.0.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v1.5.0...2.0.0 [1.5.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v1.4.0...v1.5.0 [1.4.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v1.3.1...v1.4.0 [1.3.1]: https://github.com/cucumber/cucumber-ruby-core/compare/v1.3.0...v1.3.1 [1.3.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v1.2.0...v1.3.0 [1.2.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v1.1.3...v1.2.0 [1.1.3]: https://github.com/cucumber/cucumber-ruby-core/compare/v1.1.2...v1.1.3 [1.1.2]: https://github.com/cucumber/cucumber-ruby-core/compare/v1.1.1...v1.1.2 [1.1.1]: https://github.com/cucumber/cucumber-ruby-core/compare/v1.1.0...v1.1.1 [1.1.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v1.0.0...v1.1.0 [1.0.0]: https://github.com/cucumber/cucumber-ruby-core/compare/v1.0.0.beta.4...v1.0.0 [1.0.0.beta.4]: https://github.com/cucumber/cucumber-ruby-core/compare/v1.0.0.beta.3...v1.0.0.beta.4 [1.0.0.beta.3]: https://github.com/cucumber/cucumber-ruby-core/compare/v1.0.0.beta.2...v1.0.0.beta.3 [1.0.0.beta.2]: https://github.com/cucumber/cucumber-ruby-core/compare/v1.0.0.beta.1...v1.0.0.beta.2 cucumber-cucumber-ruby-core-08c81cd/CONTRIBUTING.md000066400000000000000000000153071514133137400217040ustar00rootroot00000000000000# Contributing to Cucumber Ruby Core Thank you for considering contributing to Cucumber! If you are not sure your contribution is related to `cucumber-ruby-core`, please consider taking a look at [`cucumber-ruby`'s CONTRIBUTING.md](https://github.com/cucumber/cucumber-ruby/blob/main/CONTRIBUTING.md) first. ## Code of Conduct Everyone interacting in this codebase and issue tracker is expected to follow the Cucumber [code of conduct](https://cucumber.io/conduct). ## How can I contribute? If you just want to know how to contribute to the code of `cucumber-ruby-core`, go to [Contribute to the code](#contribute-to-the-code). ## Report bugs and submit feature requests The short version is: - Try to check there is not already an issue or pull request that deals with your bug or request - Explain your issue and include as much details as possible to help other people reproduce your problem or understand your request - Consider submitting a pull request if you feel confident enough You can find more details for each of these steps in the following sections. ### Look for existing issues and pull requests Search in [the current repository][cucumber-ruby-core-issues] or in the [whole cucumber organization][cucumber-issues] if the problem or feature has already been reported. If you find an issue or pull request which is still open, add comments to it instead of opening a new one. If you're not sure, don't hesitate to just open a new issue. We can always merge and de-duplicate later. ### Submitting a pull request When submitting a pull request: - create a [draft pull request][how-to-create-a-draft-pr] - try to follow the instructions in the [template](.github/PULL_REQUEST_TEMPLATE.md) - if possible, [sign your commits] - update CHANGELOG.md with your changes - once the PR is ready, request for reviews More info on [how to contribute to the code](#contribute-to-the-code) can be found below. ### Opening a new issue To open a good issue, be clear and precise. If you report a problem, the reader must be able to reproduce it easily. Please do your best to create a [minimal, reproducible example][minimal-reproducible-example]. Consider submitting a pull request. Even if you think you cannot fix it by yourself, a pull request with a failing test is always welcome. If your request is for an enhancement - a new feature - try to be specific and support your request with referenced facts and include examples to illustrate your proposal. ## Contribute to the code ### Development environment Development environment for `cucumber-ruby-core` is a simple Ruby environment with bundler. Use a [supported Ruby version](./README.md#supported-platforms), make sure [bundler] is set-up, and voilà! You can then [fork][how-to-fork] and clone the repository. If your environment is set-up properly, the following commands should install the dependencies and execute all the tests successfully. ```shell bundle install bundle exec rake ``` You can now create a branch for your changes and [submit a pull request](#submitting-a-pull-request)! ### Working with local cucumber dependencies You may need to use local dependencies instead of released gems for `cucumber-gherkin` or `cucumber-messages`. To do so the [`Gemfile`](./Gemfile) for `cucumber-core` allows you to specify a local path for your gems using environment variables: CUCUMBER_GHERKIN_RUBY CUCUMBER_MESSAGES_RUBY For example, the following would use a local version of `cucumber-gherkin` with the `rake` command: ```shell CUCUMBER_GHERKIN_RUBY=../common/gherkin/ruby bundle exec rake ``` In the same way, if you want to test your changes to `cucumber-core` with a local `cucumber-ruby`, checkout [`cucumber-ruby`][cucumber-ruby] and do your tests with `CUCUMBER_RUBY_CORE` pointing to your local `cucumber-core`: ```shell ~/cucumber-ruby-core> cd ../cucumber-ruby ~/cucumber-ruby> CUCUMBER_RUBY_CORE=../cucumber-ruby-core bundle exec rake ``` ### Using a local Gemfile A local Gemfile allows you to use your prefer set of gems for your own development workflow, like gems dedicated to debugging. Such gems are not part of `cucumber-ruby` standard `Gemfile`. `Gemfile.local`, `Gemfile.local.lock` and `.bundle` have been added to `.gitignore` so local changes cannot be accidentaly commited and pushed to the repository. A `Gemfile.local` may look like this: ```ruby # Gemfile.local # Include the regular Gemfile eval File.read('Gemfile') # Include your favorites development gems group :development do gem 'byebug' gem 'pry' gem 'pry-byebug' gem 'debase', require: false gem 'ruby-debug-ide', require: false end ``` Then you can execute bundler with the `--gemfile` flag: `bundle install --gemfile Gemfile.local`, or with an environment variable: `BUNDLE_GEMFILE=Gemfile.local bundle [COMMAND]`. To use your local Gemfile per default, you can also execute `bundle config set --local gemfile Gemfile.local`. ### First timer? Welcome! Looking for something simple to begin with? Look at issues with the label '[good first issue](https://github.com/cucumber/cucumber-ruby-core/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)'. ### Having trouble getting started with the code? We're here to help! If you have trouble setting-up your development environment, or getting started with the code, you can join us on [Discord][community-discord]. You will find there a lot of contributors. Full-time maintainers are also available. We would be please to have 1:1 pairing sessions to help you getting started. Look for Luke Hill or Matt Wynne on [Discord][community-discord]. ### Additional documentation and notice You can find additional documentation in the [docs](./docs) directory such as (non-exhaustive list): - [How to release cucumber-ruby-core](./docs/RELEASE_PROCESS.md) (for maintainers) - [Overview of cucumber-ruby-core](./docs/ARCHITECTURE.md) [community-discord]: https://cucumber.io/docs/community/get-in-touch/#discord [cucumber-ruby]: https://github.com/cucumber/cucumber-ruby [cucumber-ruby-core]: https://github.com/cucumber/cucumber-ruby-core [cucumber-ruby-core-issues]: https://github.com/cucumber/cucumber-ruby-core/search?q=is%3Aissue [cucumber-issues]: https://github.com/search?q=is%3Aissue+user%3Acucumber [how-to-create-a-draft-pr]: https://docs.github.com/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests#draft-pull-requests [how-to-fork]: https://docs.github.com/github/collaborating-with-pull-requests/working-with-forks/about-forks [sign your commits]: https://docs.github.com/en/github/authenticating-to-github/managing-commit-signature-verification/signing-commits [minimal-reproducible-example]: https://stackoverflow.com/help/minimal-reproducible-example [bundler]: https://bundler.io/ cucumber-cucumber-ruby-core-08c81cd/Gemfile000066400000000000000000000004031514133137400207350ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec # To hack on Cucumber together with any of these libraries, uncomment the line below: # gem 'cucumber-gherkin', path: '../gherkin/ruby' # gem 'cucumber-messages', path: '../messages/ruby' cucumber-cucumber-ruby-core-08c81cd/LICENSE000066400000000000000000000021021514133137400204450ustar00rootroot00000000000000Copyright (c) Cucumber Ltd. and Contributors MIT License (Expat) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cucumber-cucumber-ruby-core-08c81cd/README.md000066400000000000000000000100561514133137400207260ustar00rootroot00000000000000 # Cucumber [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://vshymanskyy.github.io/StandWithUkraine) [![OpenCollective](https://opencollective.com/cucumber/backers/badge.svg)](https://opencollective.com/cucumber) [![OpenCollective](https://opencollective.com/cucumber/sponsors/badge.svg)](https://opencollective.com/cucumber) [![Test cucumber-core](https://github.com/cucumber/cucumber-ruby-core/actions/workflows/test.yml/badge.svg)](https://github.com/cucumber/cucumber-ruby-core/actions/workflows/test.yml) [![Code Climate](https://codeclimate.com/github/cucumber/cucumber-ruby-core.svg)](https://codeclimate.com/github/cucumber/cucumber-ruby-core) Cucumber is a tool for running automated tests written in plain language. Because they're written in plain language, they can be read by anyone on your team. Because they can be read by anyone, you can use them to help improve communication, collaboration and trust on your team. Cucumber Gherkin Example Cucumber Core is the [inner hexagon](https://en.wikipedia.org/wiki/Hexagonal_architecture_(software)) for the [Ruby flavour of Cucumber](https://github.com/cucumber/cucumber-ruby). It contains the core domain logic to execute Cucumber features. It has no user interface, just a Ruby API. If you're interested in how Cucumber works, or in building other tools that work with Gherkin documents, you've come to the right place. See [CONTRIBUTING.md](CONTRIBUTING.md) for info on contributing to Cucumber (issues, PRs, etc.). Everyone interacting in this codebase and issue tracker is expected to follow the Cucumber [code of conduct](https://cucumber.io/conduct). ## Installation `cucumber-core` is a Ruby gem. Install it as you would install any gem: add `cucumber-core` to your Gemfile: gem 'cucumber-core' then install it: $ bundle or install the gem directly: $ gem install cucumber-core ### Supported platforms - Ruby 3.3 - Ruby 3.2 - Ruby 3.1 - Ruby 3.0 - JRuby 9.4 (with [some limitations](https://github.com/cucumber/cucumber-ruby/blob/main/docs/jruby-limitations.md)) ## Usage The following example aims to illustrate how to use `cucumber-core` gem and to make sure it is working well within your environment. For more details explanation on what it actually does and how to work with it, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). ```ruby # cucumber_core_example.rb require 'cucumber/core' require 'cucumber/core/filter' class ActivateSteps < Cucumber::Core::Filter.new def test_case(test_case) test_steps = test_case.test_steps.map do |step| step.with_action { print "processing: " } end test_case.with_steps(test_steps).describe_to(receiver) end end feature = Cucumber::Core::Gherkin::Document.new(__FILE__, <<-GHERKIN) Feature: Scenario: Given some requirements When we do something Then it should pass GHERKIN class MyRunner include Cucumber::Core end MyRunner.new.execute([feature], [ActivateSteps.new]) do |events| events.on(:test_step_finished) do |event| test_step, result = event.test_step, event.result print "#{test_step.text} #{result}\n" end end ``` If you run this Ruby script: ```shell ruby cucumber_core_example.rb ``` You should see the following output: ``` processing: some requirements ✓ processing: we do something ✓ processing: it should pass ✓ ``` ## Documentation and support - Getting started with Cucumber, writing features, step definitions, and more: https://cucumber.io/docs - Discord ([invite link here](https://cucumber.io/docs/community/get-in-touch/#discord)) - `cucumber-core` overview: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) - How to work with local repositories for `cucumber-gherkin`, `cucumber-messages` or `cucumber-ruby`: [CONTRIBUTING.md#working-with-local-cucumber-dependencies](./CONTRIBUTING.md#working-with-local-cucumber-dependencies) ## Copyright Copyright (c) Cucumber Ltd. and Contributors. See LICENSE for details. cucumber-cucumber-ruby-core-08c81cd/RELEASING.md000066400000000000000000000001001514133137400212670ustar00rootroot00000000000000See https://github.com/cucumber/.github/blob/main/RELEASING.md. cucumber-cucumber-ruby-core-08c81cd/Rakefile000066400000000000000000000006711514133137400211160ustar00rootroot00000000000000# frozen_string_literal: true require 'rubygems' require 'bundler' Bundler::GemHelper.install_tasks $LOAD_PATH.unshift File.expand_path('lib', __dir__) require 'rubocop/rake_task' RuboCop::RakeTask.new require 'rspec/core/rake_task' RSpec::Core::RakeTask.new namespace :spec do desc 'run (slow) performance tests' RSpec::Core::RakeTask.new(:slow) do |t| t.rspec_opts = %w[--tag slow] end end task default: %w[spec spec:slow] cucumber-cucumber-ruby-core-08c81cd/VERSION000066400000000000000000000000071514133137400205120ustar00rootroot0000000000000016.2.0 cucumber-cucumber-ruby-core-08c81cd/cucumber-core.gemspec000066400000000000000000000033611514133137400235500ustar00rootroot00000000000000# frozen_string_literal: true version = File.read(File.expand_path('VERSION', __dir__)).strip Gem::Specification.new do |s| s.name = 'cucumber-core' s.version = version s.authors = ['Aslak Hellesøy', 'Matt Wynne', 'Steve Tooke', 'Oleg Sukhodolsky', 'Tom Brand'] s.description = 'Core library for the Cucumber BDD app' s.summary = "cucumber-core-#{s.version}" s.email = 'cukes@googlegroups.com' s.homepage = 'https://cucumber.io' s.platform = Gem::Platform::RUBY s.license = 'MIT' s.required_ruby_version = '>= 3.2' s.required_rubygems_version = '>= 3.2.8' s.metadata = { 'bug_tracker_uri' => 'https://github.com/cucumber/cucumber-ruby-core/issues', 'changelog_uri' => 'https://github.com/cucumber/cucumber-ruby-core/blob/master/CHANGELOG.md', 'documentation_uri' => 'https://www.rubydoc.info/github/cucumber/cucumber-ruby-core', 'mailing_list_uri' => 'https://groups.google.com/forum/#!forum/cukes', 'source_code_uri' => 'https://github.com/cucumber/cucumber-ruby-core', 'funding_uri' => 'https://opencollective.com/cucumber' } s.add_dependency 'cucumber-gherkin', '> 36', '< 40' s.add_dependency 'cucumber-messages', '> 31', '< 33' s.add_dependency 'cucumber-tag-expressions', '> 6', '< 9' s.add_development_dependency 'rake', '~> 13.3' s.add_development_dependency 'rspec', '~> 3.13' s.add_development_dependency 'rubocop', '~> 1.81.0' s.add_development_dependency 'rubocop-packaging', '~> 0.6.0' s.add_development_dependency 'rubocop-rake', '~> 0.7.0' s.add_development_dependency 'rubocop-rspec', '~> 3.8.0' s.files = Dir['CHANGELOG.md', 'README.md', 'LICENSE', 'lib/**/*'] s.rdoc_options = ['--charset=UTF-8'] s.require_path = 'lib' end cucumber-cucumber-ruby-core-08c81cd/docs/000077500000000000000000000000001514133137400203755ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/docs/ARCHITECTURE.md000066400000000000000000000065621514133137400226120ustar00rootroot00000000000000Cucumber Core is the [inner hexagon](https://en.wikipedia.org/wiki/Hexagonal_architecture_(software)) for the [Ruby flavour of Cucumber](https://github.com/cucumber/cucumber-ruby). It contains the core domain logic to execute Cucumber features. It has no user interface, just a Ruby API. ## Overview The entry-point is a single method on the `Cucumber::Core` module called `#execute`. Here's what it does: 1. Parses the plain-text Gherkin documents into an **AST** 2. Compiles the AST down to **test cases** 3. Passes the test cases through any **filters** 4. Executes the test cases, emitting **events** as it goes ### The AST The Abstract Syntax Tree or AST is an object graph that represents the Gherkin documents you've passed into the core. Things like `Feature`, `Scenario` and `ExamplesTable`. These are immutable value objects. ### Test cases Your Gherkin might contain scenarios, as well as examples from tables beneath a scenario outline. Test cases represent the general case of both of these. We compile the AST down to instances of `Cucumber::Core::Test::Case`, each containing a number of instances of `Cucumber::Core::Test::Step`. It's these that are then filtered and executed. Test cases and their test steps are immutable value objects. ### Filters Once we have the test cases, and they've been activated by the mappings, you may want to pass them through a filter or two. Filters can be used to do things like activate, sort, replace or remove some of the test cases or their steps before they're executed. ### Events Events are how you find out what is happening during your test run. As the test cases and steps are executed, the runner emits events to signal what's going on. Some of the events that can be emitted during a run are: - `TestCaseStarting` - `TestStepStarting` - `TestStepFinished` - `TestCaseFinished` These are probably best illustrated with an actual example (See below). ## Example Here's an example of how you might use `Cucumber::Core#execute` ```ruby require 'cucumber/core' require 'cucumber/core/filter' # This is the most complex part of the example. The filter takes test cases as input, # activates each step with an action block, then passes a new test case with those activated # steps in it on to the next filter in the chain. class ActivateSteps < Cucumber::Core::Filter.new def test_case(test_case) test_steps = test_case.test_steps.map do |step| activate(step) end test_case.with_steps(test_steps).describe_to(receiver) end private def activate(step) case step.text when /fail/ step.with_action { raise Failure } when /pass/ step.with_action {} else step end end end # Create a Gherkin document to run feature = Cucumber::Core::Gherkin::Document.new(__FILE__, <<-GHERKIN) Feature: Scenario: Given passing And failing And undefined GHERKIN # Create a runner class that uses the Core's DSL class MyRunner include Cucumber::Core end # Now execute the feature, using the filter we built, and subscribing to # an event so we can print the output. MyRunner.new.execute([feature], [ActivateSteps.new]) do |events| events.on(:test_step_finished) do |event| test_step, result = event.test_step, event.result puts "#{test_step.text} #{result}" end end ``` If you run this little Ruby script, you should see the following output: ``` passing ✓ failing ✗ undefined ? ``` cucumber-cucumber-ruby-core-08c81cd/lib/000077500000000000000000000000001514133137400202135ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/000077500000000000000000000000001514133137400220205ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core.rb000066400000000000000000000024731514133137400233030ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/event_bus' require 'cucumber/core/gherkin/parser' require 'cucumber/core/gherkin/document' require 'cucumber/core/compiler' require 'cucumber/core/test/runner' require 'cucumber/messages' require 'gherkin/query' module Cucumber module Core def execute(gherkin_documents, filters = [], event_bus = EventBus.new) yield event_bus if block_given? receiver = Test::Runner.new(event_bus) compile gherkin_documents, receiver, filters, event_bus self end def compile(gherkin_documents, last_receiver, filters = [], event_bus = EventBus.new) first_receiver = compose(filters, last_receiver) gherkin_query = ::Gherkin::Query.new compiler = Compiler.new(first_receiver, gherkin_query, event_bus) parse gherkin_documents, compiler, event_bus, gherkin_query self end private def parse(gherkin_documents, compiler, event_bus, gherkin_query) parser = Core::Gherkin::Parser.new(compiler, event_bus, gherkin_query) gherkin_documents.each do |document| parser.document document end parser.done self end def compose(filters, last_receiver) filters.reverse.reduce(last_receiver) do |receiver, filter| filter.with_receiver(receiver) end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/000077500000000000000000000000001514133137400227505ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/compiler.rb000066400000000000000000000067751514133137400251260ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/case' require 'cucumber/core/test/data_table' require 'cucumber/core/test/doc_string' require 'cucumber/core/test/empty_multiline_argument' require 'cucumber/core/test/hook_step' require 'cucumber/core/test/step' require 'cucumber/core/test/tag' require 'cucumber/messages' module Cucumber module Core # Compiles the Pickles into test cases class Compiler attr_reader :receiver, :gherkin_query, :id_generator private :receiver, :gherkin_query, :id_generator def initialize(receiver, gherkin_query, event_bus = nil) @receiver = receiver @id_generator = Cucumber::Messages::Helpers::IdGenerator::UUID.new @gherkin_query = gherkin_query @event_bus = event_bus end def pickle(pickle) test_case = create_test_case(pickle) test_case.describe_to(receiver) end def done receiver.done self end private def create_test_case(pickle) uri = pickle.uri test_steps = pickle.steps.map { |step| create_test_step(step, uri) } location = location_from_pickle(pickle) parent_locations = parent_locations_from_pickle(pickle) tags = tags_from_pickle(pickle, uri) Test::Case.new(id_generator.new_id, pickle.name, test_steps, location, parent_locations, tags, pickle.language).tap do |test_case| @event_bus&.test_case_created(test_case, pickle) end end def create_test_step(pickle_step, uri) location = location_from_pickle_step(pickle_step, uri) multiline_arg = create_multiline_arg(pickle_step, uri) Test::Step.new(id_generator.new_id, pickle_step.text, location, multiline_arg).tap do |test_step| @event_bus&.test_step_created(test_step, pickle_step) end end def create_multiline_arg(pickle_step, _uri) if pickle_step.argument if pickle_step.argument.doc_string doc_string_from_pickle_step(pickle_step) elsif pickle_step.argument.data_table data_table_from_pickle_step(pickle_step) end else Test::EmptyMultilineArgument.new end end def location_from_pickle(pickle) lines = pickle.ast_node_ids.map { |id| source_line(id) } Test::Location.new(pickle.uri, lines.sort.reverse) end def parent_locations_from_pickle(pickle) parent_lines = gherkin_query.scenario_parent_locations(pickle.ast_node_ids[0]).map(&:line) Test::Location.new(pickle.uri, parent_lines) end def location_from_pickle_step(pickle_step, uri) lines = pickle_step.ast_node_ids.map { |id| source_line(id) } Test::Location.new(uri, lines.sort.reverse) end def tags_from_pickle(pickle, uri) pickle.tags.map do |tag| location = Test::Location.new(uri, source_line(tag.ast_node_id)) Test::Tag.new(location, tag.name) end end def source_line(id) gherkin_query.location(id).line end def doc_string_from_pickle_step(pickle_step) doc_string = pickle_step.argument.doc_string Test::DocString.new( doc_string.content, doc_string.media_type ) end def data_table_from_pickle_step(pickle_step) data_table = pickle_step.argument.data_table Test::DataTable.new( data_table.rows.map do |row| row.cells.map(&:value) end ) end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/event.rb000066400000000000000000000032301514133137400244140ustar00rootroot00000000000000# frozen_string_literal: true module Cucumber module Core class Event # Macro to generate new subclasses of {Event} with attribute readers. def self.new(*events) # Use normal constructor for subclasses of Event return super if ancestors.index(Event).positive? Class.new(Event) do # NB: We need to use metaprogramming here instead of direct variable obtainment # because JRuby does not guarantee the order in which variables are defined is equivalent # to the order in which they are obtainable # # See https://github.com/jruby/jruby/issues/7988 for more info attr_reader(*events) define_method(:initialize) do |*attributes| events.zip(attributes) do |name, value| instance_variable_set(:"@#{name}", value) end end define_method(:attributes) do events.map { |var| instance_variable_get(:"@#{var}") } end define_method(:to_h) do events.zip(attributes).to_h end def event_id self.class.event_id end end end class << self # @return [Symbol] the underscored name of the class to be used as the key in an event registry def event_id underscore(name.split('::').last).to_sym end private def underscore(string) string .to_s .gsub('::', '/') .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') .gsub(/([a-z\d])([A-Z])/, '\1_\2') .tr('-', '_') .downcase end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/event_bus.rb000066400000000000000000000046141514133137400252740ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/events' module Cucumber module Core # Event Bus # # Implements an in-process pub-sub event broadcaster allowing multiple observers # to subscribe to events that fire as your tests are executed. class EventBus attr_reader :event_types # @param registry [Hash{Symbol => Class}] a hash of event types to use on the bus def initialize(registry = Events.registry) @event_types = registry.freeze @handlers = {} @event_queue = [] end # Register for an event. The handler proc will be called back with each of the attributes of the event def on(event_id, handler_object = nil, &handler_proc) handler = handler_proc || handler_object validate_handler_and_event_id!(handler, event_id) event_class = event_types[event_id] handlers_for(event_class) << handler broadcast_queued_events_to handler, event_class end def broadcast(event) raise ArgumentError, "Event type #{event.class} is not registered. Try one of these:\n#{event_types.values.join("\n")}" unless registered_type?(event.class) handlers_for(event.class).each { |handler| handler.call(event) } @event_queue << event end def method_missing(event_id, *) event_class = event_types.fetch(event_id) { super } broadcast event_class.new(*) end def respond_to_missing?(event_id, *args) event_types.key?(event_id) || super end private def broadcast_queued_events_to(handler, event_type) @event_queue.select { |event| event.instance_of?(event_type) }.each { |event| handler.call(event) } end def handlers_for(event_class) @handlers[event_class.to_s] ||= [] end def registered_type?(event_type) event_types.values.include?(event_type) end def registered_id?(event_id) event_types.keys.include?(event_id) end def validate_handler_and_event_id!(handler, event_id) raise ArgumentError, 'Please pass either an object or a handler block' unless handler raise ArgumentError, 'Please use a symbol for the event_id' unless event_id.is_a?(Symbol) raise ArgumentError, "Event ID #{event_id} is not recognised. Try one of these:\n#{event_types.keys.join("\n")}" unless registered_id?(event_id) end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/events.rb000066400000000000000000000022361514133137400246040ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'events/envelope' require_relative 'events/gherkin_source_parsed' require_relative 'events/test_case_created' require_relative 'events/test_case_started' require_relative 'events/test_case_finished' require_relative 'events/test_step_created' require_relative 'events/test_step_started' require_relative 'events/test_step_finished' module Cucumber module Core module Events # The registry contains all the events registered in the core, that will be used by the {EventBus} by default. def self.registry build_registry( Envelope, GherkinSourceParsed, TestCaseCreated, TestCaseStarted, TestCaseFinished, TestStepCreated, TestStepStarted, TestStepFinished ) end # Build an event registry to be passed to the {EventBus} constructor from a list of types. # Each type must respond to `event_id` so that it can be added to the registry hash # # @return [Hash{Symbol => Class}] def self.build_registry(*types) types.to_h { |type| [type.event_id, type] } end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/events/000077500000000000000000000000001514133137400242545ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/events/envelope.rb000066400000000000000000000003221514133137400264130ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../event' module Cucumber module Core module Events class Envelope < Event.new(:envelope) attr_reader :envelope end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/events/gherkin_source_parsed.rb000066400000000000000000000005441514133137400311510ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../event' module Cucumber module Core module Events # Signals that a gherkin source has been parsed class GherkinSourceParsed < Event.new(:gherkin_document) # @return [GherkinDocument] the GherkinDocument Ast Node attr_reader :gherkin_document end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/events/test_case_created.rb000066400000000000000000000005761514133137400302520ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../event' module Cucumber module Core module Events # Signals that a Test::Case was created from a Pickle class TestCaseCreated < Event.new(:test_case, :pickle) # The created test step attr_reader :test_case # The source pickle step attr_reader :pickle end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/events/test_case_finished.rb000066400000000000000000000006661514133137400304340ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../event' module Cucumber module Core module Events # Signals that a {Test::Case} has finished executing class TestCaseFinished < Event.new(:test_case, :result) # @return [Test::Case] that was executed attr_reader :test_case # @return [Test::Result] the result of running the {Test::Step} attr_reader :result end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/events/test_case_started.rb000066400000000000000000000005231514133137400303010ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../event' module Cucumber module Core module Events # Signals that a {Test::Case} is about to be executed class TestCaseStarted < Event.new(:test_case) # @return [Test::Case] the test case to be executed attr_reader :test_case end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/events/test_step_created.rb000066400000000000000000000006141514133137400303030ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../event' module Cucumber module Core module Events # Signals that a Test::Step was created from a PickleStep class TestStepCreated < Event.new(:test_step, :pickle_step) # The created test step attr_reader :test_step # The source pickle step attr_reader :pickle_step end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/events/test_step_finished.rb000066400000000000000000000007041514133137400304650ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../event' module Cucumber module Core module Events # Signals that a {Test::Step} has finished executing class TestStepFinished < Event.new(:test_step, :result) # @return [Test::Step] the test step that was executed attr_reader :test_step # @return [Test::Result] the result of running the {Test::Step} attr_reader :result end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/events/test_step_started.rb000066400000000000000000000005231514133137400303410ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../event' module Cucumber module Core module Events # Signals that a {Test::Step} is about to be executed class TestStepStarted < Event.new(:test_step) # @return [Test::Step] the test step to be executed attr_reader :test_step end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/filter.rb000066400000000000000000000045441514133137400245710ustar00rootroot00000000000000# frozen_string_literal: true module Cucumber module Core # Filters process test cases. # # Each filter must respond to the following protocol: # # * `with_receiver(new_receiver)` # * `test_case(test_case, &describe_test_steps)` # * `done` # # The `with_receiver` method is used to assemble the filters into a chain. It should return a new instance of the # filter with the receiver attribute set to the new receiver. The receiver will also respond to the filter protocol. # # When a `test_case` message is received, the filter can choose to: # # 1. pass the test_case directly to its receiver (no-op) # 2. pass a modified copy of the test_case to its receiver # 3. not pass the test_case to its receiver at all # # Finally, the `done` message is sent. A filter should pass this message directly to its receiver. # module Filter # Utility method for quick construction of filter classes. # # @example Example usage: # # class BlankingFilter < Filter.new(:name_to_blank, :receiver) # def test_case(test_case) # if name_to_blank == test_case.name # test_case.with_steps([]).describe_to(receiver) # else # test_case.describe_to(receiver) # end # end # end # # The attribute names passed to the Filter constructor will become private attributes of # your filter class. # def self.new(*attributes, &block) attributes << :receiver result = Class.new do attr_reader(*attributes) private(*attributes) define_method(:initialize) do |*args| attributes.zip(args) do |name, value| instance_variable_set(:"@#{name}", value) end end def test_case(test_case) test_case.describe_to receiver self end def done receiver.done self end define_method(:with_receiver) do |new_receiver| args = attributes.map { |name| instance_variable_get(:"@#{name}") } args[-1] = new_receiver self.class.new(*args) end end if block Class.new(result, &block) else result end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/gherkin/000077500000000000000000000000001514133137400243775ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/gherkin/document.rb000066400000000000000000000006641514133137400265500ustar00rootroot00000000000000# frozen_string_literal: true module Cucumber module Core module Gherkin class Document attr_reader :uri, :body, :language def initialize(uri, body, language = nil) @uri = uri @body = body @language = language || 'en' end def to_s body end def ==(other) to_s == other.to_s end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/gherkin/parser.rb000066400000000000000000000040501514133137400262170ustar00rootroot00000000000000# frozen_string_literal: true require 'gherkin' module Cucumber module Core module Gherkin ParseError = Class.new(StandardError) class Parser attr_reader :receiver, :event_bus, :gherkin_query private :receiver, :event_bus, :gherkin_query def initialize(receiver, event_bus, gherkin_query) @receiver = receiver @event_bus = event_bus @gherkin_query = gherkin_query end def document(document) source_messages(document).each do |message| process(message, document) end end def gherkin_options(document) { default_dialect: document.language, include_source: false, include_gherkin_document: true, include_pickles: true } end def done receiver.done self end private def source_messages(document) ::Gherkin.from_source(document.uri, document.body, gherkin_options(document)) end def process(message, document) generate_envelope(message) update_gherkin_query(message) case type(message) when :gherkin_document; then event_bus.gherkin_source_parsed(message.gherkin_document) when :pickle; then receiver.pickle(message.pickle) when :parse_error; then raise ParseError, "#{document.uri}: #{message.parse_error.message}" else raise "Unknown message: #{message.to_hash}" end end def generate_envelope(message) event_bus.envelope(message) end def update_gherkin_query(message) gherkin_query.update(message) end def type(message) if !message.gherkin_document.nil? :gherkin_document elsif !message.pickle.nil? :pickle elsif message.parse_error :parse_error else :unknown end end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/gherkin/writer.rb000066400000000000000000000012461514133137400262430ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'document' require_relative 'writer/helpers' require_relative 'writer/gherkin' require_relative 'writer/feature' require_relative 'writer/background' require_relative 'writer/rule' require_relative 'writer/scenario' require_relative 'writer/example' require_relative 'writer/scenario_outline' require_relative 'writer/step' require_relative 'writer/table' require_relative 'writer/doc_string' require_relative 'writer/examples' module Cucumber module Core module Gherkin module Writer def gherkin(uri = 'features/test.feature', &) Gherkin.new(uri, &).build end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/gherkin/writer/000077500000000000000000000000001514133137400257135ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/gherkin/writer/background.rb000066400000000000000000000011721514133137400303600ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helpers' module Cucumber module Core module Gherkin module Writer class Background include HasElements include HasOptionsInitializer include HasDescription include Indentation.level 2 default_keyword 'Background' elements :step private def statements prepare_statements( comments_statement, tag_statement, name_statement, description_statement ) end end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/gherkin/writer/doc_string.rb000066400000000000000000000014661514133137400304020ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helpers' module Cucumber module Core module Gherkin module Writer class DocString include Indentation.level(6) attr_reader :strings, :content_type private :strings, :content_type def initialize(string, content_type) @strings = string.split("\n").map(&:strip) @content_type = content_type end def build(source) source + statements end private def statements prepare_statements doc_string_statement end def doc_string_statement [ %("""#{content_type}), strings, '"""' ] end end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/gherkin/writer/example.rb000066400000000000000000000004601514133137400276730ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helpers' require_relative 'scenario' module Cucumber module Core module Gherkin module Writer class Example < Scenario include Indentation.level 4 default_keyword 'Example' end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/gherkin/writer/examples.rb000066400000000000000000000013661514133137400300640ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helpers' module Cucumber module Core module Gherkin module Writer class Examples NEW_LINE = '' include HasOptionsInitializer include HasRows include HasDescription include Indentation.level(4) default_keyword 'Examples' def build(source) source + statements end private def statements prepare_statements( NEW_LINE, comments_statement, tag_statement, name_statement, description_statement, row_statements(2) ) end end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/gherkin/writer/feature.rb000066400000000000000000000020441514133137400276730ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helpers' module Cucumber module Core module Gherkin module Writer class Feature NEW_LINE = '' include HasElements include HasOptionsInitializer include HasDescription include Indentation.level(0) default_keyword 'Feature' elements :background, :rule, :scenario, :scenario_outline def build(source = []) elements.inject(source + statements) { |acc, el| el.build(acc) + [NEW_LINE] } end private def language options[:language] end def statements prepare_statements( language_statement, comments_statement, tag_statement, name_statement, description_statement, NEW_LINE ) end def language_statement "# language: #{language}" if language end end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/gherkin/writer/gherkin.rb000066400000000000000000000014771514133137400277000ustar00rootroot00000000000000# frozen_string_literal: true require_relative '../document' require_relative 'feature' module Cucumber module Core module Gherkin module Writer class Gherkin def initialize(uri, &source) @uri = uri @source = source end def comment(line) comment_lines << "# #{line}" end def comment_lines @comment_lines ||= [] end def feature(*args, &source) @feature = Feature.new(comment_lines, *args).tap do |builder| builder.instance_exec(&source) if source end self end def build instance_exec(&@source) Document.new(@uri, @feature.build.join("\n")) end end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/gherkin/writer/helpers.rb000066400000000000000000000101131514133137400276760ustar00rootroot00000000000000# frozen_string_literal: true module Cucumber module Core module Gherkin module Writer module HasOptionsInitializer def self.included(base) base.extend HasDefaultKeyword end attr_reader :name, :options private :name, :options def initialize(*args) @comments = args.shift if args.first.is_a?(Array) @comments ||= [] @options = args.pop if args.last.is_a?(Hash) @options ||= {} @name = args.first end private def comments_statement @comments end def keyword options.fetch(:keyword) { self.class.keyword } end def name_statement "#{keyword}: #{name}".strip end def tag_statement tags end def tags options[:tags] end module HasDefaultKeyword def default_keyword(keyword) @keyword = keyword end def keyword @keyword end end end module AcceptsComments def comment(line) comment_lines << "# #{line}" end def comment_lines @comment_lines ||= [] end def slurp_comments comment_lines.tap { @comment_lines = nil } end end module HasElements include AcceptsComments def self.included(base) base.extend HasElementBuilders end def build(source = []) elements.inject(source + statements) { |acc, el| el.build(acc) } end private def elements @elements ||= [] end module HasElementBuilders def elements(*names) names.each { |name| element(name) } end private def element(name) define_method(name) do |*args, &source| factory_name = String(name).split('_').map(&:capitalize).join factory = Writer.const_get(factory_name) factory.new(slurp_comments, *args).tap do |builder| builder.instance_exec(&source) if source elements << builder end self end end end end module Indentation def self.level(number) Module.new do define_method(:indent) do |string, amount = nil| return string if string.nil? || string.empty? amount ||= number "#{' ' * amount}#{string}" end define_method(:indent_level) do number end define_method(:prepare_statements) do |*statements| statements.flatten.compact.map { |s| indent(s) } end end end end module HasDescription private def description options.fetch(:description, '').split("\n").map(&:strip) end def description_statement description.map { |s| indent(s, 2) } unless description.empty? end end module HasRows def row(*cells) rows << cells end def rows @rows ||= [] end private def row_statements(indent = nil) rows.map { |row| indent(table_row(row), indent) } end def table_row(row) padded = pad(row) "| #{padded.join(' | ')} |" end def pad(row) row.map.with_index { |text, position| justify_cell(text, position) } end def column_length(column) lengths = rows.transpose.map { |r| r.map(&:length).max } lengths[column] end def justify_cell(cell, position) cell.ljust(column_length(position)) end end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/gherkin/writer/rule.rb000066400000000000000000000012201514133137400272020ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helpers' module Cucumber module Core module Gherkin module Writer class Rule NEW_LINE = '' include HasElements include HasOptionsInitializer include HasDescription include Indentation.level 2 default_keyword 'Rule' elements :example, :scenario private def statements prepare_statements( comments_statement, name_statement, description_statement, NEW_LINE ) end end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/gherkin/writer/scenario.rb000066400000000000000000000011661514133137400300470ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helpers' module Cucumber module Core module Gherkin module Writer class Scenario include HasElements include HasOptionsInitializer include HasDescription include Indentation.level 2 default_keyword 'Scenario' elements :step private def statements prepare_statements( comments_statement, tag_statement, name_statement, description_statement ) end end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/gherkin/writer/scenario_outline.rb000066400000000000000000000011111514133137400315740ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helpers' module Cucumber module Core module Gherkin module Writer class ScenarioOutline include HasElements include HasOptionsInitializer include HasDescription include Indentation.level 2 default_keyword 'Scenario Outline' elements :step, :examples private def statements prepare_statements comments_statement, tag_statement, name_statement, description_statement end end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/gherkin/writer/step.rb000066400000000000000000000013151514133137400272130ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helpers' require_relative 'doc_string' module Cucumber module Core module Gherkin module Writer class Step include HasElements include HasOptionsInitializer include Indentation.level 4 default_keyword 'Given' elements :table def doc_string(string, content_type = '') elements << DocString.new(string, content_type) end private def statements prepare_statements comments_statement, name_statement end def name_statement "#{keyword} #{name}" end end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/gherkin/writer/table.rb000066400000000000000000000007001514133137400273240ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helpers' module Cucumber module Core module Gherkin module Writer class Table include Indentation.level(6) include HasRows def initialize(*); end def build(source) source + statements end private def statements row_statements end end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/platform.rb000066400000000000000000000011031514133137400251140ustar00rootroot00000000000000# frozen_string_literal: true # Detect the platform we're running on so we can tweak behaviour # in various places. require 'rbconfig' module Cucumber unless defined?(Cucumber::VERSION) JRUBY = defined?(JRUBY_VERSION) IRONRUBY = defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ironruby' WINDOWS = RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ OS_X = RbConfig::CONFIG['host_os'] =~ /darwin/ WINDOWS_MRI = WINDOWS && !JRUBY && !IRONRUBY RUBY_2_0 = RUBY_VERSION =~ /^2\.0/ RUBY_1_9 = RUBY_VERSION =~ /^1\.9/ end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/report/000077500000000000000000000000001514133137400242635ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/report/summary.rb000066400000000000000000000025371514133137400263140ustar00rootroot00000000000000# frozen_string_literal: true module Cucumber module Core module Report class Summary attr_reader :test_cases, :test_steps def initialize(event_bus) @previous_test_case = nil @test_cases = Test::Result::Summary.new @test_steps = Test::Result::Summary.new subscribe_to(event_bus) end def ok?(strict: Test::Result::StrictConfiguration.new) test_cases.ok?(strict: strict) end private def subscribe_to(event_bus) register_test_case_finished_listener(event_bus) register_test_step_finished_listener(event_bus) self end def register_test_case_finished_listener(event_bus) event_bus.on(:test_case_finished) do |event| if event.test_case != @previous_test_case @previous_test_case = event.test_case event.result.describe_to(test_cases) elsif event.result.passed? || event.result.skipped? test_cases.flaky test_cases.decrement_failed end end end def register_test_step_finished_listener(event_bus) event_bus.on(:test_step_finished) do |event| event.result.describe_to(test_steps) unless event.test_step.hook? end end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/000077500000000000000000000000001514133137400237275ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/action.rb000066400000000000000000000004271514133137400255340ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/location' require 'cucumber/core/test/result' require 'cucumber/core/test/timer' require 'cucumber/core/test/action/defined' require 'cucumber/core/test/action/undefined' require 'cucumber/core/test/action/unskippable' cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/action/000077500000000000000000000000001514133137400252045ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/action/defined.rb000066400000000000000000000023721514133137400271330ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/location' require 'cucumber/core/test/result' require 'cucumber/core/test/timer' module Cucumber module Core module Test module Action class Defined attr_reader :location def initialize(location = nil, &block) raise ArgumentError, 'Passing a block to execute the action is mandatory.' unless block @location = location || Test::Location.new(*block.source_location[0..1]) @block = block @timer = Timer.new end def execute(*) @timer.start @block.call(*) passed rescue Result::Raisable => e e.with_duration(@timer.duration) rescue Exception => e failed(e) end def inspect "#<#{self.class}: #{location}>" end def skip(*) skipped end private def passed Result::Passed.new(@timer.duration) end def failed(exception) Result::Failed.new(@timer.duration, exception) end def skipped Result::Skipped.new end end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/action/undefined.rb000066400000000000000000000010341514133137400274700ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/result' module Cucumber module Core module Test module Action class Undefined attr_reader :location def initialize(source_location) @location = source_location end def execute(*) undefined end def skip(*) undefined end private def undefined Result::Undefined.new end end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/action/unskippable.rb000066400000000000000000000004711514133137400300500ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/result' require 'cucumber/core/test/action/defined' module Cucumber module Core module Test module Action class Unskippable < Defined def skip(*) execute(*) end end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/around_hook.rb000066400000000000000000000014541514133137400265700ustar00rootroot00000000000000# frozen_string_literal: true module Cucumber module Core module Test class AroundHook def initialize(&block) @block = block @timer = Timer.new end def describe_to(visitor, *, &) visitor.around_hook(self, *, &) end def hook? true end def execute(*_args, &continue) @timer.start @block.call(continue) Result::Unknown.new # Around hook does not know the result of the inner test steps rescue Result::Raisable => e e.with_duration(@timer.duration) rescue Exception => e failed(e) end private def failed(exception) Result::Failed.new(@timer.duration, exception) end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/case.rb000066400000000000000000000054211514133137400251710ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/result' require 'cucumber/tag_expressions' module Cucumber module Core module Test class Case attr_reader :id, :name, :test_steps, :location, :parent_locations, :tags, :language, :around_hooks def initialize(id, name, test_steps, location, parent_locations, tags, language, around_hooks = []) raise ArgumentError, "test_steps should be an Array but is a #{test_steps.class}" unless test_steps.is_a?(Array) @id = id @name = name @test_steps = test_steps @location = location @parent_locations = parent_locations @tags = tags @language = language @around_hooks = around_hooks end def step_count test_steps.count end def describe_to(visitor, *args) visitor.test_case(self, *args) do |child_visitor| compose_around_hooks(child_visitor, *args) do test_steps.each do |test_step| test_step.describe_to(child_visitor, *args) end end end self end def with_steps(test_steps) self.class.new(id, name, test_steps, location, parent_locations, tags, language, around_hooks) end def with_around_hooks(around_hooks) self.class.new(id, name, test_steps, location, parent_locations, tags, language, around_hooks) end def match_tags?(*expressions) expressions.flatten.all? { |expression| match_single_tag_expression?(expression) } end def match_name?(name_regexp) name =~ name_regexp end def match_locations?(queried_locations) queried_locations.any? do |queried_location| matching_locations.any? do |location| queried_location.match? location end end end def matching_locations [ parent_locations, location, tags.map(&:location), test_steps.map(&:matching_locations) ].flatten end def inspect "#<#{self.class}: #{location}>" end def hash location.hash end def eql?(other) other.hash == hash end def ==(other) eql?(other) end private def compose_around_hooks(visitor, *args, &block) around_hooks.reverse.reduce(block) do |continue, hook| -> { hook.describe_to(visitor, *args, &continue) } end.call end def match_single_tag_expression?(expression) Cucumber::TagExpressions::Parser.new.parse(expression).evaluate(tags.map(&:name)) end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/data_table.rb000066400000000000000000000053011514133137400263330ustar00rootroot00000000000000# frozen_string_literal: true module Cucumber module Core module Test # Step Definitions that match a plain text Step with a multiline argument table # will receive it as an instance of DataTable. A DataTable object holds the data of a # table parsed from a feature file and lets you access and manipulate the data # in different ways. # # For example: # # Given I have: # | a | b | # | c | d | # # And a matching StepDefinition: # # Given /I have:/ do |table| # data = table.raw # end # # This will store [['a', 'b'], ['c', 'd']] in the data variable. # class DataTable # Creates a new instance. +raw+ should be an Array of Array of String # or an Array of Hash # You don't typically create your own DataTable objects - Cucumber will do # it internally and pass them to your Step Definitions. # def initialize(rows) raw = ensure_array_of_array(rows) verify_rows_are_same_length(raw) @raw = raw.freeze end attr_reader :raw def describe_to(visitor, *) visitor.data_table(self, *) end def to_step_definition_arg dup end def data_table? true end def doc_string? false end # Creates a copy of this table # def dup self.class.new(raw.dup) end # Returns a new, transposed table. Example: # # | a | 7 | 4 | # | b | 9 | 2 | # # Gets converted into the following: # # | a | b | # | 7 | 9 | # | 4 | 2 | # def transpose self.class.new(raw.transpose) end def map(&block) new_raw = raw.map do |row| row.map(&block) end self.class.new(new_raw) end def lines_count raw.count end def ==(other) other.class == self.class && raw == other.raw end def inspect %{#<#{self.class} #{raw.inspect})>} end private def verify_rows_are_same_length(raw) raw.transpose rescue IndexError raise ArgumentError, 'Rows must all be the same length' end def ensure_array_of_array(array) array[0].is_a?(Hash) ? hashes_to_array(array) : array end def hashes_to_array(hashes) header = hashes[0].keys.sort [header] + hashes.map { |hash| header.map { |key| hash[key] } } end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/doc_string.rb000066400000000000000000000035131514133137400264110ustar00rootroot00000000000000# frozen_string_literal: true require 'delegate' module Cucumber module Core module Test # Represents an inline argument in a step. Example: # # Given the message # """ # I like # Cucumber sandwich # """ # # The text between the pair of """ is stored inside a DocString, # which is yielded to the StepDefinition block as the last argument. # # The StepDefinition can then access the String via the #to_s method. In the # example above, that would return: "I like\nCucumber sandwich" # # Note how the indentation from the source is stripped away. # class DocString < SimpleDelegator attr_reader :content_type, :content def initialize(content, content_type) @content = content @content_type = content_type super(@content) end def describe_to(visitor, *) visitor.doc_string(self, *) end def data_table? false end def doc_string? true end def map raise ArgumentError, 'No block given' unless block_given? new_content = yield content self.class.new(new_content, content_type) end def to_step_definition_arg self end def lines_count lines.count + 2 end def ==(other) return false if other.respond_to?(:content_type) && content_type != other.content_type return content == other.to_str if other.respond_to?(:to_str) false end def inspect [ %(#<#{self.class}), %( """#{content_type}), %( #{@content}), %( """>) ].join("\n") end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/empty_multiline_argument.rb000066400000000000000000000007071514133137400314020ustar00rootroot00000000000000# frozen_string_literal: true module Cucumber module Core module Test class EmptyMultilineArgument def describe_to(*) self end def data_table? false end def doc_string? false end def map self end def lines_count 0 end def inspect "#<#{self.class}>" end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/filters.rb000066400000000000000000000002661514133137400257300ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/filters/locations_filter' require 'cucumber/core/test/filters/name_filter' require 'cucumber/core/test/filters/tag_filter' cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/filters/000077500000000000000000000000001514133137400253775ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/filters/locations_filter.rb000066400000000000000000000016531514133137400312710ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/filter' module Cucumber module Core module Test # Sorts and filters scenarios based on a list of locations class LocationsFilter < Filter.new(:filter_locations) def test_case(test_case) test_cases[test_case.location.file] << test_case self end def done sorted_test_cases.each do |test_case| test_case.describe_to receiver end receiver.done self end private def sorted_test_cases filter_locations.map do |filter_location| test_cases[filter_location.file].select do |test_case| test_case.match_locations?([filter_location]) end end.flatten.uniq end def test_cases @test_cases ||= Hash.new { |hash, key| hash[key] = [] } end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/filters/name_filter.rb000066400000000000000000000007351514133137400302160ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/filter' module Cucumber module Core module Test class NameFilter < Filter.new(:name_regexps) def test_case(test_case) test_case.describe_to(receiver) if accept?(test_case) self end private def accept?(test_case) name_regexps.empty? || name_regexps.any? { |name_regexp| test_case.match_name?(name_regexp) } end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/filters/tag_filter.rb000066400000000000000000000020071514133137400300430ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/filter' module Cucumber module Core module Test class TagFilter < Filter.new(:filter_expressions) def test_case(test_case) test_cases << test_case test_case.describe_to(receiver) if test_case.match_tags?(filter_expressions) self end def done receiver.done self end private def test_cases @test_cases ||= TestCases.new end class TestCases attr_reader :test_cases_by_tag_name private :test_cases_by_tag_name def initialize @test_cases_by_tag_name = Hash.new { [] } end def <<(test_case) test_case.tags.each do |tag| test_cases_by_tag_name[tag.name] += [test_case] end self end def with_tag_name(tag_name) test_cases_by_tag_name[tag_name] end end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/hook_step.rb000066400000000000000000000005431514133137400262510ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/step' module Cucumber module Core module Test class HookStep < Step def initialize(id, text, location, action) super(id, text, location, Test::EmptyMultilineArgument.new, action) end def hook? true end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/location.rb000066400000000000000000000077151514133137400260760ustar00rootroot00000000000000# frozen_string_literal: true require 'forwardable' require 'cucumber/core/platform' require 'set' module Cucumber module Core module Test IncompatibleLocations = Class.new(StandardError) module Location def self.of_caller(additional_depth = 0) from_file_colon_line(*caller[1 + additional_depth]) end def self.from_file_colon_line(file_colon_line) file, raw_line = file_colon_line.match(/(.*):(\d+)/)[1..2] from_source_location(file, raw_line.to_i) end def self.from_source_location(file, line, *_args) file = File.expand_path(file) pwd = File.expand_path(Dir.pwd) pwd.force_encoding(file.encoding) if file.index(pwd) file = file[(pwd.length + 1)..] elsif file.match?(/gems\/(.+\.rb)$/) file = file.split('gems/').last end new(file, line) end def self.new(file, raw_lines = nil) if raw_lines Precise.new(file, Lines.new(raw_lines)) else Wildcard.new(file) end end Wildcard = Struct.new(:file) do def to_s file end def match?(other) other.file == file end def include?(_lines) true end end Precise = Struct.new(:file, :lines) do def include?(other_lines) lines.include?(other_lines) end def line lines.first end def match?(other) return false unless other.file == file other.include?(lines) end def to_s [file, lines.to_s].join(':') end def hash [self.class, to_s].hash end def to_str to_s end def merge(multiline_arg) new_lines = (0..multiline_arg.lines_count).map do |offset| lines.min + offset end Location.new(file, new_lines) end def on_line(new_line) Location.new(file, new_line) end def inspect "<#{self.class}: #{self}>" end end Lines = Struct.new(:data) do protected :data def initialize(raw_data) super(Array(raw_data).to_set) end def first data.first end def min data.min end def max data.max end def include?(other) other.data.subset?(data) || data.subset?(other.data) end def +(other) new_data = data + other.data self.class.new(new_data) end def to_s return first.to_s if data.length == 1 return "#{data.min}..#{data.max}" if range? data.to_a.join(':') end def inspect "<#{self.class}: #{self}>" end protected def range? data.size == (data.max - data.min + 1) end end end module HasLocation def file_colon_line location.to_s end def file location.file end def line location.line end def location raise('Please set @location in the constructor') unless defined?(@location) @location end def attributes [tags, comments, multiline_arg].flatten end def tags # will be overridden by nodes that actually have tags [] end def comments # will be overridden by nodes that actually have comments [] end def multiline_arg # will be overridden by nodes that actually have a multiline_argument Test::EmptyMultilineArgument.new end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/result.rb000066400000000000000000000277321514133137400256050ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/messages' require 'cucumber/messages/helpers/time_conversion' module Cucumber module Core module Test module Result TYPES = %i[failed ambiguous flaky skipped undefined pending passed unknown].freeze STRICT_AFFECTED_TYPES = %i[flaky undefined pending].freeze def self.ok?(type, strict: StrictConfiguration.new) class_name = type.to_s.slice(0, 1).capitalize + type.to_s.slice(1..-1) const_get(class_name).ok?(strict: strict.strict?(type)) end # Defines to_sym on a result class for the given result type # # Defines predicate methods on a result class with only the given one returning true def self.query_methods(result_type) Module.new do define_method :to_sym do result_type end TYPES.each do |possible_result_type| define_method("#{possible_result_type}?") do possible_result_type == to_sym end end end end # Null object for results. Represents the state where we haven't run anything yet class Unknown include Result.query_methods :unknown def describe_to(_visitor, *_args) self end def with_filtered_backtrace(_filter) self end def to_message Cucumber::Messages::TestStepResult.new( status: Cucumber::Messages::TestStepResultStatus::UNKNOWN, duration: UnknownDuration.new.to_message_duration ) end end class Passed include Result.query_methods :passed attr_accessor :duration def self.ok?(*) true end def initialize(duration) raise ArgumentError unless duration @duration = duration end def describe_to(visitor, *) visitor.passed(*) visitor.duration(duration, *) self end def to_s '✓' end def to_message Cucumber::Messages::TestStepResult.new( status: Cucumber::Messages::TestStepResultStatus::PASSED, duration: duration.to_message_duration ) end def ok?(*) self.class.ok? end def with_appended_backtrace(_step) self end def with_filtered_backtrace(_filter) self end end class Failed include Result.query_methods :failed attr_reader :duration, :exception def self.ok?(*) false end def initialize(duration, exception) raise ArgumentError unless duration raise ArgumentError unless exception @duration = duration @exception = exception end def describe_to(visitor, *) visitor.failed(*) visitor.duration(duration, *) visitor.exception(exception, *) if exception self end def to_s '✗' end def to_message begin message = exception.backtrace.join("\n") rescue NoMethodError message = '' end Cucumber::Messages::TestStepResult.new( status: Cucumber::Messages::TestStepResultStatus::FAILED, duration: duration.to_message_duration, message: message ) end def ok?(*) self.class.ok? end def with_duration(new_duration) self.class.new(new_duration, exception) end def with_appended_backtrace(step) exception.backtrace << step.backtrace_line if step.respond_to?(:backtrace_line) self end def with_filtered_backtrace(filter) self.class.new(duration, filter.new(exception.dup).exception) end end # Flaky is not used directly as an execution result, but is used as a # reporting result type for test cases that fails and the passes on # retry, therefore only the class method self.ok? is needed. class Flaky def self.ok?(strict: false) !strict end end # Base class for exceptions that can be raised in a step definition causing the step to have that result. class Raisable < StandardError attr_reader :message, :duration def initialize(message = '', duration = UnknownDuration.new, backtrace = nil) @message = message @duration = duration super(message) set_backtrace(backtrace) if backtrace end def with_message(new_message) self.class.new(new_message, duration, backtrace) end def with_duration(new_duration) self.class.new(message, new_duration, backtrace) end def with_appended_backtrace(step) return self unless step.respond_to?(:backtrace_line) set_backtrace([]) unless backtrace backtrace << step.backtrace_line self end def with_filtered_backtrace(filter) return self unless backtrace filter.new(dup).exception end def ok?(strict: StrictConfiguration.new) self.class.ok?(strict: strict.strict?(to_sym)) end end class Ambiguous < Raisable include Result.query_methods :ambiguous def self.ok?(*) false end def describe_to(visitor, *) visitor.ambiguous(*) visitor.duration(duration, *) self end def to_s 'A' end def to_message Cucumber::Messages::TestStepResult.new( status: Cucumber::Messages::TestStepResultStatus::AMBIGUOUS, duration: duration.to_message_duration ) end end class Undefined < Raisable include Result.query_methods :undefined def self.ok?(strict: false) !strict end def describe_to(visitor, *) visitor.undefined(*) visitor.duration(duration, *) self end def to_s '?' end def to_message Cucumber::Messages::TestStepResult.new( status: Cucumber::Messages::TestStepResultStatus::UNDEFINED, duration: duration.to_message_duration ) end end class Skipped < Raisable include Result.query_methods :skipped def self.ok?(*) true end def describe_to(visitor, *) visitor.skipped(*) visitor.duration(duration, *) self end def to_s '-' end def to_message Cucumber::Messages::TestStepResult.new( status: Cucumber::Messages::TestStepResultStatus::SKIPPED, duration: duration.to_message_duration ) end end class Pending < Raisable include Result.query_methods :pending def self.ok?(strict: false) !strict end def describe_to(visitor, *) visitor.pending(self, *) visitor.duration(duration, *) self end def to_s 'P' end def to_message Cucumber::Messages::TestStepResult.new( status: Cucumber::Messages::TestStepResultStatus::PENDING, duration: duration.to_message_duration ) end end # Handles the strict settings for the result types that are # affected by the strict options (that is the STRICT_AFFECTED_TYPES). class StrictConfiguration attr_accessor :settings private :settings def initialize(strict_types = []) @settings = STRICT_AFFECTED_TYPES.to_h { |t| [t, :default] } strict_types.each do |type| set_strict(true, type) end end def strict?(type = nil) if type.nil? settings.each_value do |value| return true if value == true end false else return false unless settings.key?(type) return false unless set?(type) settings[type] end end def set_strict(setting, type = nil) if type.nil? STRICT_AFFECTED_TYPES.each { |type| set_strict(setting, type) } else settings[type] = setting end end def merge!(other) settings.each_key do |type| set_strict(other.strict?(type), type) if other.set?(type) end self end def set?(type) settings[type] != :default end end # # An object that responds to the description protocol from the results and collects summary information. # # e.g. # summary = Result::Summary.new # Result::Passed.new(0).describe_to(summary) # puts summary.total_passed # => 1 # class Summary attr_reader :exceptions, :durations def initialize @totals = Hash.new { 0 } @exceptions = [] @durations = [] end def method_missing(name, *_args) if name =~ /^total_/ get_total(name) else increment_total(name) end end def respond_to_missing?(*) true end def ok?(strict: StrictConfiguration.new) TYPES.each do |type| return false if get_total(type).positive? && !Result.ok?(type, strict: strict) end true end def exception(exception) @exceptions << exception self end def duration(duration) @durations << duration self end def total(for_status = nil) if for_status @totals.fetch(for_status, 0) else @totals.values.reduce(0) { |total, count| total + count } end end def decrement_failed @totals[:failed] -= 1 end private def get_total(method_name) status = method_name.to_s.gsub('total_', '').to_sym @totals.fetch(status, 0) end def increment_total(status) @totals[status] += 1 self end end class Duration include Cucumber::Messages::Helpers::TimeConversion attr_reader :nanoseconds def initialize(nanoseconds) @nanoseconds = nanoseconds end def to_message_duration duration_hash = seconds_to_duration(nanoseconds.to_f / NANOSECONDS_PER_SECOND) Cucumber::Messages::Duration.new(seconds: duration_hash[:seconds], nanos: duration_hash[:nanos]) end def seconds_to_duration(seconds_float) seconds, second_modulus = seconds_float.divmod(1) nanos = second_modulus * NANOSECONDS_PER_SECOND { seconds: seconds, nanos: nanos.to_i } end end class UnknownDuration def tap self end def nanoseconds raise '#nanoseconds only allowed to be used in #tap block' end def to_message_duration Cucumber::Messages::Duration.new(seconds: 0, nanos: 0) end end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/runner.rb000066400000000000000000000107171514133137400255730ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/timer' module Cucumber module Core module Test class Runner attr_reader :event_bus, :running_test_case, :running_test_step private :event_bus, :running_test_case, :running_test_step def initialize(event_bus) @event_bus = event_bus end def test_case(test_case, &descend) @running_test_case = RunningTestCase.new @running_test_step = nil event_bus.test_case_started(test_case) descend.call(self) result = running_test_case.result result = Result::Undefined.new('The test case has no steps', Result::UnknownDuration.new, ["#{test_case.location}:in `#{test_case.name}'"]) if result.unknown? event_bus.test_case_finished(test_case, result) self end def test_step(test_step) @running_test_step = test_step event_bus.test_step_started test_step step_result = running_test_case.execute(test_step) event_bus.test_step_finished test_step, step_result @running_test_step = nil self end def around_hook(hook, &) result = running_test_case.execute(hook, &) event_bus.test_step_finished running_test_step, result if running_test_step @running_test_step = nil self end def done self end class RunningTestCase def initialize @timer = Timer.new.start @status = Status::Unknown.new(Result::Unknown.new) end def execute(test_step, &) status.execute(test_step, self, &) end def result status.result(@timer.duration) end def failed(step_result) @status = Status::Failing.new(step_result) self end def ambiguous(step_result) @status = Status::Ambiguous.new(step_result) self end def passed(step_result) @status = Status::Passing.new(step_result) self end def pending(_message, step_result) @status = Status::Pending.new(step_result) self end def skipped(step_result) @status = Status::Skipping.new(step_result) self end def undefined(step_result) failed(step_result) self end def exception(_step_exception, _step_result) self end def duration(_step_duration, _step_result) self end attr_reader :status private :status module Status class Base attr_reader :step_result private :step_result def initialize(step_result) @step_result = step_result end def execute(test_step, monitor, &) result = test_step.execute(monitor.result, &) result = result.with_message(%(Undefined step: "#{test_step.text}")) if result.undefined? result = result.with_appended_backtrace(test_step) unless test_step.hook? result.describe_to(monitor, result) end def result raise NoMethodError, 'Override me' end end class Unknown < Base def result(_duration) Result::Unknown.new end end class Passing < Base def result(duration) Result::Passed.new(duration) end end class Failing < Base def execute(test_step, monitor) result = test_step.skip(monitor.result) if result.undefined? result = result.with_message(%(Undefined step: "#{test_step.text}")) result = result.with_appended_backtrace(test_step) end result end def result(duration) step_result.with_duration(duration) end end Pending = Class.new(Failing) Ambiguous = Class.new(Failing) class Skipping < Failing def result(duration) step_result.with_duration(duration) end end end end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/step.rb000066400000000000000000000031311514133137400252250ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/result' require 'cucumber/core/test/action' require 'cucumber/core/test/empty_multiline_argument' module Cucumber module Core module Test class Step attr_reader :id, :text, :location, :multiline_arg def initialize(id, text, location, multiline_arg = Test::EmptyMultilineArgument.new, action = Test::Action::Undefined.new(location)) raise ArgumentError if text.nil? || text.empty? @id = id @text = text @location = location @multiline_arg = multiline_arg @action = action end def describe_to(visitor, *) visitor.test_step(self, *) end def hook? false end def skip(*) @action.skip(*) end def execute(*) @action.execute(*) end def with_action(action_location = nil, &) self.class.new(id, text, location, multiline_arg, Test::Action::Defined.new(action_location, &)) end def with_unskippable_action(action_location = nil, &) self.class.new(id, text, location, multiline_arg, Test::Action::Unskippable.new(action_location, &)) end def backtrace_line "#{location}:in `#{text}'" end def to_s text end def action_location @action.location end def matching_locations [location.merge(multiline_arg)] end def inspect "#<#{self.class}: #{location}>" end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/tag.rb000066400000000000000000000005611514133137400250310ustar00rootroot00000000000000# frozen_string_literal: true module Cucumber module Core module Test class Tag include HasLocation attr_reader :name def initialize(location, name) @location = location @name = name end def inspect %{#<#{self.class} "#{name}" (#{location})>} end end end end end cucumber-cucumber-ruby-core-08c81cd/lib/cucumber/core/test/timer.rb000066400000000000000000000021361514133137400253760ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/result' module Cucumber module Core module Test class Timer def start @start_time = time_in_nanoseconds self end def duration Result::Duration.new(nsec) end def nsec time_in_nanoseconds - @start_time end def sec nsec / (10**9.0) end private def time_in_nanoseconds MonotonicTime.time_in_nanoseconds end module MonotonicTime module_function if defined?(Process::CLOCK_MONOTONIC) def time_in_nanoseconds Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) end elsif (defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby') == 'jruby' def time_in_nanoseconds java.lang.System.nanoTime() end else def time_in_nanoseconds t = Time.now (t.to_i * (10**9)) + t.nsec end end end end end end end cucumber-cucumber-ruby-core-08c81cd/spec/000077500000000000000000000000001514133137400203775ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/000077500000000000000000000000001514133137400222045ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/000077500000000000000000000000001514133137400231345ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/compiler_spec.rb000066400000000000000000000145151514133137400263130ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core' require 'cucumber/core/compiler' require 'cucumber/core/gherkin/writer' describe Cucumber::Core::Compiler do include Cucumber::Core::Gherkin::Writer include Cucumber::Core let(:empty_gherkin_document) do gherkin do feature do scenario end end end let(:single_step_gherkin_document) do gherkin do feature do scenario do step 'passing' end end end end let(:double_step_gherkin_document) do gherkin do feature do scenario do step 'passing' step 'passing' end end end end let(:background_step_gherkin_document) do gherkin do feature do background do step 'passing' end scenario do step 'passing' end end end end it 'compiles a feature with a single scenario' do compile([single_step_gherkin_document]) do |visitor| expect(visitor).to receive(:test_case).once.ordered.and_yield(visitor) expect(visitor).to receive(:test_step).once.ordered expect(visitor).to receive(:done).once.ordered end end context 'when the event_bus is provided' do let(:event_bus_class) do Class.new(Cucumber::Core::EventBus) do def gherkin_source_parsed(*); end def test_case_created(*); end def test_step_created(*); end def envelope(*); end end end let(:event_bus) { event_bus_class.new } it 'emits a TestCaseCreated event with the created Test::Case and Pickle' do compile([single_step_gherkin_document], event_bus) do |visitor| allow(visitor).to receive(:test_case) allow(visitor).to receive(:test_step) allow(visitor).to receive(:done) expect(event_bus).to receive(:test_case_created).once end end it 'emits a TestStepCreated event with the created Test::Step and PickleStep' do compile([double_step_gherkin_document], event_bus) do |visitor| allow(visitor).to receive(:test_case) allow(visitor).to receive(:test_step) allow(visitor).to receive(:done) allow(event_bus).to receive(:envelope) expect(event_bus).to receive(:test_step_created).twice end end end it 'compiles a feature with a background' do gherkin_documents = [ gherkin do feature do background do step 'passing' end scenario do step 'passing' end end end ] compile(gherkin_documents) do |visitor| expect(visitor).to receive(:test_case).once.ordered.and_yield(visitor) expect(visitor).to receive(:test_step).twice.ordered expect(visitor).to receive(:done).once.ordered end end it 'compiles multiple features' do compile([background_step_gherkin_document, background_step_gherkin_document]) do |visitor| expect(visitor).to receive(:test_case).once.ordered expect(visitor).to receive(:test_step).twice.ordered expect(visitor).to receive(:test_case).once.ordered expect(visitor).to receive(:test_step).twice.ordered expect(visitor).to receive(:done).once end end context 'when compiling scenario outlines' do let(:gherkin_documents_with_examples) do [ gherkin do feature do background do step 'passing' end scenario_outline do step 'passing ' step 'passing' examples 'examples 1' do row 'arg' row '1' row '2' end examples 'examples 2' do row 'arg' row 'a' end end end end ] end let(:gherkin_documents_with_examples_and_arguments) do [ gherkin do feature do scenario_outline do step 'passing with ' step 'as well as ' examples do row 'arg1', 'arg2', 'arg3' row '1', '2', '3' end end end end ] end it 'produces test cases' do compile(gherkin_documents_with_examples) do |visitor| expect(visitor).to receive(:test_case).exactly(3).times.and_yield(visitor) allow(visitor).to receive(:test_step) allow(visitor).to receive(:done) end end it 'produces test steps' do compile(gherkin_documents_with_examples) do |visitor| allow(visitor).to receive(:done) expect(visitor).to receive(:test_step).exactly(9).times end end it 'finishes the compilation once' do compile(gherkin_documents_with_examples) do |visitor| allow(visitor).to receive(:test_step) expect(visitor).to receive(:done).once end end it 'replaces arguments correctly when generating test steps' do compile(gherkin_documents_with_examples_and_arguments) do |visitor| expect(visitor).to receive(:test_step) do |test_step| expect(test_step.text).to eq 'passing 1 with 2' end.once.ordered expect(visitor).to receive(:test_step) do |test_step| expect(test_step.text).to eq 'as well as 3' end.once.ordered expect(visitor).to receive(:done).once.ordered end end end context 'with no scenarios' do it 'creates a single test case' do compile([empty_gherkin_document]) do |visitor| allow(visitor).to receive(:done) expect(visitor).to receive(:test_case).once.ordered end end it 'finishes the compilation once' do compile([empty_gherkin_document]) do |visitor| allow(visitor).to receive(:test_case) expect(visitor).to receive(:done).once.ordered end end end def compile(gherkin_documents, event_bus = nil) visitor = double allow(visitor).to receive(:test_suite).and_yield(visitor) allow(visitor).to receive(:test_case).and_yield(visitor) if event_bus.nil? event_bus = double allow(event_bus).to receive_messages( envelope: nil, gherkin_source_parsed: nil, test_case_created: nil, test_step_created: nil ) end yield visitor super(gherkin_documents, visitor, [], event_bus) end end cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/event_bus_spec.rb000066400000000000000000000112061514133137400264650ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/event_bus' module Cucumber module Core module Events class TestEvent < Core::Event.new(:some_attribute) end AnotherTestEvent = Core::Event.new UnregisteredEvent = Core::Event.new end describe EventBus do let(:event_bus) { described_class.new(registry) } let(:registry) { { test_event: Events::TestEvent, another_test_event: Events::AnotherTestEvent } } context 'when broadcasting events' do it 'can broadcast by calling a method named after the event ID' do called = false event_bus.on(:test_event) { called = true } event_bus.test_event expect(called).to be true end it 'can broadcast by calling the `broadcast` method with an instance of the event type' do called = false event_bus.on(:test_event) { called = true } event_bus.broadcast(Events::TestEvent.new(:some_attribute)) expect(called).to be true end it 'calls a subscriber for an event, passing details of the event' do received_payload = nil event_bus.on(:test_event) { |event| received_payload = event } event_bus.test_event :some_attribute expect(received_payload.some_attribute).to eq(:some_attribute) end it 'does not call subscribers for other events' do handler_called = false event_bus.on :test_event do handler_called = true end event_bus.another_test_event expect(handler_called).to be false end it 'broadcasts to multiple subscribers' do received_events = [] event_bus.on :test_event do received_events << :event end event_bus.on :test_event do received_events << :event end event_bus.test_event(:some_attribute) expect(received_events.length).to eq(2) end it "raises an error when given an event to broadcast that it doesn't recognise" do expect { event_bus.some_unknown_event }.to raise_error(NameError) end describe '#broadcast method' do it 'must be passed an instance of a registered event type' do expect { event_bus.broadcast(Events::UnregisteredEvent) }.to raise_error(ArgumentError) end end end context 'when subscribing to events' do let(:regular_handler) do Class.new do attr_reader :received_payload def call(event) @received_payload = event end end end let(:proc_handler) do Class.new do attr_reader :received_payload def initialize(event_bus) event_bus.on :test_event, &method(:on_test_event) end def on_test_event(event) @received_payload = event end end end it 'allows subscription by symbol (Event ID)' do received_payload = nil event_bus.on(:test_event) do |event| received_payload = event end event_bus.test_event :some_attribute expect(received_payload.some_attribute).to eq(:some_attribute) end it 'raises an error if you use an unknown Event ID' do expect { event_bus.on(:some_unknown_event) { :whatever } }.to raise_error(ArgumentError) end it 'allows handlers that are objects with a `call` method' do handler = regular_handler.new event_bus.on(:test_event, handler) event_bus.test_event :some_attribute expect(handler.received_payload.some_attribute).to eq(:some_attribute) end it 'allows handlers that are procs' do handler = proc_handler.new(event_bus) event_bus.test_event :some_attribute expect(handler.received_payload.some_attribute).to eq(:some_attribute) end it 'sends events that were broadcast before you subscribed' do event_bus.test_event(:some_attribute) event_bus.another_test_event received_payload = nil event_bus.on(:test_event) do |event| received_payload = event end expect(received_payload.some_attribute).to eq(:some_attribute) end end it 'lets you inspect the registry' do expect(event_bus.event_types[:test_event]).to eq(Events::TestEvent) end it 'does not let you modify the registry' do expect { event_bus.event_types[:foo] = :bar }.to raise_error(RuntimeError) end end end end cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/event_spec.rb000066400000000000000000000021541514133137400256160ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/event' describe Cucumber::Core::Event do describe '.new' do it 'generates new types of events' do my_event_type = described_class.new my_event = my_event_type.new expect(my_event).to be_a(described_class) end it 'generates events with attributes' do my_event_type = described_class.new(:foo, :bar) my_event = my_event_type.new(1, 2) expect(my_event.attributes).to eq([1, 2]) expect(my_event.foo).to eq(1) expect(my_event.bar).to eq(2) end end describe 'a generated event' do let(:my_event_type) do Class.new(described_class.new(:foo, :bar)) do # Anonymous classes don't respond to .name. So we override it! def self.name 'Cucumber::Core::MyEventType' end end end it 'can be converted to a hash' do expect(my_event_type.new(1, 2).to_h).to eq(foo: 1, bar: 2) end it 'has an event_id' do expect(my_event_type.event_id).to eq(:my_event_type) expect(my_event_type.new(1, 2).event_id).to eq(:my_event_type) end end end cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/filter_spec.rb000066400000000000000000000057161514133137400257710ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/gherkin/writer' require 'cucumber/core' require 'cucumber/core/filter' describe Cucumber::Core::Filter do include Cucumber::Core::Gherkin::Writer include Cucumber::Core describe '.new' do let(:receiver) { double.as_null_object } let(:doc) do gherkin do feature do scenario 'First Scenario' do step 'a step' end scenario 'Second Scenario' do step 'a different step' end end end end it 'creates a filter class that can pass-through by default' do my_filter_class = described_class.new my_filter = my_filter_class.new expect(receiver).to receive(:test_case) do |test_case| expect(test_case.test_steps.length).to eq(1) expect(test_case.test_steps.first.text).to match(/a(?: different)? step/) end.twice compile([doc], receiver, [my_filter]) end context 'when customizing using a subclass' do let(:basic_blanking_filter) do # Each filter implicitly gets a :receiver attribute that you need to call with the new test case # once you've received yours and modified it. Class.new(described_class.new) do def test_case(test_case) test_case.with_steps([]).describe_to(receiver) end end end let(:named_blanking_filter) do # You can pass the names of attributes when building a filter, allowing you to have custom attributes. Class.new(described_class.new(:name_pattern)) do def test_case(test_case) if test_case.name =~ name_pattern test_case.with_steps([]).describe_to(receiver) else test_case.describe_to(receiver) # or just call `super` end self end end end it 'can override methods from the base class' do expect(receiver).to receive(:test_case) { |test_case| expect(test_case.test_steps.length).to eq(0) }.twice run(basic_blanking_filter.new) end it 'can take arguments' do expect(receiver).to receive(:test_case) { |test_case| expect(test_case.test_steps.length).to eq(0) }.once.ordered expect(receiver).to receive(:test_case) { |test_case| expect(test_case.test_steps.length).to eq(1) }.once.ordered run(named_blanking_filter.new(/First Scenario/)) end end context 'when customizing using a block' do let(:block_blanking_filter) do Class.new(described_class.new) do def test_case(test_case) test_case.with_steps([]).describe_to(receiver) end end end it 'allows methods to be overridden' do expect(receiver).to receive(:test_case) { |test_case| expect(test_case.test_steps.length).to eq(0) }.twice run(block_blanking_filter.new) end end def run(filter) compile([doc], receiver, [filter]) end end end cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/gherkin/000077500000000000000000000000001514133137400245635ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/gherkin/parser_spec.rb000066400000000000000000000073721514133137400274270ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/gherkin/parser' require 'cucumber/core/gherkin/writer' describe Cucumber::Core::Gherkin::Parser do include Cucumber::Core::Gherkin::Writer let(:receiver) { double } let(:event_bus) { double } let(:gherkin_query) { double } let(:parser) { described_class.new(receiver, event_bus, gherkin_query) } let(:visitor) { double } before do allow(event_bus).to receive(:gherkin_source_parsed) allow(event_bus).to receive(:envelope) allow(gherkin_query).to receive(:update) end def self.source(&block) let(:source) { gherkin(&block) } end def parse parser.document(source) end RSpec::Matchers.define :pickle_with_language do |language| match { |actual| actual.language == language } end context 'when invalid gherkin is used as a source' do let(:source) { Cucumber::Core::Gherkin::Document.new(path, "\nnot gherkin\n\nFeature: \n") } let(:path) { 'path_to/the.feature' } it 'raises an error' do expect { parse }.to raise_error(Cucumber::Core::Gherkin::ParseError).with_message(/#{path}.*not gherkin/) end end context 'when valid gherkin is used as a source' do let(:source) { Cucumber::Core::Gherkin::Document.new(path, 'Feature:') } let(:path) { 'path_to/the.feature' } it 'issues a gherkin_source_parsed event' do expect(event_bus).to receive(:gherkin_source_parsed) parse end it 'emits an `:envelope` event for every message produced by Gherkin' do # Only one message emitted, there's no pickles generated expect(event_bus).to receive(:envelope).once parse end end context 'with an empty file' do let(:source) { Cucumber::Core::Gherkin::Document.new(path, '') } let(:path) { 'path_to/the.feature' } it 'passes on no pickles' do expect(receiver).not_to receive(:pickle) parse end end context 'when the Gherkin has a language header' do source do feature(language: 'ja', keyword: '機能') do scenario(keyword: 'シナリオ') end end it 'the pickles have the correct language' do expect(receiver).to receive(:pickle).with(pickle_with_language('ja')) parse end end context 'when the Gherkin produces one pickle' do source do feature do scenario do step 'text' end end end it 'passes on the pickle' do expect(receiver).to receive(:pickle) parse end it 'emits an `:envelope` event containing the pickle' do allow(receiver).to receive(:pickle) # Once for the gherkin document, once with the pickle expect(event_bus).to receive(:envelope).twice parse end end context 'when scenario is inside a rule' do source do feature do rule do scenario name: 'My scenario' end end end it 'passes on the pickle' do expect(receiver).to receive(:pickle) parse end end context 'when example is inside a rule' do source do feature do rule do example name: 'My example' end end end it 'passes on the pickle' do expect(receiver).to receive(:pickle) parse end end context 'when there are multiple rules and scenarios or examples' do source do feature do rule description: 'First rule' do scenario name: 'Do not talk about the fight club' do step 'text' end end rule description: 'Second rule' do example name: 'Do NOT talk about the fight club' do step 'text' end end end end it 'passes on the pickles' do expect(receiver).to receive(:pickle).twice parse end end end cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/gherkin/writer_spec.rb000066400000000000000000000166221514133137400274450ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/gherkin/writer' describe Cucumber::Core::Gherkin::Writer do include described_class it 'generates a uri by default' do source = gherkin { feature } expect(source.uri).to eq('features/test.feature') end it 'allows you to specify a URI' do source = gherkin('features/path/to/my.feature') { feature } expect(source.uri).to eq('features/path/to/my.feature') end it 'generates the feature statement by default' do source = gherkin { feature } expect(source).to eq("Feature:\n") end context 'when a name is provided' do it 'includes the name in the feature statement' do source = gherkin do feature "A Feature\n" end expect(source).to eq("Feature: A Feature\n") end end context 'when a description is provided' do let(:expected) do <<~FEATURE Feature: A Feature This is the description which can span multiple lines. FEATURE end it 'includes the description in the feature statement' do source = gherkin do feature 'A Feature', description: <<~FEATURE This is the description which can span multiple lines. FEATURE end expect(source).to eq(expected) end end context 'when a keyword is provided' do it 'uses the supplied keyword' do source = gherkin do feature 'A Feature', keyword: 'Business Need' end expect(source).to eq("Business Need: A Feature\n") end end context 'when a language is supplied' do it 'inserts a language statement' do source = gherkin do feature language: 'ru' end expect(source).to eq("# language: ru\nFeature:\n") end end context 'when a comment is supplied' do it 'inserts a comment' do source = gherkin do comment 'wow' comment 'great' feature end expect(source.to_s).to eq("# wow\n# great\nFeature:\n") end end context 'with a scenario' do it 'includes the scenario statement' do source = gherkin do feature 'A Feature' do scenario end end expect(source.to_s).to match(/Scenario:/) end context 'when a comment is provided' do let(:expected) do <<~FEATURE Feature: # wow Scenario: FEATURE end it 'includes the comment in the scenario statement' do source = gherkin do feature do comment 'wow' scenario end end expect(source.to_s).to eq(expected) end end context 'when a description is provided' do let(:expected) do <<~FEATURE Feature: Scenario: This is the description which can span multiple lines. FEATURE end it 'includes the description in the scenario statement' do source = gherkin do feature do scenario description: <<~SCENARIO This is the description which can span multiple lines. SCENARIO end end expect(source).to eq(expected) end end context 'with a step' do it 'includes the step statement' do source = gherkin do feature 'A Feature' do scenario do step 'passing' end end end expect(source.to_s).to match(/Given passing\Z/m) end context 'when a docstring is provided' do let(:expected) do <<~FEATURE Feature: Scenario: Given failing """text/plain some text """ FEATURE end it 'includes the content type when provided' do source = gherkin do feature do scenario do step 'failing' do doc_string 'some text', 'text/plain' end end end end expect(source).to eq(expected) end end end end context 'with a background' do let(:expected) do <<~FEATURE Feature: Background: One line, and two.. FEATURE end it 'can have a description' do source = gherkin do feature do background description: "One line,\nand two.." end end expect(source).to eq(expected) end end context 'with a scenario outline' do let(:expected) do <<~FEATURE Feature: Scenario Outline: Doesn't need to be multi-line. FEATURE end it 'can have a description' do source = gherkin do feature do scenario_outline description: "Doesn't need to be multi-line." end end expect(source).to eq(expected) end context 'with an examples table' do let(:expected) do <<~FEATURE Feature: Scenario Outline: Examples: Doesn't need to be multi-line. FEATURE end it 'can have a description' do source = gherkin do feature do scenario_outline do examples description: "Doesn't need to be multi-line." end end end expect(source).to eq(expected) end end end context 'with a feature file with many options' do let(:expected) do <<~FEATURE # language: en # wow @always Feature: Fully featured # cool Background: Given passing Scenario: Given passing # here @first @second Scenario: with doc string # and here Given failing """ I wish I was a little bit taller """ # yay Scenario Outline: eating Given there are cucumbers When I eat cucumbers Then I should have cucumbers # hmmm Examples: | start | eat | left | | 12 | 5 | 7 | | 20 | 5 | 15 | FEATURE end let(:gherkin_document) do gherkin do comment 'wow' feature 'Fully featured', language: 'en', tags: '@always' do comment 'cool' background do step 'passing' end scenario do step 'passing' end comment 'here' scenario 'with doc string', tags: '@first @second' do comment 'and here' step 'failing' do doc_string 'I wish I was a little bit taller' end end comment 'yay' scenario_outline 'eating' do step 'there are cucumbers' step 'I eat cucumbers', keyword: 'When' step 'I should have cucumbers', keyword: 'Then' comment 'hmmm' examples do row 'start', 'eat', 'left' row '12', '5', '7' row '20', '5', '15' end end end end end it 'can generate a complex feature' do expect(gherkin_document.to_s).to eq(expected) end end end cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/report/000077500000000000000000000000001514133137400244475ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/report/summary_spec.rb000066400000000000000000000133431514133137400275070ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/event_bus' require 'cucumber/core/events' require 'cucumber/core/report/summary' require 'cucumber/core/test/result' require 'cucumber/core/test/step' describe Cucumber::Core::Report::Summary do subject(:summary) { described_class.new(event_bus) } let(:event_bus) { Cucumber::Core::EventBus.new(registry) } let(:registry) { Cucumber::Core::Events.registry } let(:passed_result) { Cucumber::Core::Test::Result::Passed.new(duration) } let(:failed_result) { Cucumber::Core::Test::Result::Failed.new(duration, exception) } let(:pending_result) { Cucumber::Core::Test::Result::Pending.new(duration) } let(:skipped_result) { Cucumber::Core::Test::Result::Skipped.new(duration) } let(:undefined_result) { Cucumber::Core::Test::Result::Undefined.new(duration) } let(:duration) { double } let(:exception) { double } describe 'test case summary' do let(:test_case) { double } it 'counts passed test cases' do event_bus.send(:test_case_finished, test_case, passed_result) expect(summary.test_cases.total(:passed)).to eq(1) expect(summary.test_cases.total).to eq(1) end it 'counts failed test cases' do event_bus.send(:test_case_finished, test_case, failed_result) expect(summary.test_cases.total(:failed)).to eq(1) expect(summary.test_cases.total).to eq(1) end it 'counts pending test cases' do event_bus.send(:test_case_finished, test_case, pending_result) expect(summary.test_cases.total(:pending)).to eq(1) expect(summary.test_cases.total).to eq(1) end it 'counts skipped test cases' do event_bus.send(:test_case_finished, test_case, skipped_result) expect(summary.test_cases.total(:skipped)).to eq(1) expect(summary.test_cases.total).to eq(1) end it 'counts undefined test cases' do event_bus.send(:test_case_finished, test_case, undefined_result) expect(summary.test_cases.total(:undefined)).to eq(1) expect(summary.test_cases.total).to eq(1) end it 'handles flaky test cases' do allow(test_case).to receive(:==).and_return(false, true) event_bus.send(:test_case_finished, test_case, failed_result) event_bus.send(:test_case_finished, test_case, passed_result) expect(summary.test_cases.total(:failed)).to eq(0) expect(summary.test_cases.total(:flaky)).to eq(1) expect(summary.test_cases.total).to eq(1) end it 'handles flaky with following skip test cases' do allow(test_case).to receive(:==).and_return(false, true) event_bus.send(:test_case_finished, test_case, failed_result) event_bus.send(:test_case_finished, test_case, skipped_result) expect(summary.test_cases.total(:failed)).to eq(0) expect(summary.test_cases.total(:skipped)).to eq(0) expect(summary.test_cases.total(:flaky)).to eq(1) expect(summary.test_cases.total).to eq(1) end end describe 'test step summary' do context 'with test steps from gherkin steps' do let(:test_step) { instance_double(Cucumber::Core::Test::Step, hook?: false) } it 'counts passed test steps' do event_bus.send(:test_step_finished, test_step, passed_result) expect(summary.test_steps.total(:passed)).to eq(1) expect(summary.test_steps.total).to eq(1) end it 'counts failed test cases' do event_bus.send(:test_step_finished, test_step, failed_result) expect(summary.test_steps.total(:failed)).to eq(1) expect(summary.test_steps.total).to eq(1) end it 'counts pending test cases' do event_bus.send(:test_step_finished, test_step, pending_result) expect(summary.test_steps.total(:pending)).to eq(1) expect(summary.test_steps.total).to eq(1) end it 'counts skipped test cases' do event_bus.send(:test_step_finished, test_step, skipped_result) expect(summary.test_steps.total(:skipped)).to eq(1) expect(summary.test_steps.total).to eq(1) end it 'counts undefined test cases' do event_bus.send(:test_step_finished, test_step, undefined_result) expect(summary.test_steps.total(:undefined)).to eq(1) expect(summary.test_steps.total).to eq(1) end end context 'with test steps not from gherkin steps' do let(:test_step) { instance_double(Cucumber::Core::Test::Step, hook?: true) } it 'ignores test steps not defined by gherkin steps' do event_bus.send(:test_step_finished, test_step, passed_result) expect(summary.test_steps.total).to eq(0) end end end describe '#ok?' do let(:test_case) { double } it 'passed test cases are ok' do event_bus.send(:test_case_finished, test_case, passed_result) expect(summary.ok?).to be true end it 'skipped test cases are ok' do event_bus.send(:test_case_finished, test_case, skipped_result) expect(summary.ok?).to be true end it 'failed test cases are not ok' do event_bus.send(:test_case_finished, test_case, failed_result) expect(summary.ok?).to be false end it 'pending test cases are not ok if strict is configured for pending tests' do event_bus.send(:test_case_finished, test_case, pending_result) expect(summary.ok?).to be true strict = Cucumber::Core::Test::Result::StrictConfiguration.new([:pending]) expect(summary.ok?(strict: strict)).to be false end it 'undefined test cases are not ok if strict is configured for undefined tests' do event_bus.send(:test_case_finished, test_case, undefined_result) expect(summary.ok?).to be true strict = Cucumber::Core::Test::Result::StrictConfiguration.new([:undefined]) expect(summary.ok?(strict: strict)).to be false end end end cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/test/000077500000000000000000000000001514133137400241135ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/test/action/000077500000000000000000000000001514133137400253705ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/test/action/defined_spec.rb000066400000000000000000000075261514133137400303370ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/action' require 'support/duration_matcher' describe Cucumber::Core::Test::Action::Defined do it 'requires a block' do expect { described_class.new } .to raise_error(ArgumentError) .with_message('Passing a block to execute the action is mandatory.') end describe '#location' do context 'with a location passed to the constructor' do subject(:action) { described_class.new(location) { :no_op } } let(:location) { double } it 'returns the location passed to the constructor' do expect(action.location).to eq(location) end end context 'without location passed to the constructor' do subject(:action) { described_class.new(&block) } let(:block) { proc {} } it 'returns the location of the block passed to the constructor' do expect(action.location).to eq(Cucumber::Core::Test::Location.new(*block.source_location[0..1])) end end end describe '#execute' do it 'executes the block passed to the constructor' do executed = false action = described_class.new { executed = true } action.execute expect(executed).to be_truthy end it "returns a passed result if the block doesn't fail" do action = described_class.new { :no_op } expect(action.execute).to be_passed end it 'returns a failed result when the block raises an error' do exception = StandardError.new action = described_class.new { raise exception } result = action.execute expect(result).to be_failed expect(result.exception).to eq exception end it 'yields the args passed to #execute to the block' do args = [double, double] args_spy = nil action = described_class.new { |arg1, arg2| args_spy = [arg1, arg2] } action.execute(*args) expect(args_spy).to eq args end it 'returns a pending result if a Result::Pending error is raised' do exception = Cucumber::Core::Test::Result::Pending.new('TODO') action = described_class.new { raise exception } result = action.execute expect(result).to be_pending expect(result.message).to eq 'TODO' end it 'returns a skipped result if a Result::Skipped error is raised' do exception = Cucumber::Core::Test::Result::Skipped.new('Not working right now') action = described_class.new { raise exception } result = action.execute expect(result).to be_skipped expect(result.message).to eq 'Not working right now' end it 'returns an undefined result if a Result::Undefined error is raised' do exception = Cucumber::Core::Test::Result::Undefined.new('new step') action = described_class.new { raise exception } result = action.execute expect(result).to be_undefined expect(result.message).to eq 'new step' end describe '#duration' do before do allow(Cucumber::Core::Test::Timer::MonotonicTime).to receive(:time_in_nanoseconds).and_return(525_702_744_080_000, 525_702_744_080_001) end it 'records the nanoseconds duration of the execution on the result' do action = described_class.new { :no_op } res = action.execute duration = res.duration expect(duration).to be_duration(1) end it 'records the duration of a failed execution' do action = described_class.new { raise StandardError } duration = action.execute.duration expect(duration).to be_duration(1) end end end describe '#skip' do it 'does not execute the block' do executed = false action = described_class.new { executed = true } action.skip expect(executed).to be_falsey end it 'returns a skipped result' do action = described_class.new { :no_op } expect(action.skip).to be_skipped end end end cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/test/action/undefined_spec.rb000066400000000000000000000012161514133137400306700ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/action' require 'support/duration_matcher' describe Cucumber::Core::Test::Action::Undefined do let(:location) { double } let(:action) { described_class.new(location) } let(:test_step) { double } describe '#location' do it 'returns the location passed to the constructor' do expect(action.location).to be location end end describe '#execute' do it 'returns an undefined result' do expect(action.execute).to be_undefined end end describe '#skip' do it 'returns an undefined result' do expect(action.skip).to be_undefined end end end cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/test/case_spec.rb000066400000000000000000000104771514133137400263760ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core' require 'cucumber/core/gherkin/writer' require 'cucumber/core/platform' require 'cucumber/core/test/case' describe Cucumber::Core::Test::Case do include Cucumber::Core include Cucumber::Core::Gherkin::Writer let(:id) { double } let(:name) { double } let(:location) { double } let(:parent_locations) { double } let(:tags) { double } let(:language) { double } let(:test_case) { described_class.new(id, name, test_steps, location, parent_locations, tags, language) } let(:test_steps) { [double, double] } describe '#describe_to' do let(:visitor) { double } let(:args) { double } it 'describes itself to a visitor' do expect(visitor).to receive(:test_case).with(test_case, args) test_case.describe_to(visitor, args) end it 'asks each test_step to describe themselves to the visitor' do expect(test_steps).to all receive(:describe_to).with(visitor, args) allow(visitor).to receive(:test_case).and_yield(visitor) test_case.describe_to(visitor, args) end it 'describes around hooks in order' do allow(visitor).to receive(:test_case).and_yield(visitor) first_hook = double second_hook = double expect(first_hook).to receive(:describe_to).ordered.and_yield expect(second_hook).to receive(:describe_to).ordered.and_yield around_hooks = [first_hook, second_hook] described_class.new(id, name, [], location, parent_locations, tags, language, around_hooks).describe_to(visitor, double) end end describe '#name' do it 'the name is passed when creating the test case' do expect(test_case.name).to eq(name) end end describe '#location' do it 'the location is passed when creating the test case' do expect(test_case.location).to eq(location) end end describe '#tags' do it 'the tags are passed when creating the test case' do expect(test_case.tags).to eq(tags) end end describe '#match_tags?' do let(:tags) { %w[@a @b @c].map { |value| Cucumber::Core::Test::Tag.new(location, value) } } it 'matches tags using and tag expression logic' do expect(test_case).to be_match_tags(['@a and @b']) end it 'matches tags using or tag expression logic' do expect(test_case).to be_match_tags(['@a or @d']) end it 'matches tags using not tag expression logic' do expect(test_case).to be_match_tags(['not @d']) end it 'fails when a tag does not match the tag expression' do expect(test_case).not_to be_match_tags(['@a and @d']) end it 'matches multiple tag expressions' do expect(test_case).to be_match_tags(['@a and @b', 'not @d']) end it 'fails when it does not match multiple tag expressions' do expect(test_case).not_to be_match_tags(['@a and @b', 'not @c']) end end describe '#match_name?' do let(:name) { 'scenario' } it 'matches names against regexp' do expect(test_case).to be_match_name(/scenario/) end end describe '#language' do let(:language) { 'en-pirate' } it 'the language is passed when creating the test case' do expect(test_case.language).to eq('en-pirate') end end describe 'equality' do let(:feature_code) do gherkin('features/foo.feature') do feature do scenario do step 'text' end end end end let(:test_case_instances) { [] } it 'creates multiple test cases' do receiver = double.as_null_object allow(receiver).to receive(:test_case) { |test_case| test_case_instances << test_case } 2.times { compile([feature_code], receiver) } expect(test_case_instances.length).to eq(2) end it 'does not distinguish between identical test cases' do receiver = double.as_null_object allow(receiver).to receive(:test_case) { |test_case| test_case_instances << test_case } 2.times { compile([feature_code], receiver) } expect(test_case_instances.uniq.length).to eq(1) end it 'is equal to another test case at the same location' do receiver = double.as_null_object allow(receiver).to receive(:test_case) { |test_case| test_case_instances << test_case } 2.times { compile([feature_code], receiver) } expect(test_case_instances[0]).to eq(test_case_instances[1]) end end end cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/test/data_table_spec.rb000066400000000000000000000035601514133137400275360ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/data_table' module Cucumber module Core module Test describe DataTable do before do @table = described_class.new([ %w[one four seven], %w[4444 55555 666666] ]) end describe '#data_table?' do let(:table) { described_class.new([[1, 2], [3, 4]]) } it 'returns true' do expect(table).to be_data_table end end describe '#doc_string' do let(:table) { described_class.new([[1, 2], [3, 4]]) } it 'returns false' do expect(table).not_to be_doc_string end end describe '#map' do let(:table) { described_class.new([%w[foo bar], %w[1 2]]) } it 'yields the contents of each cell to the block' do expect { |b| table.map(&b) }.to yield_successive_args('foo', 'bar', '1', '2') end it 'returns a new table with the cells modified by the block' do expect(table.map { |cell| "*#{cell}*" }).to eq described_class.new([%w[*foo* *bar*], %w[*1* *2*]]) end end describe '#transpose' do before do @table = described_class.new([ %w[one 1111], %w[two 22222] ]) end it 'transposes the table' do transposed = described_class.new([ %w[one two], %w[1111 22222] ]) expect(@table.transpose).to eq(transposed) end end end end end end cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/test/doc_string_spec.rb000066400000000000000000000056561514133137400276210ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/doc_string' describe Cucumber::Core::Test::DocString do let(:doc_string) { described_class.new(content, content_type) } describe '#data_table?' do let(:doc_string) { described_class.new('test', 'text/plain') } it 'returns false' do expect(doc_string).not_to be_data_table end end describe '#doc_string' do let(:doc_string) { described_class.new('test', 'text/plain') } it 'returns true' do expect(doc_string).to be_doc_string end end describe '#map' do let(:content) { 'original content' } let(:content_type) { double } it 'yields with the content' do expect { |b| doc_string.map(&b) }.to yield_with_args(content) end it 'returns a new docstring with new content' do expect(doc_string.map { 'foo' }.content).to eq('foo') end it 'raises an error if no block is given' do expect { doc_string.map }.to raise_error ArgumentError end end describe 'DocString equality properties' do let(:content) { 'foo' } let(:content_type) { 'text/plain' } it 'is equal to another DocString with the same content and content_type' do expect(doc_string).to eq(described_class.new(content, content_type)) end it 'is not equal to another DocString with different content' do expect(doc_string).not_to eq(described_class.new('bar', content_type)) end it 'is not equal to another DocString with different content_type' do expect(doc_string).not_to eq(described_class.new(content, 'text/html')) end it 'is equal to a string with the same content' do expect(doc_string).to eq('foo') end it 'returns false when compared with something odd' do expect(doc_string).not_to eq(5) end end describe 'DocString has String-like properties' do let(:content) { String.new('content') } let(:content_type) { 'text/plain' } it 'delegates #encoding to the content string' do content.force_encoding('us-ascii') expect(doc_string.encoding).to eq(Encoding.find('US-ASCII')) end it 'allows implicit conversion to a String' do expect("I have a string of #{doc_string}").to eq('I have a string of content') end it 'allows explicit conversion to a String' do expect(doc_string.to_s).to eq('content') end it 'delegates #gsub to the content string' do expect(doc_string.gsub('n', '_')).to eq('co_te_t') end it 'delegates #split to the content string' do expect(doc_string.split('n')).to eq(%w[co te t]) end end describe '#inspect' do let(:content_type) { 'text/plain' } let(:doc_string) { described_class.new('some text', content_type) } it 'provides a useful inspect method' do expect(doc_string.inspect).to eq(<<~DOC_STRING.chomp) # DOC_STRING end end end cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/test/empty_multiline_argument_spec.rb000066400000000000000000000004611514133137400325750ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/empty_multiline_argument' describe Cucumber::Core::Test::EmptyMultilineArgument do describe '#data_table?' do it { is_expected.not_to be_data_table } end describe '#doc_string' do it { is_expected.not_to be_doc_string } end end cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/test/location_spec.rb000066400000000000000000000122641514133137400272670ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/location' module Cucumber module Core module Test RSpec::Matchers.define :be_included_in do |expected| match do |actual| expected.include? actual end end describe Location do let(:line) { 12 } let(:file) { 'foo.feature' } describe 'equality' do it 'is equal to another Location on the same line of the same file' do one_location = described_class.new(file, line) another_location = described_class.new(file, line) expect(one_location).to eq another_location end it 'is not equal to a wild card of the same file' do expect(described_class.new(file, line)).not_to eq described_class.new(file) end context 'with a collection of locations' do it 'behave as expected with uniq' do unique_collection = [described_class.new(file, line), described_class.new(file, line)].uniq expect(unique_collection).to eq([described_class.new(file, line)]) end end end describe '#to_s' do it 'is file:line for a precise location' do expect(described_class.new('foo.feature', 12).to_s).to eq 'foo.feature:12' end it 'is file for a wildcard location' do expect(described_class.new('foo.feature').to_s).to eq 'foo.feature' end it 'is file:first_line..last_line for a ranged location' do expect(described_class.new('foo.feature', 13..19).to_s).to eq 'foo.feature:13..19' end it 'is file:line:line:line for an arbitrary set of lines' do expect(described_class.new('foo.feature', [1, 3, 5]).to_s).to eq 'foo.feature:1:3:5' end end describe '#match?' do let(:matching) { described_class.new(file, line) } let(:same_file_other_line) { described_class.new(file, double) } let(:not_matching) { described_class.new(other_file, line) } let(:other_file) { double } context 'with a precise location' do let(:precise) { described_class.new(file, line) } it 'matches a precise location of the same file and line' do expect(matching).to be_match(precise) end it 'does not match a precise location on a different line in the same file' do expect(matching).not_to be_match(same_file_other_line) end end context 'with a wildcard' do let(:wildcard) { described_class.new(file) } it 'matches any location with the same filename' do expect(wildcard).to be_match(matching) end it 'is matched by any location of the same file' do expect(matching).to be_match(wildcard) end it 'does not match a location in a different file' do expect(wildcard).not_to be_match(not_matching) end end end describe '.from_source_location' do context 'when the location is in the tree below pwd' do it 'creates a relative path from pwd' do expect(described_class.from_source_location("#{Dir.pwd}/path/file.rb", 1).file).to eq('path/file.rb') end end context 'when the location is in an installed gem' do it 'creates a relative path from the gem directory' do expect(described_class.from_source_location('/path/gems/gem-name/path/file.rb', 1).file).to eq('gem-name/path/file.rb') end end context 'when provided extraneous ruby 3.5+ information about the proc location' do it 'only stores the first 2 arguments' do expect(described_class.from_source_location('/path/gems/gem-name/path/file.rb', 7, :foo, :bar, :baz).to_s).to eq('gem-name/path/file.rb:7') end end context 'when the location is neither below pwd nor in an installed gem' do it 'uses the absolute path to the file' do # Use File.expand on expectation to ensure tests work on multiple platform. # On Windows, it will return "C:/path/file.rb" as an absolute path while it will return "/path/file.rb" on Linux. expect(described_class.from_source_location('/path/file.rb', 1).file).to eq(File.expand_path('/path/file.rb')) end end end describe '.from_file_colon_line' do it 'handles also Windows paths' do # NOTE: running this test on Windows will produce "c:/path/file.rb", but "c:\path\file.rb" on Linux. expect(described_class.from_file_colon_line('c:\\path\\file.rb:123').file).to match(/c:(\\|\/)path(\\|\/)file.rb/) end end describe '.of_caller' do it 'use the location of the caller' do expect(described_class.of_caller.to_s).to be_included_in caller[0] end it 'uses the location of the nth caller' do expect(described_class.of_caller(1).to_s).to be_included_in caller[1] end end end end end end cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/test/locations_filter_spec.rb000066400000000000000000000362541514133137400310240ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/gherkin/writer' require 'cucumber/core' require 'cucumber/core/test/filters/locations_filter' require 'timeout' require 'cucumber/core/test/location' describe Cucumber::Core::Test::LocationsFilter do include Cucumber::Core::Gherkin::Writer include Cucumber::Core let(:spy_receiver) do Class.new do def test_case(test_case) test_cases << test_case end def done; end def test_case_locations test_cases.map(&:location) end def test_cases @test_cases ||= [] end end end let(:receiver) { spy_receiver.new } let(:doc) do gherkin('features/test.feature') do feature do scenario 'x' do step 'step for scenario x' end scenario 'y' do step 'step for scenario y' end end end end it 'sorts by the given locations' do locations = [ Cucumber::Core::Test::Location.new('features/test.feature', 6), Cucumber::Core::Test::Location.new('features/test.feature', 3) ] filter = described_class.new(locations) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq(locations) end it 'works with wildcard locations' do locations = [Cucumber::Core::Test::Location.new('features/test.feature')] filter = described_class.new(locations) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq( [ Cucumber::Core::Test::Location.new('features/test.feature', 3), Cucumber::Core::Test::Location.new('features/test.feature', 6) ] ) end it "filters out scenarios that don't match" do locations = [Cucumber::Core::Test::Location.new('features/test.feature', 3)] filter = described_class.new(locations) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq(locations) end describe 'matching location' do let(:file) { 'features/path/to/the.feature' } let(:test_cases) do receiver = double.as_null_object result = [] allow(receiver).to receive(:test_case) { |test_case| result << test_case } compile([doc], receiver) result end context 'with a scenario' do let(:doc) do Cucumber::Core::Gherkin::Document.new(file, <<-FEATURE) Feature: Background: Given background Scenario: one Given one a # comment @tags Scenario: two Given two a And two b Scenario: three Given three b Scenario: with docstring Given a docstring """ this is a docstring """ Scenario: with a table Given a table | a | b | | 1 | 2 | | 3 | 4 | Rule: A rule with a background Background: A background rule Given background Scenario: with a rule and background Given a rule with a background Scenario: another with a rule and background Given a rule with a background Rule: A rule without a background Scenario: with a rule and no background Given a rule without a background Scenario: another with a rule and no background Given a rule without a background FEATURE end def test_case_named(name) test_cases.detect { |tc| tc.name == name } end it 'matches the feature location to all scenarios' do location = Cucumber::Core::Test::Location.new(file, 1) filter = described_class.new([location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq(test_cases.map(&:location)) end it 'matches the feature background location to all scenarios' do location = Cucumber::Core::Test::Location.new(file, 2) filter = described_class.new([location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq(test_cases.map(&:location)) end it 'matches a feature background step location to all scenarios' do location = Cucumber::Core::Test::Location.new(file, 3) filter = described_class.new([location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq(test_cases.map(&:location)) end it "matches a rule location (containing a background) to all of the rule's scenarios" do location = Cucumber::Core::Test::Location.new(file, 29) filter = described_class.new([location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq( [ test_case_named('with a rule and background').location, test_case_named('another with a rule and background').location ] ) end it "matches the rule background location to all of the rule's scenarios" do location = Cucumber::Core::Test::Location.new(file, 30) filter = described_class.new([location]) compile [doc], receiver, [filter] expect(receiver.test_case_locations).to eq( [ test_case_named('with a rule and background').location, test_case_named('another with a rule and background').location ] ) end it "matches a rule background step location to all of the rule's scenarios" do location = Cucumber::Core::Test::Location.new(file, 31) filter = described_class.new([location]) compile [doc], receiver, [filter] expect(receiver.test_case_locations).to eq( [ test_case_named('with a rule and background').location, test_case_named('another with a rule and background').location ] ) end it "matches a rule location (without a background) to all of the rule's scenarios" do location = Cucumber::Core::Test::Location.new(file, 39) filter = described_class.new([location]) compile [doc], receiver, [filter] expect(receiver.test_case_locations).to eq( [ test_case_named('with a rule and no background').location, test_case_named('another with a rule and no background').location ] ) end it 'matches a scenario location to the scenario' do location = test_case_named('two').location filter = described_class.new([location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq([test_case_named('two').location]) end it 'matches multiple locations, ignoring whitespace locations' do scenario_location = Cucumber::Core::Test::Location.new(file, 5) another_scenario_location = Cucumber::Core::Test::Location.new(file, 10) whitespace_location = Cucumber::Core::Test::Location.new(file, 7) filter = described_class.new([scenario_location, another_scenario_location, whitespace_location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq( [ test_case_named('one').location, test_case_named('two').location ] ) end it 'matches the first scenario step location to the scenario' do location = Cucumber::Core::Test::Location.new(file, 11) filter = described_class.new([location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq([test_case_named('two').location]) end it 'matches the last scenario step location to the scenario' do location = Cucumber::Core::Test::Location.new(file, 12) filter = described_class.new([location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq([test_case_named('two').location]) end it "matches a scenario's tag location to the scenario" do location = Cucumber::Core::Test::Location.new(file, 9) filter = described_class.new([location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq([test_case_named('two').location]) end it 'does not match a whitespace location to any scenarios' do location = Cucumber::Core::Test::Location.new(file, 13) filter = described_class.new([location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq([]) end context 'with a docstring' do it 'matches a location at the start the docstring' do location = Cucumber::Core::Test::Location.new(file, 17) filter = described_class.new([location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq([test_case_named('with docstring').location]) end it 'matches a location in the middle of the docstring' do location = Cucumber::Core::Test::Location.new(file, 18) filter = described_class.new([location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq([test_case_named('with docstring').location]) end it 'matches a location at the end of the docstring' do location = Cucumber::Core::Test::Location.new(file, 19) filter = described_class.new([location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq([test_case_named('with docstring').location]) end end context 'with a table' do let(:test_case) { test_cases.detect { |tc| tc.name == 'with a table' } } let(:starting_location) { Cucumber::Core::Test::Location.new(file, 23) } let(:midpoint_location) { Cucumber::Core::Test::Location.new(file, 24) } let(:ending_location) { Cucumber::Core::Test::Location.new(file, 25) } it 'matches a location at the start of the table' do filter = described_class.new([starting_location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq([test_case_named('with a table').location]) end it 'matches a location at the middle of the table' do filter = described_class.new([midpoint_location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq([test_case_named('with a table').location]) end it 'matches a location at the end of the table' do filter = described_class.new([ending_location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq([test_case_named('with a table').location]) end end context 'with duplicate locations in the filter' do it 'matches each test case only once' do location_tc_two = test_case_named('two').location location_tc_one = test_case_named('one').location location_last_step_tc_two = Cucumber::Core::Test::Location.new(file, 12) filter = described_class.new([location_tc_two, location_tc_one, location_last_step_tc_two]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq([test_case_named('two').location, test_case_named('one').location]) end end end context 'with a scenario outline' do let(:doc) do Cucumber::Core::Gherkin::Document.new(file, <<-FEATURE) Feature: Scenario: one Given one a # comment on line 6 @tag-on-line-7 Scenario Outline: two Given two a And two """ docstring """ # comment on line 15 @tag-on-line-16 Examples: x1 | arg | | b | Examples: x2 | arg | | c | | d | Scenario: three Given three b FEATURE end let(:test_case) { test_cases.detect { |tc| tc.name == 'two b' } } let(:feature_location) { Cucumber::Core::Test::Location.new(file, 1) } let(:row_location) { Cucumber::Core::Test::Location.new(file, 19) } let(:start_of_outline_location) { Cucumber::Core::Test::Location.new(file, 8) } let(:middle_of_outline_location) { Cucumber::Core::Test::Location.new(file, 10) } let(:outline_tags_location) { Cucumber::Core::Test::Location.new(file, 7) } it 'matches the feature line to all scenarios' do filter = described_class.new([feature_location]) compile [doc], receiver, [filter] expect(receiver.test_case_locations).to eq(test_cases.map(&:location)) end it 'matches row location to the test case of the row' do filter = described_class.new([row_location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq([test_case.location]) end it 'matches outline location with the all test cases of all the tables' do filter = described_class.new([start_of_outline_location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations.map(&:line)).to eq([19, 23, 24]) end it 'matches a location on a step of the scenario outline with all test cases of all the tables' do filter = described_class.new([middle_of_outline_location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations.map(&:line)).to eq([19, 23, 24]) end it "matches a location on the scenario outline's tags with all test cases of all the tables" do filter = described_class.new([outline_tags_location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations.map(&:line)).to eq([19, 23, 24]) end it "doesn't match the location of the examples line" do location = Cucumber::Core::Test::Location.new(file, 17) filter = described_class.new([location]) compile([doc], receiver, [filter]) expect(receiver.test_case_locations).to eq([]) end end end context 'when under extreme load', :slow do let(:number_of_features) { 50 } let(:number_of_scenarios_per_feature) { 50 } let(:docs) do 50.times.map do |feature_number| gherkin("features/test_#{feature_number}.feature") do feature do 50.times.each do |scenario_number| scenario "scenario #{scenario_number}" do step 'text' end end end end end end let(:locations) do 50.times.map do |feature_number| 50.times.map do |scenario_number| scenario_line = 3 + (scenario_number * 3) Cucumber::Core::Test::Location.new("features/test_#{feature_number}.feature", scenario_line) end end.flatten end let(:max_duration_ms) { defined?(JRUBY_VERSION) ? 25_000 : 10_000 } it 'filters all test cases within an appropriate time' do filter = described_class.new(locations) Timeout.timeout(max_duration_ms / 1000.0) do compile(docs, receiver, [filter]) end expect(receiver.test_cases.length).to eq(number_of_features * number_of_scenarios_per_feature) end end end cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/test/result_spec.rb000066400000000000000000000521501514133137400267730ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/result' require 'support/duration_matcher' module Cucumber module Core module Test describe Result do let(:visitor) { double } let(:args) { double } describe Result::Passed do subject(:result) { described_class.new(duration) } let(:duration) { Result::Duration.new(1 * 1000 * 1000) } before do allow(visitor).to receive(:duration) allow(visitor).to receive(:passed) end it 'is described as a passing test' do expect(visitor).to receive(:passed).with(args) result.describe_to(visitor, args) end it 'converts to a string' do expect(result.to_s).to eq('✓') end it 'converts to a `Cucumber::Message::TestResult`' do expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::PASSED) end it 'has a duration' do expect(result.duration).to eq(duration) end it 'does nothing when appending the backtrace' do expect(result.with_appended_backtrace(double)).to eq(result) end it 'does nothing when filtering the backtrace' do expect(result.with_filtered_backtrace(double)).to eq(result) end it { expect(result.to_sym).to eq(:passed) } it { expect(result).to be_passed } it { expect(result).not_to be_failed } it { expect(result).not_to be_ambiguous } it { expect(result).not_to be_undefined } it { expect(result).not_to be_unknown } it { expect(result).not_to be_skipped } it { expect(result).not_to be_flaky } it { expect(result).to be_ok } end describe Result::Failed do subject(:result) { described_class.new(duration, exception) } let(:duration) { Result::Duration.new(1 * 1000 * 1000) } let(:exception) { StandardError.new('error message') } before do allow(visitor).to receive(:failed) allow(visitor).to receive(:duration) allow(visitor).to receive(:exception) end it 'is described as a failing test' do expect(visitor).to receive(:failed).with(args) result.describe_to(visitor, args) end it 'contains an exception message' do expect(visitor).to receive(:exception).with(exception, args) result.describe_to(visitor, args) end it 'has a duration' do expect(result.duration).to eq(duration) end it 'converts to a Cucumber::Message::TestResult' do expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::FAILED) end it 'requires both constructor arguments' do expect { described_class.new }.to raise_error(ArgumentError) expect { described_class.new(duration) }.to raise_error(ArgumentError) end it 'does nothing if step has no backtrace line' do result.exception.set_backtrace('exception backtrace') step = 'does not respond_to?(:backtrace_line)' expect(result.with_appended_backtrace(step).exception.backtrace).to eq(['exception backtrace']) end it 'appends the backtrace line of the step' do result.exception.set_backtrace('exception backtrace') step = double allow(step).to receive(:backtrace_line).and_return('step_line') expect(result.with_appended_backtrace(step).exception.backtrace).to eq(['exception backtrace', 'step_line']) end it 'applies filters to the exception' do filter_class = double filter = double filtered_exception = double allow(filter_class).to receive(:new).with(result.exception).and_return(filter) allow(filter).to receive(:exception).and_return(filtered_exception) expect(result.with_filtered_backtrace(filter_class).exception).to eq(filtered_exception) end it { expect(result.to_sym).to eq(:failed) } it { expect(result).not_to be_passed } it { expect(result).to be_failed } it { expect(result).not_to be_ambiguous } it { expect(result).not_to be_undefined } it { expect(result).not_to be_unknown } it { expect(result).not_to be_skipped } it { expect(result).not_to be_flaky } it { expect(result).not_to be_ok } end describe Result::Unknown do subject(:result) { described_class.new } it 'defines a with_filtered_backtrace method' do expect(result.with_filtered_backtrace(double)).to eq(result) end it { expect(result.to_sym).to eq(:unknown) } it { expect(result).not_to be_passed } it { expect(result).not_to be_failed } it { expect(result).not_to be_ambiguous } it { expect(result).not_to be_undefined } it { expect(result).to be_unknown } it { expect(result).not_to be_skipped } it { expect(result).not_to be_flaky } it 'converts to a Cucumber::Message::TestResult' do expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::UNKNOWN) end end describe Result::Raisable do context 'with or without backtrace' do subject(:result) { described_class.new } it 'does nothing if step has no backtrace line' do step = 'does not respond_to?(:backtrace_line)' expect(result.with_appended_backtrace(step).backtrace).to be_nil end end context 'without backtrace' do subject(:result) { described_class.new } it 'set the backtrace to the backtrace line of the step' do step = double allow(step).to receive(:backtrace_line).and_return('step_line') expect(result.with_appended_backtrace(step).backtrace).to eq(['step_line']) end it 'does nothing when filtering the backtrace' do expect(result.with_filtered_backtrace(double)).to eq(result) end end context 'with backtrace' do subject(:result) { described_class.new('message', 0, 'backtrace') } it 'appends the backtrace line of the step' do step = double allow(step).to receive(:backtrace_line).and_return('step_line') expect(result.with_appended_backtrace(step).backtrace).to eq(%w[backtrace step_line]) end it 'apply filters to the backtrace' do filter_class = double filter = double filtered_result = double allow(filter_class).to receive(:new).with(result.exception).and_return(filter) allow(filter).to receive(:exception).and_return(filtered_result) expect(result.with_filtered_backtrace(filter_class)).to eq(filtered_result) end end end describe Result::Ambiguous do subject(:result) { described_class.new } it 'describes itself to a visitor' do expect(visitor).to receive(:ambiguous).with(args) expect(visitor).to receive(:duration).with(an_unknown_duration, args) result.describe_to(visitor, args) end it 'converts to a Cucumber::Message::TestResult' do expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::AMBIGUOUS) end it { expect(result.to_sym).to eq(:ambiguous) } it { expect(result).not_to be_passed } it { expect(result).not_to be_failed } it { expect(result).to be_ambiguous } it { expect(result).not_to be_undefined } it { expect(result).not_to be_unknown } it { expect(result).not_to be_skipped } it { expect(result).not_to be_flaky } it { expect(result).not_to be_ok } end describe Result::Undefined do subject(:result) { described_class.new } let(:undefined_strictness) { Result::StrictConfiguration.new([:undefined]) } it 'describes itself to a visitor' do expect(visitor).to receive(:undefined).with(args) expect(visitor).to receive(:duration).with(an_unknown_duration, args) result.describe_to(visitor, args) end it 'converts to a Cucumber::Message::TestResult' do expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::UNDEFINED) end it { expect(result.to_sym).to eq(:undefined) } it { expect(result).not_to be_passed } it { expect(result).not_to be_failed } it { expect(result).not_to be_ambiguous } it { expect(result).to be_undefined } it { expect(result).not_to be_unknown } it { expect(result).not_to be_skipped } it { expect(result).not_to be_flaky } it { expect(result).to be_ok } it { expect(result).not_to be_ok(strict: undefined_strictness) } end describe Result::Skipped do subject(:result) { described_class.new } it 'describes itself to a visitor' do expect(visitor).to receive(:skipped).with(args) expect(visitor).to receive(:duration).with(an_unknown_duration, args) result.describe_to(visitor, args) end it 'converts to a Cucumber::Message::TestResult' do expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::SKIPPED) end it { expect(result.to_sym).to eq(:skipped) } it { expect(result).not_to be_passed } it { expect(result).not_to be_failed } it { expect(result).not_to be_ambiguous } it { expect(result).not_to be_undefined } it { expect(result).not_to be_unknown } it { expect(result).to be_skipped } it { expect(result).not_to be_flaky } it { expect(result).to be_ok } end describe Result::Pending do subject(:result) { described_class.new } let(:strict_configuration) { Result::StrictConfiguration.new([:pending]) } it 'describes itself to a visitor' do expect(visitor).to receive(:pending).with(result, args) expect(visitor).to receive(:duration).with(an_unknown_duration, args) result.describe_to(visitor, args) end it 'converts to a Cucumber::Message::TestResult' do expect(result.to_message.status).to eq(Cucumber::Messages::TestStepResultStatus::PENDING) end it { expect(result.to_sym).to eq(:pending) } it { expect(result).not_to be_passed } it { expect(result).not_to be_failed } it { expect(result).not_to be_ambiguous } it { expect(result).not_to be_undefined } it { expect(result).not_to be_unknown } it { expect(result).not_to be_skipped } it { expect(result).not_to be_flaky } it { expect(result).to be_ok } it { expect(result).not_to be_ok(strict: strict_configuration) } end describe Result::Flaky do it { expect(described_class).to be_ok(strict: false) } it { expect(described_class).not_to be_ok(strict: true) } end describe Result::StrictConfiguration do subject(:strict_configuration) { described_class.new } describe '#set_strict' do context 'without a type argument' do it 'can set undefined to be strict' do strict_configuration.set_strict(true) expect(strict_configuration).to be_strict(:undefined) end it 'can set pending to be strict' do strict_configuration.set_strict(true) expect(strict_configuration).to be_strict(:pending) end it 'can set flaky to be strict' do strict_configuration.set_strict(true) expect(strict_configuration).to be_strict(:flaky) end it 'can set undefined not to be strict' do strict_configuration.set_strict(false) expect(strict_configuration).not_to be_strict(:undefined) end it 'can set pending not to be strict' do strict_configuration.set_strict(false) expect(strict_configuration).not_to be_strict(:pending) end it 'can set flaky not to be strict' do strict_configuration.set_strict(false) expect(strict_configuration).not_to be_strict(:flaky) end end context 'with a type argument' do it 'can set undefined only to be strict' do strict_configuration.set_strict(true, :undefined) expect(strict_configuration).to be_strict(:undefined) end it 'can set pending only to be strict' do strict_configuration.set_strict(true, :pending) expect(strict_configuration).to be_strict(:pending) end it 'can set flaky only to be strict' do strict_configuration.set_strict(true, :flaky) expect(strict_configuration).to be_strict(:flaky) end it 'can set undefined only not to be strict' do strict_configuration.set_strict(false, :undefined) expect(strict_configuration).not_to be_strict(:undefined) end it 'can set pending only not to be strict' do strict_configuration.set_strict(false, :pending) expect(strict_configuration).not_to be_strict(:pending) end it 'can set flaky only not to be strict' do strict_configuration.set_strict(false, :flaky) expect(strict_configuration).not_to be_strict(:flaky) end end end describe '#strict?' do context 'without a type argument' do it 'returns true if any result type is set to strict' do strict_configuration.set_strict(false, :pending) expect(strict_configuration).not_to be_strict strict_configuration.set_strict(true, :flaky) expect(strict_configuration).to be_strict end end context 'with a type argument' do it 'returns true if the specified result type is set to strict' do strict_configuration.set_strict(false, :pending) strict_configuration.set_strict(true, :flaky) expect(strict_configuration).not_to be_strict(:undefined) expect(strict_configuration).not_to be_strict(:pending) expect(strict_configuration).to be_strict(:flaky) end end end describe '#merge!' do let(:merged_configuration) { described_class.new } before do strict_configuration.set_strict(false, :undefined) strict_configuration.set_strict(false, :pending) strict_configuration.set_strict(true, :flaky) merged_configuration.set_strict(true, :pending) merged_configuration.set_strict(false, :flaky) strict_configuration.merge!(merged_configuration) end it 'sets the not default values from the argument accordingly' do expect(strict_configuration).not_to be_strict(:undefined) expect(strict_configuration).to be_strict(:pending) expect(strict_configuration).not_to be_strict(:flaky) end end end describe Result::Summary do let(:summary) { described_class.new } let(:failed) { Result::Failed.new(Result::Duration.new(10), exception) } let(:passed) { Result::Passed.new(Result::Duration.new(11)) } let(:skipped) { Result::Skipped.new } let(:unknown) { Result::Unknown.new } let(:pending) { Result::Pending.new } let(:undefined) { Result::Undefined.new } let(:exception) { StandardError.new } it 'counts failed results' do failed.describe_to(summary) expect(summary.total_failed).to eq(1) expect(summary.total(:failed)).to eq(1) expect(summary.total).to eq(1) end it 'counts passed results' do passed.describe_to(summary) expect(summary.total_passed).to eq(1) expect(summary.total(:passed)).to eq(1) expect(summary.total).to eq(1) end it 'counts skipped results' do skipped.describe_to(summary) expect(summary.total_skipped).to eq(1) expect(summary.total(:skipped)).to eq(1) expect(summary.total).to eq(1) end it 'counts undefined results' do undefined.describe_to(summary) expect(summary.total_undefined).to eq(1) expect(summary.total(:undefined)).to eq(1) expect(summary.total).to eq(1) end it 'counts arbitrary raisable results' do flickering = Class.new(Result::Raisable) do def describe_to(visitor, *args) visitor.flickering(*args) end end flickering.new.describe_to(summary) expect(summary.total_flickering).to eq(1) expect(summary.total(:flickering)).to eq(1) expect(summary.total).to eq(1) end it 'returns zero for a status where no messages have been received' do expect(summary.total_passed).to eq(0) expect(summary.total(:passed)).to eq(0) expect(summary.total_ponies).to eq(0) expect(summary.total(:ponies)).to eq(0) end it "doesn't count unknown results" do unknown.describe_to(summary) expect(summary.total).to eq(0) end it 'counts combinations' do [passed, passed, failed, skipped, undefined].each { |result| result.describe_to(summary) } expect(summary.total).to eq(5) expect(summary.total_passed).to eq(2) expect(summary.total_failed).to eq(1) expect(summary.total_skipped).to eq(1) expect(summary.total_undefined).to eq(1) end it 'records durations' do [passed, failed].each { |result| result.describe_to(summary) } expect(summary.durations[0]).to be_duration(11) expect(summary.durations[1]).to be_duration(10) end it 'records exceptions' do [passed, failed].each { |result| result.describe_to(summary) } expect(summary.exceptions).to eq([exception]) end describe '#ok?' do it 'passed result is ok' do passed.describe_to(summary) expect(summary.ok?).to be true end it 'skipped result is ok' do skipped.describe_to(summary) expect(summary.ok?).to be true end it 'failed result is not ok' do failed.describe_to(summary) expect(summary.ok?).to be false end it 'pending result is ok if not strict' do pending.describe_to(summary) expect(summary.ok?).to be true strict = Result::StrictConfiguration.new([:pending]) expect(summary.ok?(strict: strict)).to be false end it 'undefined result is ok if not strict' do undefined.describe_to(summary) expect(summary.ok?).to be true strict = Result::StrictConfiguration.new([:undefined]) expect(summary.ok?(strict: strict)).to be false end it 'flaky result is ok if not strict' do summary.flaky expect(summary.ok?).to be true strict = Result::StrictConfiguration.new([:flaky]) expect(summary.ok?(strict: strict)).to be false end end end describe Result::Duration do subject(:duration) { described_class.new(10) } it '#nanoseconds can be accessed in #tap' do expect(duration.tap { |duration| @duration = duration.nanoseconds }).to eq(duration) expect(@duration).to eq(10) end end describe Result::UnknownDuration do subject(:duration) { described_class.new } it '#tap does not execute the passed block' do expect(duration.tap { raise 'tap executed block' }).to eq duration end it 'accessing #nanoseconds outside #tap block raises exception' do expect { duration.nanoseconds }.to raise_error(RuntimeError) end end end end end end cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/test/runner_spec.rb000066400000000000000000000313661514133137400267740ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/around_hook' require 'cucumber/core/test/hook_step' require 'cucumber/core/test/runner' require 'cucumber/core/test/case' require 'cucumber/core/test/step' require 'support/duration_matcher' describe Cucumber::Core::Test::Runner do include_context 'with different types of test steps' let(:test_case) { Cucumber::Core::Test::Case.new(double, double, test_steps, double, double, double, double) } let(:runner) { described_class.new(event_bus) } let(:event_bus) { double.as_null_object } before { allow(event_bus).to receive(:test_case_started) } context 'when reporting the duration of a test case' do before do allow(Cucumber::Core::Test::Timer::MonotonicTime).to receive(:time_in_nanoseconds).and_return(525_702_744_080_000, 525_702_744_080_001) end context 'with a passing test case' do let(:test_steps) { [passing_step] } it 'records the nanoseconds duration of the execution on the result' do allow(event_bus).to receive(:test_case_finished) do |_reported_test_case, result| expect(result.duration).to be_duration(1) end test_case.describe_to(runner) end end context 'with a failing test case' do let(:test_steps) { [failing_step] } it 'records the nanoseconds duration of the execution on the result' do allow(event_bus).to receive(:test_case_finished) do |_reported_test_case, result| expect(result.duration).to be_duration(1) end test_case.describe_to(runner) end end end context 'when reporting the exception that failed a test case' do let(:test_steps) { [failing_step] } it 'sets the exception on the `Cucumber::Core::Test::Result::Failed` instance' do allow(event_bus).to receive(:before_test_case) allow(event_bus).to receive(:test_case_finished) do |_reported_test_case, result| expect(result.exception).to be_a StandardError end test_case.describe_to(runner) end end context 'without steps' do let(:test_steps) { [] } it 'emits a `test_case_started` event before running the test case' do expect(event_bus).to receive(:test_case_started).with(test_case) test_case.describe_to(runner) end it 'emits the `test_case_finished` event after running the the test case' do expect(event_bus).to receive(:test_case_finished) test_case.describe_to(runner) end it 'reports the `test_case` inside the `test_case_finished` event' do allow(event_bus).to receive(:test_case_finished) do |reported_test_case, _result| expect(reported_test_case).to eq(test_case) end test_case.describe_to(runner) end it 'reports that the test result was undefined inside the `test_case_finished` event' do allow(event_bus).to receive(:test_case_finished) do |_reported_test_case, result| expect(result).to be_undefined end test_case.describe_to(runner) end end context 'with steps' do context 'with steps that all pass' do let(:test_steps) { [passing_step, passing_step] } it 'emits the `test_case_finished` event' do expect(event_bus).to receive(:test_case_finished) test_case.describe_to(runner) end it 'reports that the test result was passed inside the `test_case_finished` event' do allow(event_bus).to receive(:test_case_finished) do |_reported_test_case, result| expect(result).to be_passed end test_case.describe_to(runner) end end context 'with an undefined step' do let(:test_steps) { [undefined_step] } it 'emits the `test_case_finished` event' do expect(event_bus).to receive(:test_case_finished) test_case.describe_to(runner) end it 'reports that the test result was undefined inside the `test_case_finished` event' do allow(event_bus).to receive(:test_case_finished) do |_reported_test_case, result| expect(result).to be_undefined end test_case.describe_to(runner) end it 'sets the message on the result' do allow(event_bus).to receive(:test_case_finished) do |_reported_test_case, result| expect(result.message).to eq('Undefined step: "step name"') end allow(undefined_step).to receive(:text).and_return('step name') test_case.describe_to(runner) end it 'appends the backtrace of the result' do allow(event_bus).to receive(:test_case_finished) do |_reported_test_case, result| expect(result.backtrace).to eq(['step line']) end allow(undefined_step).to receive(:backtrace_line).and_return('step line') test_case.describe_to(runner) end end context 'with a pending step' do let(:test_steps) { [pending_step] } it 'emits the `test_case_finished` event' do expect(event_bus).to receive(:test_case_finished) test_case.describe_to(runner) end it 'reports that the test result was pending inside the `test_case_finished` event' do allow(event_bus).to receive(:test_case_finished) do |_reported_test_case, result| expect(result).to be_pending end test_case.describe_to(runner) end it 'appends the backtrace of the result' do allow(event_bus).to receive(:test_case_finished) do |_reported_test_case, result| expect(result.backtrace.last).to eq('step line') end allow(pending_step).to receive(:backtrace_line).and_return('step line') test_case.describe_to(runner) end end context 'with a skipping step' do let(:test_steps) { [skipping_step] } it 'emits the `test_case_finished` event' do expect(event_bus).to receive(:test_case_finished) test_case.describe_to(runner) end it 'reports that the test result was skipped inside the `test_case_finished` event' do allow(event_bus).to receive(:test_case_finished) do |_reported_test_case, result| expect(result).to be_skipped end test_case.describe_to(runner) end it 'appends the backtrace of the result' do allow(event_bus).to receive(:test_case_finished) do |_reported_test_case, result| expect(result.backtrace.last).to eq('step line') end allow(skipping_step).to receive(:backtrace_line).and_return('step line') test_case.describe_to(runner) end end context 'with failing steps' do let(:test_steps) { [failing_step] } it 'emits the `test_case_finished` event' do expect(event_bus).to receive(:test_case_finished) test_case.describe_to(runner) end it 'reports that the test result was failed inside the `test_case_finished` event' do allow(event_bus).to receive(:test_case_finished) do |_reported_test_case, result| expect(result).to be_failed end test_case.describe_to(runner) end it 'appends the backtrace of the exception of the result' do allow(event_bus).to receive(:test_case_finished) do |_reported_test_case, result| expect(result.exception.backtrace.last).to eq('step line') end allow(failing_step).to receive(:backtrace_line).and_return('step line') test_case.describe_to(runner) end end context 'with an initial failing step' do let(:test_steps) { [failing_step, passing_step] } it 'emits the test_step_finished event with a failed result' do expect(event_bus).to receive(:test_step_finished).with(failing_step, anything) do |_reported_test_case, result| expect(result).to be_failed end test_case.describe_to(runner) end it 'emits a test_step_finished event with a skipped result' do expect(event_bus).to receive(:test_step_finished).with(passing_step, anything) do |_reported_test_case, result| expect(result).to be_skipped end test_case.describe_to(runner) end it 'emits a test_case_finished event with a failed result' do allow(event_bus).to receive(:test_case_finished) do |_reported_test_case, result| expect(result).to be_failed expect(result.exception).to be_a StandardError end test_case.describe_to(runner) end it 'skips, rather than executing the second step' do expect(passing_step).not_to receive(:execute) allow(passing_step).to receive(:skip).and_return(Cucumber::Core::Test::Result::Skipped.new) test_case.describe_to(runner) end end end context 'with multiple test cases' do let(:first_test_case) { Cucumber::Core::Test::Case.new(double, double, [failing_step], double, double, double, double) } let(:last_test_case) { Cucumber::Core::Test::Case.new(double, double, [passing_step], double, double, double, double) } let(:test_cases) { [first_test_case, last_test_case] } it 'reports the results correctly for test cases after a failing test case' do allow(event_bus).to receive(:test_case_finished) { |reported_test_case, result| expect(result).to be_failed if reported_test_case == first_test_case expect(result).to be_passed if reported_test_case == last_test_case }.twice test_cases.each { |test_case| test_case.describe_to(runner) } end end context 'when passing the latest result to a mapping' do let(:hook_mapping) { Cucumber::Core::Test::Action::Unskippable.new { :no_op } } let(:after_hook) { Cucumber::Core::Test::HookStep.new(double, 'After Hook Step', double, hook_mapping) } let(:test_steps) { [failing_step, after_hook] } it 'passes a failed result when the scenario is failing' do allow(event_bus).to receive(:test_case_finished) do |_reported_test_case, result| expect(result).to be_failed end test_case.describe_to(runner) end end context 'with around hooks' do let(:passing_around_hook) do Cucumber::Core::Test::AroundHook.new(&:call) end let(:failing_around_hook) do Cucumber::Core::Test::AroundHook.new do |block| block.call raise StandardError end end it "passes normally when around hooks don't fail" do test_case = Cucumber::Core::Test::Case.new(double, double, [passing_step], double, double, double, double, [passing_around_hook]) allow(event_bus).to receive(:test_case_finished).with(test_case, anything) do |_reported_test_case, result| expect(result).to be_passed end test_case.describe_to(runner) end it 'gets a failed result if the Around hook fails before the test case is run' do around_hook = Cucumber::Core::Test::AroundHook.new { |_block| raise StandardError } test_case = Cucumber::Core::Test::Case.new(double, double, [passing_step], double, double, double, double, [around_hook]) allow(event_bus).to receive(:test_case_finished).with(test_case, anything) do |_reported_test_case, result| expect(result).to be_failed expect(result.exception).to be_a StandardError end test_case.describe_to(runner) end it 'gets a failed result if the Around hook fails after the test case is run' do test_case = Cucumber::Core::Test::Case.new(double, double, [passing_step], double, double, double, double, [failing_around_hook]) allow(event_bus).to receive(:test_case_finished).with(test_case, anything) do |_reported_test_case, result| expect(result).to be_failed expect(result.exception).to be_a(StandardError) end test_case.describe_to(runner) end it 'fails when a step fails if the around hook works' do test_case = Cucumber::Core::Test::Case.new(double, double, [failing_step], double, double, double, double, [passing_around_hook]) allow(event_bus).to receive(:test_case_finished).with(test_case, anything) do |_reported_test_case, result| expect(result).to be_failed expect(result.exception).to be_a(StandardError) end test_case.describe_to(runner) end it 'sends after_test_step for a step interrupted by (a timeout in) the around hook' do test_case = Cucumber::Core::Test::Case.new(double, double, [], double, double, double, double, [failing_around_hook]) allow(runner).to receive(:running_test_step).and_return(passing_step) allow(event_bus).to receive(:test_step_finished).with(passing_step, anything) do |_reported_test_case, result| expect(result).to be_failed expect(result.exception).to be_a StandardError end allow(event_bus).to receive(:test_case_finished).with(test_case, anything) do |_reported_test_case, result| expect(result).to be_failed expect(result.exception).to be_a StandardError end test_case.describe_to(runner) end end end cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/test/step_spec.rb000066400000000000000000000053221514133137400264270ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/step' describe Cucumber::Core::Test::Step do let(:id) { 'some-random-uid' } let(:text) { 'step text' } let(:location) { double } describe 'describing itself' do it 'describes itself to a visitor' do visitor = double args = double test_step = described_class.new(id, text, location) expect(visitor).to receive(:test_step).with(test_step, args) test_step.describe_to(visitor, args) end end describe 'backtrace line' do let(:text) { 'this step passes' } let(:location) { Cucumber::Core::Test::Location.new('path/file.feature', 10) } let(:test_step) { described_class.new(id, text, location) } it 'knows how to form the backtrace line' do expect(test_step.backtrace_line).to eq("path/file.feature:10:in `this step passes'") end end describe 'executing' do it "passes arbitrary arguments to the action's block" do args_spy = nil expected_args = [double, double] test_step = described_class.new(id, text, location).with_action do |*actual_args| args_spy = actual_args end test_step.execute(*expected_args) expect(args_spy).to eq expected_args end context 'when a passing action exists' do it 'returns a passing result' do test_step = described_class.new(id, text, location).with_action { :no_op } expect(test_step.execute).to be_passed end end context 'when a failing action exists' do let(:exception) { StandardError.new('oops') } it 'returns a failing result' do test_step = described_class.new(id, text, location).with_action { raise exception } result = test_step.execute expect(result).to be_failed expect(result.exception).to eq exception end end context 'with no action' do it 'returns an Undefined result' do test_step = described_class.new(id, text, location) result = test_step.execute expect(result).to be_undefined end end end it 'exposes the text and location of as attributes' do test_step = described_class.new(id, text, location) expect(test_step.text).to eq text expect(test_step.location).to eq location end it 'exposes the location of the action as attribute' do location = double action = instance_double(Cucumber::Core::Test::Action::Defined, location: location) test_step = described_class.new(id, text, location, action) expect(test_step.action_location).to eq(location) end it 'returns the text when converted to a string' do text = 'a passing step' test_step = described_class.new(id, text, location) expect(test_step.to_s).to eq 'a passing step' end end cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core/test/timer_spec.rb000066400000000000000000000010141514133137400265660ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/timer' require 'support/duration_matcher' describe Cucumber::Core::Test::Timer do let(:random_time) { 525_702_744_080_000 } let(:other_random_time) { 525_702_744_080_001 } before do allow(Cucumber::Core::Test::Timer::MonotonicTime).to receive(:time_in_nanoseconds).and_return(random_time, other_random_time) end it 'returns a Result::Duration object' do timer = described_class.new.start expect(timer.duration).to be_duration(1) end end cucumber-cucumber-ruby-core-08c81cd/spec/cucumber/core_spec.rb000066400000000000000000000161321514133137400244760ustar00rootroot00000000000000# frozen_string_literal: true require 'support/report_api_spy' require 'support/activate_steps_for_self_test' require 'cucumber/core' require 'cucumber/core/filter' require 'cucumber/core/gherkin/writer' require 'cucumber/core/platform' require 'cucumber/core/report/summary' require 'cucumber/core/test/around_hook' require 'cucumber/core/test/filters' describe Cucumber::Core do include described_class include Cucumber::Core::Gherkin::Writer let(:tagged_gherkin_document) do gherkin do feature do scenario tags: '@b' do step 'text' end scenario_outline 'foo' do step '' examples tags: '@a' do row 'arg' row 'x' end examples 'bar', tags: '@a @b' do row 'arg' row 'y' end end end end end describe 'compiling features to a test suite' do context 'with two scenarios' do let(:gherkin_document) do gherkin do feature do background do step 'text' end scenario do step 'text' end scenario do step 'text' step 'text' end end end end it 'compiles the scenarios into two test cases' do visitor = ReportAPISpy.new compile([gherkin_document], visitor) expect(visitor.messages).to eq( %i[ test_case test_step test_step test_case test_step test_step test_step done ] ) end end context 'when compiling using a tag expression' do it 'filters out test cases based on a tag expression' do visitor = double.as_null_object expect(visitor).to receive(:test_case) { |test_case| expect(test_case.name).to eq('foo') }.once compile([tagged_gherkin_document], visitor, [Cucumber::Core::Test::TagFilter.new(['@a', '@b'])]) end end end describe 'executing a test suite' do subject(:gherkin_document) do gherkin do feature 'Feature name' do scenario 'The one that passes' do step 'passing' end scenario 'The one that fails' do step 'passing' step 'failing' step 'passing' step 'undefined' end end end end let(:event_bus) { Cucumber::Core::EventBus.new } let(:observed_events) { [] } let(:report) { Cucumber::Core::Report::Summary.new(event_bus) } before do execute [gherkin_document], [ActivateStepsForSelfTest.new] do |event_bus| event_bus.on(:test_case_started) do |event| test_case = event.test_case observed_events << [:test_case_started, test_case.name] end event_bus.on(:test_case_finished) do |event| test_case, result = *event.attributes observed_events << [:test_case_finished, test_case.name, result.to_sym] end event_bus.on(:test_step_started) do |event| test_step = event.test_step observed_events << [:test_step_started, test_step.text] end event_bus.on(:test_step_finished) do |event| test_step, result = *event.attributes observed_events << [:test_step_finished, test_step.text, result.to_sym] end end end it 'fires events' do expect(observed_events).to eq( [ [:test_case_started, 'The one that passes'], [:test_step_started, 'passing'], [:test_step_finished, 'passing', :passed], [:test_case_finished, 'The one that passes', :passed], [:test_case_started, 'The one that fails'], [:test_step_started, 'passing'], [:test_step_finished, 'passing', :passed], [:test_step_started, 'failing'], [:test_step_finished, 'failing', :failed], [:test_step_started, 'passing'], [:test_step_finished, 'passing', :skipped], [:test_step_started, 'undefined'], [:test_step_finished, 'undefined', :undefined], [:test_case_finished, 'The one that fails', :failed] ] ) end context 'without hooks' do before do execute([gherkin_document], [ActivateStepsForSelfTest.new], event_bus) end it 'reports on how many total test cases there were' do expect(report.test_cases.total).to eq(2) end it 'reports on how many total test cases passed' do expect(report.test_cases.total_passed).to eq(1) end it 'reports on how many total test cases failed' do expect(report.test_cases.total_failed).to eq(1) end it 'reports on how many total test steps there were' do expect(report.test_steps.total).to eq(5) end it 'reports on how many total test steps failed' do expect(report.test_steps.total_failed).to eq(1) end it 'reports on how many total test steps passed' do expect(report.test_steps.total_passed).to eq(2) end it 'reports on how many total test steps were skipped' do expect(report.test_steps.total_skipped).to eq(1) end it 'reports on how many total test steps were undefined' do expect(report.test_steps.total_undefined).to eq(1) end end context 'with around hooks' do let(:around_hooks_filter) do Class.new(Cucumber::Core::Filter.new(:logger)) do def test_case(test_case) test_steps = [base_step.with_action { logger << :step }] test_case.with_steps(test_steps).with_around_hooks([around_hook]).describe_to(receiver) end private def around_hook Cucumber::Core::Test::AroundHook.new do |run_scenario| logger << :before_all run_scenario.call logger << :middle run_scenario.call logger << :after_all end end def base_step Cucumber::Core::Test::Step.new('some-random-uid', 'text', nil, nil, nil) end end end let(:gherkin_document) do gherkin do feature do scenario do step 'text' end end end end let(:logger) { [] } it 'executes the test cases in the suite' do execute [gherkin_document], [around_hooks_filter.new(logger)], event_bus expect(report.test_cases.total).to eq(1) expect(report.test_cases.total_passed).to eq(1) expect(report.test_cases.total_failed).to eq(0) expect(logger).to eq(%i[before_all step middle step after_all]) end end it 'filters test cases by tag' do execute([tagged_gherkin_document], [Cucumber::Core::Test::TagFilter.new(['@a'])], event_bus) expect(report.test_cases.total).to eq(2) end it 'filters test cases by name' do execute([gherkin_document], [Cucumber::Core::Test::NameFilter.new([/passes/])], event_bus) expect(report.test_cases.total).to eq(1) end end end cucumber-cucumber-ruby-core-08c81cd/spec/spec_helper.rb000066400000000000000000000002411514133137400232120ustar00rootroot00000000000000# frozen_string_literal: true require 'rspec' require 'cucumber/core' require 'cucumber/core/filter' require_relative 'support/shared_context/test_step_types' cucumber-cucumber-ruby-core-08c81cd/spec/support/000077500000000000000000000000001514133137400221135ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/spec/support/activate_steps_for_self_test.rb000066400000000000000000000013361514133137400303770ustar00rootroot00000000000000# frozen_string_literal: true # This filter is used for testing Cucumber itself. It adds step definitions that # will activate steps to have passed / failed / pending results if they use expected names. class ActivateStepsForSelfTest < Cucumber::Core::Filter.new Failure = Class.new(StandardError) def test_case(test_case) test_case.with_steps(test_steps(test_case)).describe_to(receiver) end private def test_steps(test_case) test_case.test_steps.map do |step| case step.text when /fail/ then step.with_action { raise Failure } when /pending/ then step.with_action { raise Test::Result::Pending } when /pass/ then step.with_action { :no_op } else; step end end end end cucumber-cucumber-ruby-core-08c81cd/spec/support/duration_matcher.rb000066400000000000000000000010361514133137400257700ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/result' require 'rspec/expectations' module Cucumber module Core module Test RSpec::Matchers.define :be_duration do |expected| match do |actual| actual.nanoseconds == expected end end RSpec::Matchers.define :an_unknown_duration do match do |actual| actual.tap { raise '#tap block was executed, not an UnknownDuration' } expect(actual).to respond_to(:nanoseconds) end end end end end cucumber-cucumber-ruby-core-08c81cd/spec/support/report_api_spy.rb000066400000000000000000000004501514133137400254760ustar00rootroot00000000000000# frozen_string_literal: true class ReportAPISpy def initialize @result = [] end def test_case(*_args) @result << :test_case yield self end def test_step(*_args) @result << :test_step end def done @result << :done end def messages @result end end cucumber-cucumber-ruby-core-08c81cd/spec/support/shared_context/000077500000000000000000000000001514133137400251255ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/spec/support/shared_context/test_case_types.rb000066400000000000000000000011301514133137400306430ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/step' require_relative 'test_step_types' RSpec.shared_context 'with different types of test cases' do include_context 'with different types of test steps' let(:passing_test_case) { Cucumber::Core::Test::Case.new(1, 'Passing Test', [passing_step], double, double, [], 'en', []) } let(:failing_test_case) { Cucumber::Core::Test::Case.new(2, 'Failing Test', [failing_step], double, double, [], 'en', []) } let(:pending_test_case) { Cucumber::Core::Test::Case.new(3, 'Pending Test', [pending_step], double, double, [], 'en', []) } end cucumber-cucumber-ruby-core-08c81cd/spec/support/shared_context/test_step_types.rb000066400000000000000000000013621514133137400307120ustar00rootroot00000000000000# frozen_string_literal: true require 'cucumber/core/test/step' RSpec.shared_context 'with different types of test steps' do let(:passing_step) { Cucumber::Core::Test::Step.new(1, 'Passing Step', double).with_action { :no_op } } let(:failing_step) { Cucumber::Core::Test::Step.new(2, 'Failing Step', double).with_action { raise StandardError, 'Error' } } let(:pending_step) { Cucumber::Core::Test::Step.new(3, 'Pending Step', double).with_action { raise Cucumber::Core::Test::Result::Pending, 'TODO' } } let(:skipping_step) { Cucumber::Core::Test::Step.new(4, 'Skipped Step', double).with_action { raise Cucumber::Core::Test::Result::Skipped } } let(:undefined_step) { Cucumber::Core::Test::Step.new(5, 'Undefined Step', double) } end cucumber-cucumber-ruby-core-08c81cd/upgrading_notes/000077500000000000000000000000001514133137400226355ustar00rootroot00000000000000cucumber-cucumber-ruby-core-08c81cd/upgrading_notes/13.0.0.md000066400000000000000000000016641514133137400237050ustar00rootroot00000000000000# Upgrading to cucumber-core 13.0.0 ## Summary#ok? / Summary.ok? `strict` argument The `strict` argument for all the result / summary classes has changed to a keyword argument. This was typically used in `.ok?` and `#ok?` checks for the summary reporter. ### Before cucumber-core 13.0.0 ```ruby summary = Cucumber::Core::Report::Summary.new(event_bus) # There are many examples of the strict configuration strict = ::Cucumber::Core::Test::Result::StrictConfiguration.new([:undefined]) summary.ok?(strict) ``` ```ruby # There are many examples of result classes Result::Flaky.ok?(false) ``` ### With cucumber-core 13.0.0 ```ruby summary = Cucumber::Core::Report::Summary.new(event_bus) # There are many examples of the strict configuration strict = ::Cucumber::Core::Test::Result::StrictConfiguration.new([:undefined]) summary.ok?(strict: strict) ``` ```ruby # There are many examples of result classes Result::Flaky.ok?(strict: false) ``` cucumber-cucumber-ruby-core-08c81cd/upgrading_notes/14.0.0.md000066400000000000000000000007221514133137400237000ustar00rootroot00000000000000# Upgrading to cucumber-core 14.0.0 ## Test::Action This class has now had an extra namespace placed, so the new namespace mappings are as follows ### Before cucumber-core 14.0.0 ```ruby Cucumber::Core::Test::Action Cucumber::Core::Test::UndefinedAction Cucumber::Core::Test::UnskippableAction ``` ### With cucumber-core 14.0.0 ```ruby Cucumber::Core::Test::Action::Defined Cucumber::Core::Test::Action::Undefined Cucumber::Core::Test::Action::Unskippable ```