@@ -30,7 +30,7 @@ test.before((t) => {
3030
3131test . beforeEach ( ( t ) => {
3232 const exists = sinon . stub ( ) . returns ( true ) ;
33- const readlink = sinon . stub ( ) ;
33+ const readlink = sinon . stub ( ) . throws ( ) ;
3434 const which = sinon . stub ( ) ;
3535
3636 t . context . deps = { exists, readlink, which } ;
@@ -187,15 +187,91 @@ test("the executable exists and is a (sym)link", (t) => {
187187 const args = { env, executable } ;
188188
189189 t . context . deps . exists . returns ( true ) ;
190- t . context . deps . readlink . returns ( linkedExecutable ) ;
191190 t . context . deps . which . returns ( resolvedExecutable ) ;
192191
192+ t . context . deps . readlink . onCall ( 0 ) . returns ( linkedExecutable ) ;
193+ t . context . deps . readlink . onCall ( 1 ) . throws ( ) ;
194+
193195 const result = resolveExecutable ( args , t . context . deps ) ;
194196 t . is ( result , linkedExecutable ) ;
195197
196- t . is ( t . context . deps . readlink . callCount , 1 ) ;
197- t . true ( t . context . deps . exists . calledWithExactly ( resolvedExecutable ) ) ;
198+ t . is ( t . context . deps . readlink . callCount , 2 ) ;
199+ t . true ( t . context . deps . readlink . calledWithExactly ( resolvedExecutable ) ) ;
198200
199201 t . is ( t . context . deps . which . callCount , 1 ) ;
200202 t . is ( t . context . deps . exists . callCount , 1 ) ;
201203} ) ;
204+
205+ test ( "the executable exists and is a (sym)link to a (sym)link (absolute)" , ( t ) => {
206+ const { env, executable, linkedExecutable, resolvedExecutable } = t . context ;
207+ const args = { env, executable } ;
208+ const intermediaryLink = "/path/to/link-to-link" ;
209+
210+ t . context . deps . exists . returns ( true ) ;
211+ t . context . deps . which . returns ( resolvedExecutable ) ;
212+
213+ t . context . deps . readlink . onCall ( 0 ) . returns ( intermediaryLink ) ;
214+ t . context . deps . readlink . onCall ( 1 ) . returns ( linkedExecutable ) ;
215+ t . context . deps . readlink . onCall ( 2 ) . throws ( ) ;
216+
217+ const result = resolveExecutable ( args , t . context . deps ) ;
218+ t . is ( result , linkedExecutable ) ;
219+
220+ t . is ( t . context . deps . readlink . callCount , 3 ) ;
221+ t . true ( t . context . deps . readlink . calledWithExactly ( resolvedExecutable ) ) ;
222+ t . true ( t . context . deps . readlink . calledWithExactly ( intermediaryLink ) ) ;
223+
224+ t . is ( t . context . deps . which . callCount , 1 ) ;
225+ t . is ( t . context . deps . exists . callCount , 1 ) ;
226+ } ) ;
227+
228+ test ( "the executable exists and is a (sym)link to a (sym)link (relative)" , ( t ) => {
229+ const { env, executable, linkedExecutable, resolvedExecutable } = t . context ;
230+ const args = { env, executable } ;
231+ const intermediaryLink = "./link-to-link" ;
232+
233+ t . context . deps . exists . returns ( true ) ;
234+ t . context . deps . which . returns ( resolvedExecutable ) ;
235+
236+ t . context . deps . readlink . onCall ( 0 ) . returns ( intermediaryLink ) ;
237+ t . context . deps . readlink . onCall ( 1 ) . returns ( linkedExecutable ) ;
238+ t . context . deps . readlink . onCall ( 2 ) . throws ( ) ;
239+
240+ const result = resolveExecutable ( args , t . context . deps ) ;
241+ t . is ( result , linkedExecutable ) ;
242+
243+ t . is ( t . context . deps . readlink . callCount , 3 ) ;
244+ t . true ( t . context . deps . readlink . calledWithExactly ( resolvedExecutable ) ) ;
245+ t . true ( t . context . deps . readlink . calledWithExactly ( "/path/to/link-to-link" ) ) ;
246+
247+ t . is ( t . context . deps . which . callCount , 1 ) ;
248+ t . is ( t . context . deps . exists . callCount , 1 ) ;
249+ } ) ;
250+
251+ testProp (
252+ "the executable exists but there is a link cycle" ,
253+ [
254+ fc . array ( fc . stringMatching ( / ^ \/ [ a - z ] { 2 , } $ / u) , {
255+ minLength : 1 ,
256+ maxLength : 64 ,
257+ } ) ,
258+ ] ,
259+ ( t , links ) => {
260+ const { env, executable, resolvedExecutable } = t . context ;
261+ const args = { env, executable } ;
262+
263+ t . context . deps . exists . returns ( true ) ;
264+ t . context . deps . which . returns ( resolvedExecutable ) ;
265+
266+ t . context . deps . readlink = sinon . stub ( ) ;
267+ for ( const index in links ) {
268+ t . context . deps . readlink . onCall ( index ) . returns ( links [ index ] ) ;
269+ }
270+ t . context . deps . readlink . onCall ( links . length ) . returns ( links [ 0 ] ) ;
271+
272+ t . throws ( ( ) => resolveExecutable ( args , t . context . deps ) , {
273+ instanceOf : Error ,
274+ message : `${ executable } points to a link loop, cannot resolve shell` ,
275+ } ) ;
276+ } ,
277+ ) ;
0 commit comments