Skip to content

Commit 210c68d

Browse files
authored
Merge pull request #569 from AAVSO/static-analysis
Static analysis
2 parents bd9e187 + 9486edc commit 210c68d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+941
-20
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
name: Checker Framework
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
8+
9+
permissions:
10+
pull-requests: write
11+
12+
jobs:
13+
nullness:
14+
runs-on: ubuntu-latest
15+
timeout-minutes: 30
16+
17+
name: Nullness Checker
18+
19+
steps:
20+
- uses: actions/checkout@v4
21+
22+
- name: Set up Java
23+
uses: actions/setup-java@v4
24+
with:
25+
java-version: '17'
26+
distribution: 'temurin'
27+
28+
- name: Create plugin dirs
29+
run: |
30+
mkdir -p ~/vstar_plugins
31+
mkdir -p ~/vstar_plugin_libs
32+
33+
- name: Run Checker Framework Nullness Checker
34+
run: ant -noinput -buildfile build.xml checker
35+
36+
- name: Extract warning counts
37+
if: always()
38+
id: cf-warnings
39+
run: |
40+
REPORT="test_report/checkerframework/checker-report.txt"
41+
if [ -f "$REPORT" ]; then
42+
TOTAL=$(grep -c 'warning:' "$REPORT" || true)
43+
NULLNESS=$(grep -c '\[nullness\]\|dereference.of.nullable\|argument\]\|assignment\]\|return\]' "$REPORT" || true)
44+
INIT=$(grep -c '\[initialization' "$REPORT" || true)
45+
echo "found=true" >> "$GITHUB_OUTPUT"
46+
echo "total=$TOTAL" >> "$GITHUB_OUTPUT"
47+
echo "nullness=$NULLNESS" >> "$GITHUB_OUTPUT"
48+
echo "init=$INIT" >> "$GITHUB_OUTPUT"
49+
else
50+
echo "found=false" >> "$GITHUB_OUTPUT"
51+
fi
52+
53+
- name: Post step summary
54+
if: always() && steps.cf-warnings.outputs.found == 'true'
55+
run: |
56+
cat >> "$GITHUB_STEP_SUMMARY" <<EOF
57+
## Checker Framework Nullness Analysis
58+
59+
| Metric | Count |
60+
|--------|-------|
61+
| **Total warnings** | **${{ steps.cf-warnings.outputs.total }}** |
62+
| Nullness warnings | ${{ steps.cf-warnings.outputs.nullness }} |
63+
| Initialization warnings | ${{ steps.cf-warnings.outputs.init }} |
64+
65+
Download the **checker-framework-report** artifact for the full report.
66+
EOF
67+
68+
- name: Comment on PR
69+
if: always() && github.event_name == 'pull_request' && steps.cf-warnings.outputs.found == 'true'
70+
uses: actions/github-script@v7
71+
with:
72+
script: |
73+
const total = '${{ steps.cf-warnings.outputs.total }}';
74+
const nullness = '${{ steps.cf-warnings.outputs.nullness }}';
75+
const init = '${{ steps.cf-warnings.outputs.init }}';
76+
77+
const body = [
78+
'## Checker Framework Nullness Analysis',
79+
'',
80+
'| Metric | Count |',
81+
'|--------|-------|',
82+
`| **Total warnings** | **${total}** |`,
83+
`| Nullness warnings | ${nullness} |`,
84+
`| Initialization warnings | ${init} |`,
85+
'',
86+
'<details>',
87+
'<summary>What does this mean?</summary>',
88+
'',
89+
'The Checker Framework Nullness Checker verifies that the code is free from',
90+
'null pointer dereferences. Warnings indicate places where a `@Nullable` value',
91+
'may flow into a `@NonNull` context. Download the **checker-framework-report**',
92+
'artifact for the full report.',
93+
'</details>',
94+
].join('\n');
95+
96+
const { data: comments } = await github.rest.issues.listComments({
97+
owner: context.repo.owner,
98+
repo: context.repo.repo,
99+
issue_number: context.issue.number,
100+
});
101+
const existing = comments.find(c =>
102+
c.user.type === 'Bot' && c.body.startsWith('## Checker Framework Nullness Analysis')
103+
);
104+
105+
if (existing) {
106+
await github.rest.issues.updateComment({
107+
owner: context.repo.owner,
108+
repo: context.repo.repo,
109+
comment_id: existing.id,
110+
body: body,
111+
});
112+
} else {
113+
await github.rest.issues.createComment({
114+
owner: context.repo.owner,
115+
repo: context.repo.repo,
116+
issue_number: context.issue.number,
117+
body: body,
118+
});
119+
}
120+
121+
- name: Upload report
122+
if: always()
123+
uses: actions/upload-artifact@v4
124+
with:
125+
name: checker-framework-report
126+
path: test_report/checkerframework/checker-report.txt

