Skip to main content

battest

Runtime test runner for Windows batch files (.bat / .cmd). battest launches real cmd.exe and asserts on exit code, stdout, stderr, environment, filesystem side effects, and calls to mocked external commands.

Python versions CI License

It is a trusted-fixture runner, not a sandbox. Destructive scripts can still harm the host. Use --safe-defaults (or the GitHub Action, which enables it) and a disposable VM or CI runner for untrusted suites. Details: Safety.

battest is a sibling of Blinter (static analysis). It does not depend on Blinter.

Requirements: Python 3.11+ and Windows for battest run.

Features

  • Real cmd.exe in an isolated temp workdir per case (Job Object, kill-on-close)
  • Assertions: exit code, stdout/stderr, environment, and files
  • PATH mocks for external commands (ipconfig, reg, …) with call recording
  • Param overlays: one YAML document, many variants
  • setup / teardown, stdin, env, and copy-in fixtures
  • Parallel --jobs, JUnit XML, and a Windows GitHub Action
  • Optional --safe-defaults PATH stubs for common destructive utilities

cmd.exe internals (del, copy, rd, …) cannot be shadowed via PATH. See Mocking.

Quick start

pip install battest

Create hello.cmd:

@echo off
echo hello
exit /b 0

Create hello.battest.yaml next to it:

description: hello prints hello
script: hello.cmd
expect:
  exit_code: 0
  stdout:
    contains: hello

Run:

battest run hello.battest.yaml

python -m battest is the same as battest. A passing case prints PASS. A failing case prints a diff and exits 1. Invalid YAML or usage exits 2.

Case-directory form is equivalent:

tests/hello/input.cmd
tests/hello/expect.yaml

Then battest run tests. From this repository, battest run examples runs the bundled fixtures, including a mocked ipconfig /flushdns script with param overlays.

CLI --safe-defaults is off. The GitHub Action turns it on. That flag PATH-stubs common destructive externals (format, shutdown, reg, and others); it does not isolate the filesystem. See CLI and Mocking.

Mocking externals

PATH stubs replace named executables for the case. This fixture asserts ipconfig /flushdns is invoked, then overlays a non-admin variant:

description: flush DNS when admin
script: flush_dns.cmd
timeout_seconds: 15
mocks:
  net:
    exit_code: 0
  ipconfig:
    exit_code: 0
    expect_calls:
      - args_contains: "/flushdns"
  timeout:
    exit_code: 0
expect:
  exit_code: 0
  stdout:
    contains: Flushing DNS cache
params:
  - id: not-admin
    mocks:
      net:
        exit_code: 2
      ipconfig:
        expect_calls:
          - not_called: true
      timeout:
        exit_code: 0
    expect:
      exit_code: 1
      stdout:
        contains: administrator

Full field list: Fixture format. Bundled example: examples/windowsrescue/.

CLI

battest [--version] run [path] [--junit-xml FILE] [--jobs N]
        [--timeout SECONDS] [--max-diff N] [--safe-defaults]
        [--no-safe-defaults] [-v]
Flag Meaning
path Fixture file or directory. Default: ./tests when it contains battest fixtures, otherwise the current directory
--jobs Parallel case execution (each case has its own temp dir). 1–256
--timeout Default timeout when a case omits timeout_seconds. Default: 30
--junit-xml Write xunit2 JUnit XML
--safe-defaults PATH-stub common destructive externals unless mocked or listed in allow
-v Debug logging to stderr

Exit codes: 0 all passed, 1 one or more FAIL/ERROR/TIMEOUT, 2 usage or schema error. Full flag list: CLI.

GitHub Action

Requires a Windows runner. Use the moving major tag (@v1), not a commit SHA.

jobs:
  test-batch:
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v7
      - id: battest
        uses: tboy1337/battest@v1
        with:
          path: tests
          safe-defaults: "true"
      - uses: actions/upload-artifact@v7
        if: always()
        with:
          name: battest-junit
          path: ${{ steps.battest.outputs.junit-xml }}

Inputs, outputs, and -- before path are documented in GitHub Action.

Installation

pip install battest

Standalone executable (no Python)

Run this from cmd.exe (not PowerShell). It downloads the bootstrap script, installs the latest battest.exe to %LOCALAPPDATA%\Programs\battest\bin, adds that directory to your user PATH, and returns the installer exit code after deleting the downloaded .cmd:

curl -L https://raw.githubusercontent.com/tboy1337/battest/main/scripts/install_battest.cmd -o install_battest.cmd && call install_battest.cmd & set "BATTEST_INSTALL_EXIT=%ERRORLEVEL%" & del install_battest.cmd & exit /b %BATTEST_INSTALL_EXIT%

The installer always fetches the latest GitHub release and verifies the zip SHA-256 digest before extract. Download URLs must be https on github.com, objects.githubusercontent.com, or release-assets.githubusercontent.com. The bootstrap .cmd itself is not digest-pinned; the exe payload is. Pinning the curl URL to a release tag (instead of main) is stricter if you want a known installer script. Restart the terminal or IDE after install so PATH updates are visible.

Manual zip: download Battest-vX.Y.Z.zip from GitHub Releases and run Battest-vX.Y.Z\battest.exe. Some antivirus products flag PyInstaller unpacking as a false positive. The source is public; pip avoids that class of heuristic.

Uninstall

Standalone install (cmd.exe):

curl -L https://raw.githubusercontent.com/tboy1337/battest/main/scripts/uninstall_battest.cmd -o uninstall_battest.cmd && call uninstall_battest.cmd & set "BATTEST_UNINSTALL_EXIT=%ERRORLEVEL%" & del uninstall_battest.cmd & exit /b %BATTEST_UNINSTALL_EXIT%

pip:

pip uninstall battest

Python API

from battest import load_case, run_case, run_cases

cases = load_case("hello.battest.yaml")
result = run_case(cases[0], safe_defaults=False)
results = run_cases(cases, jobs=1, safe_defaults=False)

run_case / run_cases require Windows cmd.exe. safe_defaults defaults to off, matching the CLI. Full notes: CLI.

Documentation

Getting started:

Behavior:

License

AGPL-3.0-or-later (COPYING).

Metadata

Release files for battest 1.0.12

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for battest 1.0.12
File Size Uploaded
battest-1.0.12.tar.gz 281.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for battest 1.0.12
File Interpreter ABI Platform
battest-1.0.12-py3-none-any.whl Python 3 none any Details

Total release size: 499.2 kB

Release files / battest-1.0.12.tar.gz

Download URL battest-1.0.12.tar.gz
Size 281.4 kB
Tags Source
SHA-256 checksum
How to use checksums
a2fdaa4043e814e278f13c1bc65f2d2cd777a36e10ea9d37615778ff109f73d8
BLAKE2b-256 checksum
How to use checksums
b8b33f8ca72ea8926e7fed96c628fd92b5424bcf9d7bb2675cd4b24351863609
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release files / battest-1.0.12-py3-none-any.whl

Download URL battest-1.0.12-py3-none-any.whl
Size 217.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d22bf2adda65e615a90cd817001f123ac285b16edf40972081a661c3017cf9f2
BLAKE2b-256 checksum
How to use checksums
52aab5a2fbf0ac254ac7f9474310b09dc532d55306b6011b95e80508ad2c9041
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release history Release notifications | RSS feed

1.0.14

2 release files

1.0.13

2 release files

This release

1.0.12 This release

2 release files

1.0.11

2 release files

1.0.10

2 release files

1.0.9

2 release files

1.0.8

2 release files

1.0.4

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page