Skip to content

Commit 6019f47

Browse files
HrisShterevvgvassilev
authored andcommitted
added CI
1 parent 4496333 commit 6019f47

3 files changed

Lines changed: 131 additions & 0 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Check C++ Notebooks
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
8+
jobs:
9+
execute-notebooks:
10+
name: Execute C++ Notebooks
11+
runs-on: ubuntu-latest
12+
defaults:
13+
run:
14+
shell: bash -el {0}
15+
16+
steps:
17+
- name: Checkout repository
18+
uses: actions/checkout@v4
19+
20+
- name: Set up micromamba environment
21+
uses: mamba-org/setup-micromamba@v2
22+
with:
23+
environment-file: environment.yml
24+
cache-environment: true
25+
26+
- name: List available kernels
27+
run: jupyter kernelspec list
28+
29+
- name: Run C++ Tests via Pytest
30+
run: |
31+
mkdir -p executed
32+
$CONDA_PREFIX/bin/pytest tests/test_notebooks.py -sv
33+
34+
- name: Upload executed notebooks as artifact
35+
if: always()
36+
uses: actions/upload-artifact@v4
37+
with:
38+
name: executed-cpp-notebooks
39+
path: executed/
40+
if-no-files-found: ignore

environment.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name: livecpp-ci
2+
channels:
3+
- conda-forge
4+
dependencies:
5+
- python>=3.11
6+
- xeus-cpp
7+
- jupyter
8+
- papermill
9+
- pytest

tests/test_notebooks.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import unittest
2+
import nbformat
3+
import papermill as pm
4+
import os
5+
import re
6+
7+
class NotebookTests(unittest.TestCase):
8+
kernel_name = 'xcpp23' # Your standard C++ kernel name
9+
notebook_dir = 'xeus-cpp'
10+
11+
def test_notebooks(self):
12+
# Scan the target folder for notebooks
13+
notebook_files = [
14+
f for f in os.listdir(self.notebook_dir)
15+
if f.endswith('.ipynb')
16+
]
17+
18+
if not notebook_files:
19+
self.fail(f"No notebooks found in directory: {self.notebook_dir}")
20+
21+
os.makedirs('executed', exist_ok=True)
22+
23+
for name in notebook_files:
24+
25+
if name == "03_redefinition.ipynb":
26+
print(f"--> Skipping intentional error notebook: {name}")
27+
continue
28+
29+
inp = os.path.join(self.notebook_dir, name)
30+
out = os.path.join('executed', name)
31+
32+
# Load the reference notebook (Expected)
33+
with open(inp) as f:
34+
input_nb = nbformat.read(f, as_version=4)
35+
36+
# Run the notebook via Papermill
37+
try:
38+
executed_notebook = pm.execute_notebook(
39+
inp,
40+
out,
41+
log_output=True,
42+
kernel_name=self.kernel_name
43+
)
44+
if executed_notebook is None:
45+
self.fail(f"Execution of notebook {name} returned None")
46+
except Exception as e:
47+
self.fail(f"Notebook {name} failed to execute smoothly: {e}")
48+
49+
# Load the freshly executed notebook (Got)
50+
with open(out) as f:
51+
output_nb = nbformat.read(f, as_version=4)
52+
53+
# Compare cell outputs exactly
54+
for i, (input_cell, output_cell) in enumerate(
55+
zip(input_nb.cells, output_nb.cells)
56+
):
57+
if input_cell.cell_type == 'code' and output_cell.cell_type == 'code':
58+
# Extract and clean exact raw text streams
59+
expected_text = ''.join(
60+
o.get('text', '') for o in input_cell.outputs if o.get('output_type') == 'stream'
61+
).strip()
62+
63+
got_text = ''.join(
64+
o.get('text', '') for o in output_cell.outputs if o.get('output_type') == 'stream'
65+
).strip()
66+
67+
expected_text = re.sub(r'0x[0-9a-fA-F]+', '0x7fffffff', expected_text)
68+
got_text = re.sub(r'0x[0-9a-fA-F]+', '0x7fffffff', got_text)
69+
70+
ub_pattern = r'(Undefined Behavior \(Reading freed heap\):\s*)-?\d+'
71+
expected_text = re.sub(ub_pattern, r'\1[GARBAGE_INT]', expected_text)
72+
got_text = re.sub(ub_pattern, r'\1[GARBAGE_INT]', got_text)
73+
74+
if expected_text != got_text:
75+
self.fail(
76+
f"Cell {i} in notebook '{name}' has mismatched output.\n\n"
77+
f"--- Expected ---\n{expected_text}\n\n"
78+
f"--- Got ---\n{got_text}"
79+
)
80+
81+
if __name__ == '__main__':
82+
unittest.main()

0 commit comments

Comments
 (0)