Skip to content

Commit f7b35e8

Browse files
committed
V2: New format for specifying test grading
1 parent ed0e37a commit f7b35e8

File tree

10 files changed

+168
-17
lines changed

10 files changed

+168
-17
lines changed

dist/grader/builder/Builder.d.ts

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/grader/builder/pitest.d.ts

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/grader/types.d.ts

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js

Lines changed: 61 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/grader/Grader.ts

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ class Grader {
342342
let studentTestResults: TestResult[] | undefined
343343
if (
344344
this.config.submissionFiles.testFiles.length > 0 &&
345-
this.config.build.student_tests?.grading === 'mutation'
345+
this.config.build.student_tests?.instructor_impl?.run_tests
346346
) {
347347
console.log(
348348
'Resetting to have student tests with the instructor solution'
@@ -379,14 +379,16 @@ class Grader {
379379
this.logger.log('visible', result.output)
380380
}
381381
}
382-
mutantFailureAdvice += '\n\nPlease fix the above errors and resubmit.'
382+
mutantFailureAdvice +=
383+
'\n\nPlease fix the above errors and resubmit for grading.'
383384
} else {
384385
console.log('Running student tests against buggy solutions')
385386
mutantResults = await this.builder.mutationTest()
386387
}
387-
} else if (
388-
this.config.build.student_tests?.grading ===
389-
'student-impl-coverage-report-only' &&
388+
}
389+
if (
390+
(this.config.build.student_tests?.student_impl?.report_branch_coverage ||
391+
this.config.build.student_tests?.student_impl?.run_tests) &&
390392
this.config.submissionFiles.testFiles.length > 0
391393
) {
392394
console.log('Running student tests against student implementation')
@@ -411,9 +413,65 @@ class Grader {
411413
const expectedArtifacts = this.config.build.artifacts || []
412414

413415
if (
414-
this.config.build.student_tests?.grading ===
415-
'student-impl-coverage-report-only'
416+
this.config.build.student_tests?.instructor_impl?.report_mutation_coverage
416417
) {
418+
let studentMutationOutput =
419+
'Please refer to your assignment instructions for the specifications of how (if at all) your tests will be graded. These results are purely informational: '
420+
if (mutantResults) {
421+
const getMutantPrompt = (mutantName: string) => {
422+
if (this.config.mutantAdvice) {
423+
const [sourceClass, targetClass] = mutantName.split(' ')
424+
const mutantAdvice = this.config.mutantAdvice.find(
425+
(ma) =>
426+
ma.sourceClass === sourceClass && ma.targetClass === targetClass
427+
)
428+
if (mutantAdvice) {
429+
return mutantAdvice.prompt
430+
}
431+
}
432+
return mutantName
433+
}
434+
const getMutantShortName = (mutantName: string) => {
435+
if (this.config.mutantAdvice) {
436+
const [sourceClass, targetClass] = mutantName.split(' ')
437+
const mutantAdvice = this.config.mutantAdvice.find(
438+
(ma) =>
439+
ma.sourceClass === sourceClass && ma.targetClass === targetClass
440+
)
441+
if (mutantAdvice) {
442+
return mutantAdvice.name
443+
}
444+
}
445+
return undefined
446+
}
447+
448+
const mutantsDetected = mutantResults
449+
.filter((mr) => mr.status === 'pass')
450+
.map((mr) => {
451+
const prompt = getMutantPrompt(mr.name)
452+
const shortName = getMutantShortName(mr.name)
453+
return `* ${shortName} (${prompt})\n\t * Detected by: ${mr.tests.join(', ')}`
454+
})
455+
const mutantsNotDetected = mutantResults
456+
.filter((mr) => mr.status === 'fail')
457+
.map((mr) => {
458+
const prompt = getMutantPrompt(mr.name)
459+
return `* **${mr.name}** (${prompt})`
460+
})
461+
studentMutationOutput += `Faults detected: ${mutantsDetected.length}:\n`
462+
studentMutationOutput += `${mutantsDetected.join('\n')}\n\n`
463+
studentMutationOutput += `Faults not detected: ${mutantsNotDetected.length}:\n`
464+
studentMutationOutput += `${mutantsNotDetected.join('\n')}`
465+
}
466+
testFeedbacks.push({
467+
name: 'Fault Coverage Report',
468+
output: studentMutationOutput,
469+
output_format: 'markdown',
470+
score: 0,
471+
max_score: 0
472+
})
473+
}
474+
if (this.config.build.student_tests?.student_impl?.report_branch_coverage) {
417475
const passingTestCount = studentTestResults?.filter(
418476
(result) => result.status === 'pass'
419477
).length

src/grader/builder/Builder.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type MutantResult = {
1717
name: string
1818
location: string
1919
status: 'pass' | 'fail'
20+
tests: string[]
2021
output: string
2122
output_format?: OutputFormat
2223
}

src/grader/builder/GradleBuilder.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ export default class GradleBuilder extends Builder {
134134
name: eachMutation.mutator,
135135
location: eachMutation.mutatedClass + ':' + eachMutation.lineNumber,
136136
status: eachMutation.status === 'KILLED' ? 'pass' : 'fail',
137+
tests: eachMutation.killingTests
138+
? eachMutation.killingTests.split('|')
139+
: [],
137140
output: eachMutation.killingTest
138141
? 'Found by ' + eachMutation.killingTest
139142
: '',

src/grader/builder/pitest.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export interface Mutation {
3333
index: number
3434
block: number
3535
killingTest?: string
36+
killingTests?: string
3637
description: string
3738
}
3839

@@ -105,6 +106,9 @@ export function parsePitestXml(filePath: string): MutationTestSummary {
105106
if (mut.killingTest) {
106107
mutation.killingTest = mut.killingTest
107108
}
109+
if (mut.killingTests) {
110+
mutation.killingTests = mut.killingTests
111+
}
108112

109113
// Update statistics
110114
report.statistics.totalMutations++

src/grader/types.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@ export interface BuildConfig {
1010
policy: 'fail' | 'warn' | 'ignore'
1111
}
1212
student_tests?: {
13-
grading: 'none' | 'mutation' | 'student-impl-coverage-report-only'
13+
student_impl?: {
14+
run_tests?: boolean
15+
report_branch_coverage?: boolean
16+
}
17+
instructor_impl?: {
18+
run_tests?: boolean
19+
run_mutation?: boolean
20+
report_mutation_coverage?: boolean
21+
}
1422
}
1523
}
1624

@@ -28,7 +36,7 @@ export interface BreakPoint {
2836

2937
export interface MutationTestUnit {
3038
name: string
31-
locations: string[] // format: "file:line-line"
39+
locations: string[] // format: "file:line-line" (for normal pit mutators) OR format oldFile-newFile (for prebake mutators)
3240
breakPoints: BreakPoint[]
3341
}
3442

@@ -59,6 +67,12 @@ export interface PawtograderConfig {
5967
files: string[]
6068
testFiles: string[]
6169
}
70+
mutantAdvice?: {
71+
sourceClass: string
72+
targetClass: string
73+
name: string
74+
prompt: string
75+
}[]
6276
}
6377

6478
// Type guard to check if a unit is a mutation test unit

0 commit comments

Comments
 (0)