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.13

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.13
File Size Uploaded
battest-1.0.13.tar.gz 281.8 kB Details

Built distribution (wheel)

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

Total release size: 499.9 kB

Release files / battest-1.0.13.tar.gz

Download URL battest-1.0.13.tar.gz
Size 281.8 kB
Tags Source
SHA-256 checksum
How to use checksums
d321477a52b108abf7e0fae7c519ee484f63b2c63840e2f416bc629c2a2df15b
BLAKE2b-256 checksum
How to use checksums
1eb46eab046eaf67a4a32f5fb5c94cab7851517dbd45cd37011bca0713faaf92
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.13-py3-none-any.whl

Download URL battest-1.0.13-py3-none-any.whl
Size 218.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2b78f57d42436f6fa434824b06285f0e366c93f230ff5e72d7d2b782afea50f0
BLAKE2b-256 checksum
How to use checksums
1b4f598200d72a0b26ac04acc52a099fcadf9c4f3270152ba51bda045981046d
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

This release

1.0.13 This release

2 release files

1.0.12

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