Skip to content

Commit 2691aff

Browse files
committed
Optimise how Lua scripts are called when returning data. Add full suite integration tests and makefile with install script for easy development
1 parent 03a849e commit 2691aff

File tree

12 files changed

+1991
-33
lines changed

12 files changed

+1991
-33
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

55
## [staging]
6+
### Added
7+
- REAPER ExtState API bindings (`SetExtState`, `GetExtState`, `HasExtState`, `DeleteExtState`).
8+
- Go integration test suite (`tests/integration/reaserve_test.go`) that exercises every method in PROTOCOL.md against a live REAPER instance. Uses `//go:build integration` tag so it is never picked up by normal `go test` runs.
9+
- Makefile with targets: `release`, `debug`, `test`, `test-integration`, `install`, `clean`, `rebuild`, `help`.
10+
- `make install` auto-detects REAPER UserPlugins path per platform (Linux/macOS/Windows).
11+
### Changed
12+
- **Breaking:** `lua.execute_and_read` no longer requires `state_path` parameter. Lua code must now call `reaserve_output(json_string)` to return data instead of writing to a file. The file-based result passing mechanism has been replaced with REAPER's in-memory ExtState API (`SetExtState`/`GetExtState`).
13+
- User Lua code in `lua.execute_and_read` is now wrapped in `pcall()` — Lua runtime errors are captured and returned in the JSON-RPC error response instead of being silently swallowed.
14+
- Updated PROTOCOL.md to reflect new `lua.execute_and_read` interface and document `reaserve_output()`.
15+
616

717

818
## [0.1.1] - 2026-03-17

DEVELOPMENT.md

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,68 @@
44

55
- CMake 3.15+
66
- C++17 compiler (GCC 7+, Clang 5+, MSVC 2017+)
7+
- Go 1.21+ (for integration tests only)
8+
9+
## Quick Start
10+
11+
```bash
12+
make # Build release
13+
make test # Build + run unit tests
14+
make install # Copy plugin to REAPER UserPlugins directory
15+
```
16+
17+
Run `make help` to see all targets.
718

819
## Building
920

1021
```bash
11-
cmake -B build -DCMAKE_BUILD_TYPE=Release
12-
cmake --build build --config Release
22+
make # Release build
23+
make debug # Debug build
24+
make rebuild # Clean + release build
1325
```
1426

15-
For a debug build:
27+
Or using CMake directly:
1628

1729
```bash
18-
cmake -B build -DCMAKE_BUILD_TYPE=Debug
19-
cmake --build build --config Debug
30+
cmake -B build -DCMAKE_BUILD_TYPE=Release
31+
cmake --build build --config Release
2032
```
2133

2234
The plugin binary is output as:
2335
- Linux: `build/reaper_reaserve.so`
2436
- macOS: `build/reaper_reaserve.dylib`
2537
- Windows: `build/reaper_reaserve.dll`
2638

39+
## Installing
40+
41+
```bash
42+
make install
43+
```
44+
45+
This copies the plugin to your REAPER `UserPlugins/` directory. Override the path with:
46+
47+
```bash
48+
make install USERPLUGINS=/path/to/UserPlugins
49+
```
50+
51+
Restart REAPER after installing to load the updated plugin.
52+
2753
## Running Tests
2854

55+
### Unit tests
56+
2957
Tests use plain `assert()` — no external test framework required.
3058

3159
```bash
32-
# Run all tests via CTest
60+
make test
61+
```
62+
63+
Or directly:
64+
65+
```bash
3366
ctest --test-dir build --output-on-failure
3467

35-
# Or run individual test binaries directly
68+
# Individual test binaries
3669
./build/test_json_rpc
3770
./build/test_command_queue
3871
./build/test_framing
@@ -44,6 +77,38 @@ To disable building tests:
4477
cmake -B build -DREASERVE_BUILD_TESTS=OFF
4578
```
4679

80+
### Integration tests
81+
82+
Integration tests run every method in [PROTOCOL.md](PROTOCOL.md) against a live REAPER instance. They require Go 1.21+.
83+
84+
**Setup:**
85+
86+
1. Build and install the plugin (`make install`)
87+
2. Open REAPER with a **new, blank project** (no tracks, items, or markers)
88+
3. Confirm ReaServe is running (startup message in the console)
89+
90+
**Run:**
91+
92+
```bash
93+
make test-integration
94+
```
95+
96+
Or directly:
97+
98+
```bash
99+
cd tests/integration && go test -tags integration -v -count=1 .
100+
```
101+
102+
To connect to a non-default address:
103+
104+
```bash
105+
REASERVE_ADDR=192.168.1.10:9876 make test-integration
106+
```
107+
108+
The tests create tracks, items, FX, MIDI notes, markers, sends, and envelope points — then clean everything up, leaving the project blank.
109+
110+
The `integration` build tag ensures these tests are never compiled during normal `go test` runs or CI.
111+
47112
## Versioning
48113

