Skip to content

Commit 9580029

Browse files
committed
Add CI tests for clj-kondo hooks
- Add test fixtures exercising all require-python patterns - Add test-clj-kondo-hooks.sh for direct hook testing - Add test-copy-configs.sh for end-to-end config testing - Add clj-kondo-hooks CI job (runs before unit tests, no JVM/Python needed) Test coverage includes: - :bind-ns, :reload, :no-arglists flags - :refer with vectors, :all, and :* - Multiple specs in single require-python call - import-python builtin namespace aliases
1 parent 700e125 commit 9580029

File tree

5 files changed

+250
-0
lines changed

5 files changed

+250
-0
lines changed

.github/workflows/test.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,23 @@ on:
1010
pull_request:
1111

1212
jobs:
13+
clj-kondo-hooks:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v3
17+
18+
- name: Install clj-kondo
19+
run: |
20+
curl -sLO https://raw.githubusercontent.com/clj-kondo/clj-kondo/master/script/install-clj-kondo
21+
chmod +x install-clj-kondo
22+
sudo ./install-clj-kondo
23+
24+
- name: Test clj-kondo hooks
25+
run: ./script/test-clj-kondo-hooks.sh
26+
27+
- name: Test end-to-end config
28+
run: ./script/test-copy-configs.sh
29+
1330
unit-test:
1431
runs-on: ${{matrix.os}}
1532
strategy:

script/test-clj-kondo-hooks.sh

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
6+
CONFIG_DIR="$PROJECT_ROOT/resources/clj-kondo.exports/clj-python/libpython-clj"
7+
FIXTURES_DIR="$PROJECT_ROOT/test/clj_kondo/fixtures"
8+
9+
echo "=== clj-kondo Hook Tests ==="
10+
echo "Config dir: $CONFIG_DIR"
11+
echo "Fixtures dir: $FIXTURES_DIR"
12+
echo ""
13+
14+
if ! command -v clj-kondo &> /dev/null; then
15+
echo "ERROR: clj-kondo not found in PATH"
16+
exit 1
17+
fi
18+
19+
echo "clj-kondo version: $(clj-kondo --version)"
20+
echo ""
21+
22+
FAILED=0
23+
24+
run_test() {
25+
local name="$1"
26+
local file="$2"
27+
28+
echo -n "Testing $name... "
29+
30+
OUTPUT=$(clj-kondo --lint "$file" --config-dir "$CONFIG_DIR" 2>&1) || true
31+
32+
REQUIRE_PYTHON_ERRORS=$(echo "$OUTPUT" | grep -cE "(Unknown require option|:bind-ns|:reload|:no-arglists)" || true)
33+
34+
if [[ "$REQUIRE_PYTHON_ERRORS" -gt 0 ]]; then
35+
echo "FAILED"
36+
echo " Found require-python related errors/warnings:"
37+
echo "$OUTPUT" | grep -E "(Unknown require option|:bind-ns|:reload|:no-arglists)" | sed 's/^/ /'
38+
FAILED=1
39+
return 1
40+
fi
41+
42+
UNRESOLVED_SYMBOL_ERRORS=$(echo "$OUTPUT" | grep -cE "Unresolved symbol: (webpush|secure_filename|urlencode|urlparse)" || true)
43+
44+
if [[ "$UNRESOLVED_SYMBOL_ERRORS" -gt 0 ]]; then
45+
echo "FAILED"
46+
echo " Found unresolved symbol errors for referred symbols:"
47+
echo "$OUTPUT" | grep -E "Unresolved symbol:" | sed 's/^/ /'
48+
FAILED=1
49+
return 1
50+
fi
51+
52+
echo "PASSED"
53+
return 0
54+
}
55+
56+
run_test "require_python_test.clj" "$FIXTURES_DIR/require_python_test.clj"
57+
run_test "require_python_edge_cases.clj" "$FIXTURES_DIR/require_python_edge_cases.clj"
58+
59+
echo ""
60+
if [[ "$FAILED" -eq 0 ]]; then
61+
echo "=== All tests passed ==="
62+
exit 0
63+
else
64+
echo "=== Some tests failed ==="
65+
exit 1
66+
fi

