Skip to content

Commit c080337

Browse files
authored
Add Lean Python unit tests CI workflow (#142)
* Add Lean Python unit tests CI workflow Runs Lean's Python/Pandas unit test suites plus every other Lean unit test class that exercises Python code against the freshly built Python.Runtime.dll. Test classes are selected by scanning Lean's test sources for Python interop usage and passing the resulting FullyQualifiedName filter through a runsettings file. Regression suites are excluded since Python regression algorithms are already covered by the Lean Python Regression Tests workflow. * Run Lean Python unit tests job on self-hosted runner Follow the pattern of Lean's own unit test CI job and this repo's main workflow: run directly in a quantconnect/lean:foundation container on a self-hosted runner instead of manually managing a docker container on a GitHub-hosted runner. * Use bash for the test filter generation step Container jobs default to sh, which does not support the here-string used when writing the runsettings file. * Run Lean Python regression tests job on self-hosted runner Follow the pattern of Lean's own regression tests CI job: run directly in a quantconnect/lean:foundation container on a self-hosted runner instead of manually managing a docker container on a GitHub-hosted runner.
1 parent 4bf4b28 commit c080337

2 files changed

Lines changed: 101 additions & 29 deletions

File tree

.github/workflows/lean-python-regression-tests.yml

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ concurrency:
1818

1919
jobs:
2020
lean-python-regression:
21-
runs-on: ubuntu-24.04
22-
timeout-minutes: 360
21+
runs-on: self-hosted
22+
container:
23+
image: quantconnect/lean:foundation
24+
options: --cpus 12 --memory 12g
2325
steps:
2426
- name: Checkout pythonnet
2527
uses: actions/checkout@v4
@@ -32,41 +34,19 @@ jobs:
3234
repository: QuantConnect/Lean
3335
path: Lean
3436

35-
- name: Liberate disk space
36-
uses: jlumbroso/free-disk-space@main
37-
with:
38-
tool-cache: true
39-
large-packages: false
40-
docker-images: false
41-
swap-storage: false
42-
43-
- name: Define docker helper
44-
run: |
45-
echo 'runInContainer() { docker exec test-container "$@"; }' > $HOME/ci_functions.sh
46-
echo "BASH_ENV=$HOME/ci_functions.sh" >> $GITHUB_ENV
47-
48-
- name: Start container
49-
run: |
50-
docker run -d \
51-
--workdir /__w/pythonnet/pythonnet \
52-
-v /home/runner/work:/__w \
53-
--name test-container \
54-
quantconnect/lean:foundation \
55-
tail -f /dev/null
56-
5737
- name: Build Python.Runtime.dll
5838
run: |
59-
runInContainer dotnet build pythonnet/src/runtime/Python.Runtime.csproj \
39+
dotnet build pythonnet/src/runtime/Python.Runtime.csproj \
6040
-c Release /v:quiet /p:WarningLevel=1
6141
6242
- name: Build Lean
6343
run: |
64-
runInContainer dotnet build /p:Configuration=Release /v:quiet /p:WarningLevel=1 \
44+
dotnet build /p:Configuration=Release /v:quiet /p:WarningLevel=1 \
6545
Lean/QuantConnect.Lean.sln
6646
6747
- name: Inject freshly built Python.Runtime.dll into Lean test output
6848
run: |
69-
runInContainer cp pythonnet/pythonnet/runtime/Python.Runtime.dll \
49+
cp pythonnet/pythonnet/runtime/Python.Runtime.dll \
7050
Lean/Tests/bin/Release/Python.Runtime.dll
7151
7252
- name: Restrict regression tests to Python only
@@ -75,12 +55,12 @@ jobs:
7555
# key (defaults to CSharp + Python). Setting it to Python only makes the
7656
# test factory emit Python test cases exclusively. The config file is
7757
# JSONC; Newtonsoft tolerates the inserted line.
78-
runInContainer sed -i '1a\ "regression-test-languages": ["Python"],' \
58+
sed -i '1a\ "regression-test-languages": ["Python"],' \
7959
Lean/Tests/bin/Release/config.json
8060
8161
- name: Run Lean Python regression tests
8262
run: |
83-
runInContainer dotnet test Lean/Tests/bin/Release/QuantConnect.Tests.dll \
63+
dotnet test Lean/Tests/bin/Release/QuantConnect.Tests.dll \
8464
--blame-hang-timeout 300seconds --blame-crash \
8565
--filter "TestCategory=RegressionTests & Name~Python/" \
8666
-- TestRunParameters.Parameter\(name=\"log-handler\", value=\"ConsoleErrorLogHandler\"\) \
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
name: Lean Python Unit Tests
2+
3+
# Validates a Python.Runtime.dll change against Lean's Python unit tests:
4+
# the Python/Pandas test suites (QuantConnect.Tests.Python) plus every other
5+
# Lean unit test class that exercises Python code. We build this repo's
6+
# Python.Runtime.dll, build Lean against its NuGet QuantConnect.pythonnet
7+
# reference, drop the freshly built DLL over Lean's test output, and run only
8+
# the test classes whose sources reference the Python interop layer,
9+
# mirroring Lean's own .github/workflows/gh-actions.yml unit test job.
10+
11+
on:
12+
push:
13+
branches:
14+
- master
15+
pull_request:
16+
17+
concurrency:
18+
group: ${{ github.workflow }}-${{ github.ref }}
19+
cancel-in-progress: true
20+
21+
jobs:
22+
lean-python-unit-tests:
23+
runs-on: self-hosted
24+
container:
25+
image: quantconnect/lean:foundation
26+
options: --cpus 12 --memory 12g
27+
steps:
28+
- name: Checkout pythonnet
29+
uses: actions/checkout@v4
30+
with:
31+
path: pythonnet
32+
33+
- name: Checkout Lean
34+
uses: actions/checkout@v4
35+
with:
36+
repository: QuantConnect/Lean
37+
path: Lean
38+
39+
- name: Build Python.Runtime.dll
40+
run: |
41+
dotnet build pythonnet/src/runtime/Python.Runtime.csproj \
42+
-c Release /v:quiet /p:WarningLevel=1
43+
44+
- name: Build Lean
45+
run: |
46+
dotnet build /p:Configuration=Release /v:quiet /p:WarningLevel=1 \
47+
Lean/QuantConnect.Lean.sln
48+
49+
- name: Inject freshly built Python.Runtime.dll into Lean test output
50+
run: |
51+
cp pythonnet/pythonnet/runtime/Python.Runtime.dll \
52+
Lean/Tests/bin/Release/Python.Runtime.dll
53+
54+
- name: Generate Python unit test filter
55+
shell: bash
56+
run: |
57+
# Select every test class whose source references Lean's Python
58+
# interop layer (Py.GIL, PythonEngine, PyObject, Language.Python
59+
# test cases, ...). Class names are extracted per matching file and
60+
# turned into a FullyQualifiedName filter, so the selection tracks
61+
# Lean automatically. Non-test helper classes that slip in simply
62+
# match nothing. Regression suites are excluded: Python regression
63+
# algorithms already run in the Lean Python Regression Tests
64+
# workflow, and TravisExclude/ResearchRegressionTests mirror Lean's
65+
# own unit test CI exclusions. The filter is passed through a
66+
# runsettings file because it is far too long for a command line.
67+
filter=$(grep -rlE 'Py\.GIL|PythonEngine|PyModule|Language\.Python|\bPyObject\b' \
68+
Lean/Tests --include='*.cs' --exclude-dir=bin --exclude-dir=obj \
69+
| while read -r file; do
70+
ns=$(sed -n 's/^namespace \([A-Za-z0-9_.]*\).*/\1/p' "$file" | head -1)
71+
[ -z "$ns" ] && continue
72+
sed -n 's/.*\bclass \([A-Za-z0-9_]*\).*/\1/p' "$file" | sort -u \
73+
| while read -r cls; do echo "FullyQualifiedName~$ns.$cls"; done
74+
done | sort -u | paste -sd'|')
75+
echo "Selected $(tr '|' '\n' <<< "$filter" | wc -l) test class name filters"
76+
cat > lean-python-unit-tests.runsettings <<EOF
77+
<?xml version="1.0" encoding="utf-8"?>
78+
<RunSettings>
79+
<RunConfiguration>
80+
<TestCaseFilter>($filter)&amp;TestCategory!=TravisExclude&amp;TestCategory!=ResearchRegressionTests&amp;TestCategory!=RegressionTests</TestCaseFilter>
81+
</RunConfiguration>
82+
<TestRunParameters>
83+
<Parameter name="log-handler" value="ConsoleErrorLogHandler" />
84+
</TestRunParameters>
85+
</RunSettings>
86+
EOF
87+
88+
- name: Run Lean Python unit tests
89+
run: |
90+
dotnet test Lean/Tests/bin/Release/QuantConnect.Tests.dll \
91+
--blame-hang-timeout 300seconds --blame-crash \
92+
--settings lean-python-unit-tests.runsettings

0 commit comments

Comments
 (0)