49114
The version is defined in a single place: the `project(VERSION ...)` line in `CMakeLists.txt`.

Makefile

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# ReaServe Makefile
2+
#
3+
# Usage:
4+
# make Build release
5+
# make debug Build debug
6+
# make test Run unit tests
7+
# make test-integration Run integration tests (requires REAPER running with blank project)
8+
# make install Copy plugin to REAPER UserPlugins directory
9+
# make clean Remove build directory
10+
# make rebuild Clean + build
11+
# make help Show all targets
12+
13+
ROOT_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
14+
BUILD_DIR := $(ROOT_DIR)build
15+
BUILD_TYPE ?= Release
16+
PLUGIN_NAME := reaper_reaserve
17+
18+
# Detect platform-specific plugin extension and REAPER UserPlugins path
19+
UNAME := $(shell uname -s)
20+
ifeq ($(UNAME),Darwin)
21+
PLUGIN_EXT := .dylib
22+
USERPLUGINS ?= $(HOME)/Library/Application Support/REAPER/UserPlugins
23+
else ifeq ($(UNAME),Linux)
24+
PLUGIN_EXT := .so
25+
USERPLUGINS ?= $(HOME)/.config/REAPER/UserPlugins
26+
else
27+
PLUGIN_EXT := .dll
28+
USERPLUGINS ?= $(APPDATA)/REAPER/UserPlugins
29+
endif
30+
31+
PLUGIN_BIN := $(BUILD_DIR)/$(PLUGIN_NAME)$(PLUGIN_EXT)
32+
33+
# ---------------------------------------------------------------------------
34+
# Build
35+
# ---------------------------------------------------------------------------
36+
37+
.PHONY: all
38+
all: release
39+
40+
.PHONY: configure
41+
configure:
42+
cmake -S $(ROOT_DIR) -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
43+
44+
.PHONY: release
45+
release: BUILD_TYPE = Release
46+
release: configure
47+
cmake --build $(BUILD_DIR) --config Release
48+
49+
.PHONY: debug
50+
debug: BUILD_TYPE = Debug
51+
debug: configure
52+
cmake --build $(BUILD_DIR) --config Debug
53+
54+
.PHONY: rebuild
55+
rebuild: clean release
56+
57+
# ---------------------------------------------------------------------------
58+
# Test
59+
# ---------------------------------------------------------------------------
60+
61+
.PHONY: test
62+
test: release
63+
ctest --test-dir $(BUILD_DIR) --output-on-failure
64+
65+
.PHONY: test-integration
66+
test-integration:
67+
cd $(ROOT_DIR)tests/integration && go test -tags integration -v -count=1 .
68+
69+
# ---------------------------------------------------------------------------
70+
# Install
71+
# ---------------------------------------------------------------------------
72+
73+
.PHONY: install
74+
install: release
75+
@mkdir -p "$(USERPLUGINS)"
76+
cp "$(PLUGIN_BIN)" "$(USERPLUGINS)/"
77+
@echo "Installed $(PLUGIN_NAME)$(PLUGIN_EXT) to $(USERPLUGINS)"
78+
@echo "Restart REAPER to load the updated plugin."
79+
80+
# ---------------------------------------------------------------------------
81+
# Clean
82+
# ---------------------------------------------------------------------------
83+
84+
.PHONY: clean
85+
clean:
86+
rm -rf $(BUILD_DIR)
87+
88+
# ---------------------------------------------------------------------------
89+
# Help
90+
# ---------------------------------------------------------------------------
91+
92+
.PHONY: help
93+
help:
94+
@echo "ReaServe build targets:"
95+
@echo ""
96+
@echo " make Build release (default)"
97+
@echo " make debug Build debug"
98+
@echo " make test Build + run unit tests"
99+
@echo " make test-integration Run Go integration tests (REAPER must be running with blank project)"
100+
@echo " make install Build + copy plugin to REAPER UserPlugins directory"
101+
@echo " make clean Remove build directory"
102+
@echo " make rebuild Clean + release build"
103+
@echo ""
104+
@echo "Variables:"
105+
@echo " USERPLUGINS=path Override REAPER UserPlugins directory"
106+
@echo " REASERVE_ADDR=h:p Override integration test address (default: localhost:9876)"

