|
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 |
7 | 5 | permissions: |
8 | 6 | checks: write |
9 | 7 | actions: read |
10 | 8 | contents: read |
11 | | - pull-requests: write |
12 | | - |
13 | 9 | jobs: |
14 | | - autograding: |
15 | | - name: Autograding |
| 10 | + run-autograding-tests: |
16 | 11 | runs-on: ubuntu-latest |
17 | | - outputs: |
18 | | - grading_status: ${{ steps.grading.outcome }} |
| 12 | + if: github.actor != 'github-classroom[bot]' |
19 | 13 | 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