Skip to content

Commit 34e6982

Browse files
Create GitHub Classroom Autograding Workflow
1 parent 500f5c9 commit 34e6982

File tree

1 file changed

+43
-262
lines changed

1 file changed

+43
-262
lines changed

.github/workflows/classroom.yml

Lines changed: 43 additions & 262 deletions
Original file line numberDiff line numberDiff line change
@@ -1,269 +1,50 @@
1-
name: GitHub Classroom Workflow
2-
3-
on:
4-
push:
5-
branches: [main]
6-
1+
name: Autograding Tests
2+
'on':
3+
- push
4+
- repository_dispatch
75
permissions:
86
checks: write
97
actions: read
108
contents: read
11-
pull-requests: write
12-
139
jobs:
14-
autograding:
15-
name: Autograding
10+
run-autograding-tests:
1611
runs-on: ubuntu-latest
17-
outputs:
18-
grading_status: ${{ steps.grading.outcome }}
12+
if: github.actor != 'github-classroom[bot]'
1913
steps:
20-
- uses: actions/setup-java@v5
21-
with:
22-
distribution: 'temurin'
23-
java-version: '25'
24-
- uses: actions/checkout@v5
25-
26-
# 🧪 Compilation
27-
- name: Compilation Check
28-
id: compile
29-
run: |
30-
mvn -ntp compile
31-
if [ $? -eq 0 ]; then
32-
echo "result=1/1" >> $GITHUB_OUTPUT
33-
else
34-
echo "result=0/1" >> $GITHUB_OUTPUT
35-
fi
36-
37-
# ✅ Basic functionality
38-
- name: Basic Tests
39-
id: basic
40-
run: |
41-
mvn -ntp test -Dtest=BasicTest
42-
if [ $? -eq 0 ]; then
43-
echo "result=1/1" >> $GITHUB_OUTPUT
44-
else
45-
echo "result=0/1" >> $GITHUB_OUTPUT
46-
fi
47-
48-
# ⚠️ Optional edge cases
49-
- name: Edge Case Tests
50-
id: edge
51-
continue-on-error: true
52-
run: |
53-
set +e
54-
mvn -ntp test -Dtest=EdgeCaseTest
55-
EXIT_CODE=$?
56-
if [ $EXIT_CODE -eq 0 ]; then
57-
echo "result=1/1" >> $GITHUB_OUTPUT
58-
else
59-
echo "result=0/1" >> $GITHUB_OUTPUT
60-
fi
61-
- name: Autograding Reporter
62-
uses: classroom-resources/autograding-grading-reporter@v1
63-
env:
64-
COMPILATION_RESULTS: "${{ steps.compile.outputs.result }}"
65-
BASIC_RESULTS: "${{ steps.basic.outputs.result }}"
66-
EDGE_RESULTS: "${{ steps.edge.outputs.result }}"
67-
with:
68-
runners: compile,basic,edge
69-
70-
# 🧮 Scoring and conditional failure
71-
# - name: Calculate and Report Points
72-
# run: |
73-
# POINTS=0
74-
# MAX=3
75-
#
76-
# if [ "${{ steps.compile.outcome }}" == "success" ]; then
77-
# POINTS=$((POINTS + 1))
78-
# else
79-
# echo "❌ Compilation failed"
80-
# fi
81-
#
82-
# if [ "${{ steps.basic.outcome }}" == "success" ]; then
83-
# POINTS=$((POINTS + 1))
84-
# else
85-
# echo "❌ Basic tests failed"
86-
# fi
87-
#
88-
# if [ "$exit_code" -eq 0 ]; then
89-
# POINTS=$((POINTS + 1))
90-
# else
91-
# echo "⚠️ Edge case tests failed (optional)"
92-
# fi
93-
#
94-
# echo "::notice title=Autograding complete::Points ${POINTS}/${MAX}"
95-
# echo "::notice title=Autograding report::{\"totalPoints\":${POINTS},\"maxPoints\":${MAX}}"
96-
# exit 0
97-
98-
ai_feedback:
99-
name: AI-Powered Feedback
100-
needs: autograding
101-
if: ${{ needs.autograding.result == 'success' }}
102-
runs-on: ubuntu-latest
103-
permissions:
104-
pull-requests: write
105-
env:
106-
OPENROUTER_MODEL: ${{ vars.OPENROUTER_MODEL }}
107-
SYSTEM_PROMPT: ${{ vars.SYSTEM_PROMPT }}
108-
steps:
109-
- name: Checkout repository
110-
uses: actions/checkout@v5
111-
112-
- name: Read assignment instructions
113-
id: instructions
114-
run: |
115-
# Reads the content of the README.md file into an output variable.
116-
# The `EOF` marker is used to handle multi-line file content.
117-
echo "instructions=$(cat README.md | sed 's/\"/\\\"/g' | sed 's/$/\\n/' | tr -d '\n' | sed 's/\\n/\\\\n/g')" >> $GITHUB_OUTPUT
118-
119-
- name: Read source code
120-
id: source_code
121-
run: |
122-
{
123-
echo 'source_code<<EOF'
124-
find src/main/java -type f -name "*.java" | while read -r file; do
125-
echo "=== File: $file ==="
126-
cat "$file"
127-
echo
128-
done
129-
echo 'EOF'
130-
} >> "$GITHUB_OUTPUT"
131-
132-
- name: Read test code
133-
id: test_code
134-
run: |
135-
{
136-
echo 'test_code<<EOF'
137-
if [ -d "src/test/java" ]; then
138-
find src/test/java -type f -name "*.java" | while read -r file; do
139-
echo "=== File: $file ==="
140-
cat "$file"
141-
echo
142-
done
143-
else
144-
echo "No test code found."
145-
fi
146-
echo 'EOF'
147-
} >> "$GITHUB_OUTPUT"
148-
- name: Generate AI Feedback
149-
id: ai_feedback
150-
run: |
151-
# This step sends the collected data to the OpenRouter API.
152-
INSTRUCTIONS=$(jq -Rs . <<'EOF'
153-
${{ steps.instructions.outputs.instructions }}
154-
EOF
155-
)
156-
SOURCE_CODE=$(jq -Rs . <<'EOF'
157-
${{ steps.source_code.outputs.source_code }}
158-
EOF
159-
)
160-
TEST_CODE=$(jq -Rs . <<'EOF'
161-
${{ steps.test_code.outputs.test_code }}
162-
EOF
163-
)
164-
165-
if [ -z "$INSTRUCTIONS" ] || [ -z "$SOURCE_CODE" ] || [ -z "$TEST_CODE" ]; then
166-
echo "Error: One or more required variables are not set."
167-
exit 1
168-
fi
169-
170-
# Assigning to USER_CONTENT with variable expansion
171-
PAYLOAD="Please provide feedback on the following Java assignment.
172-
173-
--- Assignment Instructions ---
174-
${INSTRUCTIONS}
175-
176-
--- Source files ---
177-
${SOURCE_CODE}
178-
179-
--- Test files ---
180-
${TEST_CODE}"
181-
182-
JSON_CONTENT=$(jq -n \
183-
--argjson model "$OPENROUTER_MODEL" \
184-
--arg system_prompt "$SYSTEM_PROMPT" \
185-
--arg payload "$PAYLOAD" \
186-
'{
187-
models: $model,
188-
messages: [
189-
{role: "system", content: $system_prompt},
190-
{role: "user", content: $payload}
191-
]
192-
}')
193-
194-
echo "$JSON_CONTENT"
195-
196-
API_RESPONSE=$(echo "$JSON_CONTENT" | curl https://openrouter.ai/api/v1/chat/completions \
197-
-H "Authorization: Bearer ${{ secrets.OPENROUTER_API_KEY }}" \
198-
-H "Content-Type: application/json" \
199-
-d @-)
200-
201-
echo "$API_RESPONSE"
202-
203-
FEEDBACK_CONTENT=$(echo "$API_RESPONSE" | jq -r '.choices[0].message.content')
204-
echo "feedback<<EOF" >> $GITHUB_OUTPUT
205-
echo "$FEEDBACK_CONTENT" >> $GITHUB_OUTPUT
206-
echo "EOF" >> $GITHUB_OUTPUT
207-
- name: Post Feedback as PR Comment ✍️
208-
uses: actions/github-script@v7
209-
env:
210-
FEEDBACK_BODY: ${{ steps.ai_feedback.outputs.feedback }}
211-
with:
212-
github-token: ${{ secrets.GITHUB_TOKEN }}
213-
script: |
214-
const { owner, repo } = context.repo;
215-
const targetTitle = "Feedback";
216-
const signature = "🤖 AI Feedback";
217-
218-
// Find the PR by title
219-
const { data: pullRequests } = await github.rest.pulls.list({
220-
owner,
221-
repo,
222-
state: "open",
223-
per_page: 100
224-
});
225-
226-
const matchingPR = pullRequests.find(pr => pr.title.trim().toLowerCase() === targetTitle.toLowerCase());
227-
if (!matchingPR) {
228-
throw new Error(`No open pull request found with title '${targetTitle}'`);
229-
}
230-
231-
const prNumber = matchingPR.number;
232-
233-
// Get all comments on the PR
234-
const { data: comments } = await github.rest.issues.listComments({
235-
owner,
236-
repo,
237-
issue_number: prNumber,
238-
per_page: 100
239-
});
240-
241-
// Find existing comment from github-actions with our signature
242-
const existing = comments.find(c =>
243-
c.user?.login === "github-actions[bot]" &&
244-
c.body?.includes(signature)
245-
);
246-
247-
const timestamp = new Date().toISOString();
248-
const newEntry = `🕒 _Posted on ${timestamp}_\n\n${process.env.FEEDBACK_BODY}\n\n---\n`;
249-
250-
if (existing) {
251-
const updatedBody = `${newEntry}${existing.body}`;
252-
await github.rest.issues.updateComment({
253-
owner,
254-
repo,
255-
comment_id: existing.id,
256-
body: updatedBody
257-
});
258-
console.log(`🔄 Updated existing comment on PR #${prNumber}`);
259-
} else {
260-
const body = `### ${signature}\n\n${newEntry}`;
261-
await github.rest.issues.createComment({
262-
owner,
263-
repo,
264-
issue_number: prNumber,
265-
body
266-
});
267-
console.log(`🆕 Posted new comment on PR #${prNumber}`);
268-
}
269-
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
- name: Compilation Check
17+
id: compilation-check
18+
uses: classroom-resources/autograding-command-grader@v1
19+
with:
20+
test-name: Compilation Check
21+
setup-command: ''
22+
command: mvn -ntp compile
23+
timeout: 10
24+
max-score: 1
25+
- name: Basic Tests
26+
id: basic-tests
27+
uses: classroom-resources/autograding-command-grader@v1
28+
with:
29+
test-name: Basic Tests
30+
setup-command: ''
31+
command: mvn - ntp test -DBasicTest
32+
timeout: 10
33+
max-score: 1
34+
- name: Edge Case Tests
35+
id: edge-case-tests
36+
uses: classroom-resources/autograding-command-grader@v1
37+
with:
38+
test-name: Edge Case Tests
39+
setup-command: ''
40+
command: mvn -ntp test -Dtest=EdgeCaseTest
41+
timeout: 10
42+
max-score: 1
43+
- name: Autograding Reporter
44+
uses: classroom-resources/autograding-grading-reporter@v1
45+
env:
46+
COMPILATION-CHECK_RESULTS: "${{steps.compilation-check.outputs.result}}"
47+
BASIC-TESTS_RESULTS: "${{steps.basic-tests.outputs.result}}"
48+
EDGE-CASE-TESTS_RESULTS: "${{steps.edge-case-tests.outputs.result}}"
49+
with:
50+
runners: compilation-check,basic-tests,edge-case-tests

0 commit comments

Comments
 (0)