Skip to content

Commit 78620da

Browse files
committed
fix rest script
1 parent 32c4c3c commit 78620da

20 files changed

Lines changed: 210 additions & 247 deletions

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ jobs:
2020
buildCache: false
2121

2222
- name: Run tests
23-
run: lua ./bin/test.lua
23+
run: TEST_IGNORE_SLUGS=engine_consistency_integration lua ./bin/test.lua

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ Engines register with **DeltaChess.Engines** and implement:
4646
- `:Elo(elo)` – optional. Requested difficulty. Default: average of engine's ELO range.
4747
- `:TimeLimitMs(ms)` – optional. Max time in ms. Default: 20000 (20 seconds).
4848
- `:OnComplete(cb)``function(result, err)`. Required before `Run()`. If the engine returns an illegal move, the runner calls `onComplete(nil, err)` with `err` an object `{ message = "illegal move", move = "<uci>" }`.
49-
- `:Scheduler(fn)` – optional. `function(next)`. Default: call `next()` immediately.
49+
- `:DelayFn(fn)` – optional. `function(next)`. Default: call `next()` immediately.
50+
- `:LoopFn(fn)` – optional. `function(stepFn, doneFn)` loop driver for async iteration. Default: runs all steps synchronously.
5051
- `:Run()` – start calculation.
5152

5253
## Global registration (WoW addon compatible)
@@ -102,10 +103,10 @@ DeltaChess.EngineRunner.Create("beat_highest_piece")
102103
if err then return end
103104
-- use result.move
104105
end)
105-
:Scheduler(function(next) C_Timer.After(0, next) end)
106+
:DelayFn(function(next) C_Timer.After(0, next) end)
106107
:Run()
107108
```
108-
Engines receive `yieldFn(next)` and call it when they want to yield; the scheduler runs `next()` on the next frame so the game stays responsive.
109+
Engines receive `yieldFn(next)` and call it when they want to yield; the delay function runs `next()` on the next frame so the game stays responsive.
109110

110111
**CLI game (bin/play.lua):** Run from repo root. Engine vs itself or engine1 vs engine2, with optional ELOs:
111112
```bash

bin/benchmark.lua

100644100755
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ local function randomLegalMove(board)
5050
end
5151

5252
-- Run one test: engine vs random, up to MAX_ENGINE_MOVES engine moves; return average thinking time in ms or nil on error.
53-
-- Runs synchronously via Scheduler(next) => next().
53+
-- Runs synchronously via DelayFn(next) => next().
5454
local function runOneTest(engineId, elo)
5555
local board = Board.New()
5656
local times = {} -- list of thinking times in seconds (from os.clock())
@@ -85,7 +85,7 @@ local function runOneTest(engineId, elo)
8585
EngineRunner.Create(engineId)
8686
:Fen(fen)
8787
:Elo(elo)
88-
:Scheduler(function(next) next() end)
88+
:DelayFn(function(next) next() end)
8989
:OnComplete(function(res, err)
9090
local t1 = os.clock()
9191
if err or not res or not res.move then
@@ -120,7 +120,7 @@ local function runOneTest(engineId, elo)
120120
:Run()
121121
end
122122

123-
-- Engine plays white (first move); runs synchronously via Scheduler(next) next()
123+
-- Engine plays white (first move); runs synchronously via DelayFn(next) => next()
124124
doEngineMove(function() end)
125125

126126
if errMsg then return nil, errMsg end

bin/duel.lua

100644100755
File mode changed.

bin/rate.lua

100644100755
File mode changed.

bin/test.lua

100644100755
Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,39 @@
1010
TEST_WORKERS=4 lua bin/test.lua -- spawn 4 parallel workers (main spawns, then workers run slice)
1111
lua bin/test.lua --lua=C:\path\to\lua.exe
1212
set LUA_BIN=C:\path\to\lua.exe && lua bin/test.lua
13+
lua bin/test.lua --only=slug1,slug2 -- run only tests with these slugs
14+
lua bin/test.lua --ignore=slug1,slug2 -- skip tests with these slugs
1315
]]
1416

