Metadata-Version: 2.4
Name: sakura-lsltest
Version: 1.0.0
Summary: Pytest-like test framework for LSL — drives sakura-lslc and sakura-slemu
Author-email: Shiho Sakura <shiho@sakurastudios.eu>
License: MIT
Project-URL: Homepage, https://github.com/ShihoSakura/sakura-lsltest
Project-URL: Compiler, https://github.com/ShihoSakura/sakura-lslc
Project-URL: Emulator, https://github.com/ShihoSakura/sakura-slemu
Keywords: lsl,second-life,testing,compiler,emulator,sakura
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# sakura-lsltest

[![CI](https://github.com/Sakura-Studios-IKE/sakura-lsltest/actions/workflows/ci.yml/badge.svg)](https://github.com/Sakura-Studios-IKE/sakura-lsltest/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![GitHub release](https://img.shields.io/github/v/release/Sakura-Studios-IKE/sakura-lsltest)](https://github.com/Sakura-Studios-IKE/sakura-lsltest/releases)

**A pytest-style test framework for LSL — by Sakura Studios, IKE.**

`sakura-lsltest` drives [`sakura-lslc`](https://github.com/ShihoSakura/sakura-lslc)
(the offline compiler) and
[`sakura-slemu`](https://github.com/ShihoSakura/sakura-slemu) (the headless
region emulator) so you can write **fully automated unit & integration
tests against real LSL source** — no viewer, no in-world prim, no
manual button-clicking.

```python
import lsltest

ALICE = "22222222-2222-2222-2222-222222222222"

@lsltest.compile_ok("scripts/vendor.lsl")
def test_vendor_compiles(): pass

@lsltest.scene(
    scripts=["scripts/vendor.lsl"],
    owner=("11111111-...", "Shop", 100),
    avatars=[(ALICE, "Alice", 50)],
    fixtures=[("https://api/x", 200, '{"ok":true}')],
)
def test_alice_buys_apple(world):
    world.touch(link=1, avatar=ALICE)
    world.assert_dialog_open(ALICE)
    world.dialog_reply(avatar=ALICE, button="Apple")
    world.assert_hud(link=1, substring="Sold Apple")
    world.assert_said(kind="region-to", channel=0, substring="thanks")
    world.assert_balance(ALICE, 51)
```

```
$ lsltest run examples/
  PASS  test_basics.py::test_broken_rejected
  PASS  test_basics.py::test_hello_compiles
  PASS  test_vendor.py::test_alice_buys_apple
  PASS  test_vendor.py::test_bob_cancels

Result: 76 passing / 76 tests
```

## What you can assert

| Concern                          | API |
|----------------------------------|-----|
| Compiles cleanly                 | `@lsltest.compile_ok(path)` |
| Fails to compile (with diagnostic) | `@lsltest.compile_fails(path, diagnostic=...)` |
| Compiles but warns               | `@lsltest.compile_warns(path, warning=...)` |
| Touch / dialog / textbox / listen / IM | `world.touch / dialog_reply / textbox_reply / listen` |
| Player pays the object           | `world.money_in(avatar=, amount=)` |
| External system calls llRequestURL endpoint | `world.http_in(url=, method=, body=)` |
| Attach / detach as HUD           | `world.attach / detach` |
| HUD floating text                | `world.assert_hud(link=, substring=)` |
| What was said (any channel)      | `world.assert_said(kind=, channel=, substring=)` |
| Dialog open for an avatar        | `world.assert_dialog_open(avatar=)` |
| Avatar's L$ balance              | `world.assert_balance(avatar, amount)` / `world.balance(avatar)` |
| Arbitrary event predicate        | `world.first("dialog", to=ALICE)` |
| Inspect entire event stream      | `world.events("hud")`, `world.chat()` |

## Install

### From PyPI

```sh
pip install sakura-lsltest
```

(plus the two C binaries — `lslc` and `slemu` from the sibling repos,
either built locally with `make` or installed system-wide.)

### Arch Linux (AUR)

```sh
yay -S sakura-lsltest            # latest tagged release
yay -S sakura-lsltest-git        # follow main
```

See [`packaging/aur/README.md`](./packaging/aur/README.md) for the
PKGBUILDs.

### From source (editable)

```sh
git clone https://github.com/Sakura-Studios-IKE/sakura-lsltest.git
cd sakura-lsltest
pip install -e .
```

## Run

```sh
lsltest run tests/                # discover and run all test_*.py
lsltest run tests/ -k vendor      # filter by name substring
lsltest run tests/ -v             # show failure details
lsltest run tests/ --keep         # keep slemu artefacts for inspection
lsltest run tests/ --lslc ./lslc --slemu ./slemu
```

Inside Python, the framework is a regular library — you can also call
`World()`, `Scene()`, and the decorators from a pytest run, a Hypothesis
strategy, a Makefile, or a CI step.

## How it works

1. **Compile.** For each test, lsltest calls `lslc -c <path>` and parses
   the result; failures bubble up as Python assertion errors with the
   exact diagnostic stderr.
2. **Scene setup.** It writes a `world.cfg` file describing your
   avatars, owners, groups, and HTTP fixtures, plus a `session.cmds`
   file listing the player actions you queued.
3. **Drive slemu.** It launches `slemu --json-events --config … --commands …`
   with all your compiled `.lslbc`. Every event slemu emits (chat,
   HUD update, dialog opening, money transfer, link-message, HTTP
   request) becomes a parseable JSON line.
4. **Verify.** Any `world.assert_*(…)` calls embed assertions into the
   command file; slemu evaluates them in line with the rest of the
   simulation. Any failures (and any Python-side post-run checks via
   `world.balance(…)` / `world.events(…)` / `world.first(…)`) raise
   `LsltestError` which the runner reports with full context.

Because every assertion is observable through the JSON event stream,
your tests are **deterministic, reproducible, and CI-friendly** — same
artefacts, same answers, every run.

## Project layout

```
sakura-lsltest/
├── README.md
├── LICENSE                 MIT
├── pyproject.toml
├── Makefile
├── src/lsltest/
│   ├── __init__.py        public API
│   ├── core.py            decorators, World, Scene, runner
│   └── __main__.py        `python -m lsltest` / `lsltest` CLI
└── examples/
    ├── scripts/           example LSL sources
    ├── test_basics.py     compile-only tests
    └── test_vendor.py     interactive scene tests
```

## Status

`sakura-lsltest` is the test harness in Sakura Studios' five-tool
open-source LSL toolchain:

1. [`sakura-lslc`](https://github.com/ShihoSakura/sakura-lslc) — offline
   compiler.
2. [`sakura-slemu`](https://github.com/ShihoSakura/sakura-slemu) —
   headless region emulator.
3. **`sakura-lsltest`** *(this repo)* — automated test harness.
4. [`sakura-lsldb`](https://github.com/ShihoSakura/sakura-lsldb) —
   gdb-style CLI debugger.
5. [`sakura-intellij-lsl`](https://github.com/ShihoSakura/sakura-intellij-lsl)
   — IntelliJ plugin.

## Author / Attribution

Authored and maintained by **Shiho Sakura**
([@ShihoSakura](https://github.com/ShihoSakura)) on behalf of
**Sakura Studios, IKE**.

## License

MIT — see [`LICENSE`](./LICENSE).
