1@echo off
2setlocal enableextensions
3setlocal enabledelayedexpansion
4
5
6::
7:: Tests for GOTO and CALL.
8::
9
10
11:: GOTO/CALL jump to labels present forward to their call-point. Only when
12:: the label cannot be found forward, the search is then restarted from the
13:: beginning of the batch file onwards up to the original call-point.
14
15:: GOTO with a label parameter without ':' works.
16goto test_start
17
18:: Execution must never go there!
19:test_goto
20echo Unexpected GOTO jump^^!
21exit
22:test_call
23echo Unexpected CALL jump^^!
24goto :EOF
25
26
27:test_start
28
29:: Testing GOTO/CALL forwards.
30echo --------- Testing GOTO ---------
31goto :test_goto
32
33:do_test_call
34echo --------- Testing CALL within batch ---------
35call :test_call
36goto :continue
37
38:test_goto
39echo Test GOTO ok
40:: GOTO also understands '+' instead of ':' in its label parameter.
41goto +do_test_call
42
43:test_call
44echo Test CALL ok from %0
45:: We exit this CALL invocation
46goto :EOF
47
48
49::
50:: Next suite of tests.
51::
52
53:: GOTO label search algorithm ignores any whitespace between ':'
54:: and the label name, as well as leading and trailing whitespace.
55  :@tab@continue@space@@space@
56
57:: CALL does an extra round of variable substitution.
58
59:: Existing labels (see below)
60set VAR=TOTO
61call :dest%VAR%
62call :dest%%VAR%%
63call :dest!VAR!
64call :dest!!VAR!!
65
66set VAR=1
67call :dest%VAR%
68call :dest%%VAR%%
69call :dest!VAR!
70call :dest!!VAR!!
71
72:: Exercise different whitespace separations
73call :dest1@space@@tab@ a b c
74call @space@:dest1@space@@space@
75
76:: Similar to GOTO, whitespace between ':' and the label name parameter are NOT ignored.
77call :setError 0
78call @space@:@space@dest1@space@@space@
79if %errorlevel% equ 0 (echo Unexpected: CALL did not fail^^!) else echo OK
80
81:: Only GOTO understands '+' instead of ':'.
82:: Here '+dest1' is understood as a (non-existing) command.
83call :setError 0
84call @space@+dest1@space@@space@
85if %errorlevel% equ 0 (echo Unexpected: CALL did not fail^^!) else echo OK
86
87
88:: Calling a label with escape carets.
89call :la^^bel1
90
91:: Jumping to a label with escape carets.
92goto :la^^bel2
93
94
95:: Label with percents (should not be called)
96:dest%%VAR%%
97echo Unexpected CALL/GOTO jump^^!
98
99:: Valid label (called from the variable substitution tests above)
100:destTOTO
101echo Hi there^^!
102:: We exit this CALL invocation
103goto :EOF
104
105:: Valid label with arbitrary first character before ':'
106:: (not just '@' as supposed by cmd_winetests!)
107?:de^st1
108echo goto with unrelated first character, and escape carets worked
109echo Params: '%0', '%1', '%2', '%3'
110:: We exit this CALL invocation
111goto :EOF
112
113
114:: Label with escape carets
115:la^bel1
116echo Unexpected CALL jump^^!
117:la^^bel1
118echo CALL with escape caret worked
119:: We exit this CALL invocation
120goto :EOF
121
122:la^bel2
123echo Unexpected GOTO jump^^!
124:la^^bel2
125echo GOTO with escape caret worked
126
127
128:: Go to the next tests below.
129goto :continue
130
131
132::
133:: Next suite of tests.
134::
135:continue
136
137
138::
139:: Extra GOTO syntax checks: separators in the label parameter
140::
141
142:: Whitespace
143goto :testLbl1@tab@ignored
144:testLbl1
145echo Hi there^^!
146
147:: Colon
148goto :testLbl2:ignored
149:testLbl2
150echo Hi there^^!
151
152:: Plus sign
153goto :testLbl3+ignored
154:testLbl3
155echo Hi there^^!
156
157:: Comma
158goto :testLbl4,ignored
159:testLbl4
160echo Hi there^^!
161
162:: Semicolon
163goto :testLbl5;ignored
164:testLbl5
165echo Hi there^^!
166
167:: Equals
168goto :testLbl6;ignored
169:testLbl6
170echo Hi there^^!
171
172
173::
174:: Testing :EOF support
175::
176echo --------- Testing :EOF support ---------
177
178:: Use an auxiliary CMD file to test GOTO :EOF
179mkdir foobar && cd foobar
180
181:: This CALL will fail: :EOF is indeed a reserved label for GOTO only.
182call :setError 0
183call :EOF
184if %errorlevel% equ 0 (echo Unexpected: CALL :EOF did not fail^^!) else echo OK
185
186:: This CALL will succeed silently: only the first ':' of ::EOF is stripped,
187:: thus calling in a new batch context GOTO :EOF.
188call :setError 0
189call ::EOF
190if %errorlevel% neq 0 (echo Unexpected: CALL ::EOF did fail^^!) else echo OK
191
192:: GOTO :EOF is available only if commands extensions are enabled
193echo @echo off> tmp.cmd
194echo setlocal disableextensions>> tmp.cmd
195echo goto :eof>> tmp.cmd
196call :setError 0
197cmd /c tmp.cmd
198if %errorlevel% equ 0 (echo Unexpected: GOTO :EOF did not fail^^!) else echo OK
199
200:: GOTO :EOF is done only if the ":EOF" part is followed by whitespace or ends.
201:: The following two GOTO's fail because the labels cannot be found.
202echo @echo off> tmp.cmd
203echo setlocal enableextensions>> tmp.cmd
204echo goto :eof,lol>> tmp.cmd
205echo echo Batch continues^^!>> tmp.cmd
206call :setError 0
207cmd /c tmp.cmd
208if %errorlevel% equ 0 (echo Unexpected: GOTO :eof,lol did not fail^^!) else echo OK
209
210echo @echo off> tmp.cmd
211echo setlocal enableextensions>> tmp.cmd
212echo goto :eof:lol>> tmp.cmd
213echo echo Batch continues^^!>> tmp.cmd
214call :setError 0
215cmd /c tmp.cmd
216if %errorlevel% equ 0 (echo Unexpected: GOTO :eof:lol did not fail^^!) else echo OK
217
218:: GOTO :EOF expects at least one whitespace character before anything else.
219:: Not even '+',':' or other separators are allowed.
220echo @echo off> tmp.cmd
221echo setlocal enableextensions>> tmp.cmd
222echo goto :eof+lol>> tmp.cmd
223echo echo Batch continues^^!>> tmp.cmd
224call :setError 0
225cmd /c tmp.cmd
226if %errorlevel% equ 0 (echo Unexpected: GOTO :eof+lol did not fail^^!) else echo OK
227
228:: This GOTO :EOF works.
229echo @echo off> tmp.cmd
230echo setlocal enableextensions>> tmp.cmd
231echo goto :eof@tab@+lol>> tmp.cmd
232echo echo You should not see this^^!>> tmp.cmd
233call :setError 0
234cmd /c tmp.cmd
235if %errorlevel% neq 0 (echo Unexpected: GOTO :EOF did fail^^!) else echo OK
236
237
238:: Cleanup
239cd .. & rd /s/q foobar
240
241
242::
243:: Testing GOTO/CALL from and to within parenthesized blocks.
244::
245
246echo --------- Testing GOTO within block ---------
247(echo Block-test 1: Single-line& goto :block2 & echo Unexpected Block-test 1^^!)
248echo Unexpected echo 1^^!
249
250:block2
251(
252echo Block-test 2: Multi-line
253goto :block3
254echo Unexpected Block-test 2^^!
255)
256echo Unexpected echo 2-3^^!
257
258:test_call_block
259echo Test CALL in block OK from %0
260:: We exit this CALL invocation
261goto :EOF
262
263(
264:block3
265echo --------- Testing CALL within block ---------
266echo Block-test 3: CALL in block
267call :test_call_block
268echo CALL done
269)
270
271goto :block4
272echo Unexpected echo 4^^!
273(
274:block4
275echo Block-test 4 OK
276)
277
278
279::
280:: Testing GOTO/CALL from within FOR and IF.
281:: This is a situation similar to the parenthesized blocks.
282:: See bug-report CORE-13713
283::
284
285:: Testing CALL within FOR
286echo --------- Testing CALL within FOR ---------
287for /L %%A IN (0,1,3) DO (
288    set Number=%%A
289    if %%A==2 call :out_of_loop_1 %%A
290    if %%A==2 (echo %%A IS equal to 2) else (echo %%A IS NOT equal to 2)
291)
292goto :continue_2
293:out_of_loop_1
294echo Out of FOR 1 CALL from %0, number is %1
295:: We exit this CALL invocation
296goto :EOF
297:continue_2
298
299
300:: Testing GOTO within FOR
301echo --------- Testing GOTO within FOR ---------
302for /L %%A IN (0,1,3) DO (
303    set Number=%%A
304    if %%A==2 goto :out_of_loop_2
305    echo %%A IS NOT equal to 2
306)
307echo Unexpected FOR echo 2^^!
308:out_of_loop_2
309echo Out of FOR 2, number is %Number%
310
311
312
313::
314:: Show how each different FOR-loop stops when a GOTO is encountered.
315::
316echo --------- Testing FOR loop stopping with GOTO ---------
317
318:: FOR - Stops directly
319echo --- FOR
320@echo on
321for %%A in (1,2,3,4,5,6,7,8,9,10) do (
322    set Number=%%A
323    if %%A==5 goto :out_of_loop_2a
324)
325echo Unexpected FOR echo 2a^^!
326:out_of_loop_2a
327echo Out of FOR 2a, number is %Number%
328@echo off
329
330
331:: FOR /R - Stops directly
332echo --- FOR /R
333
334:: Use auxiliary directoreis to test for /R
335mkdir foobar && cd foobar
336mkdir foo1
337mkdir foo2
338mkdir bar1
339
340@echo on
341for /r %%A in (1,2,3,4,5,6,7,8,9,10) do (
342    set Number=%%~nA
343    if %%~nA==5 goto :out_of_loop_2b
344)
345echo Unexpected FOR echo 2b^^!
346:out_of_loop_2b
347echo Out of FOR 2b, number is %Number%
348@echo off
349
350:: Cleanup
351cd .. & rd /s/q foobar
352
353
354:: FOR /L - Does not stop directly. It continues looping until the end
355:: but does not execute its body code. This can cause problems e.g. for
356:: infinite loops "for /l %a in () do ( ... )" that are exited by EXIT /B,
357:: since the body code stops being executed, but the loop itself continues
358:: running forever.
359echo --- FOR /L
360@echo on
361for /l %%A in (1,1,10) do (
362    set Number=%%A
363    if %%A==5 goto :out_of_loop_2c
364)
365echo Unexpected FOR echo 2c^^!
366:out_of_loop_2c
367echo Out of FOR 2c, number is %Number%
368@echo off
369
370
371:: FOR /F - Stops directly.
372echo --- FOR /F
373@echo on
374for %%T in ( "1:2:3" "4:5:6:7" "8:9:10" ) do (
375   set "pc=%%~T"
376   for /f "delims=" %%A in (^"!pc::^=^
377% New line %
378!^") do (
379
380    set Number=%%A
381    if %%A==5 goto :out_of_loop_2d
382)
383)
384echo Unexpected FOR echo 2d^^!
385:out_of_loop_2d
386echo Out of FOR 2d, number is %Number%
387@echo off
388
389
390
391:: Testing CALL within IF
392echo --------- Testing CALL within IF ---------
393if 1==1 (
394    call :out_of_if_1 123
395    echo Success IF echo 1
396)
397goto :continue_3
398:out_of_if_1
399echo Out of IF CALL from %0, number is %1
400:: We exit this CALL invocation
401goto :EOF
402:continue_3
403
404
405:: Testing GOTO within IF
406echo --------- Testing GOTO within IF ---------
407if 1==1 (
408    goto :out_of_if_2
409    echo Unexpected IF echo 2a^^!
410)
411echo Unexpected IF echo 2b^^!
412:out_of_if_2
413echo Out of IF ok
414
415:: Same, but with line-continuation at the closing parenthesis of the IF block.
416if 1==1 (
417:labelA
418    echo A
419) ^
420else (
421:labelB
422    echo B
423    goto :continue
424)
425:: We are jumping inside the IF, whose block will be interpreted as
426:: separate commands; thus we will also run the :labelB block as well.
427goto :labelA
428
429
430::
431:: Next suite of tests.
432::
433:continue
434
435:: Testing EXIT within IF
436echo --------- Testing EXIT within IF ---------
437
438:: Use a CALL context, and we will only check EXIT /B.
439call :doExitIfTest 1
440call :doExitIfTest 2
441goto :continue
442
443:doExitIfTest
444if %1==1 (
445    echo First block
446    exit /b
447    echo Unexpected first block^^!
448) else (
449    echo Second block
450    exit /b
451    echo Unexpected second block^^!
452)
453echo You won't see this^^!
454exit /b
455
456
457::
458:: Next suite of tests.
459::
460:continue
461
462echo --------- Testing CALL (triggers GOTO /?) ---------
463
464::
465:: Test that shows that CALL :label with the label name containing /?
466:: at delayed time, internally calls GOTO, and will end up calling GOTO /?,
467:: then jumps to the next command.
468::
469:: Adapted from https://stackoverflow.com/q/31987023/13530036
470:: and from https://stackoverflow.com/a/38938416/13530036
471:: The author of this test calls that "anonymous functions".
472::
473set "label=/?"
474
475:: Test 1: GOTO /? will be called.
476:: Since it is expected that the code below the CALL will also be called in
477:: its context, but we want its output to be redirected to STDOUT, we use a
478:: different redirection file so that it does not go into the tmp.txt file.
479echo --- Direct label, redirection
480
481:: Use an auxiliary CMD file
482mkdir foobar && cd foobar
483
484:: Call the label and redirect STDOUT to a file for later filtering.
485call :%%label%% argument > tmp.txt
486:: The following commands are called also within the CALL context.
487:: The message will be displayed both in the CALL context, and in the main one.
488echo Test message -- '%0','%*'>> tmpMsg.txt
489:: Within the CALL context, this is the expected value of %0. Quit early.
490if "%0" == ":/?" (
491    echo Arguments: '%*'>> tmpMsg.txt
492    exit /B
493)
494
495:: Back to the main context.
496:: Display the redirected messages, filtering out the batch file name.
497for /f "delims=" %%x in (tmpMsg.txt) do (
498    set "msg=%%x"
499    echo !msg:%0=!
500)
501
502:: Check whether CALL displayed GOTO help or CALL help.
503:: We differentiate between both, because GOTO /? mentions CALL /?, but CALL
504:: help does not mention GOTO, and contains the substring "CALL [" (part of the
505:: syntax example) that allows to discriminate between both cases.
506find "GOTO" tmp.txt > NUL && echo OK, GOTO help.|| echo Unexpected, no GOTO help^^!
507find "CALL [" tmp.txt > NUL && echo Unexpected CALL help^^!
508
509:: Cleanup
510del tmp.txt tmpMsg.txt > NUL
511
512:: Test 2: CALL /? will be called if piping.
513echo --- Direct label, piping
514
515call :%%label%% argument | (find "CALL [" > NUL && echo OK, CALL help.|| echo Unexpected, no CALL help^^!)
516echo Test message -- '%0','%*'>> tmpMsg.txt
517if "%0" == ":/?" (
518    echo Arguments: '%*'>> tmpMsg.txt
519    exit /B
520)
521
522:: Back to the main context.
523:: Display the redirected messages, filtering out the batch file name.
524for /f "delims=" %%x in (tmpMsg.txt) do (
525    set "msg=%%x"
526    echo !msg:%0=!
527)
528
529:: Cleanup
530del tmpMsg.txt > NUL
531cd .. & rd /s/q foobar
532
533
534::
535:: Repeat the same tests as above, but now with a slightly different label.
536::
537echo --------- Testing CALL with escape carets (triggers GOTO /?) ---------
538
539set "help=^ /? ^^^^arg"
540
541:: Test 1: GOTO /? will be called.
542:: See the explanations in the previous tests above
543echo --- Direct label, redirection
544
545:: Use an auxiliary CMD file
546mkdir foobar && cd foobar
547
548:: Call the label and redirect STDOUT to a file for later filtering.
549call :myLabel%%help%% ^^^^argument > tmp.txt
550echo Test message -- '%0','%*'>> tmpMsg.txt
551if "%0" == ":myLabel /?" (
552    echo Arguments: '%*'>> tmpMsg.txt
553    exit /B
554)
555
556:: Back to the main context.
557:: Display the redirected messages, filtering out the batch file name.
558for /f "delims=" %%x in (tmpMsg.txt) do (
559    set "msg=%%x"
560    echo !msg:%0=!
561)
562
563:: Check whether CALL displayed GOTO help or CALL help.
564find "GOTO" tmp.txt > NUL && echo OK, GOTO help.|| echo Unexpected, no GOTO help^^!
565find "CALL [" tmp.txt > NUL && echo Unexpected CALL help^^!
566
567:: Cleanup
568del tmp.txt tmpMsg.txt > NUL
569
570:: Test 2: CALL /? will be called if piping.
571echo --- Direct label, piping
572
573call :myLabel%%help%% ^^^^argument | (find "CALL [" > NUL && echo OK, CALL help.|| echo Unexpected, no CALL help^^!)
574echo Test message -- '%0','%*'>> tmpMsg.txt
575if "%0" == ":myLabel /?" (
576    echo Arguments: '%*'>> tmpMsg.txt
577    exit /B
578)
579
580:: Back to the main context.
581:: Display the redirected messages, filtering out the batch file name.
582for /f "delims=" %%x in (tmpMsg.txt) do (
583    set "msg=%%x"
584    echo !msg:%0=!
585)
586
587:: Cleanup
588del tmpMsg.txt > NUL
589cd .. & rd /s/q foobar
590
591goto :continue
592
593:/?
594:myLabel
595echo Unexpected CALL or GOTO! Arguments: '%*'
596exit /b 0
597
598
599::
600:: Next suite of tests.
601::
602:continue
603
604::
605:: This test will actually call the label.
606:: Adapted from https://stackoverflow.com/a/38938416/13530036
607::
608echo --------- Testing CALL (NOT triggering GOTO /? or CALL /?) ---------
609
610set "var=/?"
611call :sub %%var%%
612goto :otherTest
613
614:sub
615echo Arguments: '%*'
616exit /b
617
618
619:otherTest
620call :sub2 "/?" Hello World
621goto :continue
622
623:sub2
624:: 'args' contains the list of arguments
625set "args=%*"
626:: !args:%1=! will remove the first argument specified by %1 and keep the rest.
627echo %~1 | (find "ECHO [" > NUL && echo OK, ECHO help.|| echo Unexpected, no ECHO help^^!)
628echo/
629echo !args:%1=!
630exit /b
631
632
633::
634:: Next suite of tests.
635::
636:continue
637
638::
639:: CALL supports double delayed expansion.
640:: Test from https://stackoverflow.com/a/31990563/13530036
641::
642echo --------- Testing CALL double delayed expansion ---------
643
644set "label=myLabel"
645set "pointer=^!label^!"
646call :!pointer!
647goto :finished
648
649:myLabel
650echo It works^^!
651exit /b
652
653
654
655::
656:: Finished!
657::
658:finished
659echo --------- Finished  --------------
660goto :EOF
661
662:: Subroutine to set errorlevel and return
663:: in windows nt 4.0, this always sets errorlevel 1, since /b isn't supported
664:setError
665exit /B %1
666:: This line runs under cmd in windows NT 4, but not in more modern versions.
667