# Testing Files Generator - make real test files at any exact size

Follow me:   Discord   Facebook   X (Twitter)   Bluesky   Steam

[![CI](https://github.com/donislawdev/TestingFilesGenerator/actions/workflows/ci.yml/badge.svg)](https://github.com/donislawdev/TestingFilesGenerator/actions/workflows/ci.yml) [![Latest release](https://img.shields.io/github/v/release/donislawdev/TestingFilesGenerator?sort=semver)](https://github.com/donislawdev/TestingFilesGenerator/releases/latest) [![Downloads](https://img.shields.io/github/downloads/donislawdev/TestingFilesGenerator/total)](https://github.com/donislawdev/TestingFilesGenerator/releases) [![License: GPLv3](https://img.shields.io/badge/License-GPLv3-blue.svg)](LICENSE) ![Platform: Windows](https://img.shields.io/badge/platform-Windows-0078D6) ![Platform: Linux](https://img.shields.io/badge/platform-Linux-FCC624) ![Platform: macOS](https://img.shields.io/badge/platform-macOS-000000) **Testing Files Generator** is a tool for QA engineers and developers who need real files to test against - an upload form, a parser, anything that takes a file and has an opinion about it. You pick one of its 31 formats and the size you want, and you get **exactly that**: ask for a 10 MB PDF and you get a PDF that a reader will open, at 10 MB to the byte. Every run also leaves a manifest saying **what your system should do with each file**, which is the part other generators leave to you. GUI + CLI ⭐ **If it saved you time, leave a star.** That is how the next tester who needs it finds out it exists. ## ⚑ What it can do - **Hit an exact size, to the byte** - ask for 10485761 bytes and get exactly that, never a silently rounded file. - **Write 31 real formats** - a generated PNG opens in an image viewer, a DOCX opens in Word, a ZIP extracts. Not padded zeros with an extension. - **Say what should happen to each file** - the manifest carries an expected outcome, so your test reads the assertion instead of you writing it out. - **Repeat itself byte for byte** - same recipe and seed, same bytes, on any machine. Commit a small recipe instead of large binary fixtures. - **Build a boundary set in one command** - one byte under a limit, the limit itself, one byte over. That is where off by one errors live. - **Produce ten thousand files at once** - one command, one manifest, and sizes drawn from the seed. - **Fill an archive for real** - a ZIP that holds 200 documents, not a stub with the right extension. - **Take settings per format** - image dimensions, JPEG quality, PDF pages, spreadsheet rows and columns. - **Fit into CI** - an exit code for every ending, machine readable output, and nothing on standard output when a run fails. - **Clean up after itself** - `verify` tells you nothing moved, `cleanup` removes exactly what it wrote and nothing else. - **Work completely offline** - no account, no cloud, no telemetry, no update check. The command line binary has no network stack compiled into it at all. - **Cost nothing and stay out of your way** - GPL-3.0, and the files you generate are yours with no strings attached. ![The desktop window of Testing Files Generator setting up a batch of log files and picking a format, then the size-boundaries and filename-handling presets writing their files into a folder beside it](.github/tfg-in-work.gif) ![The Star button at the top of this page, with a cursor pressing it](.github/star-the-repo.gif) ## 🧭 Table of contents This README is also the manual. The short version is above the line, the full reference is below it. - [What it can do](#-what-it-can-do) - [Install](#-install) - [Quick start](#-quick-start) - [Formats it generates](#-formats-it-generates) - [Presets](#-presets) - [The problem it solves](#-the-problem-it-solves) - [What makes it different](#-what-makes-it-different) - [Reference](#reference) - [Commands](#️-commands) - [Recipes](#-recipes) - [Formats in detail](#-formats-in-detail) - [The manifest](#-the-manifest) - [The desktop window](#️-the-desktop-window) - [Using it in CI](#️-using-it-in-ci) - [Questions](#-questions) - [Where this is](#-where-this-is) - [Everything inside a generated file is made up](#-everything-inside-a-generated-file-is-made-up) - [Licence](#-licence) ## πŸ“¦ Install **Download a binary.** Take the archive for your system from the [releases page](https://github.com/donislawdev/TestingFilesGenerator/releases), unpack it and run it. `tfg` is the command line, `tfg-gui` is the desktop window. The Windows and macOS downloads are signed, so they start without a warning about an unknown developer. The Linux ones are not, because desktop Linux has no equivalent to sign them with. **With Docker.** The command line also comes as an image for Linux, on x64 and Arm. It holds the program from the release archive and nothing else, and it runs with no network: ``` docker run --rm --network none -v "${PWD}:/work" ghcr.io/donislawdev/tfg:0.5.0 generate --format pdf --size 1mb ``` The files land in the current directory. On Linux add `--user "$(id -u):$(id -g)"`, so the image may write there and the files belong to you. With no directory mounted the run stops with exit code 5 instead of writing files that disappear with the container. **With Go installed:** ``` go install -tags noasm github.com/donislawdev/TestingFilesGenerator/cmd/tfg@latest ``` **From source.** Needs Go 1.27.3 or newer, and nothing else: ``` git clone https://github.com/donislawdev/TestingFilesGenerator cd TestingFilesGenerator go build -tags "$(cat .github/build-tags)" ./cmd/tfg ``` **The tag is not optional.** The AVIF encoder has an assembly path that reads past the end of a buffer and takes the process down on some picture sizes, and the tag turns it off. Building without it does not compile, and says so. The files it produces are the same either way. The desktop window is a second binary, `go build -tags "$(cat .github/build-tags)" ./cmd/tfg-gui`. It draws through OpenGL and reaches it through C, so that one needs a C compiler and is built natively on each system. Built without one it still compiles, and says on start that it has no window in it and that everything is on the command line. The window needs OpenGL 2.1 to draw. On Windows the archive carries a software renderer for machines whose graphics driver offers none - Mesa llvmpipe, in an `opengl` folder next to `tfg-gui.exe` - and the window uses it by itself when the driver refuses: a virtual machine without 3D acceleration, a remote desktop, a server. Drawn that way it is slower and says so on its About screen. On a machine with a driver the folder is not touched unless you ask: `tfg-gui --software-gl` loads the renderer on any Windows machine, which is the way to see the window as a machine without a driver sees it. Keep the folder next to the program: without it, and without a driver, the window says what it looked for in a dialog and on standard error, and exits 1. Linux has Mesa in the system and macOS has never lacked what the toolkit needs, so nothing of the kind ships there, and there the flag says so and changes nothing. The command line needs no graphics driver and does everything the window does. ## πŸš€ Quick start **1. Make a file.** One PNG, exactly two megabytes: ``` tfg generate --format png --size 2mb --out ./out ``` **2. Make a lot of files.** Ten thousand log files, each between one and eight kilobytes, with the sizes drawn from the seed so tomorrow gives the same set. **Give each run its own directory** - the manifest is the only record of what a run wrote, so the tool refuses to write a second one over it: ``` tfg generate --format log --size-range 1kb-8kb --count 10000 --out ./logs ``` **3. Check them, then remove them.** ``` tfg verify ./logs/manifest.json tfg cleanup ./logs/manifest.json --yes ``` ``` logs matches ./logs/manifest.json: 10000 files checked ``` **Sizes count in 1024s**, the way your file manager does, so `2mb` means 2097152 bytes. A plain byte count works too: `--size 2097152`. ## πŸ“ Formats it generates Every one is a **real file of that format** - it opens in the software that owns it, at the exact size you asked for: | group | formats | |---|---| | πŸ“„ **Documents** | `pdf`, `docx` (Word), `xlsx` (Excel), `pptx` (PowerPoint), `rtf` (Rich Text Format) | | πŸ–ΌοΈ **Images** | `png`, `jpg`, `bmp`, `gif`, `ico`, `svg`, `tiff`, `webp`, `avif`, `jxl` | | πŸ“ **Text and markup** | `txt`, `md`, `csv`, `json`, `xml`, `html`, `log` | | βš™οΈ **Configuration** | `yaml`, `toml` | | πŸ—ΊοΈ **Maps** | `geojson` (RFC 7946) | | βœ‰οΈ **Mail** | `eml` (RFC 5322 with MIME, real files attached) | | πŸ—œοΈ **Archives** | `zip`, `targz` (`.tar.gz`) | | πŸ”Š **Audio** | `wav` | | 🎞️ **Video** | `webm`, `mp4` (both AV1, no sound) | Coming next: `7z`, `mp3`. Most of them take settings of their own - image dimensions, JPEG quality, PDF page count, rows and columns in a spreadsheet, what goes inside an archive. See [format settings](#per-format-settings). ## πŸ§ͺ Presets A preset is a ready-made set of files that answers one common testing question, so you do not have to design the set yourself. Every file in it says what it is for, and the manifest says how your system should react to it: | preset | the question it answers | |---|---| | `empty-and-minimal` | Does a file that is valid and as small as the format allows get through? | | `filename-handling` | Will my system store, show and give back a file name it did not expect? | | `size-boundaries` | Is a size limit enforced exactly where it is declared? | | `tabular-import` | Does my table import survive what real tools export? | | `text-encoding` | Does my reader know which encoding a file is in, or is it guessing? | | `upload-validation` | Does my upload form take what it should and turn the rest away? | ``` tfg preset show size-boundaries tfg generate --preset size-boundaries --limit 10mb --out ./limits ``` `show` tells you what the set would cost before you build it, and says outright when a number is a placeholder of ours rather than a limit of yours. Presets are ordinary recipes underneath - `tfg preset eject size-boundaries` prints the recipe and you edit it from there. The Presets screen of the window offers the same ones. ## πŸ€” The problem it solves You are testing software that accepts files from people. Sooner or later you need: - a PDF of **exactly** 10 MB, to find out whether the upload limit is real - the three files that sit either side of that limit, to catch off by one errors - 10,000 log files, to see what the nightly job does when the folder is big - a ZIP that genuinely holds 200 documents, not a stub with the right extension - a 4 GB file, without keeping a 4 GB file in your repository - the **same** fixtures on your laptop and on the build server, byte for byte Making one such file by hand is easy. Making them repeatedly, at exact sizes, in formats that really open in real software, is the tedious part - and that is the part this replaces. **Who it is for:** QA engineers, test automation, and anyone whose code has an upload form, an import routine, a parser or a storage quota behind it. ## ⭐ What makes it different Other generators stop at the bytes. They hand you a folder, and you are still the one deciding what each file is supposed to prove. This one answers the question your test actually asks: **what should happen when this file arrives?** Every run writes a `manifest.json` beside the files - a plain list of everything it produced, and for each entry a declared expectation. Say your upload endpoint allows 1 MB. Ask for the three files that sit on that line: ``` tfg generate --preset size-boundaries --limit 1mb --spread 1B --format pdf --out ./edges ``` | file | bytes | your system should | because | |---|---|---|---| | `1mb_under_1b.pdf` | 1048575 | **accept** it | it is inside the limit | | `1mb_at_limit.pdf` | 1048576 | **accept** it | the limit itself is allowed | | `1mb_over_1b.pdf` | 1048577 | **reject** it | `size_limit` | Three files, three different answers, in machine readable form. Your test reads the manifest instead of you hand writing the assertions: ```python import json import os directory = "edges" manifest = json.load(open(os.path.join(directory, "manifest.json"))) for entry in manifest["files"]: response = upload(os.path.join(directory, entry["path"])) outcome = entry["expected"]["outcome"] if outcome == "accept": assert response.ok, entry["path"] elif outcome == "reject": assert not response.ok, entry["path"] ``` A run from a preset also writes `manifest.instructions.md` - the same facts for a person to read: every file, what your system should do with it and why the file is in the set. And where the right answer genuinely depends on your own policy, the manifest says `unspecified` instead of inventing one. A generator that guesses produces false failures, and a suite that cries wolf gets switched off. --- # Reference Everything the tool does, in one place. ## πŸŽ›οΈ Commands ``` tfg generate produce files, from a recipe or from flags tfg validate check a recipe and write nothing tfg verify check a directory against a manifest tfg cleanup remove the files a manifest lists tfg recipe fmt print a recipe in its settled shape tfg preset build a set of files from a named test question tfg formats list the formats this build supports tfg damage list the ways this build can break a file on purpose tfg tool small things to do with files you already have tfg version print the tool version tfg license print the licence and what it means for generated files ``` ### `tfg generate` ``` tfg generate [flags] settings come from the file tfg generate --format txt --size 1mb settings come from the flags ``` | flag | what it does | |---|---| | `--format ` | format of the files, for example `txt` | | `--size ` | exact size of every file, such as `10mb` or a plain byte count | | `--size-range ` | a size drawn per file from a range, such as `1kb-8kb`. The draw comes from the seed | | `--boundary ` | three files around a limit: one byte under, the limit, one byte over | | `--count ` | how many files to produce. Default `1` | | `--id ` | target id, the anchor the seeds are derived from. Default `files` | | `--name