Testing guides

Testing a command-line tool: arguments, pipes, encodings and exit codes

A CLI is used by scripts more than by people, and scripts are unforgiving. The shell rewrites arguments before your tool sees them, pipes close early, exit codes decide whether a CI job passes, and input text arrives in whatever encoding produced it. Here is what to test, with files for each case.

1. Arguments the shell has already touched

InputWhat happensCorrect behaviour
"spaces in name.txt"Unquoted, it arrives as three arguments.Works when quoted; the error for the unquoted form names the missing file.
A file called -rfParsed as an option.Accepted after --, or as ./-rf.
*.txt in cmd.exeWindows' command prompt does not expand wildcards; the tool receives the pattern itself.The tool expands it, or reports no match, rather than looking for a file called *.txt.
A glob that matches nothingbash passes the pattern through unchanged by default.A clear "no files match" error.
Unicode namesFine in the shell, broken by code that assumes ASCII or the wrong code page.Reads and prints them intact.

For names, use the Filename Compatibility pack: spaces, quotes, #, %, ;, emoji, Cyrillic, Japanese and a right-to-left override, all in one archive. Create dash names yourself with touch -- -rf.

2. Pipes and streams

A pipe that closes early

yourtool big.txt | head -1 closes the pipe after one line. The tool should stop quietly, not print a broken-pipe stack trace. Python, for one, raises BrokenPipeError here.

Reading stdin

cat file | yourtool - and yourtool < file should behave like a file argument, including for a large file that must be streamed, not read whole.

1,000,000-row CSV

stdout for data, stderr for noise

Progress and warnings go to stderr, so yourtool > out.csv produces a clean file. Test by redirecting stdout and reading what landed in it.

No terminal

Piped or run by CI, stdout is not a TTY. Colours and progress bars should switch off, and NO_COLOR should be honoured. Check the output for escape codes.

3. Exit codes

Scripts and CI read nothing else, so they have to be right every time.

CodeMeaning by conventionTest
0SuccessOnly when everything asked for was done.
1General failureA missing input file, a parse error.
2Misuse of the command lineAn unknown option or a missing required argument.
130Interrupted (128 + SIGINT)Press Ctrl+C mid-run: no partial output left looking complete.

Check them with echo $? in bash and zsh, echo %ERRORLEVEL% in cmd.exe, and $LASTEXITCODE in PowerShell.

4. Text in every encoding

InputWhere it comes from
UTF-16 LE with BOMWindows PowerShell 5.1 redirection (>). PowerShell 7 writes UTF-8 without a BOM instead.
UTF-8 with BOMNotepad and many Windows tools. The BOM ends up glued to the first field.
CRLF line endingsAny Windows editor. A trailing \r on every value breaks comparisons.
No trailing newlineThe last line is lost by tools that only count complete lines.
Latin-1Legacy exports. Not valid UTF-8: fail clearly rather than print garbage.
A very long lineLine-based readers with a fixed buffer.

Run it under LC_ALL=C as well, the minimal locale many containers and CI images use, and check that non-ASCII input still works.

5. Files that are not what the name says

Empty

A 0-byte input should give a defined result, not a crash or a hang waiting for data.

0-byte file

A directory

Pass a folder where a file is expected. The error should say so, not "permission denied".

Damaged

Malformed JSON and CSV should report the line, then exit non-zero.

Trailing comma · Ragged CSV

Need exactly these files in a script? Every fixture has a direct URL and a SHA-256, and generate test files from the command line covers making your own.

Questions

How do I test a file name that starts with a dash?

Create one with touch -- -rf and check your tool accepts it after -- or as ./-rf.

Why does my CLI get *.txt literally on Windows?

cmd.exe does not expand wildcards; the program has to. Test from cmd.exe as well as bash.

What exit codes should a CLI return?

0 for success, non-zero for failure, 2 for command-line misuse; Ctrl+C conventionally gives 130.

Why does my PowerShell output look spaced out?

Windows PowerShell 5.1 redirects as UTF-16 LE. PowerShell 7 writes UTF-8. Test both.

Keep going

Generate files from the command line Character encodings → Desktop app testing →

More from Learning

Guides and references for test data, file handling and AI evals. All free, no sign-up. See the full hub.

Synthetic data for AI evalsWhat you can download, what you buildRAG test corpusDocuments with extraction trapsJSONL for evalsThe format, and ten ways it breaksMock API for agentsRecords, pagination, failures on demandSafe test dataReserved domains, IPs, numbers & cardsSynthetic PIIFake people that reach nobodyTest card numbersEvery brand, declines, 3D SecureTest phone numbersReserved US, UK and Australian rangesReserved IP addressesDocumentation ranges and SSRF casesExample domains.test, .invalid, example.com, and trapsQA test stringsUnicode, emoji, injection & edge casesFilename compatibilityWhat breaks across Windows, Mac & LinuxFile systemsPOSIX, NTFS, APFS, ext4, SMB, NFS, NASFile signaturesMagic numbers that identify every formatCharacter encodingsUTF-8, UTF-16, BOM & normalizationUpload validationThe checklist, with a file for each checkMIME typesThe right Content-Type for every extensionHTTP status codesEvery code and when you actually see itDates & timesISO 8601, epoch, Excel serials, DSTHTTP headersContent-Type, Disposition, Range, CORSRegex cheat sheetSyntax, flags and ready-made patternsGenerate files (CLI)Make test files on any OSCrypto & certificatesSSL/PEM, hashing, encryptionWeb app testingWhat to test, with a file for each caseDesktop app testingPaths, names, encodings and savingMobile app testingPickers, photos, flaky networksLarge file upload testingDefault limits, 413s and exact sizes1 GB test file1 GB and 2 GB, with SHA-25610 GB test fileMake one locally in secondsDownload speed test files10 MB to 2 GB of random data