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