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.

PyPI 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.11

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.11
File Size Uploaded
battest-1.0.11.tar.gz 281.3 kB Details

Built distribution (wheel)

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

Total release size: 499.2 kB

Release files / battest-1.0.11.tar.gz

Download URL battest-1.0.11.tar.gz
Size 281.3 kB
Tags Source
SHA-256 checksum
How to use checksums
3eef876b5e8f3e3bc8d8c5d4cefbbbeb513dc48f5d9dfd51489227dfc7f76baa
BLAKE2b-256 checksum
How to use checksums
ea6099a57b0b9da593dfe813108301ba4f9d8779be2ad43f29b6fc656504159b
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.11-py3-none-any.whl

Download URL battest-1.0.11-py3-none-any.whl
Size 217.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bad7bfbdb1341e3dab68bd5011eaae2840bf3698970b32668b5d5517c836e093
BLAKE2b-256 checksum
How to use checksums
2c0d4d6b0d18982d67d196ef3495e562b12500ec529f24d193ff3ca59f72161e
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

1.0.12

2 release files

This release

1.0.11 This release

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