script/test-copy-configs.sh

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
6+
CONFIG_SOURCE="$PROJECT_ROOT/resources/clj-kondo.exports/clj-python/libpython-clj"
7+
TEST_PROJECT="/tmp/libpython-clj-kondo-test-$$"
8+
9+
echo "=== End-to-End clj-kondo Config Test ==="
10+
echo "Project root: $PROJECT_ROOT"
11+
echo "Config source: $CONFIG_SOURCE"
12+
echo "Test project: $TEST_PROJECT"
13+
echo ""
14+
echo "Note: This test simulates what 'clj-kondo --copy-configs' does when"
15+
echo " libpython-clj is distributed as a JAR (which includes resources/)."
16+
echo ""
17+
18+
cleanup() {
19+
if [[ -d "$TEST_PROJECT" ]]; then
20+
rm -rf "$TEST_PROJECT"
21+
fi
22+
}
23+
trap cleanup EXIT
24+
25+
mkdir -p "$TEST_PROJECT/src"
26+
mkdir -p "$TEST_PROJECT/.clj-kondo/clj-python"
27+
28+
cp -r "$CONFIG_SOURCE" "$TEST_PROJECT/.clj-kondo/clj-python/libpython-clj"
29+
30+
cat > "$TEST_PROJECT/src/test_app.clj" << 'EOF'
31+
(ns test-app
32+
(:require [libpython-clj2.require :refer [require-python import-python]]))
33+
34+
(import-python)
35+
36+
(require-python '[numpy :as np]
37+
'[pathlib :bind-ns true]
38+
'[pywebpush :bind-ns true :refer [webpush]]
39+
'[werkzeug.utils :refer [secure_filename]]
40+
'[sklearn :reload true]
41+
'[pandas :no-arglists true])
42+
43+
(defn main []
44+
(pathlib/Path "/tmp")
45+
(webpush {})
46+
(secure_filename "test.txt")
47+
(np/array [1 2 3])
48+
(python/len [1 2 3]))
49+
EOF
50+
51+
echo "Step 1: Created test project with copied configs"
52+
echo ""
53+
54+
cd "$TEST_PROJECT"
55+
56+
echo "Step 2: Verifying config structure..."
57+
if [[ -f "$TEST_PROJECT/.clj-kondo/clj-python/libpython-clj/config.edn" ]]; then
58+
echo " ✓ config.edn exists"
59+
else
60+
echo " ✗ config.edn NOT found"
61+
exit 1
62+
fi
63+
64+
if [[ -f "$TEST_PROJECT/.clj-kondo/clj-python/libpython-clj/hooks/libpython_clj/require/require_python.clj" ]]; then
65+
echo " ✓ require_python.clj hook exists"
66+
else
67+
echo " ✗ require_python.clj hook NOT found"
68+
exit 1
69+
fi
70+
71+
if [[ -f "$TEST_PROJECT/.clj-kondo/clj-python/libpython-clj/hooks/libpython_clj/require/import_python.clj" ]]; then
72+
echo " ✓ import_python.clj hook exists"
73+
else
74+
echo " ✗ import_python.clj hook NOT found"
75+
exit 1
76+
fi
77+
echo ""
78+
79+
echo "Step 3: Running clj-kondo on test file..."
80+
OUTPUT=$(clj-kondo --lint "$TEST_PROJECT/src/test_app.clj" 2>&1) || true
81+
echo "$OUTPUT"
82+
echo ""
83+
84+
REQUIRE_PYTHON_ERRORS=$(echo "$OUTPUT" | grep -cE "(Unknown require option|:bind-ns|:reload|:no-arglists)" || true)
85+
UNRESOLVED_REFER_ERRORS=$(echo "$OUTPUT" | grep -cE "Unresolved symbol: (webpush|secure_filename)" || true)
86+
87+
if [[ "$REQUIRE_PYTHON_ERRORS" -gt 0 ]]; then
88+
echo "✗ FAILED: Found require-python related errors"
89+
exit 1
90+
fi
91+
92+
if [[ "$UNRESOLVED_REFER_ERRORS" -gt 0 ]]; then
93+
echo "✗ FAILED: Found unresolved symbol errors for :refer symbols"
94+
exit 1
95+
fi
96+
97+
echo "=== All end-to-end tests passed ==="
98+
echo ""
99+
echo "The clj-kondo config will work correctly when libpython-clj is"
100+
echo "installed via clj-kondo --copy-configs --dependencies"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
(ns clj-kondo.fixtures.require-python-edge-cases
2+
"Edge case tests for require-python clj-kondo hook."
3+
(:require [libpython-clj2.require :refer [require-python]]))
4+
5+
(require-python '[json :refer :all])
6+
7+
(require-python '[collections :refer :*])
8+
9+
(require-python '[datetime :bind-ns true :reload true :no-arglists true])
10+
11+
(require-python '[urllib.parse :as parse :bind-ns true :refer [urlencode urlparse]])
12+
13+
(defn test-refer-all []
14+
(datetime/now))
15+
16+
(defn test-combined-refer []
17+
(urlencode {})
18+
(urlparse "http://example.com"))
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
(ns clj-kondo.fixtures.require-python-test
2+
"Test fixtures for require-python clj-kondo hook.
3+
This file should produce ZERO errors when linted with the hook."
4+
(:require [libpython-clj2.require :refer [require-python import-python]]))
5+
6+
(import-python)
7+
8+
(require-python '[numpy :as np]
9+
'[pandas :as pd]
10+
'[matplotlib.pyplot :as plt])
11+
12+
(require-python '[pathlib :bind-ns true])
13+
14+
(require-python '[requests :reload true])
15+
16+
(require-python '[sklearn.linear_model :no-arglists true])
17+
18+
(require-python '[pywebpush :bind-ns true :refer [webpush]])
19+
20+
(require-python '[werkzeug.utils :refer [secure_filename]])
21+
22+
(require-python '[google.cloud.secretmanager :as secretmanager :bind-ns true])
23+
24+
(require-python 'operator
25+
'base64
26+
'socket)
27+
28+
(require-python '[cryptography.fernet.Fernet :as Fernet :bind-ns true]
29+
'[cryptography.hazmat.primitives.hashes :as hashes :bind-ns true])
30+
31+
(require-python '[os :reload])
32+
33+
(defn test-bind-ns-usage []
34+
(pathlib/Path "/tmp")
35+
(Fernet/generate_key))
36+
37+
(defn test-refer-usage []
38+
(webpush {})
39+
(secure_filename "test.txt"))
40+
41+
(defn test-alias-usage []
42+
(np/array [1 2 3])
43+
(pd/DataFrame {})
44+
(secretmanager/SecretManagerServiceClient))
45+
46+
(defn test-python-builtins []
47+
(python/len [1 2 3])
48+
(python.list/append [] 1)
49+
(python.dict/keys {}))

0 commit comments

Comments
 (0)