Skip to content

Commit e15b17d

Browse files
committed
test: LSP server verso tests
We add LSP tests for Verso documents; we reuse the core LSP testing infrastructure from lean4 upstream. See `doc/dev/testing.md` there for more details. This is a draft PR, some comments: - maybe we should make the effort and use the lake test runner instead of the ad-hoc test runner here - location of files / entry point to be debated - we need to ship more tests
1 parent 82eba5d commit e15b17d

File tree

9 files changed

+217
-0
lines changed

9 files changed

+217
-0
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ jobs:
6868
run: |
6969
lake test
7070
71+
- name: Run LSP tests
72+
run: |
73+
cd src/tests && ./run_interactive.sh
74+
7175
- name: Generate the test website
7276
run: |
7377
lake exe demosite --output _out/test-projects/demosite

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ _out
1212
*.hash
1313
profile.json
1414
profile.json.gz
15+
*.produced.out

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
unreleased
2+
----------
3+
4+
(Changes that can break existing programs are marked with a "*")
5+
6+
### Tests
7+
8+
- Added infrastructure for testing of LSP server and Verso documents

src/tests/common.sh

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
set -euo pipefail
2+
3+
ulimit -s ${MAIN_STACK_SIZE:-8192}
4+
DIFF=diff
5+
if diff --color --help >/dev/null 2>&1; then
6+
DIFF="diff --color";
7+
fi
8+
9+
function fail {
10+
echo $1
11+
exit 1
12+
}
13+
14+
INTERACTIVE=no
15+
if [ $1 == "-i" ]; then
16+
INTERACTIVE=yes
17+
shift
18+
fi
19+
f="$1"
20+
shift
21+
[ $# -eq 0 ] || fail "Usage: test_single.sh [-i] test-file.lean"
22+
23+
function lean_has_llvm_support {
24+
lean --features | grep -q "LLVM"
25+
}
26+
27+
function compile_lean_c_backend {
28+
lean --c="$f.c" "$f" || fail "Failed to compile $f into C file"
29+
leanc ${LEANC_OPTS-} -O3 -DNDEBUG -o "$f.out" "$@" "$f.c" || fail "Failed to compile C file $f.c"
30+
}
31+
32+
function compile_lean_llvm_backend {
33+
set -o xtrace
34+
rm "*.ll" || true # remove debugging files.
35+
rm "*.bc" || true # remove bitcode files
36+
rm "*.o" || true # remove object files
37+
lean --bc="$f.linked.bc" "$f" || fail "Failed to compile $f into bitcode file"
38+
leanc ${LEANC_OPTS-} -O3 -DNDEBUG -o "$f.out" "$@" "$f.linked.bc" || fail "Failed to link object file '$f.linked.bc'"
39+
set +o xtrace
40+
}
41+
42+
function exec_capture_raw {
43+
# backtraces are system-specific, strip them (might be captured in `#guard_msgs`)
44+
LEAN_BACKTRACE=0 "$@" 2>&1 > "$f.produced.out"
45+
}
46+
47+
# produces filtered output intended for usage with `diff_produced`
48+
function exec_capture {
49+
# backtraces are system-specific, strip them
50+
# mvar suffixes like in `?m.123` are deterministic but prone to change on minor changes, so strip them
51+
# similarly, links to the language reference may have URL components depending on the toolchain, so normalize those
52+
LEAN_BACKTRACE=0 "$@" 2>&1 \
53+
| perl -pe 's/(\?(\w|_\w+))\.[0-9]+/\1/g' \
54+
| perl -pe 's/https:\/\/lean-lang\.org\/doc\/reference\/(v?[0-9.]+(-rc[0-9]+)?|latest)/REFERENCE/g' > "$f.produced.out"
55+
}
56+
57+
58+
# Remark: `${var+x}` is a parameter expansion which evaluates to nothing if `var` is unset, and substitutes the string `x` otherwise.
59+
function check_ret {
60+
[ -n "${expected_ret+x}" ] || expected_ret=0
61+
[ -f "$f.expected.ret" ] && expected_ret=$(< "$f.expected.ret")
62+
if [ -n "$expected_ret" ] && [ $ret -ne $expected_ret ]; then
63+
echo "Unexpected return code $ret executing '$@'; expected $expected_ret. Output:"
64+
cat "$f.produced.out"
65+
exit 1
66+
fi
67+
}
68+
69+
function exec_check_raw {
70+
ret=0
71+
exec_capture_raw "$@" || ret=$?
72+
check_ret "$@"
73+
}
74+
75+
# produces filtered output intended for usage with `diff_produced`
76+
function exec_check {
77+
ret=0
78+
exec_capture "$@" || ret=$?
79+
check_ret "$@"
80+
}
81+
82+
function diff_produced {
83+
if test -f "$f.expected.out"; then
84+
if $DIFF -au --strip-trailing-cr -I "executing external script" "$f.expected.out" "$f.produced.out"; then
85+
:
86+
else
87+
echo "ERROR: file $f.produced.out does not match $f.expected.out"
88+
if [ $INTERACTIVE == "yes" ]; then
89+
meld "$f.produced.out" "$f.expected.out"
90+
if diff -I "executing external script" "$f.expected.out" "$f.produced.out"; then
91+
echo "-- mismatch was fixed"
92+
fi
93+
fi
94+
exit 1
95+
fi
96+
else
97+
echo "ERROR: file $f.expected.out does not exist"
98+
if [ $INTERACTIVE == "yes" ]; then
99+
read -p "copy $f.produced.out (y/n)? "
100+
if [ $REPLY == "y" ]; then
101+
cp -- "$f.produced.out" "$f.expected.out"
102+
echo "-- copied $f.produced.out --> $f.expected.out"
103+
fi
104+
fi
105+
exit 1
106+
fi
107+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import Verso
2+
import VersoManual
3+
import VersoBlueprint
4+
5+
open Verso.Genre Manual
6+
7+
#doc (Manual) "Test for Goals in Inline Lean" =>
8+
9+
```lean
10+
theorem test : ∀ (n : Nat), n = n := by
11+
intros
12+
--^ $/lean/plainGoal
13+
rfl
14+
```
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{"textDocument": {"uri": "file:///interactive/inline_goals.lean"},
2+
"position": {"line": 10, "character": 8}}
3+
null

src/tests/run.lean

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Lean.Server.Test.Runner
2+
3+
def main := Lean.Server.Test.Runner.main

src/tests/run_interactive.sh

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/bin/bash
2+
3+
# set -ex
4+
5+
# Configuration
6+
TEST_DIR="interactive"
7+
RUNNER="./test_single.sh"
8+
PASS_COUNT=0
9+
FAIL_COUNT=0
10+
FAILED_TESTS=()
11+
12+
# Check if the runner exists and is executable
13+
if [[ ! -x "$RUNNER" ]]; then
14+
echo "Error: Runner script '$RUNNER' not found or not executable."
15+
exit 1
16+
fi
17+
18+
echo "------------------------------------------------"
19+
echo " Starting Verso Interactive Test Suite: $(date) "
20+
echo "------------------------------------------------"
21+
22+
# Loop through every file in the test directory
23+
for test_file in "$TEST_DIR"/*.lean; do
24+
# Skip if it's not a file (e.g., a subdirectory)
25+
[[ -f "$test_file" ]] || continue
26+
27+
echo -n "Running test: $(basename "$test_file")... "
28+
29+
# Capture both stdout and stderr into a variable
30+
# This allows us to hide it on PASS and show it on FAIL
31+
TEST_OUTPUT=$("$RUNNER" "$test_file" 2>&1)
32+
EXIT_CODE=$?
33+
34+
if [ $EXIT_CODE -eq 0 ]; then
35+
echo "✅ PASS"
36+
((PASS_COUNT++))
37+
else
38+
echo "❌ FAIL (Exit Code: $EXIT_CODE)"
39+
echo "--- FAILURE LOG: $(basename "$test_file") ---"
40+
echo "$TEST_OUTPUT"
41+
echo "-----------------------------------------------"
42+
43+
((FAIL_COUNT++))
44+
FAILED_TESTS+=("$(basename "$test_file")")
45+
fi
46+
done
47+
48+
# --- Summary Output ---
49+
echo "-----------------------------------------------"
50+
echo "Test Summary:"
51+
echo " Total: $((PASS_COUNT + FAIL_COUNT))"
52+
echo " Passed: $PASS_COUNT"
53+
echo " Failed: $FAIL_COUNT"
54+
55+
if [[ $FAIL_COUNT -gt 0 ]]; then
56+
echo "-----------------------------------------------"
57+
echo "Failed Tests:"
58+
for failed in "${FAILED_TESTS[@]}"; do
59+
echo " - $failed"
60+
done
61+
echo "-----------------------------------------------"
62+
exit 1
63+
else
64+
echo "All tests passed successfully!"
65+
echo "-----------------------------------------------"
66+
exit 0
67+
fi

src/tests/test_single.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/env bash
2+
source ./common.sh
3+
4+
# IO.Process.exit (used by the file worker) seems to be incompatible with LSAN
5+
# TODO: investigate or work around
6+
export ASAN_OPTIONS=detect_leaks=0
7+
8+
# these tests don't have to succeed
9+
exec_capture lean -Dlinter.all=false --run ./run.lean "$f" || true
10+
diff_produced

0 commit comments

Comments
 (0)