Skip to content

Commit 244e5f5

Browse files
committed
feature: Add support for sysman python bindings
Related-To: NEO-NEO-17257 Signed-off-by: shubham kumar <shubham.kumar@intel.com>
1 parent 9086388 commit 244e5f5

4 files changed

Lines changed: 446 additions & 28 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Python Bindings - Security Scan
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- '**'
7+
paths:
8+
- 'bindings/sysman/python/**'
9+
push:
10+
branches:
11+
- main
12+
- master
13+
- python_bindings
14+
paths:
15+
- 'bindings/sysman/python/**'
16+
workflow_dispatch:
17+
18+
jobs:
19+
bandit:
20+
name: Bandit Security Analysis
21+
runs-on: ubuntu-latest
22+
defaults:
23+
run:
24+
working-directory: bindings/sysman/python
25+
permissions:
26+
contents: read
27+
28+
steps:
29+
- name: Checkout code
30+
uses: actions/checkout@v4
31+
32+
- name: Set up Python
33+
uses: actions/setup-python@v5
34+
with:
35+
python-version: '3.x'
36+
37+
- name: Install Bandit
38+
run: |
39+
python -m pip install --upgrade pip
40+
pip install bandit
41+
42+
- name: Run Bandit security scan
43+
run: |
44+
bandit -r source/ test/ -f json -o bandit-report.json
45+
bandit -r source/ test/ -ll -f screen
46+
47+
- name: Upload Bandit results as artifact
48+
uses: actions/upload-artifact@v4
49+
if: always()
50+
with:
51+
name: bandit-security-report
52+
path: bandit-report.json
53+
retention-days: 30
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
name: Python Bindings - Unit Tests & Coverage
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- '**'
7+
paths:
8+
- 'bindings/sysman/python/**'
9+
push:
10+
branches:
11+
- main
12+
- master
13+
- python_bindings
14+
paths:
15+
- 'bindings/sysman/python/**'
16+
workflow_dispatch:
17+
18+
env:
19+
PYTHON_VERSION: '3.10'
20+
21+
jobs:
22+
unit-tests-linux:
23+
name: Linux - Unit Tests & Coverage Analysis
24+
runs-on: ubuntu-latest
25+
defaults:
26+
run:
27+
working-directory: bindings/sysman/python
28+
permissions:
29+
contents: read
30+
pull-requests: write # For PR comments
31+
checks: write # For check results
32+
33+
steps:
34+
- name: Checkout code
35+
uses: actions/checkout@v4
36+
with:
37+
fetch-depth: 0 # Full history for better coverage comparison
38+
39+
- name: Set up Python ${{ env.PYTHON_VERSION }}
40+
uses: actions/setup-python@v5
41+
with:
42+
python-version: ${{ env.PYTHON_VERSION }}
43+
cache: 'pip'
44+
45+
- name: Install dependencies
46+
run: |
47+
python -m pip install --upgrade pip
48+
pip install pytest pytest-cov pytest-html pytest-xdist
49+
pip install coverage[toml]
50+
# Linting, formatting, and type checking tools
51+
pip install flake8 black isort mypy
52+
53+
# Install any project-specific dependencies if requirements.txt exists
54+
if [ -f requirements.txt ]; then
55+
pip install -r requirements.txt
56+
fi
57+
58+
- name: Code Quality Checks
59+
run: |
60+
echo "Running code quality checks..."
61+
62+
# Check code formatting with black
63+
echo "Checking code formatting..."
64+
black --check --diff source/ test/ || {
65+
echo "❌ Code formatting issues found. Run 'black source/ test/' to fix."
66+
exit 1
67+
}
68+
69+
# Check import sorting
70+
echo "Checking import sorting..."
71+
isort --check-only --diff source/ test/ || {
72+
echo "❌ Import sorting issues found. Run 'isort source/ test/' to fix."
73+
exit 1
74+
}
75+
76+
# Run linting
77+
echo "Running flake8 linting..."
78+
flake8 source/ test/ || {
79+
echo "❌ Linting issues found. Check flake8 output above."
80+
exit 1
81+
}
82+
83+
# Run type checking
84+
echo "Running mypy type checking..."
85+
mypy source/ --ignore-missing-imports --no-strict-optional || {
86+
echo "❌ Type checking issues found. Check mypy output above."
87+
exit 1
88+
}
89+
90+
echo "✅ All code quality checks passed!"
91+
92+
- name: Run Unit Tests with Coverage
93+
run: |
94+
# Run tests with coverage and generate multiple report formats
95+
python -m pytest test/unit_tests/ \
96+
--cov=source \
97+
--cov-report=term-missing \
98+
--cov-report=html:htmlcov \
99+
--cov-report=xml:coverage.xml \
100+
--cov-report=json:coverage.json \
101+
--junit-xml=test-results.xml \
102+
--html=test-report.html \
103+
--self-contained-html \
104+
-v \
105+
--tb=short \
106+
--durations=10
107+
108+
- name: Extract Coverage Percentage
109+
id: coverage
110+
run: |
111+
# Extract coverage percentage from JSON report
112+
COVERAGE_PCT=$(python -c 'import json; data = json.load(open("coverage.json")); print("{:.1f}".format(data["totals"]["percent_covered"]))')
113+
echo "coverage_pct=$COVERAGE_PCT" >> $GITHUB_OUTPUT
114+
echo "Current Coverage: $COVERAGE_PCT%"
115+
116+
- name: Get Baseline Coverage from Target Branch
117+
id: baseline
118+
continue-on-error: true
119+
run: |
120+
# ============================================================================
121+
# TEMPORARY: First Baseline Handling
122+
# TODO: Remove the special handling below after first PR merge to master
123+
# Once master has test coverage baseline, this graceful fallback is no longer needed
124+
# ============================================================================
125+
SKIP_BASELINE_ON_MISSING=true # Set to false after first merge
126+
127+
# Get the target branch (base of the PR or parent commit for push events)
128+
if [ "${{ github.event_name }}" == "pull_request" ]; then
129+
TARGET_BRANCH="${{ github.event.pull_request.base.ref }}"
130+
echo "Pull request detected - comparing against base branch: $TARGET_BRANCH"
131+
else
132+
# For push events, compare against the parent commit
133+
TARGET_BRANCH="${{ github.ref_name }}"
134+
echo "Push event detected - comparing against parent commit on branch: $TARGET_BRANCH"
135+
# Use HEAD~1 to get the previous commit
136+
git checkout HEAD~1 2>/dev/null || {
137+
if [ "$SKIP_BASELINE_ON_MISSING" = true ]; then
138+
echo "⚠️ No parent commit found (possibly first commit). Skipping baseline comparison."
139+
echo "baseline_coverage=0" >> $GITHUB_OUTPUT
140+
exit 0
141+
else
142+
echo "❌ No parent commit found"
143+
exit 1
144+
fi
145+
}
146+
fi
147+
148+
echo "Target branch/commit: $TARGET_BRANCH"
149+
150+
# Checkout target branch/commit to get baseline coverage
151+
if [ "${{ github.event_name }}" == "pull_request" ]; then
152+
git fetch origin $TARGET_BRANCH
153+
git checkout origin/$TARGET_BRANCH 2>/dev/null || {
154+
if [ "$SKIP_BASELINE_ON_MISSING" = true ]; then
155+
echo "⚠️ Could not checkout target branch. Skipping baseline comparison."
156+
echo "baseline_coverage=0" >> $GITHUB_OUTPUT
157+
exit 0
158+
else
159+
echo "❌ Could not checkout target branch: $TARGET_BRANCH"
160+
exit 1
161+
fi
162+
}
163+
fi
164+
165+
# Check if tests exist in baseline
166+
if [ ! -d "bindings/sysman/python/test/unit_tests" ]; then
167+
if [ "$SKIP_BASELINE_ON_MISSING" = true ]; then
168+
echo "⚠️ No unit tests found in baseline. This is likely the first commit with tests."
169+
echo "Skipping baseline comparison."
170+
echo "baseline_coverage=0" >> $GITHUB_OUTPUT
171+
exit 0
172+
else
173+
echo "❌ No unit tests found in baseline branch"
174+
exit 1
175+
fi
176+
fi
177+
178+
# Install dependencies and run tests to get baseline coverage
179+
python -m pip install --upgrade pip
180+
pip install pytest pytest-cov coverage[toml]
181+
182+
# Run tests with coverage for baseline
183+
python -m pytest test/unit_tests/ \
184+
--cov=source \
185+
--cov-report=json:baseline-coverage.json \
186+
-q || {
187+
if [ "$SKIP_BASELINE_ON_MISSING" = true ]; then
188+
echo "⚠️ Baseline tests failed or not found. Skipping baseline comparison."
189+
echo "baseline_coverage=0" >> $GITHUB_OUTPUT
190+
exit 0
191+
else
192+
echo "❌ Baseline tests failed on $TARGET_BRANCH"
193+
echo "The target branch has broken tests. Fix the baseline before merging."
194+
exit 1
195+
fi
196+
}
197+
198+
# Extract baseline coverage
199+
BASELINE_COVERAGE=$(python -c 'import json; data = json.load(open("baseline-coverage.json")); print("{:.1f}".format(data["totals"]["percent_covered"]))')
200+
echo "baseline_coverage=$BASELINE_COVERAGE" >> $GITHUB_OUTPUT
201+
echo "Baseline Coverage: $BASELINE_COVERAGE%"
202+
203+
# Switch back to PR branch
204+
git checkout ${{ github.sha }}
205+
206+
- name: Check Coverage Threshold
207+
run: |
208+
CURRENT_COVERAGE="${{ steps.coverage.outputs.coverage_pct }}"
209+
BASELINE_COVERAGE="${{ steps.baseline.outputs.baseline_coverage }}"
210+
211+
echo "Current Coverage: $CURRENT_COVERAGE%"
212+
echo "Baseline Coverage: $BASELINE_COVERAGE%"
213+
214+
# ============================================================================
215+
# TEMPORARY: First Baseline Handling
216+
# TODO: Remove this section after first PR merge to master
217+
# After first merge, baseline should always exist and this check is unnecessary
218+
# ============================================================================
219+
# If baseline is 0, this is the first commit with tests - always pass
220+
if [ "$BASELINE_COVERAGE" == "0" ] || [ -z "$BASELINE_COVERAGE" ]; then
221+
echo "✅ No baseline coverage found (first commit with tests)."
222+
echo "Current coverage: $CURRENT_COVERAGE%"
223+
echo "Establishing baseline for future comparisons."
224+
exit 0
225+
fi
226+
# ============================================================================
227+
# END TEMPORARY SECTION
228+
# ============================================================================
229+
230+
# Use awk for floating point comparison (more portable than bc)
231+
if [ $(echo "$CURRENT_COVERAGE >= $BASELINE_COVERAGE" | awk '{print ($1 >= $3)}') -eq 1 ]; then
232+
DELTA=$(echo "$CURRENT_COVERAGE - $BASELINE_COVERAGE" | awk '{printf "%.1f", $1 - $3}')
233+
echo "✅ Coverage check passed: $CURRENT_COVERAGE% >= $BASELINE_COVERAGE% (Δ ${DELTA}%)"
234+
else
235+
REGRESSION=$(echo "$BASELINE_COVERAGE - $CURRENT_COVERAGE" | awk '{printf "%.1f", $1 - $3}')
236+
echo "❌ Coverage regression detected!"
237+
echo "Current coverage ($CURRENT_COVERAGE%) is below baseline coverage ($BASELINE_COVERAGE%)"
238+
echo "Regression: -${REGRESSION}%"
239+
echo "This would cause coverage to regress from the baseline."
240+
echo "Please add tests to maintain or improve coverage."
241+
exit 1
242+
fi
243+
244+
- name: Coverage Summary
245+
if: always()
246+
run: |
247+
echo "## 📊 Test Coverage Report" >> $GITHUB_STEP_SUMMARY
248+
echo "" >> $GITHUB_STEP_SUMMARY
249+
echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY
250+
echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY
251+
echo "| Current Coverage | ${{ steps.coverage.outputs.coverage_pct }}% |" >> $GITHUB_STEP_SUMMARY
252+
echo "| Target Branch Coverage | ${{ steps.baseline.outputs.baseline_coverage }}% |" >> $GITHUB_STEP_SUMMARY
253+
254+
THRESHOLD="${{ steps.baseline.outputs.baseline_coverage }}"
255+
if [ $(echo "${{ steps.coverage.outputs.coverage_pct }} >= $THRESHOLD" | awk '{print ($1 >= $3)}') -eq 1 ]; then
256+
echo "| Status | ✅ PASSED |" >> $GITHUB_STEP_SUMMARY
257+
else
258+
echo "| Status | ❌ FAILED |" >> $GITHUB_STEP_SUMMARY
259+
fi
260+
echo "" >> $GITHUB_STEP_SUMMARY
261+
262+
# Add detailed coverage report
263+
echo "### Detailed Coverage Report" >> $GITHUB_STEP_SUMMARY
264+
echo '```' >> $GITHUB_STEP_SUMMARY
265+
python -m coverage report >> $GITHUB_STEP_SUMMARY
266+
echo '```' >> $GITHUB_STEP_SUMMARY
267+
268+
- name: Comment PR with Coverage
269+
if: github.event_name == 'pull_request'
270+
uses: actions/github-script@v7
271+
with:
272+
script: |
273+
const coverage = '${{ steps.coverage.outputs.coverage_pct }}';
274+
const baselineCoverage = '${{ steps.baseline.outputs.baseline_coverage }}';
275+
const passed = parseFloat(coverage) >= parseFloat(baselineCoverage);
276+
const improvement = parseFloat(coverage) - parseFloat(baselineCoverage);
277+
278+
const body = `## 📊 Unit Tests & Coverage Report
279+
280+
${passed ? '✅' : '❌'} **Coverage:** ${coverage}% vs ${baselineCoverage}% (target branch)
281+
282+
### Test Results
283+
- **Status:** ${passed ? 'PASSED' : 'FAILED'}
284+
- **Current Coverage:** ${coverage}%
285+
- **Target Branch Coverage:** ${baselineCoverage}%
286+
- **Change:** ${improvement > 0 ? '+' : ''}${improvement.toFixed(1)}%
287+
288+
${passed ?
289+
(improvement > 0 ?
290+
`🎉 Coverage improved by ${improvement.toFixed(1)}%! Great work on adding tests.` :
291+
'✅ Coverage maintained. No regression detected.') :
292+
'⚠️ Coverage regression detected. This PR would reduce test coverage. Please add more tests.'
293+
}
294+
295+
📁 Detailed reports are available in the workflow artifacts.
296+
`;
297+
298+
github.rest.issues.createComment({
299+
issue_number: context.issue.number,
300+
owner: context.repo.owner,
301+
repo: context.repo.repo,
302+
body: body
303+
});
304+
305+
- name: Upload Test Results
306+
uses: actions/upload-artifact@v4
307+
if: always()
308+
with:
309+
name: test-results-${{ github.run_number }}
310+
path: |
311+
test-results.xml
312+
test-report.html
313+
htmlcov/
314+
coverage.xml
315+
coverage.json
316+
retention-days: 30
317+
318+
- name: Upload Coverage Reports
319+
uses: actions/upload-artifact@v4
320+
if: always()
321+
with:
322+
name: coverage-report-${{ github.run_number }}
323+
path: |
324+
htmlcov/
325+
coverage.xml
326+
coverage.json
327+
retention-days: 30

0 commit comments

Comments
 (0)