PNG  IHDRQgAMA a cHRMz&u0`:pQ<bKGDgmIDATxwUﹻ& ^CX(J I@ "% (** BX +*i"]j(IH{~R)[~>h{}gy)I$Ij .I$I$ʊy@}x.: $I$Ii}VZPC)I$IF ^0ʐJ$I$Q^}{"r=OzI$gRZeC.IOvH eKX $IMpxsk.쒷/&r[޳<v| .I~)@$updYRa$I |M.e JaֶpSYR6j>h%IRز if&uJ)M$I vLi=H;7UJ,],X$I1AҒJ$ XY XzI@GNҥRT)E@;]K*Mw;#5_wOn~\ DC&$(A5 RRFkvIR}l!RytRl;~^ǷJj اy뷦BZJr&ӥ8Pjw~vnv X^(I;4R=P[3]J,]ȏ~:3?[ a&e)`e*P[4]T=Cq6R[ ~ޤrXR Հg(t_HZ-Hg M$ãmL5R uk*`%C-E6/%[t X.{8P9Z.vkXŐKjgKZHg(aK9ڦmKjѺm_ \#$5,)-  61eJ,5m| r'= &ڡd%-]J on Xm|{ RҞe $eڧY XYrԮ-a7RK6h>n$5AVڴi*ֆK)mѦtmr1p| q:흺,)Oi*ֺK)ܬ֦K-5r3>0ԔHjJئEZj,%re~/z%jVMڸmrt)3]J,T K֦OvԒgii*bKiNO~%PW0=dii2tJ9Jݕ{7"I P9JKTbu,%r"6RKU}Ij2HKZXJ,妝 XYrP ެ24c%i^IK|.H,%rb:XRl1X4Pe/`x&P8Pj28Mzsx2r\zRPz4J}yP[g=L) .Q[6RjWgp FIH*-`IMRaK9TXcq*I y[jE>cw%gLRԕiFCj-ďa`#e~I j,%r,)?[gp FI˨mnWX#>mʔ XA DZf9,nKҲzIZXJ,L#kiPz4JZF,I,`61%2s $,VOϚ2/UFJfy7K> X+6 STXIeJILzMfKm LRaK9%|4p9LwJI!`NsiazĔ)%- XMq>pk$-$Q2x#N ؎-QR}ᶦHZډ)J,l#i@yn3LN`;nڔ XuX5pF)m|^0(>BHF9(cզEerJI rg7 4I@z0\JIi䵙RR0s;$s6eJ,`n 䂦0a)S)A 1eJ,堌#635RIgpNHuTH_SԕqVe ` &S)>p;S$魁eKIuX`I4춒o}`m$1":PI<[v9^\pTJjriRŭ P{#{R2,`)e-`mgj~1ϣLKam7&U\j/3mJ,`F;M'䱀 .KR#)yhTq;pcK9(q!w?uRR,n.yw*UXj#\]ɱ(qv2=RqfB#iJmmL<]Y͙#$5 uTU7ӦXR+q,`I}qL'`6Kͷ6r,]0S$- [RKR3oiRE|nӦXR.(i:LDLTJjY%o:)6rxzҒqTJjh㞦I.$YR.ʼnGZ\ֿf:%55 I˼!6dKxm4E"mG_ s? .e*?LRfK9%q#uh$)i3ULRfK9yxm܌bj84$i1U^@Wbm4uJ,ҪA>_Ij?1v32[gLRD96oTaR׿N7%L2 NT,`)7&ƝL*꽙yp_$M2#AS,`)7$rkTA29_Iye"|/0t)$n XT2`YJ;6Jx".e<`$) PI$5V4]29SRI>~=@j]lp2`K9Jaai^" Ԋ29ORI%:XV5]JmN9]H;1UC39NI%Xe78t)a;Oi Ҙ>Xt"~G>_mn:%|~ޅ_+]$o)@ǀ{hgN;IK6G&rp)T2i୦KJuv*T=TOSV>(~D>dm,I*Ɛ:R#ۙNI%D>G.n$o;+#RR!.eU˽TRI28t)1LWϚ>IJa3oFbu&:tJ*(F7y0ZR ^p'Ii L24x| XRI%ۄ>S1]Jy[zL$adB7.eh4%%누>WETf+3IR:I3Xה)3אOۦSRO'ٺ)S}"qOr[B7ϙ.edG)^ETR"RtRݜh0}LFVӦDB^k_JDj\=LS(Iv─aTeZ%eUAM-0;~˃@i|l @S4y72>sX-vA}ϛBI!ݎߨWl*)3{'Y|iSlEڻ(5KtSI$Uv02,~ԩ~x;P4ցCrO%tyn425:KMlD ^4JRxSهF_}شJTS6uj+ﷸk$eZO%G*^V2u3EMj3k%)okI]dT)URKDS 7~m@TJR~荪fT"֛L \sM -0T KfJz+nإKr L&j()[E&I ߴ>e FW_kJR|!O:5/2跌3T-'|zX ryp0JS ~^F>-2< `*%ZFP)bSn"L :)+pʷf(pO3TMW$~>@~ū:TAIsV1}S2<%ޟM?@iT ,Eūoz%i~g|`wS(]oȤ8)$ ntu`өe`6yPl IzMI{ʣzʨ )IZ2= ld:5+請M$-ї;U>_gsY$ÁN5WzWfIZ)-yuXIfp~S*IZdt;t>KūKR|$#LcԀ+2\;kJ`]YǔM1B)UbG"IRߊ<xܾӔJ0Z='Y嵤 Leveg)$znV-º^3Ւof#0Tfk^Zs[*I꯳3{)ˬW4Ւ4 OdpbZRS|*I 55#"&-IvT&/윚Ye:i$ 9{LkuRe[I~_\ؠ%>GL$iY8 9ܕ"S`kS.IlC;Ҏ4x&>u_0JLr<J2(^$5L s=MgV ~,Iju> 7r2)^=G$1:3G< `J3~&IR% 6Tx/rIj3O< ʔ&#f_yXJiގNSz; Tx(i8%#4 ~AS+IjerIUrIj362v885+IjAhK__5X%nV%Iͳ-y|7XV2v4fzo_68"S/I-qbf; LkF)KSM$ Ms>K WNV}^`-큧32ŒVؙGdu,^^m%6~Nn&͓3ŒVZMsRpfEW%IwdǀLm[7W&bIRL@Q|)* i ImsIMmKmyV`i$G+R 0tV'!V)֏28vU7͒vHꦼtxꗞT ;S}7Mf+fIRHNZUkUx5SAJㄌ9MqμAIRi|j5)o*^'<$TwI1hEU^c_j?Е$%d`z cyf,XO IJnTgA UXRD }{H}^S,P5V2\Xx`pZ|Yk:$e ~ @nWL.j+ϝYb퇪bZ BVu)u/IJ_ 1[p.p60bC >|X91P:N\!5qUB}5a5ja `ubcVxYt1N0Zzl4]7­gKj]?4ϻ *[bg$)+À*x쳀ogO$~,5 زUS9 lq3+5mgw@np1sso Ӻ=|N6 /g(Wv7U;zωM=wk,0uTg_`_P`uz?2yI!b`kĸSo+Qx%!\οe|އԁKS-s6pu_(ֿ$i++T8=eY; צP+phxWQv*|p1. ά. XRkIQYP,drZ | B%wP|S5`~́@i޾ E;Չaw{o'Q?%iL{u D?N1BD!owPHReFZ* k_-~{E9b-~P`fE{AܶBJAFO wx6Rox5 K5=WwehS8 (JClJ~ p+Fi;ŗo+:bD#g(C"wA^ r.F8L;dzdIHUX݆ϞXg )IFqem%I4dj&ppT{'{HOx( Rk6^C٫O.)3:s(۳(Z?~ٻ89zmT"PLtw䥈5&b<8GZ-Y&K?e8,`I6e(֍xb83 `rzXj)F=l($Ij 2*(F?h(/9ik:I`m#p3MgLaKjc/U#n5S# m(^)=y=đx8ŬI[U]~SцA4p$-F i(R,7Cx;X=cI>{Km\ o(Tv2vx2qiiDJN,Ҏ!1f 5quBj1!8 rDFd(!WQl,gSkL1Bxg''՞^ǘ;pQ P(c_ IRujg(Wz bs#P­rz> k c&nB=q+ؔXn#r5)co*Ũ+G?7< |PQӣ'G`uOd>%Mctz# Ԫڞ&7CaQ~N'-P.W`Oedp03C!IZcIAMPUۀ5J<\u~+{9(FbbyAeBhOSܳ1 bÈT#ŠyDžs,`5}DC-`̞%r&ڙa87QWWp6e7 Rϫ/oY ꇅ Nܶըtc!LA T7V4Jsū I-0Pxz7QNF_iZgúWkG83 0eWr9 X]㾮݁#Jˢ C}0=3ݱtBi]_ &{{[/o[~ \q鯜00٩|cD3=4B_b RYb$óBRsf&lLX#M*C_L܄:gx)WΘsGSbuL rF$9';\4Ɍq'n[%p.Q`u hNb`eCQyQ|l_C>Lb꟟3hSb #xNxSs^ 88|Mz)}:](vbۢamŖ࿥ 0)Q7@0=?^k(*J}3ibkFn HjB׻NO z x}7p 0tfDX.lwgȔhԾŲ }6g E |LkLZteu+=q\Iv0쮑)QٵpH8/2?Σo>Jvppho~f>%bMM}\//":PTc(v9v!gոQ )UfVG+! 35{=x\2+ki,y$~A1iC6#)vC5^>+gǵ@1Hy٪7u;p psϰu/S <aʸGu'tD1ԝI<pg|6j'p:tպhX{o(7v],*}6a_ wXRk,O]Lܳ~Vo45rp"N5k;m{rZbΦ${#)`(Ŵg,;j%6j.pyYT?}-kBDc3qA`NWQū20/^AZW%NQ MI.X#P#,^Ebc&?XR tAV|Y.1!؅⨉ccww>ivl(JT~ u`ٵDm q)+Ri x/x8cyFO!/*!/&,7<.N,YDŽ&ܑQF1Bz)FPʛ?5d 6`kQձ λc؎%582Y&nD_$Je4>a?! ͨ|ȎWZSsv8 j(I&yj Jb5m?HWp=g}G3#|I,5v珿] H~R3@B[☉9Ox~oMy=J;xUVoj bUsl_35t-(ՃɼRB7U!qc+x4H_Qo֮$[GO<4`&č\GOc[.[*Af%mG/ ňM/r W/Nw~B1U3J?P&Y )`ѓZ1p]^l“W#)lWZilUQu`-m|xĐ,_ƪ|9i:_{*(3Gѧ}UoD+>m_?VPۅ15&}2|/pIOʵ> GZ9cmíتmnz)yߐbD >e}:) r|@R5qVSA10C%E_'^8cR7O;6[eKePGϦX7jb}OTGO^jn*媓7nGMC t,k31Rb (vyܴʭ!iTh8~ZYZp(qsRL ?b}cŨʊGO^!rPJO15MJ[c&~Z`"ѓޔH1C&^|Ш|rʼ,AwĴ?b5)tLU)F| &g٣O]oqSUjy(x<Ϳ3 .FSkoYg2 \_#wj{u'rQ>o;%n|F*O_L"e9umDds?.fuuQbIWz |4\0 sb;OvxOSs; G%T4gFRurj(֍ڑb uԖKDu1MK{1^ q; C=6\8FR艇!%\YÔU| 88m)֓NcLve C6z;o&X x59:q61Z(T7>C?gcļxѐ Z oo-08jہ x,`' ҔOcRlf~`jj".Nv+sM_]Zk g( UOPyεx%pUh2(@il0ݽQXxppx-NS( WO+轾 nFߢ3M<;z)FBZjciu/QoF 7R¥ ZFLF~#ȣߨ^<쩡ݛкvџ))ME>ώx4m#!-m!L;vv#~Y[đKmx9.[,UFS CVkZ +ߟrY٧IZd/ioi$%͝ب_ֶX3ܫhNU ZZgk=]=bbJS[wjU()*I =ώ:}-蹞lUj:1}MWm=̛ _ ¾,8{__m{_PVK^n3esw5ӫh#$-q=A̟> ,^I}P^J$qY~Q[ Xq9{#&T.^GVj__RKpn,b=`żY@^՝;z{paVKkQXj/)y TIc&F;FBG7wg ZZDG!x r_tƢ!}i/V=M/#nB8 XxЫ ^@CR<{䤭YCN)eKOSƟa $&g[i3.C6xrOc8TI;o hH6P&L{@q6[ Gzp^71j(l`J}]e6X☉#͕ ׈$AB1Vjh㭦IRsqFBjwQ_7Xk>y"N=MB0 ,C #o6MRc0|$)ف"1!ixY<B9mx `,tA>)5ػQ?jQ?cn>YZe Tisvh# GMމȇp:ԴVuږ8ɼH]C.5C!UV;F`mbBk LTMvPʍϤj?ԯ/Qr1NB`9s"s TYsz &9S%U԰> {<ؿSMxB|H\3@!U| k']$U+> |HHMLޢ?V9iD!-@x TIî%6Z*9X@HMW#?nN ,oe6?tQwڱ.]-y':mW0#!J82qFjH -`ѓ&M0u Uγmxϵ^-_\])@0Rt.8/?ٰCY]x}=sD3ojަЫNuS%U}ԤwHH>ڗjܷ_3gN q7[q2la*ArǓԖ+p8/RGM ]jacd(JhWko6ڎbj]i5Bj3+3!\j1UZLsLTv8HHmup<>gKMJj0@H%,W΃7R) ">c, xixј^ aܖ>H[i.UIHc U1=yW\=S*GR~)AF=`&2h`DzT󑓶J+?W+}C%P:|0H܆}-<;OC[~o.$~i}~HQ TvXΈr=b}$vizL4:ȰT|4~*!oXQR6Lk+#t/g lԁߖ[Jڶ_N$k*". xsxX7jRVbAAʯKҎU3)zSNN _'s?f)6X!%ssAkʱ>qƷb hg %n ~p1REGMHH=BJiy[<5 ǁJҖgKR*倳e~HUy)Ag,K)`Vw6bRR:qL#\rclK/$sh*$ 6덤 KԖc 3Z9=Ɣ=o>X Ώ"1 )a`SJJ6k(<c e{%kϊP+SL'TcMJWRm ŏ"w)qc ef꒵i?b7b('"2r%~HUS1\<(`1Wx9=8HY9m:X18bgD1u ~|H;K-Uep,, C1 RV.MR5άh,tWO8WC$ XRVsQS]3GJ|12 [vM :k#~tH30Rf-HYݺ-`I9%lIDTm\ S{]9gOڒMNCV\G*2JRŨ;Rҏ^ڽ̱mq1Eu?To3I)y^#jJw^Ńj^vvlB_⋌P4x>0$c>K†Aļ9s_VjTt0l#m>E-,,x,-W)سo&96RE XR.6bXw+)GAEvL)͞K4$p=Ũi_ѱOjb HY/+@θH9޼]Nԥ%n{ &zjT? Ty) s^ULlb,PiTf^<À] 62R^V7)S!nllS6~͝V}-=%* ʻ>G DnK<y&>LPy7'r=Hj 9V`[c"*^8HpcO8bnU`4JȪAƋ#1_\ XϘHPRgik(~G~0DAA_2p|J묭a2\NCr]M_0 ^T%e#vD^%xy-n}-E\3aS%yN!r_{ )sAw ڼp1pEAk~v<:`'ӭ^5 ArXOI驻T (dk)_\ PuA*BY]yB"l\ey hH*tbK)3 IKZ򹞋XjN n *n>k]X_d!ryBH ]*R 0(#'7 %es9??ښFC,ՁQPjARJ\Ρw K#jahgw;2$l*) %Xq5!U᢯6Re] |0[__64ch&_}iL8KEgҎ7 M/\`|.p,~`a=BR?xܐrQ8K XR2M8f ?`sgWS%" Ԉ 7R%$ N}?QL1|-эټwIZ%pvL3Hk>,ImgW7{E xPHx73RA @RS CC !\ȟ5IXR^ZxHл$Q[ŝ40 (>+ _C >BRt<,TrT {O/H+˟Pl6 I B)/VC<6a2~(XwV4gnXR ϱ5ǀHٻ?tw똤Eyxp{#WK qG%5],(0ӈH HZ])ג=K1j&G(FbM@)%I` XRg ʔ KZG(vP,<`[ Kn^ SJRsAʠ5xՅF`0&RbV tx:EaUE/{fi2;.IAwW8/tTxAGOoN?G}l L(n`Zv?pB8K_gI+ܗ #i?ޙ.) p$utc ~DžfՈEo3l/)I-U?aԅ^jxArA ΧX}DmZ@QLےbTXGd.^|xKHR{|ΕW_h] IJ`[G9{).y) 0X YA1]qp?p_k+J*Y@HI>^?gt.06Rn ,` ?);p pSF9ZXLBJPWjgQ|&)7! HjQt<| ؅W5 x W HIzYoVMGP Hjn`+\(dNW)F+IrS[|/a`K|ͻ0Hj{R,Q=\ (F}\WR)AgSG`IsnAR=|8$}G(vC$)s FBJ?]_u XRvύ6z ŨG[36-T9HzpW̞ú Xg큽=7CufzI$)ki^qk-) 0H*N` QZkk]/tnnsI^Gu't=7$ Z;{8^jB% IItRQS7[ϭ3 $_OQJ`7!]W"W,)Iy W AJA;KWG`IY{8k$I$^%9.^(`N|LJ%@$I}ֽp=FB*xN=gI?Q{٥4B)mw $Igc~dZ@G9K X?7)aK%݅K$IZ-`IpC U6$I\0>!9k} Xa IIS0H$I H ?1R.Чj:4~Rw@p$IrA*u}WjWFPJ$I➓/6#! LӾ+ X36x8J |+L;v$Io4301R20M I$-E}@,pS^ޟR[/s¹'0H$IKyfŸfVOπFT*a$I>He~VY/3R/)>d$I>28`Cjw,n@FU*9ttf$I~<;=/4RD~@ X-ѕzἱI$: ԍR a@b X{+Qxuq$IЛzo /~3\8ڒ4BN7$IҀj V]n18H$IYFBj3̵̚ja pp $Is/3R Ӻ-Yj+L;.0ŔI$Av? #!5"aʄj}UKmɽH$IjCYs?h$IDl843.v}m7UiI=&=0Lg0$I4: embe` eQbm0u? $IT!Sƍ'-sv)s#C0:XB2a w I$zbww{."pPzO =Ɔ\[ o($Iaw]`E).Kvi:L*#gР7[$IyGPI=@R 4yR~̮´cg I$I/<tPͽ hDgo 94Z^k盇΄8I56^W$I^0̜N?4*H`237}g+hxoq)SJ@p|` $I%>-hO0eO>\ԣNߌZD6R=K ~n($I$y3D>o4b#px2$yڪtzW~a $I~?x'BwwpH$IZݑnC㧄Pc_9sO gwJ=l1:mKB>Ab<4Lp$Ib o1ZQ@85b̍ S'F,Fe,^I$IjEdù{l4 8Ys_s Z8.x m"+{~?q,Z D!I$ϻ'|XhB)=…']M>5 rgotԎ 獽PH$IjIPhh)n#cÔqA'ug5qwU&rF|1E%I$%]!'3AFD/;Ck_`9 v!ٴtPV;x`'*bQa w I$Ix5 FC3D_~A_#O݆DvV?<qw+I$I{=Z8".#RIYyjǪ=fDl9%M,a8$I$Ywi[7ݍFe$s1ՋBVA?`]#!oz4zjLJo8$I$%@3jAa4(o ;p,,dya=F9ً[LSPH$IJYЉ+3> 5"39aZ<ñh!{TpBGkj}Sp $IlvF.F$I z< '\K*qq.f<2Y!S"-\I$IYwčjF$ w9 \ߪB.1v!Ʊ?+r:^!I$BϹB H"B;L'G[ 4U#5>੐)|#o0aڱ$I>}k&1`U#V?YsV x>{t1[I~D&(I$I/{H0fw"q"y%4 IXyE~M3 8XψL}qE$I[> nD?~sf ]o΁ cT6"?'_Ἣ $I>~.f|'!N?⟩0G KkXZE]ޡ;/&?k OۘH$IRۀwXӨ<7@PnS04aӶp.:@\IWQJ6sS%I$e5ڑv`3:x';wq_vpgHyXZ 3gЂ7{{EuԹn±}$I$8t;b|591nءQ"P6O5i }iR̈́%Q̄p!I䮢]O{H$IRϻ9s֧ a=`- aB\X0"+5"C1Hb?߮3x3&gşggl_hZ^,`5?ߎvĸ%̀M!OZC2#0x LJ0 Gw$I$I}<{Eb+y;iI,`ܚF:5ܛA8-O-|8K7s|#Z8a&><a&/VtbtLʌI$I$I$I$I$I$IRjDD%tEXtdate:create2022-05-31T04:40:26+00:00!Î%tEXtdate:modify2022-05-31T04:40:26+00:00|{2IENDB`Mini Shell

HOME


Mini Shell 1.0
DIR:/snap/certbot/4482/lib/python3.12/site-packages/setuptools/tests/
Upload File :
Current File : //snap/certbot/4482/lib/python3.12/site-packages/setuptools/tests/test_editable_install.py
from __future__ import annotations

import os
import platform
import stat
import subprocess
import sys
from copy import deepcopy
from importlib import import_module
from importlib.machinery import EXTENSION_SUFFIXES
from pathlib import Path
from textwrap import dedent
from typing import Any
from unittest.mock import Mock
from uuid import uuid4

import jaraco.envs
import jaraco.path
import pytest
from path import Path as _Path

from setuptools._importlib import resources as importlib_resources
from setuptools.command.editable_wheel import (
    _DebuggingTips,
    _encode_pth,
    _find_namespaces,
    _find_package_roots,
    _find_virtual_namespaces,
    _finder_template,
    _LinkTree,
    _TopLevelFinder,
    editable_wheel,
)
from setuptools.dist import Distribution
from setuptools.extension import Extension
from setuptools.warnings import SetuptoolsDeprecationWarning

from . import contexts, namespaces

from distutils.core import run_setup


@pytest.fixture(params=["strict", "lenient"])
def editable_opts(request):
    if request.param == "strict":
        return ["--config-settings", "editable-mode=strict"]
    return []


EXAMPLE = {
    'pyproject.toml': dedent(
        """\
        [build-system]
        requires = ["setuptools"]
        build-backend = "setuptools.build_meta"

        [project]
        name = "mypkg"
        version = "3.14159"
        license = {text = "MIT"}
        description = "This is a Python package"
        dynamic = ["readme"]
        classifiers = [
            "Development Status :: 5 - Production/Stable",
            "Intended Audience :: Developers"
        ]
        urls = {Homepage = "https://github.com"}

        [tool.setuptools]
        package-dir = {"" = "src"}
        packages = {find = {where = ["src"]}}
        license-files = ["LICENSE*"]

        [tool.setuptools.dynamic]
        readme = {file = "README.rst"}

        [tool.distutils.egg_info]
        tag-build = ".post0"
        """
    ),
    "MANIFEST.in": dedent(
        """\
        global-include *.py *.txt
        global-exclude *.py[cod]
        prune dist
        prune build
        """
    ).strip(),
    "README.rst": "This is a ``README``",
    "LICENSE.txt": "---- placeholder MIT license ----",
    "src": {
        "mypkg": {
            "__init__.py": dedent(
                """\
                import sys
                from importlib.metadata import PackageNotFoundError, version

                try:
                    __version__ = version(__name__)
                except PackageNotFoundError:
                    __version__ = "unknown"
                """
            ),
            "__main__.py": dedent(
                """\
                from importlib.resources import read_text
                from . import __version__, __name__ as parent
                from .mod import x

                data = read_text(parent, "data.txt")
                print(__version__, data, x)
                """
            ),
            "mod.py": "x = ''",
            "data.txt": "Hello World",
        }
    },
}


SETUP_SCRIPT_STUB = "__import__('setuptools').setup()"


@pytest.mark.xfail(sys.platform == "darwin", reason="pypa/setuptools#4328")
@pytest.mark.parametrize(
    "files",
    [
        {**EXAMPLE, "setup.py": SETUP_SCRIPT_STUB},
        EXAMPLE,  # No setup.py script
    ],
)
def test_editable_with_pyproject(tmp_path, venv, files, editable_opts):
    project = tmp_path / "mypkg"
    project.mkdir()
    jaraco.path.build(files, prefix=project)

    cmd = [
        "python",
        "-m",
        "pip",
        "install",
        "--no-build-isolation",  # required to force current version of setuptools
        "-e",
        str(project),
        *editable_opts,
    ]
    print(venv.run(cmd))

    cmd = ["python", "-m", "mypkg"]
    assert venv.run(cmd).strip() == "3.14159.post0 Hello World"

    (project / "src/mypkg/data.txt").write_text("foobar", encoding="utf-8")
    (project / "src/mypkg/mod.py").write_text("x = 42", encoding="utf-8")
    assert venv.run(cmd).strip() == "3.14159.post0 foobar 42"


def test_editable_with_flat_layout(tmp_path, venv, editable_opts):
    files = {
        "mypkg": {
            "pyproject.toml": dedent(
                """\
                [build-system]
                requires = ["setuptools", "wheel"]
                build-backend = "setuptools.build_meta"

                [project]
                name = "mypkg"
                version = "3.14159"

                [tool.setuptools]
                packages = ["pkg"]
                py-modules = ["mod"]
                """
            ),
            "pkg": {"__init__.py": "a = 4"},
            "mod.py": "b = 2",
        },
    }
    jaraco.path.build(files, prefix=tmp_path)
    project = tmp_path / "mypkg"

    cmd = [
        "python",
        "-m",
        "pip",
        "install",
        "--no-build-isolation",  # required to force current version of setuptools
        "-e",
        str(project),
        *editable_opts,
    ]
    print(venv.run(cmd))
    cmd = ["python", "-c", "import pkg, mod; print(pkg.a, mod.b)"]
    assert venv.run(cmd).strip() == "4 2"


def test_editable_with_single_module(tmp_path, venv, editable_opts):
    files = {
        "mypkg": {
            "pyproject.toml": dedent(
                """\
                [build-system]
                requires = ["setuptools", "wheel"]
                build-backend = "setuptools.build_meta"

                [project]
                name = "mod"
                version = "3.14159"

                [tool.setuptools]
                py-modules = ["mod"]
                """
            ),
            "mod.py": "b = 2",
        },
    }
    jaraco.path.build(files, prefix=tmp_path)
    project = tmp_path / "mypkg"

    cmd = [
        "python",
        "-m",
        "pip",
        "install",
        "--no-build-isolation",  # required to force current version of setuptools
        "-e",
        str(project),
        *editable_opts,
    ]
    print(venv.run(cmd))
    cmd = ["python", "-c", "import mod; print(mod.b)"]
    assert venv.run(cmd).strip() == "2"


class TestLegacyNamespaces:
    # legacy => pkg_resources.declare_namespace(...) + setup(namespace_packages=...)

    def test_nspkg_file_is_unique(self, tmp_path, monkeypatch):
        deprecation = pytest.warns(
            SetuptoolsDeprecationWarning, match=".*namespace_packages parameter.*"
        )
        installation_dir = tmp_path / ".installation_dir"
        installation_dir.mkdir()
        examples = (
            "myns.pkgA",
            "myns.pkgB",
            "myns.n.pkgA",
            "myns.n.pkgB",
        )

        for name in examples:
            pkg = namespaces.build_namespace_package(tmp_path, name, version="42")
            with deprecation, monkeypatch.context() as ctx:
                ctx.chdir(pkg)
                dist = run_setup("setup.py", stop_after="config")
                cmd = editable_wheel(dist)
                cmd.finalize_options()
                editable_name = cmd.get_finalized_command("dist_info").name
                cmd._install_namespaces(installation_dir, editable_name)

        files = list(installation_dir.glob("*-nspkg.pth"))
        assert len(files) == len(examples)

    @pytest.mark.parametrize(
        "impl",
        (
            "pkg_resources",
            #  "pkgutil",  => does not work
        ),
    )
    @pytest.mark.parametrize("ns", ("myns.n",))
    def test_namespace_package_importable(
        self, venv, tmp_path, ns, impl, editable_opts
    ):
        """
        Installing two packages sharing the same namespace, one installed
        naturally using pip or `--single-version-externally-managed`
        and the other installed in editable mode should leave the namespace
        intact and both packages reachable by import.
        (Ported from test_develop).
        """
        build_system = """\
        [build-system]
        requires = ["setuptools"]
        build-backend = "setuptools.build_meta"
        """
        pkg_A = namespaces.build_namespace_package(tmp_path, f"{ns}.pkgA", impl=impl)
        pkg_B = namespaces.build_namespace_package(tmp_path, f"{ns}.pkgB", impl=impl)
        (pkg_A / "pyproject.toml").write_text(build_system, encoding="utf-8")
        (pkg_B / "pyproject.toml").write_text(build_system, encoding="utf-8")
        # use pip to install to the target directory
        opts = editable_opts[:]
        opts.append("--no-build-isolation")  # force current version of setuptools
        venv.run(["python", "-m", "pip", "install", str(pkg_A), *opts])
        venv.run(["python", "-m", "pip", "install", "-e", str(pkg_B), *opts])
        venv.run(["python", "-c", f"import {ns}.pkgA; import {ns}.pkgB"])
        # additionally ensure that pkg_resources import works
        venv.run(["python", "-c", "import pkg_resources"])


class TestPep420Namespaces:
    def test_namespace_package_importable(self, venv, tmp_path, editable_opts):
        """
        Installing two packages sharing the same namespace, one installed
        normally using pip and the other installed in editable mode
        should allow importing both packages.
        """
        pkg_A = namespaces.build_pep420_namespace_package(tmp_path, 'myns.n.pkgA')
        pkg_B = namespaces.build_pep420_namespace_package(tmp_path, 'myns.n.pkgB')
        # use pip to install to the target directory
        opts = editable_opts[:]
        opts.append("--no-build-isolation")  # force current version of setuptools
        venv.run(["python", "-m", "pip", "install", str(pkg_A), *opts])
        venv.run(["python", "-m", "pip", "install", "-e", str(pkg_B), *opts])
        venv.run(["python", "-c", "import myns.n.pkgA; import myns.n.pkgB"])

    def test_namespace_created_via_package_dir(self, venv, tmp_path, editable_opts):
        """Currently users can create a namespace by tweaking `package_dir`"""
        files = {
            "pkgA": {
                "pyproject.toml": dedent(
                    """\
                    [build-system]
                    requires = ["setuptools", "wheel"]
                    build-backend = "setuptools.build_meta"

                    [project]
                    name = "pkgA"
                    version = "3.14159"

                    [tool.setuptools]
                    package-dir = {"myns.n.pkgA" = "src"}
                    """
                ),
                "src": {"__init__.py": "a = 1"},
            },
        }
        jaraco.path.build(files, prefix=tmp_path)
        pkg_A = tmp_path / "pkgA"
        pkg_B = namespaces.build_pep420_namespace_package(tmp_path, 'myns.n.pkgB')
        pkg_C = namespaces.build_pep420_namespace_package(tmp_path, 'myns.n.pkgC')

        # use pip to install to the target directory
        opts = editable_opts[:]
        opts.append("--no-build-isolation")  # force current version of setuptools
        venv.run(["python", "-m", "pip", "install", str(pkg_A), *opts])
        venv.run(["python", "-m", "pip", "install", "-e", str(pkg_B), *opts])
        venv.run(["python", "-m", "pip", "install", "-e", str(pkg_C), *opts])
        venv.run(["python", "-c", "from myns.n import pkgA, pkgB, pkgC"])

    def test_namespace_accidental_config_in_lenient_mode(self, venv, tmp_path):
        """Sometimes users might specify an ``include`` pattern that ignores parent
        packages. In a normal installation this would ignore all modules inside the
        parent packages, and make them namespaces (reported in issue #3504),
        so the editable mode should preserve this behaviour.
        """
        files = {
            "pkgA": {
                "pyproject.toml": dedent(
                    """\
                    [build-system]
                    requires = ["setuptools", "wheel"]
                    build-backend = "setuptools.build_meta"

                    [project]
                    name = "pkgA"
                    version = "3.14159"

                    [tool.setuptools]
                    packages.find.include = ["mypkg.*"]
                    """
                ),
                "mypkg": {
                    "__init__.py": "",
                    "other.py": "b = 1",
                    "n": {
                        "__init__.py": "",
                        "pkgA.py": "a = 1",
                    },
                },
                "MANIFEST.in": EXAMPLE["MANIFEST.in"],
            },
        }
        jaraco.path.build(files, prefix=tmp_path)
        pkg_A = tmp_path / "pkgA"

        # use pip to install to the target directory
        opts = ["--no-build-isolation"]  # force current version of setuptools
        venv.run(["python", "-m", "pip", "-v", "install", "-e", str(pkg_A), *opts])
        out = venv.run(["python", "-c", "from mypkg.n import pkgA; print(pkgA.a)"])
        assert out.strip() == "1"
        cmd = """\
        try:
            import mypkg.other
        except ImportError:
            print("mypkg.other not defined")
        """
        out = venv.run(["python", "-c", dedent(cmd)])
        assert "mypkg.other not defined" in out


def test_editable_with_prefix(tmp_path, sample_project, editable_opts):
    """
    Editable install to a prefix should be discoverable.
    """
    prefix = tmp_path / 'prefix'

    # figure out where pip will likely install the package
    site_packages_all = [
        prefix / Path(path).relative_to(sys.prefix)
        for path in sys.path
        if 'site-packages' in path and path.startswith(sys.prefix)
    ]

    for sp in site_packages_all:
        sp.mkdir(parents=True)

    # install workaround
    _addsitedirs(site_packages_all)

    env = dict(os.environ, PYTHONPATH=os.pathsep.join(map(str, site_packages_all)))
    cmd = [
        sys.executable,
        '-m',
        'pip',
        'install',
        '--editable',
        str(sample_project),
        '--prefix',
        str(prefix),
        '--no-build-isolation',
        *editable_opts,
    ]
    subprocess.check_call(cmd, env=env)

    # now run 'sample' with the prefix on the PYTHONPATH
    bin = 'Scripts' if platform.system() == 'Windows' else 'bin'
    exe = prefix / bin / 'sample'
    subprocess.check_call([exe], env=env)


class TestFinderTemplate:
    """This test focus in getting a particular implementation detail right.
    If at some point in time the implementation is changed for something different,
    this test can be modified or even excluded.
    """

    def install_finder(self, finder):
        loc = {}
        exec(finder, loc, loc)
        loc["install"]()

    def test_packages(self, tmp_path):
        files = {
            "src1": {
                "pkg1": {
                    "__init__.py": "",
                    "subpkg": {"mod1.py": "a = 42"},
                },
            },
            "src2": {"mod2.py": "a = 43"},
        }
        jaraco.path.build(files, prefix=tmp_path)

        mapping = {
            "pkg1": str(tmp_path / "src1/pkg1"),
            "mod2": str(tmp_path / "src2/mod2"),
        }
        template = _finder_template(str(uuid4()), mapping, {})

        with contexts.save_paths(), contexts.save_sys_modules():
            for mod in ("pkg1", "pkg1.subpkg", "pkg1.subpkg.mod1", "mod2"):
                sys.modules.pop(mod, None)

            self.install_finder(template)
            mod1 = import_module("pkg1.subpkg.mod1")
            mod2 = import_module("mod2")
            subpkg = import_module("pkg1.subpkg")

            assert mod1.a == 42
            assert mod2.a == 43
            expected = str((tmp_path / "src1/pkg1/subpkg").resolve())
            assert_path(subpkg, expected)

    def test_namespace(self, tmp_path):
        files = {"pkg": {"__init__.py": "a = 13", "text.txt": "abc"}}
        jaraco.path.build(files, prefix=tmp_path)

        mapping = {"ns.othername": str(tmp_path / "pkg")}
        namespaces = {"ns": []}

        template = _finder_template(str(uuid4()), mapping, namespaces)
        with contexts.save_paths(), contexts.save_sys_modules():
            for mod in ("ns", "ns.othername"):
                sys.modules.pop(mod, None)

            self.install_finder(template)
            pkg = import_module("ns.othername")
            text = importlib_resources.files(pkg) / "text.txt"

            expected = str((tmp_path / "pkg").resolve())
            assert_path(pkg, expected)
            assert pkg.a == 13

            # Make sure resources can also be found
            assert text.read_text(encoding="utf-8") == "abc"

    def test_combine_namespaces(self, tmp_path):
        files = {
            "src1": {"ns": {"pkg1": {"__init__.py": "a = 13"}}},
            "src2": {"ns": {"mod2.py": "b = 37"}},
        }
        jaraco.path.build(files, prefix=tmp_path)

        mapping = {
            "ns.pkgA": str(tmp_path / "src1/ns/pkg1"),
            "ns": str(tmp_path / "src2/ns"),
        }
        namespaces_ = {"ns": [str(tmp_path / "src1"), str(tmp_path / "src2")]}
        template = _finder_template(str(uuid4()), mapping, namespaces_)

        with contexts.save_paths(), contexts.save_sys_modules():
            for mod in ("ns", "ns.pkgA", "ns.mod2"):
                sys.modules.pop(mod, None)

            self.install_finder(template)
            pkgA = import_module("ns.pkgA")
            mod2 = import_module("ns.mod2")

            expected = str((tmp_path / "src1/ns/pkg1").resolve())
            assert_path(pkgA, expected)
            assert pkgA.a == 13
            assert mod2.b == 37

    def test_combine_namespaces_nested(self, tmp_path):
        """
        Users may attempt to combine namespace packages in a nested way via
        ``package_dir`` as shown in pypa/setuptools#4248.
        """

        files = {
            "src": {"my_package": {"my_module.py": "a = 13"}},
            "src2": {"my_package2": {"my_module2.py": "b = 37"}},
        }

        stack = jaraco.path.DirectoryStack()
        with stack.context(tmp_path):
            jaraco.path.build(files)
            attrs = {
                "script_name": "%PEP 517%",
                "package_dir": {
                    "different_name": "src/my_package",
                    "different_name.subpkg": "src2/my_package2",
                },
                "packages": ["different_name", "different_name.subpkg"],
            }
            dist = Distribution(attrs)
            finder = _TopLevelFinder(dist, str(uuid4()))
            code = next(v for k, v in finder.get_implementation() if k.endswith(".py"))

        with contexts.save_paths(), contexts.save_sys_modules():
            for mod in attrs["packages"]:
                sys.modules.pop(mod, None)

            self.install_finder(code)
            mod1 = import_module("different_name.my_module")
            mod2 = import_module("different_name.subpkg.my_module2")

            expected = str((tmp_path / "src/my_package/my_module.py").resolve())
            assert str(Path(mod1.__file__).resolve()) == expected

            expected = str((tmp_path / "src2/my_package2/my_module2.py").resolve())
            assert str(Path(mod2.__file__).resolve()) == expected

            assert mod1.a == 13
            assert mod2.b == 37

    def test_dynamic_path_computation(self, tmp_path):
        # Follows the example in PEP 420
        files = {
            "project1": {"parent": {"child": {"one.py": "x = 1"}}},
            "project2": {"parent": {"child": {"two.py": "x = 2"}}},
            "project3": {"parent": {"child": {"three.py": "x = 3"}}},
        }
        jaraco.path.build(files, prefix=tmp_path)
        mapping = {}
        namespaces_ = {"parent": [str(tmp_path / "project1/parent")]}
        template = _finder_template(str(uuid4()), mapping, namespaces_)

        mods = (f"parent.child.{name}" for name in ("one", "two", "three"))
        with contexts.save_paths(), contexts.save_sys_modules():
            for mod in ("parent", "parent.child", "parent.child", *mods):
                sys.modules.pop(mod, None)

            self.install_finder(template)

            one = import_module("parent.child.one")
            assert one.x == 1

            with pytest.raises(ImportError):
                import_module("parent.child.two")

            sys.path.append(str(tmp_path / "project2"))
            two = import_module("parent.child.two")
            assert two.x == 2

            with pytest.raises(ImportError):
                import_module("parent.child.three")

            sys.path.append(str(tmp_path / "project3"))
            three = import_module("parent.child.three")
            assert three.x == 3

    def test_no_recursion(self, tmp_path):
        # See issue #3550
        files = {
            "pkg": {
                "__init__.py": "from . import pkg",
            },
        }
        jaraco.path.build(files, prefix=tmp_path)

        mapping = {
            "pkg": str(tmp_path / "pkg"),
        }
        template = _finder_template(str(uuid4()), mapping, {})

        with contexts.save_paths(), contexts.save_sys_modules():
            sys.modules.pop("pkg", None)

            self.install_finder(template)
            with pytest.raises(ImportError, match="pkg"):
                import_module("pkg")

    def test_similar_name(self, tmp_path):
        files = {
            "foo": {
                "__init__.py": "",
                "bar": {
                    "__init__.py": "",
                },
            },
        }
        jaraco.path.build(files, prefix=tmp_path)

        mapping = {
            "foo": str(tmp_path / "foo"),
        }
        template = _finder_template(str(uuid4()), mapping, {})

        with contexts.save_paths(), contexts.save_sys_modules():
            sys.modules.pop("foo", None)
            sys.modules.pop("foo.bar", None)

            self.install_finder(template)
            with pytest.raises(ImportError, match="foobar"):
                import_module("foobar")

    def test_case_sensitivity(self, tmp_path):
        files = {
            "foo": {
                "__init__.py": "",
                "lowercase.py": "x = 1",
                "bar": {
                    "__init__.py": "",
                    "lowercase.py": "x = 2",
                },
            },
        }
        jaraco.path.build(files, prefix=tmp_path)
        mapping = {
            "foo": str(tmp_path / "foo"),
        }
        template = _finder_template(str(uuid4()), mapping, {})
        with contexts.save_paths(), contexts.save_sys_modules():
            sys.modules.pop("foo", None)

            self.install_finder(template)
            with pytest.raises(ImportError, match="'FOO'"):
                import_module("FOO")

            with pytest.raises(ImportError, match="'foo\\.LOWERCASE'"):
                import_module("foo.LOWERCASE")

            with pytest.raises(ImportError, match="'foo\\.bar\\.Lowercase'"):
                import_module("foo.bar.Lowercase")

            with pytest.raises(ImportError, match="'foo\\.BAR'"):
                import_module("foo.BAR.lowercase")

            with pytest.raises(ImportError, match="'FOO'"):
                import_module("FOO.bar.lowercase")

            mod = import_module("foo.lowercase")
            assert mod.x == 1

            mod = import_module("foo.bar.lowercase")
            assert mod.x == 2

    def test_namespace_case_sensitivity(self, tmp_path):
        files = {
            "pkg": {
                "__init__.py": "a = 13",
                "foo": {
                    "__init__.py": "b = 37",
                    "bar.py": "c = 42",
                },
            },
        }
        jaraco.path.build(files, prefix=tmp_path)

        mapping = {"ns.othername": str(tmp_path / "pkg")}
        namespaces = {"ns": []}

        template = _finder_template(str(uuid4()), mapping, namespaces)
        with contexts.save_paths(), contexts.save_sys_modules():
            for mod in ("ns", "ns.othername"):
                sys.modules.pop(mod, None)

            self.install_finder(template)
            pkg = import_module("ns.othername")
            expected = str((tmp_path / "pkg").resolve())
            assert_path(pkg, expected)
            assert pkg.a == 13

            foo = import_module("ns.othername.foo")
            assert foo.b == 37

            bar = import_module("ns.othername.foo.bar")
            assert bar.c == 42

            with pytest.raises(ImportError, match="'NS'"):
                import_module("NS.othername.foo")

            with pytest.raises(ImportError, match="'ns\\.othername\\.FOO\\'"):
                import_module("ns.othername.FOO")

            with pytest.raises(ImportError, match="'ns\\.othername\\.foo\\.BAR\\'"):
                import_module("ns.othername.foo.BAR")

    def test_intermediate_packages(self, tmp_path):
        """
        The finder should not import ``fullname`` if the intermediate segments
        don't exist (see pypa/setuptools#4019).
        """
        files = {
            "src": {
                "mypkg": {
                    "__init__.py": "",
                    "config.py": "a = 13",
                    "helloworld.py": "b = 13",
                    "components": {
                        "config.py": "a = 37",
                    },
                },
            }
        }
        jaraco.path.build(files, prefix=tmp_path)

        mapping = {"mypkg": str(tmp_path / "src/mypkg")}
        template = _finder_template(str(uuid4()), mapping, {})

        with contexts.save_paths(), contexts.save_sys_modules():
            for mod in (
                "mypkg",
                "mypkg.config",
                "mypkg.helloworld",
                "mypkg.components",
                "mypkg.components.config",
                "mypkg.components.helloworld",
            ):
                sys.modules.pop(mod, None)

            self.install_finder(template)

            config = import_module("mypkg.components.config")
            assert config.a == 37

            helloworld = import_module("mypkg.helloworld")
            assert helloworld.b == 13

            with pytest.raises(ImportError):
                import_module("mypkg.components.helloworld")


def test_pkg_roots(tmp_path):
    """This test focus in getting a particular implementation detail right.
    If at some point in time the implementation is changed for something different,
    this test can be modified or even excluded.
    """
    files = {
        "a": {"b": {"__init__.py": "ab = 1"}, "__init__.py": "a = 1"},
        "d": {"__init__.py": "d = 1", "e": {"__init__.py": "de = 1"}},
        "f": {"g": {"h": {"__init__.py": "fgh = 1"}}},
        "other": {"__init__.py": "abc = 1"},
        "another": {"__init__.py": "abcxyz = 1"},
        "yet_another": {"__init__.py": "mnopq = 1"},
    }
    jaraco.path.build(files, prefix=tmp_path)
    package_dir = {
        "a.b.c": "other",
        "a.b.c.x.y.z": "another",
        "m.n.o.p.q": "yet_another",
    }
    packages = [
        "a",
        "a.b",
        "a.b.c",
        "a.b.c.x.y",
        "a.b.c.x.y.z",
        "d",
        "d.e",
        "f",
        "f.g",
        "f.g.h",
        "m.n.o.p.q",
    ]
    roots = _find_package_roots(packages, package_dir, tmp_path)
    assert roots == {
        "a": str(tmp_path / "a"),
        "a.b.c": str(tmp_path / "other"),
        "a.b.c.x.y.z": str(tmp_path / "another"),
        "d": str(tmp_path / "d"),
        "f": str(tmp_path / "f"),
        "m.n.o.p.q": str(tmp_path / "yet_another"),
    }

    ns = set(dict(_find_namespaces(packages, roots)))
    assert ns == {"f", "f.g"}

    ns = set(_find_virtual_namespaces(roots))
    assert ns == {"a.b", "a.b.c.x", "a.b.c.x.y", "m", "m.n", "m.n.o", "m.n.o.p"}


class TestOverallBehaviour:
    PYPROJECT = """\
        [build-system]
        requires = ["setuptools"]
        build-backend = "setuptools.build_meta"

        [project]
        name = "mypkg"
        version = "3.14159"
        """

    # Any: Would need a TypedDict. Keep it simple for tests
    FLAT_LAYOUT: dict[str, Any] = {
        "pyproject.toml": dedent(PYPROJECT),
        "MANIFEST.in": EXAMPLE["MANIFEST.in"],
        "otherfile.py": "",
        "mypkg": {
            "__init__.py": "",
            "mod1.py": "var = 42",
            "subpackage": {
                "__init__.py": "",
                "mod2.py": "var = 13",
                "resource_file.txt": "resource 39",
            },
        },
    }

    EXAMPLES = {
        "flat-layout": FLAT_LAYOUT,
        "src-layout": {
            "pyproject.toml": dedent(PYPROJECT),
            "MANIFEST.in": EXAMPLE["MANIFEST.in"],
            "otherfile.py": "",
            "src": {"mypkg": FLAT_LAYOUT["mypkg"]},
        },
        "custom-layout": {
            "pyproject.toml": dedent(PYPROJECT)
            + dedent(
                """\
                [tool.setuptools]
                packages = ["mypkg", "mypkg.subpackage"]

                [tool.setuptools.package-dir]
                "mypkg.subpackage" = "other"
                """
            ),
            "MANIFEST.in": EXAMPLE["MANIFEST.in"],
            "otherfile.py": "",
            "mypkg": {
                "__init__.py": "",
                "mod1.py": FLAT_LAYOUT["mypkg"]["mod1.py"],
            },
            "other": FLAT_LAYOUT["mypkg"]["subpackage"],
        },
        "namespace": {
            "pyproject.toml": dedent(PYPROJECT),
            "MANIFEST.in": EXAMPLE["MANIFEST.in"],
            "otherfile.py": "",
            "src": {
                "mypkg": {
                    "mod1.py": FLAT_LAYOUT["mypkg"]["mod1.py"],
                    "subpackage": FLAT_LAYOUT["mypkg"]["subpackage"],
                },
            },
        },
    }

    @pytest.mark.xfail(sys.platform == "darwin", reason="pypa/setuptools#4328")
    @pytest.mark.parametrize("layout", EXAMPLES.keys())
    def test_editable_install(self, tmp_path, venv, layout, editable_opts):
        project, _ = install_project(
            "mypkg", venv, tmp_path, self.EXAMPLES[layout], *editable_opts
        )

        # Ensure stray files are not importable
        cmd_import_error = """\
        try:
            import otherfile
        except ImportError as ex:
            print(ex)
        """
        out = venv.run(["python", "-c", dedent(cmd_import_error)])
        assert "No module named 'otherfile'" in out

        # Ensure the modules are importable
        cmd_get_vars = """\
        import mypkg, mypkg.mod1, mypkg.subpackage.mod2
        print(mypkg.mod1.var, mypkg.subpackage.mod2.var)
        """
        out = venv.run(["python", "-c", dedent(cmd_get_vars)])
        assert "42 13" in out

        # Ensure resources are reachable
        cmd_get_resource = """\
        import mypkg.subpackage
        from setuptools._importlib import resources as importlib_resources
        text = importlib_resources.files(mypkg.subpackage) / "resource_file.txt"
        print(text.read_text(encoding="utf-8"))
        """
        out = venv.run(["python", "-c", dedent(cmd_get_resource)])
        assert "resource 39" in out

        # Ensure files are editable
        mod1 = next(project.glob("**/mod1.py"))
        mod2 = next(project.glob("**/mod2.py"))
        resource_file = next(project.glob("**/resource_file.txt"))

        mod1.write_text("var = 17", encoding="utf-8")
        mod2.write_text("var = 781", encoding="utf-8")
        resource_file.write_text("resource 374", encoding="utf-8")

        out = venv.run(["python", "-c", dedent(cmd_get_vars)])
        assert "42 13" not in out
        assert "17 781" in out

        out = venv.run(["python", "-c", dedent(cmd_get_resource)])
        assert "resource 39" not in out
        assert "resource 374" in out


class TestLinkTree:
    FILES = deepcopy(TestOverallBehaviour.EXAMPLES["src-layout"])
    FILES["pyproject.toml"] += dedent(
        """\
        [tool.setuptools]
        # Temporary workaround: both `include-package-data` and `package-data` configs
        # can be removed after #3260 is fixed.
        include-package-data = false
        package-data = {"*" = ["*.txt"]}

        [tool.setuptools.packages.find]
        where = ["src"]
        exclude = ["*.subpackage*"]
        """
    )
    FILES["src"]["mypkg"]["resource.not_in_manifest"] = "abc"

    def test_generated_tree(self, tmp_path):
        jaraco.path.build(self.FILES, prefix=tmp_path)

        with _Path(tmp_path):
            name = "mypkg-3.14159"
            dist = Distribution({"script_name": "%PEP 517%"})
            dist.parse_config_files()

            wheel = Mock()
            aux = tmp_path / ".aux"
            build = tmp_path / ".build"
            aux.mkdir()
            build.mkdir()

            build_py = dist.get_command_obj("build_py")
            build_py.editable_mode = True
            build_py.build_lib = str(build)
            build_py.ensure_finalized()
            outputs = build_py.get_outputs()
            output_mapping = build_py.get_output_mapping()

            make_tree = _LinkTree(dist, name, aux, build)
            make_tree(wheel, outputs, output_mapping)

            mod1 = next(aux.glob("**/mod1.py"))
            expected = tmp_path / "src/mypkg/mod1.py"
            assert_link_to(mod1, expected)

            assert next(aux.glob("**/subpackage"), None) is None
            assert next(aux.glob("**/mod2.py"), None) is None
            assert next(aux.glob("**/resource_file.txt"), None) is None

            assert next(aux.glob("**/resource.not_in_manifest"), None) is None

    def test_strict_install(self, tmp_path, venv):
        opts = ["--config-settings", "editable-mode=strict"]
        install_project("mypkg", venv, tmp_path, self.FILES, *opts)

        out = venv.run(["python", "-c", "import mypkg.mod1; print(mypkg.mod1.var)"])
        assert "42" in out

        # Ensure packages excluded from distribution are not importable
        cmd_import_error = """\
        try:
            from mypkg import subpackage
        except ImportError as ex:
            print(ex)
        """
        out = venv.run(["python", "-c", dedent(cmd_import_error)])
        assert "cannot import name 'subpackage'" in out

        # Ensure resource files excluded from distribution are not reachable
        cmd_get_resource = """\
        import mypkg
        from setuptools._importlib import resources as importlib_resources
        try:
            text = importlib_resources.files(mypkg) / "resource.not_in_manifest"
            print(text.read_text(encoding="utf-8"))
        except FileNotFoundError as ex:
            print(ex)
        """
        out = venv.run(["python", "-c", dedent(cmd_get_resource)])
        assert "No such file or directory" in out
        assert "resource.not_in_manifest" in out


@pytest.mark.filterwarnings("ignore:.*compat.*:setuptools.SetuptoolsDeprecationWarning")
def test_compat_install(tmp_path, venv):
    # TODO: Remove `compat` after Dec/2022.
    opts = ["--config-settings", "editable-mode=compat"]
    files = TestOverallBehaviour.EXAMPLES["custom-layout"]
    install_project("mypkg", venv, tmp_path, files, *opts)

    out = venv.run(["python", "-c", "import mypkg.mod1; print(mypkg.mod1.var)"])
    assert "42" in out

    expected_path = comparable_path(str(tmp_path))

    # Compatible behaviour will make spurious modules and excluded
    # files importable directly from the original path
    for cmd in (
        "import otherfile; print(otherfile)",
        "import other; print(other)",
        "import mypkg; print(mypkg)",
    ):
        out = comparable_path(venv.run(["python", "-c", cmd]))
        assert expected_path in out

    # Compatible behaviour will not consider custom mappings
    cmd = """\
    try:
        from mypkg import subpackage;
    except ImportError as ex:
        print(ex)
    """
    out = venv.run(["python", "-c", dedent(cmd)])
    assert "cannot import name 'subpackage'" in out


def test_pbr_integration(tmp_path, venv, editable_opts):
    """Ensure editable installs work with pbr, issue #3500"""
    files = {
        "pyproject.toml": dedent(
            """\
            [build-system]
            requires = ["setuptools"]
            build-backend = "setuptools.build_meta"
            """
        ),
        "setup.py": dedent(
            """\
            __import__('setuptools').setup(
                pbr=True,
                setup_requires=["pbr"],
            )
            """
        ),
        "setup.cfg": dedent(
            """\
            [metadata]
            name = mypkg

            [files]
            packages =
                mypkg
            """
        ),
        "mypkg": {
            "__init__.py": "",
            "hello.py": "print('Hello world!')",
        },
        "other": {"test.txt": "Another file in here."},
    }
    venv.run(["python", "-m", "pip", "install", "pbr"])

    with contexts.environment(PBR_VERSION="0.42"):
        install_project("mypkg", venv, tmp_path, files, *editable_opts)

    out = venv.run(["python", "-c", "import mypkg.hello"])
    assert "Hello world!" in out


class TestCustomBuildPy:
    """
    Issue #3501 indicates that some plugins/customizations might rely on:

    1. ``build_py`` not running
    2. ``build_py`` always copying files to ``build_lib``

    During the transition period setuptools should prevent potential errors from
    happening due to those assumptions.
    """

    # TODO: Remove tests after _run_build_steps is removed.

    FILES = {
        **TestOverallBehaviour.EXAMPLES["flat-layout"],
        "setup.py": dedent(
            """\
            import pathlib
            from setuptools import setup
            from setuptools.command.build_py import build_py as orig

            class my_build_py(orig):
                def run(self):
                    super().run()
                    raise ValueError("TEST_RAISE")

            setup(cmdclass={"build_py": my_build_py})
            """
        ),
    }

    def test_safeguarded_from_errors(self, tmp_path, venv):
        """Ensure that errors in custom build_py are reported as warnings"""
        # Warnings should show up
        _, out = install_project("mypkg", venv, tmp_path, self.FILES)
        assert "SetuptoolsDeprecationWarning" in out
        assert "ValueError: TEST_RAISE" in out
        # but installation should be successful
        out = venv.run(["python", "-c", "import mypkg.mod1; print(mypkg.mod1.var)"])
        assert "42" in out


class TestCustomBuildWheel:
    def install_custom_build_wheel(self, dist):
        bdist_wheel_cls = dist.get_command_class("bdist_wheel")

        class MyBdistWheel(bdist_wheel_cls):
            def get_tag(self):
                # In issue #3513, we can see that some extensions may try to access
                # the `plat_name` property in bdist_wheel
                if self.plat_name.startswith("macosx-"):
                    _ = "macOS platform"
                return super().get_tag()

        dist.cmdclass["bdist_wheel"] = MyBdistWheel

    def test_access_plat_name(self, tmpdir_cwd):
        # Even when a custom bdist_wheel tries to access plat_name the build should
        # be successful
        jaraco.path.build({"module.py": "x = 42"})
        dist = Distribution()
        dist.script_name = "setup.py"
        dist.set_defaults()
        self.install_custom_build_wheel(dist)
        cmd = editable_wheel(dist)
        cmd.ensure_finalized()
        cmd.run()
        wheel_file = str(next(Path().glob('dist/*.whl')))
        assert "editable" in wheel_file


class TestCustomBuildExt:
    def install_custom_build_ext_distutils(self, dist):
        from distutils.command.build_ext import build_ext as build_ext_cls

        class MyBuildExt(build_ext_cls):
            pass

        dist.cmdclass["build_ext"] = MyBuildExt

    @pytest.mark.skipif(
        sys.platform != "linux", reason="compilers may fail without correct setup"
    )
    def test_distutils_leave_inplace_files(self, tmpdir_cwd):
        jaraco.path.build({"module.c": ""})
        attrs = {
            "ext_modules": [Extension("module", ["module.c"])],
        }
        dist = Distribution(attrs)
        dist.script_name = "setup.py"
        dist.set_defaults()
        self.install_custom_build_ext_distutils(dist)
        cmd = editable_wheel(dist)
        cmd.ensure_finalized()
        cmd.run()
        wheel_file = str(next(Path().glob('dist/*.whl')))
        assert "editable" in wheel_file
        files = [p for p in Path().glob("module.*") if p.suffix != ".c"]
        assert len(files) == 1
        name = files[0].name
        assert any(name.endswith(ext) for ext in EXTENSION_SUFFIXES)


def test_debugging_tips(tmpdir_cwd, monkeypatch):
    """Make sure to display useful debugging tips to the user."""
    jaraco.path.build({"module.py": "x = 42"})
    dist = Distribution()
    dist.script_name = "setup.py"
    dist.set_defaults()
    cmd = editable_wheel(dist)
    cmd.ensure_finalized()

    SimulatedErr = type("SimulatedErr", (Exception,), {})
    simulated_failure = Mock(side_effect=SimulatedErr())
    monkeypatch.setattr(cmd, "get_finalized_command", simulated_failure)

    expected_msg = "following steps are recommended to help debug"
    with pytest.raises(SimulatedErr), pytest.warns(_DebuggingTips, match=expected_msg):
        cmd.run()


@pytest.mark.filterwarnings("error")
def test_encode_pth():
    """Ensure _encode_pth function does not produce encoding warnings"""
    content = _encode_pth("tkmilan_ç_utf8")  # no warnings (would be turned into errors)
    assert isinstance(content, bytes)


def install_project(name, venv, tmp_path, files, *opts):
    project = tmp_path / name
    project.mkdir()
    jaraco.path.build(files, prefix=project)
    opts = [*opts, "--no-build-isolation"]  # force current version of setuptools
    out = venv.run(
        ["python", "-m", "pip", "-v", "install", "-e", str(project), *opts],
        stderr=subprocess.STDOUT,
    )
    return project, out


def _addsitedirs(new_dirs):
    """To use this function, it is necessary to insert new_dir in front of sys.path.
    The Python process will try to import a ``sitecustomize`` module on startup.
    If we manipulate sys.path/PYTHONPATH, we can force it to run our code,
    which invokes ``addsitedir`` and ensure ``.pth`` files are loaded.
    """
    content = '\n'.join(
        ("import site",)
        + tuple(f"site.addsitedir({os.fspath(new_dir)!r})" for new_dir in new_dirs)
    )
    (new_dirs[0] / "sitecustomize.py").write_text(content, encoding="utf-8")


# ---- Assertion Helpers ----


def assert_path(pkg, expected):
    # __path__ is not guaranteed to exist, so we have to account for that
    if pkg.__path__:
        path = next(iter(pkg.__path__), None)
        if path:
            assert str(Path(path).resolve()) == expected


def assert_link_to(file: Path, other: Path) -> None:
    if file.is_symlink():
        assert str(file.resolve()) == str(other.resolve())
    else:
        file_stat = file.stat()
        other_stat = other.stat()
        assert file_stat[stat.ST_INO] == other_stat[stat.ST_INO]
        assert file_stat[stat.ST_DEV] == other_stat[stat.ST_DEV]


def comparable_path(str_with_path: str) -> str:
    return str_with_path.lower().replace(os.sep, "/").replace("//", "/")