15-
-- Parse --lua=path and env LUA_BIN / LUA for Lua binary used when spawning workers
17+
-- Parse command-line arguments
1618
local luaBin = os.getenv("LUA_BIN") or os.getenv("LUA") or "lua"
19+
local onlySlugs = {}
20+
local ignoreSlugs = {}
21+
1722
for _, a in ipairs(arg) do
18-
local path = a:match("^%-%-(lua)=(.+)$")
19-
if path then
20-
luaBin = path
21-
break
23+
local luaPath = a:match("^%-%-lua=(.+)$")
24+
if luaPath then
25+
luaBin = luaPath
26+
end
27+
28+
local only = a:match("^%-%-only=(.+)$")
29+
if only then
30+
for slug in only:gmatch("[^,]+") do
31+
local trimmed = slug:gsub("^%s+", ""):gsub("%s+$", "")
32+
if trimmed ~= "" then
33+
table.insert(onlySlugs, trimmed)
34+
end
35+
end
36+
end
37+
38+
local ignore = a:match("^%-%-ignore=(.+)$")
39+
if ignore then
40+
for slug in ignore:gmatch("[^,]+") do
41+
local trimmed = slug:gsub("^%s+", ""):gsub("%s+$", "")
42+
if trimmed ~= "" then
43+
table.insert(ignoreSlugs, trimmed)
44+
end
45+
end
2246
end
2347
end
2448

@@ -85,6 +109,40 @@ require("init")
85109
-- Load test framework
86110
local Test = require("test")
87111

112+
-- Apply slug filters from command-line arguments or environment variables
113+
local onlySlugsEnv = os.getenv("TEST_ONLY_SLUGS")
114+
local ignoreSlugsEnv = os.getenv("TEST_IGNORE_SLUGS")
115+
116+
if onlySlugsEnv and onlySlugsEnv ~= "" then
117+
for slug in onlySlugsEnv:gmatch("[^,]+") do
118+
local trimmed = slug:gsub("^%s+", ""):gsub("%s+$", "")
119+
if trimmed ~= "" then
120+
table.insert(Test.onlySlugs, trimmed)
121+
end
122+
end
123+
elseif #onlySlugs > 0 then
124+
Test.onlySlugs = onlySlugs
125+
end
126+
127+
if ignoreSlugsEnv and ignoreSlugsEnv ~= "" then
128+
for slug in ignoreSlugsEnv:gmatch("[^,]+") do
129+
local trimmed = slug:gsub("^%s+", ""):gsub("%s+$", "")
130+
if trimmed ~= "" then
131+
table.insert(Test.ignoreSlugs, trimmed)
132+
end
133+
end
134+
elseif #ignoreSlugs > 0 then
135+
Test.ignoreSlugs = ignoreSlugs
136+
end
137+
138+
-- Debug output for slug filters
139+
if #Test.onlySlugs > 0 then
140+
print("Only running tests with slugs: " .. table.concat(Test.onlySlugs, ", "))
141+
end
142+
if #Test.ignoreSlugs > 0 then
143+
print("Ignoring tests with slugs: " .. table.concat(Test.ignoreSlugs, ", "))
144+
end
145+
88146
-- Cross-platform: list immediate subdirectories (no find on Windows)
89147
local function discoverTestDirs(testsRoot)
90148
local dirs = {}
@@ -100,7 +158,8 @@ local function discoverTestDirs(testsRoot)
100158
for line in handle:lines() do
101159
local trimmed = line:gsub("^%s+", ""):gsub("%s+$", ""):gsub("\r", "")
102160
if trimmed ~= "" then
103-
local full = pathForShell .. sep .. trimmed
161+
-- find returns full paths; dir /b returns names only
162+
local full = isAbsolute(trimmed) and trimmed or (pathForShell .. sep .. trimmed)
104163
table.insert(dirs, full)
105164
end
106165
end
@@ -151,7 +210,8 @@ local workerId = os.getenv("TEST_WORKER") -- set when we are a worker
151210
-- Main process: spawn N workers in parallel when TEST_WORKERS > 1 and we are not already a worker
152211
if totalWorkers > 1 and workerId == nil then
153212
local scriptFull = projectRoot .. "bin" .. sep .. "test.lua"
154-
local resultDir = (os.getenv("TEMP") or os.getenv("TMP") or ".") .. sep .. "deltachess_test_" .. tostring(math.random(100000, 999999))
213+
local tmpBase = os.getenv("TEMP") or os.getenv("TMP") or (isWindows and "." or "/tmp")
214+
local resultDir = tmpBase .. sep .. "deltachess_test_" .. tostring(math.random(100000, 999999))
155215
if isWindows then
156216
os.execute("mkdir " .. escapeshellarg(resultDir) .. " 2>nul")
157217
else
@@ -207,6 +267,12 @@ if totalWorkers > 1 and workerId == nil then
207267
if luaBin ~= "lua" then
208268
line = line .. 'set "LUA_BIN=' .. batStr(luaBin) .. '"\r\n'
209269
end
270+
if #onlySlugs > 0 then
271+
line = line .. 'set "TEST_ONLY_SLUGS=' .. batStr(table.concat(onlySlugs, ",")) .. '"\r\n'
272+
end
273+
if #ignoreSlugs > 0 then
274+
line = line .. 'set "TEST_IGNORE_SLUGS=' .. batStr(table.concat(ignoreSlugs, ",")) .. '"\r\n'
275+
end
210276
line = line .. 'cd /d "' .. batStr(projectRoot:gsub("/", sep)) .. '"\r\n'
211277
.. '"' .. batStr(luaBin:gsub("/", sep)) .. '" "' .. batStr(scriptFull:gsub("/", sep)) .. '"\r\n'
212278
local f = io.open(batPath, "wb")
@@ -228,6 +294,12 @@ if totalWorkers > 1 and workerId == nil then
228294
if luaBin ~= "lua" then
229295
shContent = shContent .. "export LUA_BIN=\"" .. forShDoubleQuotedValue(luaBin) .. "\"\n"
230296
end
297+
if #onlySlugs > 0 then
298+
shContent = shContent .. "export TEST_ONLY_SLUGS=\"" .. forShDoubleQuotedValue(table.concat(onlySlugs, ",")) .. "\"\n"
299+
end
300+
if #ignoreSlugs > 0 then
301+
shContent = shContent .. "export TEST_IGNORE_SLUGS=\"" .. forShDoubleQuotedValue(table.concat(ignoreSlugs, ",")) .. "\"\n"
302+
end
231303
shContent = shContent .. "cd \"" .. forShDoubleQuotedValue(projectRoot) .. "\" && exec \""
232304
.. forShDoubleQuotedValue(luaBin) .. "\" \"" .. forShDoubleQuotedValue(scriptFull) .. "\"\n"
233305
local f = io.open(shPath, "wb")