PROTOCOL.md

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,38 @@ Execute arbitrary Lua code in REAPER.
8383

8484
### `lua.execute_and_read`
8585

86-
Execute Lua code that writes JSON to a file, then return the file contents.
86+
Execute Lua code and return the output. The Lua code must call `reaserve_output(json_string)` to pass data back. Lua runtime errors are captured and returned as error responses.
8787

8888
**Params:**
8989
| Field | Type | Required | Description |
9090
|-------|------|----------|-------------|
91-
| `code` | string | yes | Lua source code (should write JSON to `state_path`) |
92-
| `state_path` | string | yes | Path where Lua writes its output |
91+
| `code` | string | yes | Lua source code (must call `reaserve_output()` with a JSON string) |
9392

94-
**Result:** The parsed JSON from the state file.
93+
**Result:** The parsed JSON from `reaserve_output()`. If the output is not valid JSON, it is returned as `{"raw": "<output>"}`.
94+
95+
**Errors:**
96+
- `-32603` — Lua runtime error (message includes the Lua error)
97+
- `-32603` — Script did not call `reaserve_output()`
98+
99+
**Example:**
100+
```json
101+
{
102+
"jsonrpc": "2.0", "id": 1,
103+
"method": "lua.execute_and_read",
104+
"params": {
105+
"code": "local info = {bpm = 120}\nreaserve_output(reaper.json_encode and reaper.json_encode(info) or '{\"bpm\": 120}')"
106+
}
107+
}
108+
```
109+
110+
**Client implementation notes:**
111+
112+
The `code` value is a JSON string that is decoded and written to a `.lua` file verbatim. This means:
113+
114+
- **Newlines must be JSON-escaped.** Literal newlines are invalid inside JSON strings. Use `\n` in the JSON value, or rely on your language's JSON encoder to handle this automatically (most do).
115+
- **Double quotes inside Lua code must be JSON-escaped.** For example, `reaper.ShowConsoleMsg("hello")` must be sent as `reaper.ShowConsoleMsg(\"hello\")` in the raw JSON. Again, a JSON encoder handles this — just pass the Lua code as a native string and let the encoder do the escaping.
116+
- **Prefer Lua single quotes or `[[ ]]` long brackets** to avoid fighting with JSON double-quote escaping. `reaper.ShowConsoleMsg('hello')` is easier to read and construct than the double-escaped equivalent.
117+
- **`lua.execute_and_read` wraps your code in `pcall(function() ... end)`.** This is transparent — your code runs in a new scope but has full access to all globals (`reaper`, etc.). The only visible effect is that `reaserve_output` and the names `__ok`/`__err` are defined in the outer scope. Avoid shadowing these.
95118

96119
---
97120

README.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,46 @@ Requires CMake 3.15+ and a C++17 compiler.
8888
```bash
8989
cmake -B build -DCMAKE_BUILD_TYPE=Release
9090
cmake --build build --config Release
91-
ctest --test-dir build
9291
```
9392

9493
The plugin binary will be at `build/reaper_reaserve.so` (Linux), `.dylib` (macOS), or `.dll` (Windows).
9594

95+
## Testing
96+
97+
### Unit tests
98+
99+
Unit tests run without REAPER and cover JSON-RPC framing, command queue, and message parsing:
100+
101+
```bash
102+
ctest --test-dir build
103+
```
104+
105+
### Integration tests
106+
107+
Integration tests run against a live REAPER instance and exercise every method in the protocol. They require Go 1.21+.
108+
109+
**Setup:**
110+
111+
1. Build and install the plugin into REAPER's `UserPlugins/` directory
112+
2. Open REAPER with a **new, blank project** (no tracks, items, or markers)
113+
3. Confirm ReaServe is running (you should see the startup message in the console)
114+
115+
**Run:**
116+
117+
```bash
118+
cd tests/integration && go test -tags integration -v -count=1 .
119+
```
120+
121+
To connect to a non-default address:
122+
123+
```bash
124+
cd tests/integration && REASERVE_ADDR=192.168.1.10:9876 go test -tags integration -v -count=1 .
125+
```
126+
127+
The tests create tracks, items, FX, MIDI notes, markers, sends, and envelope points — then clean everything up, leaving the project blank. Every method in [PROTOCOL.md](PROTOCOL.md) is covered.
128+
129+
The `integration` build tag ensures these tests are **never** picked up by normal `go test` runs or CI — they only compile and run when explicitly requested.
130+
96131
## Architecture
97132

98133
Note: The timer callback processes up to 5 commands per tick

0 commit comments

Comments
 (0)