.github/workflows/pit.yml

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
name: PIT Mutation Testing
2+
3+
on:
4+
schedule:
5+
- cron: '0 4 * * 1' # Weekly Monday 4am UTC
6+
workflow_dispatch:
7+
8+
permissions:
9+
issues: write
10+
11+
jobs:
12+
mutation-test:
13+
runs-on: ubuntu-latest
14+
timeout-minutes: 120
15+
16+
name: PIT Mutation Testing
17+
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: Set up Java
22+
uses: actions/setup-java@v4
23+
with:
24+
java-version: '17'
25+
distribution: 'temurin'
26+
27+
- name: Create plugin dirs
28+
run: |
29+
mkdir -p ~/vstar_plugins
30+
mkdir -p ~/vstar_plugin_libs
31+
32+
- name: Run PIT mutation testing
33+
id: pit-run
34+
run: ant -noinput -buildfile build.xml pit
35+
36+
- name: Extract mutation score
37+
if: always()
38+
id: pit-score
39+
run: |
40+
XML=$(find mutation_coverage -name 'mutations.xml' -print -quit 2>/dev/null)
41+
if [ -z "$XML" ]; then
42+
echo "score=N/A" >> "$GITHUB_OUTPUT"
43+
echo "total=0" >> "$GITHUB_OUTPUT"
44+
echo "killed=0" >> "$GITHUB_OUTPUT"
45+
echo "survived=0" >> "$GITHUB_OUTPUT"
46+
echo "no_coverage=0" >> "$GITHUB_OUTPUT"
47+
else
48+
TOTAL=$(grep -c '<mutation ' "$XML" || true)
49+
KILLED=$(grep -c "status='KILLED'" "$XML" || true)
50+
SURVIVED=$(grep -c "status='SURVIVED'" "$XML" || true)
51+
NO_COV=$(grep -c "status='NO_COVERAGE'" "$XML" || true)
52+
if [ "$TOTAL" -gt 0 ] 2>/dev/null; then
53+
SCORE=$((KILLED * 100 / TOTAL))
54+
else
55+
SCORE=0
56+
fi
57+
echo "score=${SCORE}%" >> "$GITHUB_OUTPUT"
58+
echo "total=$TOTAL" >> "$GITHUB_OUTPUT"
59+
echo "killed=$KILLED" >> "$GITHUB_OUTPUT"
60+
echo "survived=$SURVIVED" >> "$GITHUB_OUTPUT"
61+
echo "no_coverage=$NO_COV" >> "$GITHUB_OUTPUT"
62+
fi
63+
64+
- name: Post mutation summary
65+
if: always() && steps.pit-score.outputs.score != 'N/A'
66+
run: |
67+
cat >> "$GITHUB_STEP_SUMMARY" <<EOF
68+
## PIT Mutation Testing Results
69+
70+
| Metric | Value |
71+
|--------|-------|
72+
| **Mutation Score** | **${{ steps.pit-score.outputs.score }}** |
73+
| Total Mutants | ${{ steps.pit-score.outputs.total }} |
74+
| Killed | ${{ steps.pit-score.outputs.killed }} |
75+
| Survived | ${{ steps.pit-score.outputs.survived }} |
76+
| No Coverage | ${{ steps.pit-score.outputs.no_coverage }} |
77+
EOF
78+
79+
- name: Upload mutation report
80+
if: always()
81+
uses: actions/upload-artifact@v4
82+
with:
83+
name: pit-mutation-report
84+
path: mutation_coverage/
85+
86+
- name: Open issue on failure
87+
if: always() && steps.pit-run.outcome == 'failure'
88+
uses: actions/github-script@v7
89+
with:
90+
script: |
91+
const label = 'pit-mutation-testing';
92+
const title = 'PIT Mutation Testing: Scheduled Run Failed';
93+
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
94+
const date = new Date().toISOString().slice(0, 10);
95+
96+
try {
97+
await github.rest.issues.getLabel({
98+
owner: context.repo.owner,
99+
repo: context.repo.repo,
100+
name: label,
101+
});
102+
} catch {
103+
await github.rest.issues.createLabel({
104+
owner: context.repo.owner,
105+
repo: context.repo.repo,
106+
name: label,
107+
color: 'e11d48',
108+
description: 'Automated PIT mutation testing reports',
109+
});
110+
}
111+
112+
const { data: issues } = await github.rest.issues.listForRepo({
113+
owner: context.repo.owner,
114+
repo: context.repo.repo,
115+
labels: label,
116+
state: 'open',
117+
});
118+
119+
if (issues.length > 0) {
120+
await github.rest.issues.createComment({
121+
owner: context.repo.owner,
122+
repo: context.repo.repo,
123+
issue_number: issues[0].number,
124+
body: `Failed again on ${date}.\n\n**Run:** ${runUrl}`,
125+
});
126+
} else {
127+
await github.rest.issues.create({
128+
owner: context.repo.owner,
129+
repo: context.repo.repo,
130+
title: title,
131+
body: [
132+
`The scheduled PIT mutation testing run failed on ${date}.`,
133+
'',
134+
`**Run:** ${runUrl}`,
135+
'',
136+
'Please investigate the failure. This issue will be closed automatically',
137+
'when the next scheduled run succeeds.',
138+
].join('\n'),
139+
labels: [label],
140+
});
141+
}
142+
143+
- name: Close failure issue on success
144+
if: always() && steps.pit-run.outcome == 'success'
145+
uses: actions/github-script@v7
146+
with:
147+
script: |
148+
const label = 'pit-mutation-testing';
149+
const score = '${{ steps.pit-score.outputs.score }}';
150+
const date = new Date().toISOString().slice(0, 10);
151+
152+
const { data: issues } = await github.rest.issues.listForRepo({
153+
owner: context.repo.owner,
154+
repo: context.repo.repo,
155+
labels: label,
156+
state: 'open',
157+
});
158+
159+
for (const issue of issues) {
160+
await github.rest.issues.createComment({
161+
owner: context.repo.owner,
162+
repo: context.repo.repo,
163+
issue_number: issue.number,
164+
body: `Resolved. Run on ${date} completed successfully with a mutation score of **${score}**.`,
165+
});
166+
await github.rest.issues.update({
167+
owner: context.repo.owner,
168+
repo: context.repo.repo,
169+
issue_number: issue.number,
170+
state: 'closed',
171+
state_reason: 'completed',
172+
});
173+
}

0 commit comments

Comments
 (0)