Skip to content

Commit

Permalink
Clean up error handling and grading
Browse files Browse the repository at this point in the history
  • Loading branch information
jon-bell committed Feb 12, 2025
1 parent 8849607 commit 285546c
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 57 deletions.
88 changes: 71 additions & 17 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

80 changes: 70 additions & 10 deletions src/grader/Grader.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as io from '@actions/io'
import { readdir, readFile } from 'fs/promises'
import { mkdtemp, readdir, readFile } from 'fs/promises'
import path from 'path'
import yaml from 'yaml'
import { Builder, MutantResult, TestResult } from './builder/Builder.js'
Expand All @@ -11,9 +11,11 @@ import {
GradedUnit,
isMutationTestUnit,
isRegularTestUnit,
OutputFormat,
PawtograderConfig
} from './types.js'
import { AutograderFeedback } from '../api/adminServiceSchemas.js'
import { tmpdir } from 'os'
export default async function grade(
solutionDir: string,
submissionDir: string
Expand Down Expand Up @@ -156,7 +158,7 @@ class Grader {
name: unit.name,
output: `Tests passed: ${passingTests} / ${expectedTests}\n${relevantTestResults.map((result) => `${result.name}: ${result.status}${result.output ? `\n${result.output}` : ''}`).join('\n')}`,
output_format: 'text',
score: passingTests,
score: passingTests == expectedTests ? unit.points : 0,
max_score: unit.points
}
]
Expand All @@ -166,8 +168,8 @@ class Grader {
)
}
async grade(): Promise<AutograderFeedback> {
// const tmpDir = await mkdtemp(path.join(tmpdir(), 'pawtograder-'));
const tmpDir = path.join(process.cwd(), 'pawtograder-grading')
const tmpDir = await mkdtemp(path.join(tmpdir(), 'pawtograder-'))
// const tmpDir = path.join(process.cwd(), 'pawtograder-grading')
await io.mkdirP(tmpDir)
const solutionFiles = await readdir(this.solutionDir)
await Promise.all(
Expand All @@ -180,7 +182,54 @@ class Grader {

await this.copyStudentFiles('files')

await this.builder.buildClean()
try {
await this.builder.buildClean()
} catch (err) {
const msg = err instanceof Error ? err.message : 'Unknown error'
const allTests: AutograderTestFeedback[] = this.config.gradedParts
.map((part) =>
part.gradedUnits.map((gradedUnit) => {
if (isRegularTestUnit(gradedUnit)) {
return {
name: gradedUnit.name,
output:
'Build failed, test not run. Please see overall output for more details.',
output_format: 'text' as OutputFormat,
score: 0,
max_score: gradedUnit.points
}
} else if (isMutationTestUnit(gradedUnit)) {
return {
name: gradedUnit.name,
output:
'Build failed, test not run. Please see overall output for more details.',
output_format: 'text' as OutputFormat,
score: 0,
max_score: gradedUnit.breakPoints[0].pointsToAward
}
} else {
throw new Error(
`Unknown unit type in grading config: ${JSON.stringify(gradedUnit)}`
)
}
})
)
.flat()
return {
lint: {
status: 'fail',
output: 'Gradle build failed'
},
output: {
visible: {
output: msg,
output_format: 'text'
}
},
tests: allTests,
score: 0
}
}

const lintResult = await this.builder.lint()
// console.log(lintResult);
Expand All @@ -190,9 +239,20 @@ class Grader {
if (this.config.submissionFiles.testFiles.length > 0) {
await this.resetSolutionFiles()
await this.copyStudentFiles('testFiles')
await this.builder.buildClean()
const testResults = await this.builder.test()
if (testResults.some((result) => result.status === 'fail')) {
try {
await this.builder.buildClean()
} catch (err) {
const msg = err instanceof Error ? err.message : 'Unknown error'
mutantFailureAdvice =
'Your tests failed to compile. Please see overall output for more details.'
this.logger.log(
'visible',
'Your tests failed to compile. Here is the output from building your tests with our solution:'
)
this.logger.log('visible', msg)
}
const studentTestResults = await this.builder.test()
if (studentTestResults.some((result) => result.status === 'fail')) {
console.log('some tests failed')
this.logger.log(
'visible',
Expand All @@ -201,7 +261,7 @@ class Grader {
mutantFailureAdvice =
"Some of your tests failed when run against the instructor's solution. Your tests will not be graded for this submission. Please fix them before resubmitting. Here are the failing tests:"
this.logger.log('visible', 'Here are your failing test results:')
for (const result of testResults) {
for (const result of studentTestResults) {
if (result.status === 'fail') {
mutantFailureAdvice += `\n${result.name}: ${result.status}\n${result.output}\n--------------------------------\n`
this.logger.log('visible', `${result.name}: ${result.status}`)
Expand All @@ -218,7 +278,7 @@ class Grader {
this.gradePart(part, testResults, mutantResults, mutantFailureAdvice)
)
.flat()
console.log(JSON.stringify(testFeedbacks, null, 2))

return {
lint: lintResult,
tests: testFeedbacks,
Expand Down
1 change: 0 additions & 1 deletion src/grader/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export default class Logger {
visibility: OutputVisibility
}[] = []
log(visibility: OutputVisibility, message: string) {
console.log(message)
this.output.push({
output: message,
visibility: visibility
Expand Down
10 changes: 5 additions & 5 deletions src/grader/builder/Builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export abstract class Builder {
args: string[],
logger: Logger,
ignoreFailures = false
): Promise<string> {
): Promise<{ returnCode: number; output: string }> {
let myOutput = ''
let myError = ''
try {
Expand All @@ -52,14 +52,14 @@ export abstract class Builder {
myOutput += myError
logger.log('hidden', `${myOutput}`)
logger.log('hidden', `Return code: ${returnCode}`)
return myOutput
} catch (_err) {
return { returnCode, output: myOutput }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (err) {
logger.log(
'visible',
`Command ${command} failed unexpectedly with output:\n${myOutput + myError}`
)
console.error(_err)
throw new Error(`Command failed with output:\n${myOutput + myError}`)
throw new Error(`Command failed with output:\n${myOutput}`)
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/grader/builder/GradleBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,14 @@ export default class GradleBuilder extends Builder {
}
async buildClean(): Promise<void> {
this.logger.log('hidden', 'Building clean with Gradle')
await this.executeCommandAndGetOutput(
const { returnCode, output } = await this.executeCommandAndGetOutput(
'./gradlew',
['clean', 'build'],
this.logger,
true
)
if (returnCode !== 0) {
throw new Error(`Gradle build failed: ${output}`)
}
}
}
4 changes: 3 additions & 1 deletion src/grader/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ const submissionDir: string = options.submissionDir
console.log(
`Grading submissions in ${submissionDir} against solution in ${solutionDir}`
)
grade(solutionDir, submissionDir)
grade(solutionDir, submissionDir).then((feedback) => {
console.log(JSON.stringify(feedback, null, 2))
})
Loading

0 comments on commit 285546c

Please sign in to comment.