src/EngineDuel.lua

Lines changed: 49 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -97,19 +97,20 @@ function M.PlayGame(whiteEngineId, whiteElo, blackEngineId, blackElo, maxHalfMov
9797

9898
local function doHalfMove(halfMoveNum, doneCb)
9999
local currentFen = board:GetFen()
100-
local moves = board:GetMoveHistory()
100+
local movesUci = board:GetMoveHistoryUci()
101101
local isWhite = board:IsWhiteToMove()
102-
102+
local function lastMoveSan() return (board:GetLastMove() and board:GetLastMove():GetSan()) or nil end
103+
103104
-- Check if board reports game has ended (checkmate, stalemate, 50-move rule)
104105
if board:IsEnded() then
105106
result = board:GetResult()
106107
endReason = board:GetEndReason()
107-
notify({
108-
fen = currentFen,
109-
moves = moves,
110-
halfMoveNum = #moves,
111-
lastMove = (moves[#moves] and moves[#moves]:GetUci()) or nil,
112-
lastMoveSan = (moves[#moves] and moves[#moves]:GetSan()) or nil,
108+
notify({
109+
fen = currentFen,
110+
moves = movesUci,
111+
halfMoveNum = #movesUci,
112+
lastMove = movesUci[#movesUci],
113+
lastMoveSan = lastMoveSan(),
113114
result = result,
114115
endReason = endReason
115116
})
@@ -121,12 +122,12 @@ function M.PlayGame(whiteEngineId, whiteElo, blackEngineId, blackElo, maxHalfMov
121122
if halfMoveNum > maxHalfMoves then
122123
result = Constants.TIMEOUT
123124
endReason = Constants.REASON_TIMEOUT
124-
notify({
125-
fen = currentFen,
126-
moves = moves,
127-
halfMoveNum = #moves,
128-
lastMove = (moves[#moves] and moves[#moves]:GetUci()) or nil,
129-
lastMoveSan = (moves[#moves] and moves[#moves]:GetSan()) or nil,
125+
notify({
126+
fen = currentFen,
127+
moves = movesUci,
128+
halfMoveNum = #movesUci,
129+
lastMove = movesUci[#movesUci],
130+
lastMoveSan = lastMoveSan(),
130131
result = result,
131132
endReason = endReason
132133
})
@@ -137,22 +138,22 @@ function M.PlayGame(whiteEngineId, whiteElo, blackEngineId, blackElo, maxHalfMov
137138
local engineId, elo = getEngineAndElo(isWhite)
138139
local builder = EngineRunner.Create(engineId)
139140
:Fen(currentFen)
140-
:Moves(moves)
141+
:Moves(movesUci)
141142
:OnComplete(function(res, err)
142143
if not res and err then
143144
-- Engine error
144145
result = Constants.ERROR
145146
errorMsg = err
146147
endReason = Constants.REASON_ENGINE_ERROR
147-
notify({
148-
fen = currentFen,
149-
moves = moves,
150-
halfMoveNum = #moves,
151-
lastMove = (moves[#moves] and moves[#moves]:GetUci()) or nil,
152-
lastMoveSan = (moves[#moves] and moves[#moves]:GetSan()) or nil,
153-
result = result,
148+
notify({
149+
fen = currentFen,
150+
moves = movesUci,
151+
halfMoveNum = #movesUci,
152+
lastMove = movesUci[#movesUci],
153+
lastMoveSan = lastMoveSan(),
154+
result = result,
154155
endReason = endReason,
155-
errorMsg = errorMsg
156+
errorMsg = errorMsg
156157
})
157158
if doneCb then doneCb() end
158159
return
@@ -161,48 +162,47 @@ function M.PlayGame(whiteEngineId, whiteElo, blackEngineId, blackElo, maxHalfMov
161162
-- No valid move returned (but no error message) - treat as draw
162163
result = Constants.DRAWN
163164
endReason = Constants.REASON_RESIGNATION
164-
notify({
165-
fen = currentFen,
166-
moves = moves,
167-
halfMoveNum = #moves,
168-
lastMove = (moves[#moves] and moves[#moves]:GetUci()) or nil,
169-
lastMoveSan = (moves[#moves] and moves[#moves]:GetSan()) or nil,
165+
notify({
166+
fen = currentFen,
167+
moves = movesUci,
168+
halfMoveNum = #movesUci,
169+
lastMove = movesUci[#movesUci],
170+
lastMoveSan = lastMoveSan(),
170171
result = result,
171172
endReason = endReason
172173
})
173174
if doneCb then doneCb() end
174175
return
175176
end
176-
177+
177178
local uci = res.move
178179
local boardResult, boardErr = board:MakeMoveUci(uci)
179180
if not boardResult then
180181
result = Constants.ERROR
181182
errorMsg = { message = "invalid move from engine", move = uci, detail = boardErr }
182183
endReason = Constants.REASON_INVALID_MOVE
183-
notify({
184-
fen = currentFen,
185-
moves = moves,
186-
halfMoveNum = #moves,
187-
lastMove = (moves[#moves] and moves[#moves]:GetUci()) or nil,
188-
lastMoveSan = (moves[#moves] and moves[#moves]:GetSan()) or nil,
189-
result = result,
184+
notify({
185+
fen = currentFen,
186+
moves = movesUci,
187+
halfMoveNum = #movesUci,
188+
lastMove = movesUci[#movesUci],
189+
lastMoveSan = lastMoveSan(),
190+
result = result,
190191
endReason = endReason,
191-
errorMsg = errorMsg
192+
errorMsg = errorMsg
192193
})
193194
if doneCb then doneCb() end
194195
return
195196
end
196-
197-
local newMoves = board:GetMoveHistory()
198-
local lastMoveObj = newMoves[#newMoves]
199-
notify({
200-
fen = board:GetFen(),
201-
moves = newMoves,
202-
halfMoveNum = #newMoves,
203-
lastMove = uci,
204-
lastMoveSan = (lastMoveObj and lastMoveObj:GetSan()) or nil,
205-
result = nil
197+
198+
local newMovesUci = board:GetMoveHistoryUci()
199+
notify({
200+
fen = board:GetFen(),
201+
moves = newMovesUci,
202+
halfMoveNum = #newMovesUci,
203+
lastMove = uci,
204+
lastMoveSan = lastMoveSan(),
205+
result = nil
206206
})
207207

208208
-- Schedule next move
@@ -211,7 +211,7 @@ function M.PlayGame(whiteEngineId, whiteElo, blackEngineId, blackElo, maxHalfMov
211211
end)
212212
runPending()
213213
end)
214-
:Scheduler(function(next) next() end)
214+
:DelayFn(function(next) next() end)
215215
if elo then builder:Elo(elo) end
216216
builder:Run()
217217
end

src/EngineInterface.lua

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,18 @@ DeltaChess.Engines.defaultId = nil -- No longer auto-set; default determined by
4141
-- Used to sort engines by efficiency (faster engines listed first).
4242
-- Return nil if unknown or not applicable.
4343
--
44-
-- Calculate(state, loopFn, onComplete)
44+
-- Calculate(state, loopFn, stepFn, onComplete)
4545
-- state - State table (see above). Use state.fen; never mutate state.
4646
-- loopFn - function(stepFn, doneFn). Loop driver for async iteration.
4747
-- stepFn() does one unit of work; returns false when done, truthy to continue.
4848
-- doneFn() is called once after stepFn returns false.
4949
-- The runner defers between steps (e.g. C_Timer.NewTicker in WoW) so
5050
-- the call stack stays flat and the UI remains responsive.
5151
-- Inside stepFn, check state.cancelled to detect abort requests.
52+
-- stepFn - function(items). Deferred step executor provided by the runner.
53+
-- Pass a single function to defer it via the runner's DelayFn,
54+
-- or pass a list of functions to iterate them via the runner's LoopFn.
55+
-- Errors are captured and forwarded to onComplete.
5256
-- onComplete - function(resultTable, err). Call when done. Do not return a result.
5357
-- On success, err is nil and resultTable.move (string, UCI) is required.
5458
-- Optional fields: .san, .ponder, .depth, .nodes, .score, .mate.

src/EngineRunner.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ end
3535
-- :TimeLimitMs(ms) - Optional. Max time in ms. Default: 20000 (20 seconds).
3636
-- :OnComplete(cb) - Required before Run(). function(result, err).
3737
-- :LoopFn(fn) - Optional. loopFn(stepFn, doneFn) loop driver. Default: syncLoop.
38-
-- :Scheduler(fn) - Deprecated alias for :LoopFn(). Kept for backward compatibility.
38+
-- :DelayFn(fn) - Optional. function(next) to defer callbacks. Default: call next() immediately.
3939
-- :HandleError(cb) - Optional. function(err) called on engine error. If returns false,
4040
-- error propagates. Otherwise, a random legal move is returned.
4141
-- :Run() - Start calculation.
@@ -261,7 +261,7 @@ local function Builder(engineId)
261261
end
262262

263263
local ok, err = pcall(function()
264-
engine:Calculate(state, wrappedLoopFn, wrappedDelayFn, done)
264+
engine:Calculate(state, wrappedLoopFn, wrappedStepFn, done)
265265
end)
266266
if not ok then
267267
onComplete(nil, { message = "engine error: " .. tostring(err), move = nil })

tests/integration/engine_runner_test.lua renamed to tests/integration/engine_consistency_test.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ local function getEngineList()
2727
return list
2828
end
2929

30-
Test.suite("EngineRunner integration (all engines)")
30+
Test.suite("EngineRunner consistency", "engine_consistency_integration")
3131

3232
-- Sync loopFn: runs all steps in a tight while-loop. No stack growth.
3333
local function syncLoop(step, done)

0 commit comments

Comments
 (0)