Skip to content

Commit 1ea7088

Browse files
authored
Cleaned up engine tests (#41)
* Cleaned up engine tests * Fix nesting error
1 parent 7b06df2 commit 1ea7088

File tree

1 file changed

+122
-192
lines changed

1 file changed

+122
-192
lines changed

uci/engine_test.go

Lines changed: 122 additions & 192 deletions
Original file line numberDiff line numberDiff line change
@@ -1,217 +1,147 @@
1+
//go:build engine
2+
13
package uci_test
24

35
import (
4-
"bytes"
56
"fmt"
6-
"log"
7-
"os"
8-
"path/filepath"
9-
"regexp"
10-
"strings"
7+
"os/exec"
118
"testing"
129
"time"
1310

1411
"github.com/corentings/chess/v2"
1512
"github.com/corentings/chess/v2/uci"
1613
)
1714

18-
var StockfishPath string
15+
var engines = []string{"stockfish", "lc0"}
1916

20-
//nolint:gochecknoinits // This is a test file
21-
func init() {
22-
dir, _ := os.Getwd()
23-
StockfishPath = filepath.Join(dir, "..", "stockfish")
17+
func isEngineAvailable(engine string) bool {
18+
_, err := exec.LookPath(engine)
19+
return err == nil
2420
}
2521

2622
func Test_EngineInfo(t *testing.T) {
27-
t.SkipNow()
28-
fenStr := "2n1k1r1/8/4B3/3p4/3P4/8/K7/N7 w - - 0 1"
29-
pos := &chess.Position{}
30-
if err := pos.UnmarshalText([]byte(fenStr)); err != nil {
31-
t.Fatal("failed to parse FEN", err)
32-
}
33-
34-
eng, err := uci.New(StockfishPath, uci.Debug)
35-
if err != nil {
36-
t.Fatal(err)
37-
}
38-
defer eng.Close()
39-
40-
cmdMultiPV := uci.CmdSetOption{Name: "multipv", Value: "3"}
41-
cmdPos := uci.CmdPosition{Position: pos}
42-
cmdGo := uci.CmdGo{MoveTime: time.Second / 10}
43-
if err := eng.Run(uci.CmdUCI, uci.CmdIsReady, uci.CmdUCINewGame, cmdMultiPV, cmdPos, cmdGo); err != nil {
44-
t.Fatal("failed to run command", err)
45-
}
46-
47-
move := eng.SearchResults().Info.PV[0]
48-
moveStr := chess.AlgebraicNotation{}.Encode(pos, move)
49-
50-
if moveStr != "Bg8" {
51-
t.Errorf("expected Bg8, got %s", moveStr)
23+
for _, name := range engines {
24+
fenStr := "r1bq1rk1/ppp2ppp/2n2n2/3pp3/3P4/2P1PN2/PP1N1PPP/R1BQ1RK1 w - - 0 8"
25+
26+
t.Run("EngineInfo_"+name, func(t *testing.T) {
27+
if !isEngineAvailable(name) {
28+
t.Skipf("engine %s not available", name)
29+
}
30+
31+
pos := &chess.Position{}
32+
if err := pos.UnmarshalText([]byte(fenStr)); err != nil {
33+
t.Fatal("failed to parse FEN", err)
34+
}
35+
36+
eng, err := uci.New(name, uci.Debug)
37+
if err != nil {
38+
t.Fatal(err)
39+
}
40+
defer eng.Close()
41+
42+
cmdMultiPV := uci.CmdSetOption{Name: "multipv", Value: "2"}
43+
cmdPos := uci.CmdPosition{Position: pos}
44+
cmdGo := uci.CmdGo{MoveTime: time.Second / 10}
45+
if err := eng.Run(uci.CmdUCI, uci.CmdIsReady, uci.CmdUCINewGame, cmdMultiPV, cmdPos, cmdGo); err != nil {
46+
t.Fatal("failed to run command", err)
47+
}
48+
49+
move := eng.SearchResults().Info.PV[0]
50+
moveStr := chess.AlgebraicNotation{}.Encode(pos, move)
51+
52+
if moveStr != "Ne5" {
53+
t.Errorf("expected Ne5, got %s", moveStr)
54+
}
55+
})
5256
}
5357
}
5458

5559
func Test_EngineMultiPVInfo(t *testing.T) {
56-
t.SkipNow()
57-
fenStr := "2n1k1r1/8/4B3/3p4/3P4/8/K7/N7 w - - 0 1"
58-
pos := &chess.Position{}
59-
if err := pos.UnmarshalText([]byte(fenStr)); err != nil {
60-
t.Fatal("failed to parse FEN", err)
61-
}
62-
63-
eng, err := uci.New(StockfishPath, uci.Debug)
64-
if err != nil {
65-
t.Fatal(err)
66-
}
67-
defer eng.Close()
68-
69-
cmdMultiPV := uci.CmdSetOption{Name: "multipv", Value: "3"}
70-
cmdPos := uci.CmdPosition{Position: pos}
71-
cmdGo := uci.CmdGo{MoveTime: time.Second / 10}
72-
if err := eng.Run(uci.CmdUCI, uci.CmdIsReady, uci.CmdUCINewGame, cmdMultiPV, cmdPos, cmdGo); err != nil {
73-
t.Fatal("failed to run command", err)
74-
}
75-
76-
multiPVInfo := eng.SearchResults().MultiPVInfo
77-
78-
if len(multiPVInfo) != 3 {
79-
t.Errorf("expected 3 MultiPV lines, got %d", len(multiPVInfo))
80-
}
81-
82-
move := multiPVInfo[0].PV[0]
83-
moveStr := chess.AlgebraicNotation{}.Encode(pos, move)
84-
if moveStr != "Bg8" {
85-
t.Errorf("expected Bg8, got %s", moveStr)
86-
}
87-
88-
move = multiPVInfo[1].PV[0]
89-
moveStr = chess.AlgebraicNotation{}.Encode(pos, move)
90-
if moveStr != "Bc8" {
91-
t.Errorf("expected Bc8, got %s", moveStr)
92-
}
93-
94-
move = multiPVInfo[2].PV[0]
95-
moveStr = chess.AlgebraicNotation{}.Encode(pos, move)
96-
if moveStr != "Bd5" {
97-
t.Errorf("expected Bd5, got %s", moveStr)
60+
for _, name := range engines {
61+
fenStr := "r1bq1rk1/ppp2ppp/2n2n2/3pp3/3P4/2P1PN2/PP1N1PPP/R1BQ1RK1 w - - 0 8"
62+
63+
t.Run("EngineMultiPVInfo_"+name, func(t *testing.T) {
64+
if !isEngineAvailable(name) {
65+
t.Skipf("engine %s not available", name)
66+
}
67+
68+
pos := &chess.Position{}
69+
if err := pos.UnmarshalText([]byte(fenStr)); err != nil {
70+
t.Fatal("failed to parse FEN", err)
71+
}
72+
73+
eng, err := uci.New(name, uci.Debug)
74+
if err != nil {
75+
t.Fatal(err)
76+
}
77+
defer eng.Close()
78+
79+
cmdMultiPV := uci.CmdSetOption{Name: "multipv", Value: "2"}
80+
cmdPos := uci.CmdPosition{Position: pos}
81+
cmdGo := uci.CmdGo{MoveTime: time.Second / 10}
82+
if err := eng.Run(uci.CmdUCI, uci.CmdIsReady, uci.CmdUCINewGame, cmdMultiPV, cmdPos, cmdGo); err != nil {
83+
t.Fatal("failed to run command", err)
84+
}
85+
86+
multiPVInfo := eng.SearchResults().MultiPVInfo
87+
88+
if len(multiPVInfo) != 2 {
89+
t.Errorf("expected 2 MultiPV lines, got %d", len(multiPVInfo))
90+
}
91+
92+
move := multiPVInfo[0].PV[0]
93+
moveStr := chess.AlgebraicNotation{}.Encode(pos, move)
94+
if moveStr != "Ne5" {
95+
t.Errorf("expected Ne5, got %s", moveStr)
96+
}
97+
98+
move = multiPVInfo[1].PV[0]
99+
moveStr = chess.AlgebraicNotation{}.Encode(pos, move)
100+
if moveStr != "e5" {
101+
t.Errorf("expected e5, got %s", moveStr)
102+
}
103+
})
98104
}
99105
}
100106

101107
func Test_UCIMovesTags(t *testing.T) {
102-
t.SkipNow()
103-
eng, err := uci.New(StockfishPath, uci.Debug)
104-
if err != nil {
105-
t.Fatal(err)
106-
}
107-
defer eng.Close()
108-
setOpt := uci.CmdSetOption{Name: "UCI_Elo", Value: "1500"}
109-
setPos := uci.CmdPosition{Position: chess.StartingPosition()}
110-
setGo := uci.CmdGo{MoveTime: time.Second / 10}
111-
if err := eng.Run(uci.CmdUCI, uci.CmdIsReady, setOpt, uci.CmdUCINewGame, setPos, setGo); err != nil {
112-
t.Fatal("failed to run command", err)
113-
}
114-
115-
game := chess.NewGame()
116-
notation := chess.AlgebraicNotation{}
117-
118-
for game.Outcome() == chess.NoOutcome {
119-
cmdPos := uci.CmdPosition{Position: game.Position()}
120-
cmdGo := uci.CmdGo{MoveTime: time.Second / 100}
121-
if err2 := eng.Run(cmdPos, cmdGo); err2 != nil {
122-
t.Fatal("failed to run command", err2)
123-
}
124-
125-
move := eng.SearchResults().BestMove
126-
pos := game.Position()
127-
san := notation.Encode(pos, move)
128-
129-
err = game.PushMove(san, nil)
130-
if err != nil {
131-
t.Fatal(fmt.Sprintf("failed to push move %s - %s - %v. Pos: %s", san, move.String(), move.HasTag(chess.Capture), pos.String()), err)
132-
}
108+
for _, name := range engines {
109+
t.Run("UCIMovesTags_"+name, func(t *testing.T) {
110+
if !isEngineAvailable(name) {
111+
t.Skipf("engine %s not available", name)
112+
}
113+
114+
eng, err := uci.New(name, uci.Debug)
115+
if err != nil {
116+
t.Fatal(err)
117+
}
118+
defer eng.Close()
119+
setOpt := uci.CmdSetOption{Name: "UCI_Elo", Value: "1500"}
120+
setPos := uci.CmdPosition{Position: chess.StartingPosition()}
121+
setGo := uci.CmdGo{MoveTime: time.Second / 10}
122+
if err := eng.Run(uci.CmdUCI, uci.CmdIsReady, setOpt, uci.CmdUCINewGame, setPos, setGo); err != nil {
123+
t.Fatal("failed to run command", err)
124+
}
125+
126+
game := chess.NewGame()
127+
notation := chess.AlgebraicNotation{}
128+
129+
for game.Outcome() == chess.NoOutcome {
130+
cmdPos := uci.CmdPosition{Position: game.Position()}
131+
cmdGo := uci.CmdGo{MoveTime: time.Second / 100}
132+
if err2 := eng.Run(cmdPos, cmdGo); err2 != nil {
133+
t.Fatal("failed to run command", err2)
134+
}
135+
136+
move := eng.SearchResults().BestMove
137+
pos := game.Position()
138+
san := notation.Encode(pos, move)
139+
140+
err = game.PushMove(san, nil)
141+
if err != nil {
142+
t.Fatal(fmt.Sprintf("failed to push move %s - %s - %v. Pos: %s", san, move.String(), move.HasTag(chess.Capture), pos.String()), err)
143+
}
144+
}
145+
})
133146
}
134147
}
135-
136-
func TestLogger(t *testing.T) {
137-
t.SkipNow()
138-
139-
b := bytes.NewBuffer([]byte{})
140-
logger := log.New(b, "", 0)
141-
eng, err := uci.New(StockfishPath, uci.Debug, uci.Logger(logger))
142-
if err != nil {
143-
t.Fatal(err)
144-
}
145-
defer eng.Close()
146-
setOpt := uci.CmdSetOption{Name: "UCI_Elo", Value: "1500"}
147-
setPos := uci.CmdPosition{Position: chess.StartingPosition()}
148-
setGo := uci.CmdGo{MoveTime: time.Second / 10}
149-
if err := eng.Run(uci.CmdUCI, uci.CmdIsReady, setOpt, uci.CmdUCINewGame, setPos, setGo); err != nil {
150-
t.Fatal(err)
151-
}
152-
expected := infoRegex.ReplaceAllString(logOutput, "")
153-
expectedSplit := strings.Split(expected, "\n")
154-
actual := b.String()
155-
actual = infoRegex.ReplaceAllString(actual, "")
156-
actualSplit := strings.Split(actual, "\n")
157-
158-
for i, expectedSplitted := range expectedSplit {
159-
expectedSplitted = strings.TrimSpace(expectedSplitted)
160-
actual := strings.TrimSpace(actualSplit[i])
161-
if expectedSplitted != actual {
162-
t.Fatalf("expected %s but got %s", expectedSplitted, actual)
163-
}
164-
}
165-
}
166-
167-
var infoRegex = regexp.MustCompile("(?m)[\r\n]+^.*info.*$")
168-
169-
const (
170-
logOutput = `uci
171-
Stockfish 14.1 by the Stockfish developers (see AUTHORS file)
172-
id name Stockfish 14.1
173-
id author the Stockfish developers (see AUTHORS file)
174-
175-
option name Debug Log File type string default
176-
option name Threads type spin default 1 min 1 max 512
177-
option name Hash type spin default 16 min 1 max 33554432
178-
option name Clear Hash type button
179-
option name Ponder type check default false
180-
option name MultiPV type spin default 1 min 1 max 500
181-
option name Skill Level type spin default 20 min 0 max 20
182-
option name Move Overhead type spin default 10 min 0 max 5000
183-
option name Slow Mover type spin default 100 min 10 max 1000
184-
option name nodestime type spin default 0 min 0 max 10000
185-
option name UCI_Chess960 type check default false
186-
option name UCI_AnalyseMode type check default false
187-
option name UCI_LimitStrength type check default false
188-
option name UCI_Elo type spin default 1350 min 1350 max 2850
189-
option name UCI_ShowWDL type check default false
190-
option name SyzygyPath type string default <empty>
191-
option name SyzygyProbeDepth type spin default 1 min 1 max 100
192-
option name Syzygy50MoveRule type check default true
193-
option name SyzygyProbeLimit type spin default 7 min 0 max 7
194-
option name Use NNUE type check default true
195-
option name EvalFile type string default nn-13406b1dcbe0.nnue
196-
uciok
197-
isready
198-
readyok
199-
setoption name UCI_Elo value 1500
200-
ucinewgame
201-
position fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
202-
go movetime 100
203-
info string NNUE evaluation using nn-13406b1dcbe0.nnue enabled
204-
info depth 1 seldepth 1 multipv 1 score cp 38 nodes 20 nps 20000 tbhits 0 time 1 pv d2d4
205-
info depth 2 seldepth 2 multipv 1 score cp 82 nodes 51 nps 51000 tbhits 0 time 1 pv e2e4 a7a6
206-
info depth 3 seldepth 3 multipv 1 score cp 55 nodes 154 nps 154000 tbhits 0 time 1 pv e2e4 c7c6 d2d4
207-
info depth 4 seldepth 4 multipv 1 score cp 22 nodes 807 nps 269000 tbhits 0 time 3 pv g1f3 d7d5 d2d4 g8f6
208-
info depth 5 seldepth 5 multipv 1 score cp 54 nodes 1061 nps 353666 tbhits 0 time 3 pv e2e4 c7c5 g1f3
209-
info depth 6 seldepth 6 multipv 1 score cp 54 nodes 1761 nps 440250 tbhits 0 time 4 pv e2e4 c7c5 g1f3 d7d5 e4d5 d8d5
210-
info depth 7 seldepth 8 multipv 1 score cp 50 nodes 5459 nps 545900 tbhits 0 time 10 pv e2e4 e7e5 g1f3 b8c6 b1c3 g8f6 d2d4
211-
info depth 8 seldepth 8 multipv 1 score cp 50 nodes 6998 nps 583166 tbhits 0 time 12 pv e2e4 e7e5 g1f3 b8c6 d2d4 e5d4 f3d4 g8f6 b1c3
212-
info depth 9 seldepth 11 multipv 1 score cp 54 nodes 12053 nps 573952 tbhits 0 time 21 pv e2e4 e7e5 g1f3 b8c6 d2d4 e5d4 f3d4 g8f6 d4c6
213-
info depth 10 seldepth 13 multipv 1 score cp 35 nodes 28785 nps 564411 tbhits 0 time 51 pv e2e4 e7e6 d2d4 d7d5 e4d5 e6d5 g1f3 f8d6 f1d3 g8f6 e1g1 e8g8
214-
info depth 11 seldepth 14 multipv 1 score cp 50 nodes 34551 nps 575850 tbhits 0 time 60 pv e2e4 e7e5 g1f3 b8c6 d2d4 e5d4 f3d4 g8f6 b1c3 f8b4
215-
info depth 12 seldepth 14 multipv 1 score cp 50 nodes 55039 nps 534359 tbhits 0 time 103 pv e2e4 e7e5 g1f3 b8c6 d2d4 e5d4 f3d4 g8f6 b1c3 f8b4
216-
bestmove e2e4 ponder c7c5`
217-
)

0 commit comments

Comments
 (0)