1@echo off
2
3::
4:: Some basic tests
5::
6
7echo ------------ Testing FOR loop ------------
8echo --- Multiple lines
9for %%i in (A
10B
11C) do echo %%i
12
13echo --- Lines and spaces
14for %%i in (D
15 E
16 F) do echo %%i
17
18echo --- Multiple lines and commas
19for %%i in (G,
20H,
21I
22) do echo %%i
23
24echo --- Multiple lines and %%I
25:: The FOR-variable is case-sensitive
26for %%i in (J
27 K
28 L) do echo %%I
29
30echo --- Multiple lines and %%j
31for %%i in (M,
32N,
33O
34) do echo %%j
35
36echo --- FOR /F token parsing
37:: This test requires extensions being enabled
38setlocal enableextensions
39
40set TEST_STRING="_ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ ? @ [ \ ] _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ ? @ [ \ ]"
41set "ECHO_STRING=?=%%? @=%%@ A=%%A B=%%B C=%%C D=%%D E=%%E F=%%F G=%%G H=%%H I=%%I J=%%J K=%%K L=%%L M=%%M N=%%N O=%%O P=%%P Q=%%Q R=%%R S=%%S T=%%T U=%%U V=%%V W=%%W X=%%X Y=%%Y Z=%%Z [=%%[ \=%%\ ]=%%] ^^=%%^^ _=%%_ `=%%` a=%%a b=%%b c=%%c d=%%d e=%%e f=%%f g=%%g h=%%h i=%%i j=%%j k=%%k l=%%l m=%%m n=%%n o=%%o p=%%p q=%%q r=%%r s=%%s t=%%t u=%%u v=%%v w=%%w x=%%x y=%%y z=%%z {=%%{ ^|=%%^| }=%%} ^~=%%^~"
42
43echo.
44
45:: Bug 1: Ranges that are not specified in increasing order are ignored.
46:: Token numbers strictly greater than 31 are just ignored, and if they
47:: appear in a range, the whole range is ignored.
48
49for /f "tokens=30-32" %%? in (%TEST_STRING%) do echo %ECHO_STRING%
50echo.
51for /f "tokens=5-1" %%? in (%TEST_STRING%) do echo %ECHO_STRING%
52echo.
53
54:: Bug 2: Ranges that partially overlap: too many variables are being allocated,
55:: while only a subset is actually used. This leads to the extra variables returning
56:: empty strings.
57
58for /f "tokens=1-31,31,31" %%? in (%TEST_STRING%) do echo %ECHO_STRING%
59echo.
60for /f "tokens=1-31,1-31" %%? in (%TEST_STRING%) do echo %ECHO_STRING%
61echo.
62for /f "tokens=1-5,3,5,6" %%? in (%TEST_STRING%) do echo %ECHO_STRING%
63echo.
64for /f "tokens=1-31,* tokens=1-31 tokens=1-20,*" %%? in (%TEST_STRING%) do echo %ECHO_STRING%
65echo.
66
67:: For comparison, this works:
68for /f "tokens=1-5,6" %%? in (%TEST_STRING%) do echo %ECHO_STRING%
69echo.
70for /f "tokens=1-5,6-10" %%? in (%TEST_STRING%) do echo %ECHO_STRING%
71echo.
72
73endlocal
74
75
76echo ---------- Testing AND operator ----------
77:: Test for TRUE condition - Should be displayed
78ver | find "Ver" > NUL && echo TRUE AND condition
79
80:: Test for FALSE condition - Should not display
81ver | find "1234" > NUL && echo FALSE AND condition
82
83echo ---------- Testing OR operator -----------
84:: Test for TRUE condition - Should not display
85ver | find "Ver" > NUL || echo TRUE OR condition
86
87:: Test for FALSE condition - Should be displayed
88ver | find "1234" > NUL || echo FALSE OR condition
89
90
91
92::
93:: Testing CMD exit codes and errorlevels.
94::
95:: Observations:
96:: - OR operator || converts the LHS error code to ERRORLEVEL only on failure;
97:: - Pipe operator | converts the last error code to ERRORLEVEL.
98::
99:: See https://stackoverflow.com/a/34987886/13530036
100:: and https://stackoverflow.com/a/34937706/13530036
101:: for more details.
102::
103setlocal enableextensions
104
105echo ---------- Testing CMD exit codes and errorlevels ----------
106
107:: Tests for CMD returned exit code.
108
109echo --- CMD /C Direct EXIT call
110
111call :setError 0
112cmd /c "exit 42"
113call :checkErrorLevel 42
114
115call :setError 111
116cmd /c "exit 42"
117call :checkErrorLevel 42
118
119echo --- CMD /C Direct EXIT /B call
120
121call :setError 0
122cmd /c "exit /b 42"
123call :checkErrorLevel 42
124
125call :setError 111
126cmd /c "exit /b 42"
127call :checkErrorLevel 42
128
129:: Non-existing ccommand, or command that only changes
130:: the returned code (but NOT the ERRORLEVEL) and EXIT.
131
132echo --- CMD /C Non-existing command
133
134:: EXIT alone does not change the ERRORLEVEL
135call :setError 0
136cmd /c "nonexisting & exit"
137call :checkErrorLevel 9009
138
139call :setError 111
140cmd /c "nonexisting & exit"
141call :checkErrorLevel 9009
142
143call :setError 0
144cmd /c "nonexisting & exit /b"
145call :checkErrorLevel 9009
146
147call :setError 111
148cmd /c "nonexisting & exit /b"
149call :checkErrorLevel 9009
150
151echo --- CMD /C RMDIR (no ERRORLEVEL set)
152
153call :setError 0
154cmd /c "rmdir nonexisting & exit"
155call :checkErrorLevel 0
156
157call :setError 111
158cmd /c "rmdir nonexisting & exit"
159call :checkErrorLevel 0
160
161call :setError 0
162cmd /c "rmdir nonexisting & exit /b"
163call :checkErrorLevel 0
164
165call :setError 111
166cmd /c "rmdir nonexisting & exit /b"
167call :checkErrorLevel 0
168
169:: Failing command (sets ERRORLEVEL to 1) and EXIT
170echo --- CMD /C DIR (sets ERRORLEVEL) - With failure
171
172:: EXIT alone does not change the ERRORLEVEL
173call :setError 0
174cmd /c "dir nonexisting>NUL & exit"
175call :checkErrorLevel 1
176
177call :setError 111
178cmd /c "dir nonexisting>NUL & exit"
179call :checkErrorLevel 1
180
181call :setError 0
182cmd /c "dir nonexisting>NUL & exit /b"
183call :checkErrorLevel 1
184
185call :setError 111
186cmd /c "dir nonexisting>NUL & exit /b"
187call :checkErrorLevel 1
188
189:: Here EXIT changes the ERRORLEVEL
190call :setError 0
191cmd /c "dir nonexisting>NUL & exit 42"
192call :checkErrorLevel 42
193
194call :setError 111
195cmd /c "dir nonexisting>NUL & exit 42"
196call :checkErrorLevel 42
197
198call :setError 0
199cmd /c "dir nonexisting>NUL & exit /b 42"
200call :checkErrorLevel 42
201
202call :setError 111
203cmd /c "dir nonexisting>NUL & exit /b 42"
204call :checkErrorLevel 42
205
206:: Succeeding command (sets ERRORLEVEL to 0) and EXIT
207echo --- CMD /C DIR (sets ERRORLEVEL) - With success
208
209call :setError 0
210cmd /c "dir>NUL & exit"
211call :checkErrorLevel 0
212
213call :setError 111
214cmd /c "dir>NUL & exit"
215call :checkErrorLevel 0
216
217call :setError 0
218cmd /c "dir>NUL & exit 42"
219call :checkErrorLevel 42
220
221call :setError 111
222cmd /c "dir>NUL & exit 42"
223call :checkErrorLevel 42
224
225call :setError 0
226cmd /c "dir>NUL & exit /b 42"
227call :checkErrorLevel 42
228
229call :setError 111
230cmd /c "dir>NUL & exit /b 42"
231call :checkErrorLevel 42
232
233
234:: Same sorts of tests, but now from within an external batch file:
235:: Tests for CALL command returned exit code.
236
237:: Use an auxiliary CMD file
238mkdir foobar && cd foobar
239
240:: Non-existing ccommand, or command that only changes
241:: the returned code (but NOT the ERRORLEVEL) and EXIT.
242
243echo --- CALL Batch Non-existing command
244
245:: EXIT alone does not change the ERRORLEVEL
246echo nonexisting ^& exit /b> tmp.cmd
247call :setError 0
248call tmp.cmd
249call :checkErrorLevel 9009
250
251echo nonexisting ^& exit /b> tmp.cmd
252call :setError 111
253call tmp.cmd
254call :checkErrorLevel 9009
255
256:: These tests show that || converts the returned error code
257:: from RMDIR on failure, and converts it to an ERRORLEVEL
258:: (first two tests: no ||, thus no ERRORLEVEL set;
259:: last two tests: ||used and ERRORLEVEL is set).
260::
261
262echo --- CALL Batch RMDIR (no ERRORLEVEL set)
263
264:: This test shows that if a batch returns error code 0 from CALL,
265:: then CALL will keep the existing ERRORLEVEL (here, 111)...
266echo rmdir nonexisting> tmp.cmd
267echo exit /b>> tmp.cmd
268call :setError 0
269call tmp.cmd
270call :checkErrorLevel 0
271
272echo rmdir nonexisting> tmp.cmd
273echo exit /b>> tmp.cmd
274call :setError 111
275call tmp.cmd
276call :checkErrorLevel 111
277
278echo --- CALL Batch RMDIR with ^|^| (sets ERRORLEVEL)
279
280:: ... but if a non-zero error code is returned from CALL,
281:: then CALL uses it as the new ERRORLEVEL.
282echo rmdir nonexisting ^|^| rem> tmp.cmd
283echo exit /b>> tmp.cmd
284call :setError 0
285call tmp.cmd
286call :checkErrorLevel 2
287:: This gives the same effect, since the last command's error code
288:: is returned and transformed by CALL into an ERRORLEVEL:
289echo rmdir nonexisting> tmp.cmd
290call :setError 0
291call tmp.cmd
292call :checkErrorLevel 2
293
294echo rmdir nonexisting ^|^| rem> tmp.cmd
295echo exit /b>> tmp.cmd
296call :setError 111
297call tmp.cmd
298call :checkErrorLevel 2
299:: This gives the same effect, since the last command's error code
300:: is returned and transformed by CALL into an ERRORLEVEL:
301echo rmdir nonexisting> tmp.cmd
302call :setError 111
303call tmp.cmd
304call :checkErrorLevel 2
305
306
307:: Failing command (sets ERRORLEVEL to 1) and EXIT
308echo --- CALL Batch DIR (sets ERRORLEVEL) - With failure
309
310echo dir nonexisting^>NUL> tmp.cmd
311call :setError 0
312call tmp.cmd
313call :checkErrorLevel 1
314
315echo dir nonexisting^>NUL> tmp.cmd
316call :setError 111
317call tmp.cmd
318call :checkErrorLevel 1
319
320echo dir nonexisting^>NUL ^& goto :eof> tmp.cmd
321call :setError 0
322call tmp.cmd
323call :checkErrorLevel 1
324
325echo dir nonexisting^>NUL ^& goto :eof> tmp.cmd
326call :setError 111
327call tmp.cmd
328call :checkErrorLevel 1
329
330echo dir nonexisting^>NUL ^& exit /b> tmp.cmd
331call :setError 0
332call tmp.cmd
333call :checkErrorLevel 1
334
335echo dir nonexisting^>NUL ^& exit /b> tmp.cmd
336call :setError 111
337call tmp.cmd
338call :checkErrorLevel 1
339
340echo dir nonexisting^>NUL ^& exit /b 42 > tmp.cmd
341call :setError 0
342call tmp.cmd
343call :checkErrorLevel 42
344
345echo dir nonexisting^>NUL ^& exit /b 42 > tmp.cmd
346call :setError 111
347call tmp.cmd
348call :checkErrorLevel 42
349
350:: Succeeding command (sets ERRORLEVEL to 0) and EXIT
351echo --- CALL Batch DIR (sets ERRORLEVEL) - With success
352
353echo dir^>NUL> tmp.cmd
354call :setError 0
355call tmp.cmd
356call :checkErrorLevel 0
357
358echo dir^>NUL> tmp.cmd
359call :setError 111
360call tmp.cmd
361call :checkErrorLevel 0
362
363echo dir^>NUL ^& goto :eof> tmp.cmd
364call :setError 0
365call tmp.cmd
366call :checkErrorLevel 0
367
368echo dir^>NUL ^& goto :eof> tmp.cmd
369call :setError 111
370call tmp.cmd
371call :checkErrorLevel 0
372
373echo dir^>NUL ^& exit /b> tmp.cmd
374call :setError 0
375call tmp.cmd
376call :checkErrorLevel 0
377
378echo dir^>NUL ^& exit /b> tmp.cmd
379call :setError 111
380call tmp.cmd
381call :checkErrorLevel 0
382
383echo dir^>NUL ^& exit /b 42 > tmp.cmd
384call :setError 0
385call tmp.cmd
386call :checkErrorLevel 42
387
388echo dir^>NUL ^& exit /b 42 > tmp.cmd
389call :setError 111
390call tmp.cmd
391call :checkErrorLevel 42
392
393
394:: Cleanup
395del tmp.cmd
396cd .. & rmdir /s/q foobar
397
398
399::
400:: ERRORLEVEL tests for special commands.
401::
402:: For some commands, the errorlevel is set differently,
403:: whether or not they are run within a .BAT, a .CMD, or
404:: directly from the command-line.
405::
406:: These commands are:
407:: APPEND/DPATH, ASSOC, FTYPE, PATH, PROMPT, SET.
408::
409:: See https://ss64.com/nt/errorlevel.html for more details.
410::
411
412echo ---------- Testing ERRORLEVEL in .BAT and .CMD ----------
413
414:: Use an auxiliary CMD file
415mkdir foobar && cd foobar
416
417echo --- In .BAT file
418call :outputErrLvlTestToBatch tmp.bat
419cmd /c tmp.bat
420del tmp.bat
421
422echo --- In .CMD file
423call :outputErrLvlTestToBatch tmp.cmd
424cmd /c tmp.cmd
425del tmp.cmd
426
427:: Cleanup
428cd .. & rmdir /s/q foobar
429
430:: Go to the next tests below.
431goto :continue
432
433:: ERRORLEVEL test helper function
434:outputErrLvlTestToBatch (filename)
435
436echo @echo off> %1
437echo setlocal enableextensions>> %1
438
439:: Reset the errorlevel
440echo call :zeroErrLvl>> %1
441
442:: dpath
443:: echo %errorlevel%
444:: dpath xxx
445:: echo %errorlevel%
446:: dpath
447:: echo %errorlevel%
448
449echo assoc^>NUL>> %1
450echo echo %%errorlevel%%>> %1
451echo assoc .nonexisting^>NUL>> %1
452echo echo %%errorlevel%%>> %1
453echo assoc .nonexisting^=^>NUL>> %1
454echo echo %%errorlevel%%>> %1
455
456:: ftype
457:: echo %errorlevel%
458:: ftype xxx
459:: echo %errorlevel%
460:: ftype
461:: echo %errorlevel%
462
463echo path^>NUL>> %1
464echo echo %%errorlevel%%>> %1
465echo path^;^>NUL>> %1
466echo echo %%errorlevel%%>> %1
467
468echo prompt^>NUL>> %1
469echo echo %%errorlevel%%>> %1
470echo prompt ^$p^$g^>NUL>> %1
471echo echo %%errorlevel%%>> %1
472echo prompt foobar^>NUL>> %1
473echo echo %%errorlevel%%>> %1
474
475echo set^>NUL>> %1
476echo echo %%errorlevel%%>> %1
477echo set nonexisting^>NUL>> %1
478echo echo %%errorlevel%%>> %1
479echo set nonexisting^=^>NUL>> %1
480echo echo %%errorlevel%%>> %1
481echo set nonexisting^=trololol^>NUL>> %1
482echo echo %%errorlevel%%>> %1
483echo set nonexisting^=^>NUL>> %1
484echo echo %%errorlevel%%>> %1
485
486echo goto :eof>> %1
487
488:: Zero ERRORLEVEL
489echo :zeroErrLvl>> %1
490echo exit /B 0 >> %1
491
492goto :eof
493
494
495::
496:: Next suite of tests.
497::
498:continue
499
500
501:: Testing different ERRORLEVELs from the SET command.
502:: See https://ss64.com/nt/set.html for more details.
503
504echo ---------- Testing SET /A ERRORLEVELs ----------
505
506echo --- Success
507call :setError 0
508set /a "total=1+1"
509call :checkErrorLevel 0
510echo %errorlevel%
511echo %total%
512
513echo --- Unbalanced parentheses
514call :setError 0
515set /a "total=(2+1"
516call :checkErrorLevel 1073750988
517echo %errorlevel%
518echo %total%
519
520echo --- Missing operand
521call :setError 0
522set /a "total=5*"
523call :checkErrorLevel 1073750989
524echo %errorlevel%
525echo %total%
526
527echo --- Syntax error
528call :setError 0
529set /a "total=7$3"
530call :checkErrorLevel 1073750990
531echo %errorlevel%
532echo %total%
533
534echo --- Invalid number
535call :setError 0
536set /a "total=0xdeadbeeg"
537call :checkErrorLevel 1073750991
538echo %errorlevel%
539echo %total%
540
541echo --- Number larger than 32-bits
542call :setError 0
543set /a "total=999999999999999999999999"
544call :checkErrorLevel 1073750992
545echo %errorlevel%
546echo %total%
547
548echo --- Division by zero
549call :setError 0
550set /a "total=1/0"
551call :checkErrorLevel 1073750993
552echo %errorlevel%
553echo %total%
554
555
556
557::
558:: Finished!
559::
560echo --------- Finished  --------------
561goto :EOF
562
563:checkErrorLevel
564if %errorlevel% neq %1 (echo Unexpected errorlevel %errorlevel%, expected %1) else echo OK
565goto :eof
566
567:: Subroutine to set errorlevel and return
568:: in windows nt 4.0, this always sets errorlevel 1, since /b isn't supported
569:setError
570exit /B %1
571:: This line runs under cmd in windows NT 4, but not in more modern versions.
572