├── .editorconfig ├── .git-blame-ignore-revs ├── .github ├── renovate.json └── workflows │ └── publish-docs.yml ├── .gitignore ├── .teamcity ├── pom.xml └── settings.kts ├── LICENSE ├── README.md ├── authors ├── build-logic ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ ├── jagr-publish.gradle.kts │ └── jagr-sign.gradle.kts ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── org │ └── sourcegrade │ └── jagr │ └── script │ └── DependencyConfigurationExtensions.kt ├── core ├── build.gradle.kts └── src │ └── main │ ├── kotlin │ └── org │ │ └── sourcegrade │ │ └── jagr │ │ └── core │ │ ├── CommonModule.kt │ │ ├── Parallel.kt │ │ ├── compiler │ │ ├── Encoding.kt │ │ ├── InfoJsonResourceExtractor.kt │ │ ├── MutableResourceCollector.kt │ │ ├── ProcessedContainer.kt │ │ ├── ResourceCollector.kt │ │ ├── ResourceCollectorImpl.kt │ │ ├── ResourceExtractor.kt │ │ ├── RuntimeContainer.kt │ │ └── java │ │ │ ├── CompiledClass.kt │ │ │ ├── ExtendedStandardJavaFileManager.kt │ │ │ ├── JavaCompiledContainer.kt │ │ │ ├── JavaRuntimeContainer.kt │ │ │ ├── JavaSourceContainer.kt │ │ │ ├── JavaSourceFile.kt │ │ │ ├── JavaSourceLinker.kt │ │ │ ├── RuntimeClassLoaderImpl.kt │ │ │ ├── RuntimeJarLoader.kt │ │ │ ├── RuntimeResources.kt │ │ │ └── SourceLinkingClassVisitor.kt │ │ ├── executor │ │ ├── BatchCompilation.kt │ │ ├── GradingQueueFactoryImpl.kt │ │ ├── GradingQueueImpl.kt │ │ ├── GradingRequestImpl.kt │ │ └── TimeoutHandler.kt │ │ ├── export │ │ ├── rubric │ │ │ ├── BasicHTMLExporter.kt │ │ │ ├── GermanCSVExporter.kt │ │ │ └── MoodleJSONExporter.kt │ │ └── submission │ │ │ ├── EclipseSubmissionExporter.kt │ │ │ └── GradleSubmissionExporter.kt │ │ ├── extra │ │ ├── Extra.kt │ │ ├── ExtrasManagerImpl.kt │ │ ├── MoodleUnpack.kt │ │ └── Unpack.kt │ │ ├── io │ │ └── SerializationFactoryLocatorImpl.kt │ │ ├── rubric │ │ ├── CriterionBuilderImpl.kt │ │ ├── CriterionFactoryImpl.kt │ │ ├── CriterionHolderPointCalculatorFactoryImpl.kt │ │ ├── CriterionImpl.kt │ │ ├── GradeResultFactoryImpl.kt │ │ ├── GradeResultImpl.kt │ │ ├── GradeResultMath.kt │ │ ├── GradedCriterionImpl.kt │ │ ├── GradedRubricImpl.kt │ │ ├── JUnitTestRefFactoryImpl.kt │ │ ├── RubricBuilderImpl.kt │ │ ├── RubricFactoryImpl.kt │ │ ├── RubricImpl.kt │ │ ├── TestExecutionMessaging.kt │ │ └── grader │ │ │ ├── AbstractGraderBuilder.kt │ │ │ ├── DescendingPriorityGrader.kt │ │ │ ├── GraderFactoryImpl.kt │ │ │ ├── TestAwareGraderBuilderImpl.kt │ │ │ └── TestAwareGraderImpl.kt │ │ ├── testing │ │ ├── FallbackJUnitResult.kt │ │ ├── FallbackRuntimeTester.kt │ │ ├── FallbackTestCycle.kt │ │ ├── GraderJarImpl.kt │ │ ├── JUnitResultImpl.kt │ │ ├── JavaRuntimeTester.kt │ │ ├── JavaSubmission.kt │ │ ├── JavaTestCycle.kt │ │ ├── RubricConfigurationImpl.kt │ │ ├── RuntimeGraderImpl.kt │ │ ├── RuntimeTester.kt │ │ ├── TestStatusListenerImpl.kt │ │ └── ThreadedGlobalParameterResolver.kt │ │ └── transformer │ │ ├── BytecodeReplacement.kt │ │ ├── ClassRenamingTransformer.kt │ │ ├── ClassTransformerFactoryImpl.kt │ │ ├── CommonClassTransformer.kt │ │ ├── FieldElement.kt │ │ ├── FieldInsnElement.kt │ │ ├── InjectSuperclassTransformer.kt │ │ ├── MethodInsnElement.kt │ │ ├── ReplacementTransformer.kt │ │ ├── SubmissionVerificationTransformer.kt │ │ └── TransformerApplier.kt │ └── resources │ └── jagr.json ├── docs ├── LICENSE ├── architecture │ ├── grader.md │ └── submission.md ├── assets │ └── favicon.svg ├── development │ ├── getting-started │ │ └── gradle-setup.md │ └── grader-api │ │ ├── criterion.md │ │ └── rubric.md ├── index.md └── usage │ ├── command-line │ ├── basics.md │ └── options.md │ └── getting-started │ └── installation.md ├── grader-api ├── build.gradle.kts └── src │ └── main │ └── java │ └── org │ └── sourcegrade │ └── jagr │ └── api │ ├── rubric │ ├── Criterion.java │ ├── CriterionHolder.java │ ├── CriterionHolderPointCalculator.java │ ├── Gradable.java │ ├── GradeResult.java │ ├── Graded.java │ ├── GradedCriterion.java │ ├── GradedRubric.java │ ├── Grader.java │ ├── JUnitTestRef.java │ ├── PointRange.java │ ├── Rubric.java │ ├── RubricProvider.java │ └── TestForSubmission.java │ └── testing │ ├── ClassTransformer.java │ ├── ClassTransformerOrder.java │ ├── CompileResult.java │ ├── RubricConfiguration.java │ ├── RuntimeClassLoader.java │ ├── SourceFile.java │ ├── Submission.java │ ├── TestCycle.java │ ├── TestStatusListener.java │ └── extension │ ├── JagrExecutionCondition.java │ ├── NonJagrExecutionCondition.java │ └── TestCycleResolver.java ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── launcher ├── build.gradle.kts ├── gradle-plugin │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── org │ │ └── sourcegrade │ │ └── jagr │ │ └── gradle │ │ ├── JagrGradlePlugin.kt │ │ ├── SourceSetExtensions.kt │ │ ├── extension │ │ ├── AbstractConfiguration.kt │ │ ├── DependencyConfiguration.kt │ │ ├── GraderConfiguration.kt │ │ ├── JagrDownloadExtension.kt │ │ ├── JagrExtension.kt │ │ ├── ProjectSourceSetTuple.kt │ │ ├── RelativeFiles.kt │ │ └── SubmissionConfiguration.kt │ │ └── task │ │ ├── JagrDownloadTask.kt │ │ ├── JagrTaskFactory.kt │ │ ├── TargetAssignmentTask.kt │ │ ├── TargetSourceSetsTask.kt │ │ ├── WriteInfoTask.kt │ │ ├── grader │ │ ├── GraderBuildTask.kt │ │ ├── GraderLibsTask.kt │ │ ├── GraderRunTask.kt │ │ ├── GraderTask.kt │ │ ├── GraderWriteInfoTask.kt │ │ └── GradleLaunchConfiguration.kt │ │ └── submission │ │ ├── SubmissionBuildTask.kt │ │ ├── SubmissionTask.kt │ │ └── SubmissionWriteInfoTask.kt └── src │ └── main │ ├── kotlin │ └── org │ │ └── sourcegrade │ │ └── jagr │ │ └── launcher │ │ ├── env │ │ ├── Config.kt │ │ ├── CopyConfig.kt │ │ ├── Environment.kt │ │ ├── Jagr.kt │ │ ├── JagrImpl.kt │ │ ├── LaunchConfiguration.kt │ │ ├── ModuleFactory.kt │ │ └── SystemResourceJagrFactory.kt │ │ ├── executor │ │ ├── Executor.kt │ │ ├── GradingJob.kt │ │ ├── GradingQueue.kt │ │ ├── GradingRequest.kt │ │ ├── GradingResult.kt │ │ ├── MultiWorkerExecutor.kt │ │ ├── MutableRubricCollector.kt │ │ ├── ProcessWorker.kt │ │ ├── ProcessWorkerPool.kt │ │ ├── ProgressBar.kt │ │ ├── ProgressBarProvider.kt │ │ ├── RotationProgressBar.kt │ │ ├── RubricCollector.kt │ │ ├── RubricCollectorImpl.kt │ │ ├── RuntimeGrader.kt │ │ ├── RuntimeInvoker.kt │ │ ├── RuntimeJarInvoker.kt │ │ ├── SyncExecutor.kt │ │ ├── ThreadAwarePrintStream.kt │ │ ├── ThreadWorker.kt │ │ ├── ThreadWorkerPool.kt │ │ ├── Worker.kt │ │ ├── WorkerPool.kt │ │ ├── WorkerStatus.kt │ │ └── WorkerSynchronizer.kt │ │ └── io │ │ ├── AbstractSerializationScope.kt │ │ ├── AssignmentArtifactInfo.kt │ │ ├── DataUtils.kt │ │ ├── ExtrasManager.kt │ │ ├── GradedRubricExporter.kt │ │ ├── GraderInfo.kt │ │ ├── GraderJar.kt │ │ ├── GradingBatch.kt │ │ ├── InputSerializationScopeImpl.kt │ │ ├── MagicAppender.kt │ │ ├── OutputSerializationScopeImpl.kt │ │ ├── ProgressAwareOutputStream.kt │ │ ├── RepositoryConfiguration.kt │ │ ├── Resource.kt │ │ ├── ResourceContainer.kt │ │ ├── RubricLogging.kt │ │ ├── SafeStringSerializer.kt │ │ ├── SerializationScope.kt │ │ ├── SerializerFactory.kt │ │ ├── SourceSetInfo.kt │ │ ├── SubmissionExporter.kt │ │ ├── SubmissionInfo.kt │ │ └── ZipResourceContainer.kt │ └── resources │ ├── log4j2-child.xml │ ├── log4j2-console-only.xml │ └── log4j2.xml ├── logo.svg ├── mkdocs.yml ├── settings.gradle.kts ├── src └── main │ └── kotlin │ └── org │ └── sourcegrade │ └── jagr │ ├── ChildProcGrading.kt │ ├── Files.kt │ ├── Main.kt │ └── StandardGrading.kt └── version /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_style = space 8 | indent_size = 4 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | ij_kotlin_allow_trailing_comma_on_call_site = true 12 | ij_kotlin_allow_trailing_comma = true 13 | 14 | [*.yml] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Commits to be ignored for assigning blame 2 | # 3 | # Any bulk formatting commits should be listed here 4 | # Apply this to a local environment with: git config blame.ignoreRevsFile .git-blame-ignore-revs 5 | 6 | b3a89922e7ce24089044edb0e0d022858c493ff7 # 2 -> 4 spaces 7 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ], 6 | "addLabels": [ 7 | "dependency" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.github/workflows/publish-docs.yml: -------------------------------------------------------------------------------- 1 | name: MkDocs Publish 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: mhausenblas/mkdocs-deploy-gh-pages@1.26 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | CUSTOM_DOMAIN: docs.sourcegrade.org 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Intellij ### 2 | .idea/ 3 | *.iws 4 | /out/ 5 | *.iml 6 | .idea_modules/ 7 | atlassian-ide-plugin.xml 8 | 9 | ### VS-Code ### 10 | .vscode/ 11 | .VSCodeCounter/ 12 | 13 | ### Eclipse ### 14 | .metadata 15 | bin/ 16 | tmp/ 17 | *.tmp 18 | *.bak 19 | *.swp 20 | *~.nib 21 | local.properties 22 | .settings/ 23 | .loadpath 24 | .recommenders 25 | .externalToolBuilders/ 26 | *.launch 27 | .factorypath 28 | .recommenders/ 29 | .apt_generated/ 30 | .project 31 | .classpath 32 | 33 | ### Linux ### 34 | *~ 35 | .fuse_hidden* 36 | .directory 37 | .Trash-* 38 | .nfs* 39 | 40 | ### macOS ### 41 | .DS_Store 42 | .AppleDouble 43 | .LSOverride 44 | Icon 45 | ._* 46 | .DocumentRevisions-V100 47 | .fseventsd 48 | .Spotlight-V100 49 | .TemporaryItems 50 | .Trashes 51 | .VolumeIcon.icns 52 | .com.apple.timemachine.donotpresent 53 | .AppleDB 54 | .AppleDesktop 55 | Network Trash Folder 56 | Temporary Items 57 | .apdisk 58 | 59 | ### NetBeans ### 60 | nbproject/private/ 61 | build/ 62 | nbbuild/ 63 | dist/ 64 | nbdist/ 65 | .nb-gradle/ 66 | 67 | ### Windows ### 68 | # Windows thumbnail cache files 69 | Thumbs.db 70 | ehthumbs.db 71 | ehthumbs_vista.db 72 | *.stackdump 73 | [Dd]esktop.ini 74 | $RECYCLE.BIN/ 75 | *.lnk 76 | 77 | ### Gradle ### 78 | .gradle 79 | /build/ 80 | out/ 81 | gradle-app.setting 82 | !gradle-wrapper.jar 83 | .gradletasknamecache 84 | -------------------------------------------------------------------------------- /authors: -------------------------------------------------------------------------------- 1 | alexstaeding,Alexander Städing 2 | -------------------------------------------------------------------------------- /build-logic/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | -------------------------------------------------------------------------------- /build-logic/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | dependencyResolutionManagement { 4 | repositories { 5 | gradlePluginPortal() 6 | mavenCentral() 7 | } 8 | versionCatalogs { 9 | register("libs") { 10 | from(files("../gradle/libs.versions.toml")) // include from parent project 11 | } 12 | } 13 | } 14 | 15 | rootProject.name = "build-logic" 16 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/jagr-publish.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.net.URI 2 | 3 | plugins { 4 | java 5 | `maven-publish` 6 | } 7 | 8 | extensions.configure { 9 | withJavadocJar() 10 | withSourcesJar() 11 | } 12 | 13 | extensions.configure { 14 | repositories { 15 | maven { 16 | credentials { 17 | username = project.findProperty("sonatypeUsername") as? String 18 | password = project.findProperty("sonatypePassword") as? String 19 | } 20 | val releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" 21 | val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots" 22 | url = URI(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl) 23 | } 24 | } 25 | publications.register("maven") { 26 | from(components["java"]) 27 | pom { 28 | name.set("jagr") 29 | description.set("An automated tool for grading programming assignments") 30 | url.set("https://www.sourcegrade.org") 31 | scm { 32 | url.set("https://github.com/sourcegrade/jagr") 33 | connection.set("scm:git:https://github.com/sourcegrade/jagr.git") 34 | developerConnection.set("scm:git:https://github.com/sourcegrade/jagr.git") 35 | } 36 | licenses { 37 | license { 38 | name.set("GNU AFFERO GENERAL PUBLIC LICENSE Version 3") 39 | url.set("https://www.gnu.org/licenses/agpl-3.0.html") 40 | distribution.set("repo") 41 | } 42 | } 43 | developers { 44 | rootProject.file("authors").readLines() 45 | .asSequence() 46 | .map { it.split(",") } 47 | .forEach { (_id, _name) -> developer { id.set(_id); name.set(_name) } } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/jagr-sign.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | signing 3 | } 4 | 5 | extensions.configure { 6 | publications.withType { 7 | val publication = this 8 | extensions.configure { 9 | sign(publication) 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer 2 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 3 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 4 | 5 | plugins { 6 | application 7 | alias(libs.plugins.kotlin.jvm) 8 | alias(libs.plugins.kotlin.kapt) apply false 9 | alias(libs.plugins.kotlin.serialization) apply false 10 | alias(libs.plugins.shadow) 11 | alias(libs.plugins.style) 12 | } 13 | 14 | dependencies { 15 | runtimeOnly(project("jagr-core")) 16 | implementation(project("jagr-launcher")) 17 | implementation(libs.clikt) 18 | } 19 | 20 | application { 21 | mainClass.set("org.sourcegrade.jagr.MainKt") 22 | } 23 | 24 | tasks { 25 | val runDir = File("build/run") 26 | named("run") { 27 | doFirst { 28 | error("Use runShadow instead") 29 | } 30 | } 31 | named("runShadow") { 32 | doFirst { 33 | runDir.mkdirs() 34 | } 35 | workingDir = runDir 36 | } 37 | jar { 38 | enabled = false 39 | } 40 | shadowJar { 41 | transform(Log4j2PluginsCacheFileTransformer::class.java) 42 | from("gradlew") { 43 | into("org/gradle") 44 | } 45 | from("gradlew.bat") { 46 | into("org/gradle") 47 | } 48 | from("gradle/wrapper/gradle-wrapper.properties") { 49 | into("org/gradle") 50 | } 51 | archiveFileName.set("Jagr-${project.version}.jar") 52 | } 53 | } 54 | 55 | val projectVersion = file("version").readLines().first() 56 | project.extra["apiVersion"] = projectVersion.replace("\\.[1-9]\\d*-SNAPSHOT|\\.0|\\.\\d*\$".toRegex(), "") 57 | 58 | allprojects { 59 | apply(plugin = "org.sourcegrade.style") 60 | 61 | group = "org.sourcegrade" 62 | version = projectVersion 63 | 64 | project.findProperty("buildNumber") 65 | ?.takeIf { version.toString().contains("SNAPSHOT") } 66 | ?.also { version = version.toString().replace("SNAPSHOT", "RC$it") } 67 | 68 | tasks { 69 | withType { 70 | compilerOptions.jvmTarget.set(JvmTarget.JVM_17) 71 | } 72 | withType { 73 | options.encoding = "UTF-8" 74 | sourceCompatibility = "17" 75 | targetCompatibility = "17" 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/org/sourcegrade/jagr/script/DependencyConfigurationExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.script 21 | 22 | import org.gradle.api.Project 23 | import org.gradle.api.artifacts.dsl.DependencyHandler 24 | import org.gradle.kotlin.dsl.extra 25 | import org.gradle.kotlin.dsl.project 26 | 27 | /** 28 | * Add a x.y versioned project api dependency to a x.y.z versioned project. 29 | */ 30 | fun DependencyHandler.apiProject(project: Project, path: String) = 31 | addConfiguration("api", project, path) 32 | 33 | /** 34 | * Add a x.y versioned project implementation dependency to a x.y.z versioned project. 35 | */ 36 | fun DependencyHandler.implementationProject(project: Project, path: String) = 37 | addConfiguration("implementation", project, path) 38 | 39 | /** 40 | * Add a x.y versioned runtimeOnly dependency to a x.y.z versioned project. 41 | */ 42 | fun DependencyHandler.runtimeOnlyProject(project: Project, path: String) = 43 | addConfiguration("runtimeOnly", project, path) 44 | 45 | /** 46 | * Add a x.y versioned project dependency to a x.y.z versioned project. 47 | */ 48 | private fun DependencyHandler.addConfiguration(configuration: String, project: Project, path: String) { 49 | // force release version of API on release to prevent transitive dependency on snapshot version of API 50 | val version = project.version.toString() 51 | val apiVersion = project.rootProject.extra["apiVersion"] as String 52 | if (version.endsWith("SNAPSHOT") || version.endsWith(".0")) { 53 | add(configuration, project(":$path")) 54 | } else { 55 | add(configuration, "org.sourcegrade:$path:$apiVersion") 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | kotlin("plugin.serialization") 4 | id("jagr-publish") 5 | id("jagr-sign") 6 | } 7 | 8 | dependencies { 9 | api(project(":jagr-launcher")) 10 | implementation(libs.csv) 11 | implementation(libs.asm.util) 12 | implementation(libs.serialization) 13 | implementation(libs.jansi) 14 | implementation(libs.juniversalchardet) 15 | implementation(kotlin("reflect")) 16 | implementation(files("../gradle/wrapper/gradle-wrapper.jar")) 17 | runtimeOnly(libs.apiguardian) 18 | } 19 | tasks { 20 | test { 21 | useJUnitPlatform() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/compiler/Encoding.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.compiler 21 | 22 | import org.mozilla.universalchardet.UniversalDetector 23 | import java.io.InputStream 24 | import java.nio.charset.Charset 25 | 26 | fun InputStream.readEncoded(): String { 27 | val buffer: ByteArray = readAllBytes() 28 | val detector = UniversalDetector() 29 | detector.handleData(buffer) 30 | detector.dataEnd() 31 | return if (detector.detectedCharset != null) { 32 | String(buffer, Charset.forName(detector.detectedCharset)) 33 | } else { 34 | String(buffer) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/compiler/MutableResourceCollector.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.compiler 21 | 22 | interface MutableResourceCollector : ResourceCollector { 23 | /** 24 | * Adds a resource to this collector. The provided value's runtime type is the key for the resource. 25 | * 26 | * The value is then accessible via [get]. 27 | */ 28 | fun addResource(value: Any) 29 | } 30 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/compiler/ProcessedContainer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.compiler 21 | 22 | import org.sourcegrade.jagr.launcher.io.ResourceContainerInfo 23 | 24 | interface ProcessedContainer { 25 | val info: ResourceContainerInfo 26 | val resourceCollector: ResourceCollector 27 | } 28 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/compiler/ResourceCollector.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.compiler 21 | 22 | import org.sourcegrade.jagr.launcher.io.GraderInfo 23 | import org.sourcegrade.jagr.launcher.io.SubmissionInfo 24 | import kotlin.properties.ReadOnlyProperty 25 | import kotlin.reflect.KClass 26 | 27 | interface ResourceCollector { 28 | operator fun get(type: KClass): T? 29 | } 30 | 31 | inline fun ResourceCollector.get() = get(T::class) 32 | 33 | val ProcessedContainer.submissionInfo: SubmissionInfo? by collected() 34 | 35 | val ProcessedContainer.graderInfo: GraderInfo? by collected() 36 | 37 | private inline fun collected(): ReadOnlyProperty = 38 | ReadOnlyProperty { e, _ -> e.resourceCollector.get() } 39 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/compiler/ResourceCollectorImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.compiler 21 | 22 | import org.sourcegrade.jagr.launcher.io.SerializationScope 23 | import org.sourcegrade.jagr.launcher.io.SerializerFactory 24 | import org.sourcegrade.jagr.launcher.io.readDynamicMap 25 | import org.sourcegrade.jagr.launcher.io.writeDynamicMap 26 | import kotlin.reflect.KClass 27 | 28 | data class ResourceCollectorImpl( 29 | private val backing: MutableMap, Any> = mutableMapOf(), 30 | ) : MutableResourceCollector { 31 | 32 | override fun addResource(value: Any) { 33 | backing[value::class] = value 34 | } 35 | 36 | override fun get(type: KClass): T? = backing[type] as T? 37 | 38 | companion object Factory : SerializerFactory { 39 | override fun read(scope: SerializationScope.Input) = ResourceCollectorImpl(scope.readDynamicMap().toMutableMap()) 40 | override fun write(obj: ResourceCollectorImpl, scope: SerializationScope.Output) = scope.writeDynamicMap(obj.backing) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/compiler/ResourceExtractor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.compiler 21 | 22 | import org.sourcegrade.jagr.launcher.io.Resource 23 | import org.sourcegrade.jagr.launcher.io.ResourceContainerInfo 24 | 25 | fun interface ResourceExtractor { 26 | /** 27 | * Attempts to extract a special kind of resource from [resource]. 28 | */ 29 | fun extract( 30 | containerInfo: ResourceContainerInfo, 31 | resource: Resource, 32 | data: ByteArray, 33 | collector: MutableResourceCollector, 34 | ) 35 | } 36 | 37 | fun extractorOf(vararg extractors: ResourceExtractor) = ResourceExtractor { containerInfo, resource, data, collector -> 38 | for (extractor in extractors) { 39 | extractor.extract(containerInfo, resource, data, collector) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/compiler/RuntimeContainer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.compiler 21 | 22 | import org.sourcegrade.jagr.core.compiler.java.RuntimeResources 23 | 24 | interface RuntimeContainer : ProcessedContainer { 25 | 26 | /** 27 | * The source container from which this runtime container was built if present, otherwise null. 28 | */ 29 | val source: ProcessedContainer? 30 | 31 | val runtimeResources: RuntimeResources 32 | } 33 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/compiler/java/JavaRuntimeContainer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.compiler.java 21 | 22 | import org.sourcegrade.jagr.core.compiler.ProcessedContainer 23 | import org.sourcegrade.jagr.core.compiler.ResourceCollector 24 | import org.sourcegrade.jagr.core.compiler.RuntimeContainer 25 | import org.sourcegrade.jagr.launcher.io.ResourceContainerInfo 26 | 27 | data class JavaRuntimeContainer( 28 | override val info: ResourceContainerInfo, 29 | override val resourceCollector: ResourceCollector, 30 | override val runtimeResources: RuntimeResources, 31 | ) : RuntimeContainer { 32 | override val source: ProcessedContainer? = null 33 | } 34 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/compiler/java/JavaSourceContainer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.compiler.java 21 | 22 | import org.sourcegrade.jagr.core.compiler.ProcessedContainer 23 | import org.sourcegrade.jagr.core.compiler.ResourceCollector 24 | import org.sourcegrade.jagr.launcher.io.ResourceContainerInfo 25 | import org.sourcegrade.jagr.launcher.io.SerializationScope 26 | import org.sourcegrade.jagr.launcher.io.SerializerFactory 27 | import org.sourcegrade.jagr.launcher.io.read 28 | import org.sourcegrade.jagr.launcher.io.readList 29 | import org.sourcegrade.jagr.launcher.io.readMap 30 | import org.sourcegrade.jagr.launcher.io.write 31 | import org.sourcegrade.jagr.launcher.io.writeList 32 | import org.sourcegrade.jagr.launcher.io.writeMap 33 | 34 | data class JavaSourceContainer( 35 | override val info: ResourceContainerInfo, 36 | override val resourceCollector: ResourceCollector, 37 | val sourceFiles: Map, 38 | val resources: Map, 39 | val messages: List = listOf(), 40 | ) : ProcessedContainer { 41 | companion object Factory : SerializerFactory { 42 | override fun read(scope: SerializationScope.Input) = JavaSourceContainer( 43 | scope.read(), 44 | scope.read(), 45 | scope.readMap(), 46 | scope.readMap(), 47 | scope.readList(), 48 | ) 49 | 50 | override fun write(obj: JavaSourceContainer, scope: SerializationScope.Output) { 51 | scope.write(obj.info) 52 | scope.write(obj.resourceCollector) 53 | scope.writeMap(obj.sourceFiles) 54 | scope.writeMap(obj.resources) 55 | scope.writeList(obj.messages) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/compiler/java/JavaSourceFile.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.compiler.java 21 | 22 | import org.sourcegrade.jagr.api.testing.SourceFile 23 | import org.sourcegrade.jagr.launcher.io.SerializationScope 24 | import org.sourcegrade.jagr.launcher.io.SerializerFactory 25 | import org.sourcegrade.jagr.launcher.io.readText 26 | import org.sourcegrade.jagr.launcher.io.writeText 27 | import java.net.URI 28 | import javax.tools.JavaFileObject.Kind 29 | import javax.tools.SimpleJavaFileObject 30 | 31 | data class JavaSourceFile( 32 | private val className: String, 33 | private val fileName: String, 34 | private val content: String, 35 | ) : SimpleJavaFileObject(URI.create("string:///$fileName"), Kind.SOURCE), SourceFile { 36 | override fun getFileName(): String = fileName 37 | override fun getContent(): String = content 38 | override fun getClassName(): String = className 39 | override fun getCharContent(ignoreEncodingErrors: Boolean): CharSequence = content 40 | 41 | companion object Factory : SerializerFactory { 42 | override fun read(scope: SerializationScope.Input): JavaSourceFile = 43 | JavaSourceFile(scope.input.readUTF(), scope.input.readUTF(), scope.input.readText()) 44 | 45 | override fun write(obj: JavaSourceFile, scope: SerializationScope.Output) { 46 | scope.output.writeUTF(obj.className) 47 | scope.output.writeUTF(obj.fileName) 48 | scope.output.writeText(obj.content) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/compiler/java/JavaSourceLinker.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.compiler.java 21 | 22 | import org.objectweb.asm.ClassReader 23 | 24 | class JavaSourceLinker(private val sourceFiles: Map) { 25 | fun link(compiledClass: CompiledClass) { 26 | val reader = compiledClass.reader 27 | val visitor = SourceLinkingClassVisitor(reader.className) 28 | reader.accept(visitor, ClassReader.SKIP_CODE) 29 | if (visitor.sourceFileName != null) { 30 | compiledClass.source = sourceFiles[visitor.sourceFileName] 31 | } 32 | } 33 | } 34 | 35 | fun Map.linkSource(sourceFiles: Map) { 36 | val linker = JavaSourceLinker(sourceFiles) 37 | for ((_, compiledClass) in this) { 38 | linker.link(compiledClass) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/compiler/java/RuntimeResources.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.compiler.java 21 | 22 | import org.sourcegrade.jagr.launcher.io.ResourceContainer 23 | import org.sourcegrade.jagr.launcher.io.SerializationScope 24 | import org.sourcegrade.jagr.launcher.io.SerializerFactory 25 | import org.sourcegrade.jagr.launcher.io.keyOf 26 | import org.sourcegrade.jagr.launcher.io.readMap 27 | import org.sourcegrade.jagr.launcher.io.writeMap 28 | 29 | data class RuntimeResources( 30 | val classes: Map = mapOf(), 31 | val resources: Map = mapOf(), 32 | ) { 33 | companion object Factory : SerializerFactory { 34 | val base = keyOf("base") 35 | override fun read(scope: SerializationScope.Input) = RuntimeResources(scope.readMap(), scope.readMap()) 36 | 37 | override fun write(obj: RuntimeResources, scope: SerializationScope.Output) { 38 | scope.writeMap(obj.classes) 39 | scope.writeMap(obj.resources) 40 | } 41 | } 42 | } 43 | 44 | operator fun RuntimeResources.plus(other: RuntimeResources) = 45 | RuntimeResources(classes + other.classes, resources + other.resources) 46 | 47 | fun RuntimeJarLoader.loadCompiled(containers: Sequence): RuntimeResources { 48 | return containers 49 | .map { loadCompiled(it).runtimeResources } 50 | .fold(RuntimeResources()) { a, b -> a + b } 51 | } 52 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/compiler/java/SourceLinkingClassVisitor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.compiler.java 21 | 22 | import org.objectweb.asm.ClassVisitor 23 | import org.objectweb.asm.Opcodes 24 | 25 | class SourceLinkingClassVisitor( 26 | className: String, 27 | ) : ClassVisitor(Opcodes.ASM9) { 28 | 29 | private val packageName = with(className) { lastIndexOf('/').let { if (it == -1) null else substring(0, it) } } 30 | var sourceFileName: String? = null 31 | 32 | override fun visitSource(source: String?, debug: String?) { 33 | if (source == null || sourceFileName != null) return 34 | sourceFileName = if (packageName == null) { 35 | source 36 | } else { 37 | "$packageName/$source" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/executor/GradingQueueFactoryImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.executor 21 | 22 | import com.google.inject.Inject 23 | import org.apache.logging.log4j.Logger 24 | import org.sourcegrade.jagr.launcher.executor.GradingQueue 25 | import org.sourcegrade.jagr.launcher.io.GradingBatch 26 | 27 | class GradingQueueFactoryImpl @Inject constructor( 28 | private val logger: Logger, 29 | private val compiledBatchFactory: CompiledBatchFactoryImpl, 30 | ) : GradingQueue.Factory { 31 | override fun create(batch: GradingBatch): GradingQueue = GradingQueueImpl(logger, compiledBatchFactory.compile(batch)) 32 | } 33 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/export/rubric/MoodleJSONExporter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.export.rubric 21 | 22 | import com.google.inject.Inject 23 | import kotlinx.serialization.Serializable 24 | import kotlinx.serialization.encodeToString 25 | import kotlinx.serialization.json.Json 26 | import org.sourcegrade.jagr.api.rubric.GradedRubric 27 | import org.sourcegrade.jagr.core.testing.JavaSubmission 28 | import org.sourcegrade.jagr.launcher.io.GradedRubricExporter 29 | import org.sourcegrade.jagr.launcher.io.Resource 30 | import org.sourcegrade.jagr.launcher.io.SubmissionInfo 31 | import org.sourcegrade.jagr.launcher.io.buildResource 32 | 33 | class MoodleJSONExporter @Inject constructor( 34 | private val exporterHTML: GradedRubricExporter.HTML, 35 | ) : GradedRubricExporter.Moodle { 36 | 37 | override fun export(gradedRubric: GradedRubric): Resource { 38 | val json = MoodleJSON( 39 | (gradedRubric.testCycle.submission as JavaSubmission).submissionInfo, 40 | gradedRubric.grade.minPoints, 41 | exporterHTML.export(gradedRubric).getInputStream().bufferedReader().readText(), 42 | ) 43 | val jsonString = Json.encodeToString(json) 44 | return buildResource { 45 | name = "${gradedRubric.testCycle.submission.info}.json" 46 | outputStream.bufferedWriter().use { it.write(jsonString) } 47 | } 48 | } 49 | 50 | @Serializable 51 | data class MoodleJSON( 52 | val submissionInfo: SubmissionInfo, 53 | val totalPoints: Int, 54 | val feedbackComment: String, 55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/extra/Extra.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.extra 21 | 22 | interface Extra { 23 | val name: String 24 | fun run() 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/extra/ExtrasManagerImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.extra 21 | 22 | import com.google.inject.Inject 23 | import org.apache.logging.log4j.Logger 24 | import org.sourcegrade.jagr.launcher.env.Config 25 | import org.sourcegrade.jagr.launcher.io.ExtrasManager 26 | 27 | class ExtrasManagerImpl @Inject constructor( 28 | private val config: Config, 29 | private val logger: Logger, 30 | private val moodleUnpack: MoodleUnpack, 31 | ) : ExtrasManager { 32 | 33 | private fun tryRunExtra(condition: Boolean, extra: Extra) { 34 | if (condition) { 35 | logger.info("Running extra ${extra.name}") 36 | extra.run() 37 | } 38 | } 39 | 40 | override fun runExtras() { 41 | tryRunExtra(config.extras.moodleUnpack.enabled, moodleUnpack) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/rubric/CriterionFactoryImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.rubric 21 | 22 | import org.sourcegrade.jagr.api.rubric.Criterion 23 | 24 | class CriterionFactoryImpl : Criterion.Factory { 25 | override fun builder(): Criterion.Builder = CriterionBuilderImpl() 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/rubric/CriterionHolderPointCalculatorFactoryImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.rubric 21 | 22 | import org.sourcegrade.jagr.api.rubric.Criterion 23 | import org.sourcegrade.jagr.api.rubric.CriterionHolderPointCalculator 24 | 25 | class CriterionHolderPointCalculatorFactoryImpl : CriterionHolderPointCalculator.Factory { 26 | override fun fixed(points: Int): CriterionHolderPointCalculator = 27 | CriterionHolderPointCalculator { points } 28 | 29 | override fun maxOfChildren(defaultPoints: Int): CriterionHolderPointCalculator { 30 | return CriterionHolderPointCalculator { 31 | it.childCriteria.asSequence().map(Criterion::getMaxPoints) 32 | .ifEmpty { listOf(defaultPoints).asSequence() }.sum() 33 | } 34 | } 35 | 36 | override fun minOfChildren(defaultPoints: Int): CriterionHolderPointCalculator { 37 | return CriterionHolderPointCalculator { 38 | it.childCriteria.asSequence().map(Criterion::getMinPoints) 39 | .ifEmpty { listOf(defaultPoints).asSequence() }.sum() 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/rubric/GradeResultImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.rubric 21 | 22 | import org.sourcegrade.jagr.api.rubric.GradeResult 23 | import org.sourcegrade.jagr.launcher.io.SerializationScope 24 | import org.sourcegrade.jagr.launcher.io.SerializerFactory 25 | import org.sourcegrade.jagr.launcher.io.readList 26 | import org.sourcegrade.jagr.launcher.io.writeList 27 | 28 | data class GradeResultImpl( 29 | private val minPoints: Int, 30 | private val maxPoints: Int, 31 | private val comments: List = listOf(), 32 | ) : GradeResult { 33 | init { 34 | require(minPoints <= maxPoints) { 35 | "minPoints ($minPoints) for grade result may not be greater than maxPoints ($maxPoints)" 36 | } 37 | } 38 | 39 | override fun getMinPoints(): Int = minPoints 40 | override fun getMaxPoints(): Int = maxPoints 41 | override fun getComments(): List = comments 42 | 43 | companion object Factory : SerializerFactory { 44 | override fun read(scope: SerializationScope.Input): GradeResultImpl = GradeResultImpl( 45 | scope.input.readInt(), 46 | scope.input.readInt(), 47 | scope.readList(), 48 | ) 49 | 50 | override fun write(obj: GradeResultImpl, scope: SerializationScope.Output) { 51 | scope.output.writeInt(obj.minPoints) 52 | scope.output.writeInt(obj.maxPoints) 53 | scope.writeList(obj.comments) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/rubric/GradeResultMath.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.rubric 21 | 22 | import org.sourcegrade.jagr.api.rubric.Gradable 23 | import org.sourcegrade.jagr.api.rubric.GradeResult 24 | 25 | operator fun GradeResult.plus(other: GradeResult): GradeResult = 26 | GradeResultImpl(minPoints + other.minPoints, maxPoints + other.maxPoints, comments + other.comments) 27 | 28 | fun Sequence.sum(): GradeResult = fold(GradeResult.ofNone()) { a, b -> a + b } 29 | 30 | fun GradeResult.withComments(comments: Iterable): GradeResult = GradeResult.withComments(this, comments) 31 | 32 | fun GradeResult.clamped(gradable: Gradable<*>): GradeResult = GradeResult.clamped(this, gradable) 33 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/rubric/RubricBuilderImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.rubric 21 | 22 | import org.sourcegrade.jagr.api.rubric.Criterion 23 | import org.sourcegrade.jagr.api.rubric.Rubric 24 | 25 | class RubricBuilderImpl : Rubric.Builder { 26 | 27 | private var title: String? = null 28 | private val criteria: MutableList = mutableListOf() 29 | 30 | override fun title(title: String): RubricBuilderImpl { 31 | this.title = title 32 | return this 33 | } 34 | 35 | override fun addChildCriteria(vararg criteria: Criterion): RubricBuilderImpl { 36 | for (criterion in criteria) { 37 | this.criteria.add(criterion as CriterionImpl) 38 | } 39 | return this 40 | } 41 | 42 | override fun addChildCriteria(criteria: Iterable): RubricBuilderImpl { 43 | for (criterion in criteria) { 44 | this.criteria.add(criterion as CriterionImpl) 45 | } 46 | return this 47 | } 48 | 49 | override fun build(): RubricImpl { 50 | return RubricImpl( 51 | requireNotNull(title) { "title is null" }, 52 | criteria, 53 | ) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/rubric/RubricFactoryImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.rubric 21 | 22 | import org.sourcegrade.jagr.api.rubric.Rubric 23 | 24 | class RubricFactoryImpl : Rubric.Factory { 25 | override fun builder() = RubricBuilderImpl() 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/rubric/TestExecutionMessaging.kt: -------------------------------------------------------------------------------- 1 | package org.sourcegrade.jagr.core.rubric 2 | 3 | import org.junit.platform.engine.TestExecutionResult 4 | import org.opentest4j.AssertionFailedError 5 | import java.lang.reflect.InvocationTargetException 6 | 7 | internal val TestExecutionResult.message: String? 8 | get() = throwable.orElse(null)?.run { 9 | when (this) { 10 | is AssertionFailedError, 11 | -> message.toString() 12 | // students should not see an invocation target exception 13 | // it's better to show the actual exception thrown from their code 14 | is InvocationTargetException, 15 | -> cause?.prettyMessage 16 | else -> prettyMessage 17 | } 18 | } 19 | 20 | internal val Throwable.prettyMessage 21 | get() = "${this::class.simpleName}: $message @ ${stackTrace.firstOrNull()}" 22 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/rubric/grader/AbstractGraderBuilder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.rubric.grader 21 | 22 | import org.sourcegrade.jagr.api.rubric.Grader 23 | 24 | abstract class AbstractGraderBuilder> : Grader.Builder { 25 | 26 | var graderPassed: Grader? = null 27 | var graderFailed: Grader? = null 28 | var commentIfFailed: String? = null 29 | 30 | abstract fun getThis(): B 31 | 32 | override fun pointsPassed(grader: Grader?): B { 33 | graderPassed = grader 34 | return getThis() 35 | } 36 | 37 | override fun pointsFailed(grader: Grader?): B { 38 | graderFailed = grader 39 | return getThis() 40 | } 41 | 42 | override fun commentIfFailed(comment: String?): B { 43 | commentIfFailed = comment 44 | return getThis() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/rubric/grader/GraderFactoryImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.rubric.grader 21 | 22 | import com.google.inject.Inject 23 | import org.apache.logging.log4j.Logger 24 | import org.sourcegrade.jagr.api.rubric.Grader 25 | 26 | class GraderFactoryImpl @Inject constructor( 27 | private val logger: Logger, 28 | ) : Grader.Factory { 29 | override fun testAwareBuilder() = TestAwareGraderBuilderImpl() 30 | override fun descendingPriority(vararg graders: Grader) = DescendingPriorityGrader(logger, *graders) 31 | } 32 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/rubric/grader/TestAwareGraderBuilderImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.rubric.grader 21 | 22 | import org.sourcegrade.jagr.api.rubric.GradeResult 23 | import org.sourcegrade.jagr.api.rubric.Grader 24 | import org.sourcegrade.jagr.api.rubric.JUnitTestRef 25 | 26 | class TestAwareGraderBuilderImpl : AbstractGraderBuilder(), Grader.TestAwareBuilder { 27 | 28 | override fun getThis(): Grader.TestAwareBuilder = this 29 | 30 | private val requirePass: MutableMap = mutableMapOf() 31 | private val requireFail: MutableMap = mutableMapOf() 32 | 33 | override fun requirePass(testRef: JUnitTestRef, comment: String?): Grader.TestAwareBuilder { 34 | requirePass[testRef] = comment 35 | return this 36 | } 37 | 38 | override fun requireFail(testRef: JUnitTestRef, comment: String?): Grader.TestAwareBuilder { 39 | requireFail[testRef] = comment 40 | return this 41 | } 42 | 43 | override fun build(): Grader { 44 | return TestAwareGraderImpl( 45 | graderPassed ?: Grader { _, _ -> GradeResult.ofNone() }, 46 | graderFailed ?: Grader { _, _ -> GradeResult.ofNone() }, 47 | requirePass, 48 | requireFail, 49 | commentIfFailed, 50 | ) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/testing/FallbackJUnitResult.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.testing 21 | 22 | import org.junit.platform.launcher.TestPlan 23 | import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder 24 | import org.junit.platform.launcher.core.LauncherFactory 25 | import org.junit.platform.launcher.listeners.SummaryGeneratingListener 26 | import org.sourcegrade.jagr.api.testing.TestCycle 27 | import org.sourcegrade.jagr.api.testing.TestStatusListener 28 | 29 | class FallbackJUnitResult : TestCycle.JUnitResult { 30 | private val testPlan = LauncherFactory.create().discover(LauncherDiscoveryRequestBuilder.request().build()) 31 | private val summaryListener = SummaryGeneratingListener() 32 | private val statusListener = TestStatusListener { emptyMap() } 33 | override fun getTestPlan(): TestPlan = testPlan 34 | override fun getSummaryListener(): SummaryGeneratingListener = summaryListener 35 | override fun getStatusListener(): TestStatusListener = statusListener 36 | } 37 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/testing/FallbackRuntimeTester.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.testing 21 | 22 | import org.sourcegrade.jagr.api.testing.Submission 23 | import org.sourcegrade.jagr.api.testing.TestCycle 24 | import org.sourcegrade.jagr.core.compiler.java.RuntimeClassLoaderImpl 25 | import org.sourcegrade.jagr.core.compiler.java.plus 26 | 27 | class FallbackRuntimeTester : RuntimeTester { 28 | override fun createTestCycle(grader: GraderJarImpl, submission: Submission): TestCycle? { 29 | submission as JavaSubmission 30 | var resources = grader.container.runtimeResources 31 | resources += submission.compileResult.runtimeResources + submission.libraries 32 | val classLoader = RuntimeClassLoaderImpl(resources) 33 | val notes = listOf( 34 | "The grading process was forcibly terminated.", 35 | "Please check if you have an infinite loop or infinite recursion.", 36 | ) 37 | return FallbackTestCycle(grader.info.rubricProviderName, submission, classLoader, notes) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/testing/FallbackTestCycle.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.testing 21 | 22 | import org.sourcegrade.jagr.api.testing.Submission 23 | import org.sourcegrade.jagr.api.testing.TestCycle 24 | import org.sourcegrade.jagr.core.compiler.java.RuntimeClassLoaderImpl 25 | 26 | class FallbackTestCycle( 27 | private val rubricProviderClassName: String, 28 | private val submission: Submission, 29 | private val classLoader: RuntimeClassLoaderImpl, 30 | private val notes: List, 31 | ) : TestCycle { 32 | override fun getRubricProviderName(): String = rubricProviderClassName 33 | override fun getClassLoader(): RuntimeClassLoaderImpl = classLoader 34 | override fun getSubmission(): Submission = submission 35 | override fun getTestsSucceededCount(): Int = -1 36 | override fun getTestsStartedCount(): Int = -1 37 | override fun getNotes(): List = notes 38 | override fun getJUnitResult(): TestCycle.JUnitResult = FallbackJUnitResult() 39 | } 40 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/testing/JUnitResultImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.testing 21 | 22 | import org.junit.platform.launcher.TestPlan 23 | import org.junit.platform.launcher.listeners.SummaryGeneratingListener 24 | import org.sourcegrade.jagr.api.testing.TestCycle 25 | import org.sourcegrade.jagr.api.testing.TestStatusListener 26 | 27 | data class JUnitResultImpl( 28 | private val testPlan: TestPlan, 29 | private val summaryListener: SummaryGeneratingListener, 30 | private val statusListener: TestStatusListener, 31 | ) : TestCycle.JUnitResult { 32 | override fun getTestPlan(): TestPlan = testPlan 33 | override fun getSummaryListener(): SummaryGeneratingListener = summaryListener 34 | override fun getStatusListener(): TestStatusListener = statusListener 35 | } 36 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/testing/RubricConfigurationImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.testing 21 | 22 | import org.sourcegrade.jagr.api.testing.ClassTransformer 23 | import org.sourcegrade.jagr.api.testing.ClassTransformerOrder 24 | import org.sourcegrade.jagr.api.testing.RubricConfiguration 25 | import java.util.Collections 26 | 27 | class RubricConfigurationImpl : RubricConfiguration { 28 | private val transformers = mutableMapOf>() 29 | private val fileNameSolutionOverrides = mutableListOf() 30 | private var exportBuildScriptPath: String? = null 31 | override fun getTransformers(): Map> = 32 | transformers.asSequence().map { (a, b) -> a to Collections.unmodifiableList(b) }.toMap() 33 | 34 | override fun getFileNameSolutionOverrides(): List = Collections.unmodifiableList(fileNameSolutionOverrides) 35 | 36 | override fun getExportBuildScriptPath() = exportBuildScriptPath 37 | 38 | override fun addTransformer(transformer: ClassTransformer, order: ClassTransformerOrder): RubricConfiguration { 39 | transformers.computeIfAbsent(order) { mutableListOf() } += transformer 40 | return this 41 | } 42 | 43 | override fun addFileNameSolutionOverride(fileName: String): RubricConfiguration { 44 | fileNameSolutionOverrides += fileName 45 | return this 46 | } 47 | 48 | override fun setExportBuildScriptPath(path: String?): RubricConfiguration { 49 | exportBuildScriptPath = path 50 | return this 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/testing/RuntimeTester.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.testing 21 | 22 | import org.sourcegrade.jagr.api.testing.Submission 23 | import org.sourcegrade.jagr.api.testing.TestCycle 24 | 25 | fun interface RuntimeTester { 26 | fun createTestCycle(grader: GraderJarImpl, submission: Submission): TestCycle? 27 | } 28 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/testing/ThreadedGlobalParameterResolver.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.testing 21 | 22 | import com.google.inject.Singleton 23 | import org.junit.jupiter.api.extension.ExtensionContext 24 | import org.junit.jupiter.api.extension.ParameterContext 25 | import org.junit.jupiter.api.extension.ParameterResolver 26 | import org.sourcegrade.jagr.api.testing.TestCycle 27 | import org.sourcegrade.jagr.api.testing.extension.TestCycleResolver 28 | import kotlin.reflect.KClass 29 | 30 | sealed class ThreadedGlobalParameterResolver(private val type: KClass) : ParameterResolver { 31 | 32 | private val valueStorage: InheritableThreadLocal = InheritableThreadLocal() 33 | var value: T? 34 | get() = valueStorage.get() 35 | set(value) = valueStorage.set(value) 36 | 37 | override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean = 38 | value != null && parameterContext.parameter.type == type.java 39 | 40 | override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): T = value!! 41 | 42 | fun getInternalValue(): T? = value 43 | } 44 | 45 | @Singleton 46 | class TestCycleParameterResolver : ThreadedGlobalParameterResolver(TestCycle::class), TestCycleResolver.Internal 47 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/transformer/BytecodeReplacement.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.transformer 21 | 22 | import kotlin.reflect.KClass 23 | 24 | data class BytecodeReplacement( 25 | val field: BytecodeElement.Replacer, 26 | val fieldInsn: BytecodeElement.Replacer, 27 | val methodInsn: BytecodeElement.Replacer, 28 | ) { 29 | constructor( 30 | fieldFactory: BytecodeElement.Replacer.Factory, 31 | fieldInsnFactory: BytecodeElement.Replacer.Factory, 32 | methodFactory: BytecodeElement.Replacer.Factory, 33 | original: KClass<*>, 34 | surrogate: KClass<*>, 35 | ) : this( 36 | fieldFactory.create(original, surrogate), 37 | fieldInsnFactory.create(original, surrogate), 38 | methodFactory.create(original, surrogate), 39 | ) 40 | } 41 | 42 | interface BytecodeElement { 43 | fun withSurrogate(original: KClass<*>, surrogate: KClass<*>): BytecodeElement 44 | fun interface Replacer { 45 | fun replace(element: T): T? 46 | interface Factory { 47 | fun create(original: KClass<*>, surrogate: KClass<*>): Replacer 48 | } 49 | } 50 | } 51 | 52 | infix fun KClass<*>.replaces(originalType: KClass<*>): BytecodeReplacement { 53 | return BytecodeReplacement(FieldElement, FieldInsnElement, MethodInsnElement, originalType, this) 54 | } 55 | 56 | inline fun T.replace(replacement: BytecodeElement.Replacer, block: (T) -> R): R { 57 | return block(replacement.replace(this) ?: this) 58 | } 59 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/transformer/ClassTransformerFactoryImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.transformer 21 | 22 | import org.sourcegrade.jagr.api.testing.ClassTransformer 23 | 24 | class ClassTransformerFactoryImpl : ClassTransformer.Factory { 25 | 26 | override fun injectSuperclass(targetName: String, superName: String): ClassTransformer = 27 | InjectSuperclassTransformer(targetName, superName) 28 | 29 | override fun replacement(replacement: Class<*>, original: Class<*>): ClassTransformer = 30 | ReplacementTransformer(replacement.kotlin, original.kotlin) 31 | } 32 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/transformer/FieldElement.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.transformer 21 | 22 | import org.objectweb.asm.Type 23 | import kotlin.reflect.KClass 24 | 25 | data class FieldElement( 26 | val access: Int, 27 | val name: String, 28 | val descriptor: String, 29 | val signature: String?, 30 | val value: Any?, 31 | ) : BytecodeElement { 32 | override fun withSurrogate(original: KClass<*>, surrogate: KClass<*>): FieldElement { 33 | val originalDescriptor = Type.getDescriptor(original.java) 34 | val surrogateDescriptor = Type.getDescriptor(surrogate.java) 35 | val descriptor = descriptor.replace(originalDescriptor, surrogateDescriptor) 36 | return copy(descriptor = descriptor) 37 | } 38 | 39 | companion object Factory : BytecodeElement.Replacer.Factory { 40 | override fun create(original: KClass<*>, surrogate: KClass<*>): BytecodeElement.Replacer { 41 | return BytecodeElement.Replacer { 42 | if (it.descriptor.contains(Type.getDescriptor(original.java))) { 43 | it.withSurrogate(original, surrogate) 44 | } else { 45 | null 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /core/src/main/kotlin/org/sourcegrade/jagr/core/transformer/FieldInsnElement.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.core.transformer 21 | 22 | import org.objectweb.asm.Type 23 | import kotlin.reflect.KClass 24 | 25 | data class FieldInsnElement( 26 | val opcode: Int, 27 | val owner: String, 28 | val name: String, 29 | val descriptor: String, 30 | ) : BytecodeElement { 31 | override fun withSurrogate(original: KClass<*>, surrogate: KClass<*>): FieldInsnElement { 32 | val originalDescriptor = Type.getDescriptor(original.java) 33 | val surrogateDescriptor = Type.getDescriptor(surrogate.java) 34 | val descriptor = descriptor.replace(originalDescriptor, surrogateDescriptor) 35 | return copy(descriptor = descriptor) 36 | } 37 | 38 | companion object Factory : BytecodeElement.Replacer.Factory { 39 | override fun create(original: KClass<*>, surrogate: KClass<*>): BytecodeElement.Replacer { 40 | return BytecodeElement.Replacer { 41 | if (it.descriptor.contains(Type.getDescriptor(original.java))) { 42 | it.withSurrogate(original, surrogate) 43 | } else { 44 | null 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /core/src/main/resources/jagr.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFactories": [ 3 | "org.sourcegrade.jagr.core.CommonModule$Factory" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /docs/architecture/grader.md: -------------------------------------------------------------------------------- 1 | A grader is a [Gradle](https://gradle.org/) project that contains the JUnit tests and rubric definitions for a specific assignment. 2 | To get started, create a new Gradle project and add the [jagr-gradle](/development/getting-started/gradle-setup) 3 | plugin to your buildscript. 4 | 5 | In general, a simple grader is a single-module Gradle project with the following source sets: 6 | 7 | - `grader` - The JUnit tests and rubric definitions 8 | - `main` - The solution source code (analogous to the `main` source set the student's submission) 9 | 10 | It is possible to customize the source sets used by the grader, for example by separating public and private test 11 | (public tests being tests that are distributed to the students for local execution before submission). 12 | 13 | - `graderPrivate` - The JUnit tests and rubric definitions that are kept private 14 | - `graderPublic` - The JUnit tests and rubric definitions that are distributed to the students 15 | - `main` - The solution source code (analogous to the `main` source set the student's submission) 16 | 17 | In this case, executing the `graderPrivate` will also execute the `graderPublic` tests. 18 | -------------------------------------------------------------------------------- /docs/architecture/submission.md: -------------------------------------------------------------------------------- 1 | A submission is the final product to be graded. 2 | It is created via the `mainBuildSubmission` task in the [jagr-gradle](/development/getting-started/gradle-setup) plugin. 3 | -------------------------------------------------------------------------------- /docs/assets/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/development/grader-api/criterion.md: -------------------------------------------------------------------------------- 1 | # Criterion - Overview 2 | 3 | ## Usage 4 | 5 | Create a basic `Criterion`: 6 | 7 | ```java 8 | public static final Criterion H1_1 = Criterion.builder() 9 | .shortDescription("Some short description") 10 | .grader( 11 | Grader.testAwareBuilder() 12 | .requirePass(JUnitTestRef.ofMethod(() -> Tests.class.getMethod("testPositiveInts"))) 13 | .requirePass(JUnitTestRef.ofMethod(() -> Tests.class.getMethod("testNegativeInts"))) 14 | .maxPoints(3) // default maxPoints is 1 15 | .minPoints(-1) // default minPoints is 0 16 | .pointsPassedMax() // award maximum points if ALL tests passed 17 | .pointsFailedMin() // award minimum points if ANY test failed 18 | .build() 19 | ).build(); 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Welcome to Jagr 2 | 3 | The Jagr AutoGrader is a powerful tool for grading programming assignments, 4 | providing detailed test results in the form of rubrics for each submission. 5 | 6 | ## Features 7 | 8 | - Easy Submission Process for students via [Gradle Plugin](development/getting-started/gradle-setup) 9 | - Fluent API for defining grading rubric 10 | - Write tests in familiar JUnit 5 style 11 | - Access to the submission's source code in tests 12 | - Integration with powerful bytecode manipulation tools such as [ASM](https://asm.ow2.io/) 13 | - Detailed test results in the form of rubrics 14 | 15 | 16 | ## Getting Started 17 | 18 | See basic command-line usage [here](usage/command-line/basics.md). 19 | 20 | Join us on [discord](https://discord.gg/Ag9zdX5ARt) if you have any questions. 21 | -------------------------------------------------------------------------------- /docs/usage/command-line/basics.md: -------------------------------------------------------------------------------- 1 | # Basic Command-Line Usage 2 | 3 | 1. Create a [grader](architecture/grader) 4 | 2. Create a [submission](architecture/submission) 5 | 3. Download the [latest release](https://github.com/sourcegrade/jagr/releases) 6 | 7 | !!! tip 8 | 9 | The [jagr-bin](https://aur.archlinux.org/packages/jagr-bin) package is available on the AUR for Arch Linux users. 10 | 11 | 4. Create an empty working directory and copy the Jagr jar into it 12 | 5. Run `java -jar Jagr-.jar`, which should create the following folder structure: 13 | 14 | ```text 15 | ./graders -- input folder for grader jars (tests and rubric providers) 16 | ./libs -- for libraries that are required on each submission's classpath 17 | ./logs -- saved log files 18 | ./rubrics -- the output folder for graded rubrics 19 | ./submissions -- input folder for submissions 20 | ./submissions-export -- output folder for submissions 21 | ``` 22 | 23 | 6. Prepare the grader and submission for grading 24 | 1. Prepare the grader jar by running the `graderBuildGrader` Gradle task in the grader project 25 | 2. Prepare the submission jar by running the `mainBuildSubmission` Gradle task in the submission project 26 | 3. Locate the respective jars in the `build/libs` folder of the grader and submission projects 27 | 28 | 7. Copy the grader jar into the `graders` folder and the submission jar into the `submissions` folder. 29 | If the grader requires any runtime dependencies (that are not already included in Jagr), copy them into the `libs` folder 30 | 31 | !!! tip 32 | 33 | The `graderBuildLibs` gradle task provided by the jagr-gradle plugin can be used to generate a fat jar containing all runtime dependencies. 34 | This task automatically excludes dependencies already present in the Jagr runtime. 35 | 36 | 8. Run `java -jar Jagr-x-x-x.jar` again to grade the submission 37 | -------------------------------------------------------------------------------- /docs/usage/command-line/options.md: -------------------------------------------------------------------------------- 1 | # Command-Line Options 2 | 3 | ### --no-export, -n 4 | 5 | Do not export submissions. 6 | 7 | ### --export-only, -e 8 | 9 | Do not grade, only export submissions. 10 | 11 | ### --progress 12 | 13 | Progress bar style. 14 | 15 | Choices: "rainbow", "xmas" 16 | 17 | ### --child (internal) 18 | 19 | Waits to receive grading job details via IPC 20 | -------------------------------------------------------------------------------- /docs/usage/getting-started/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ## GitHub Binary Download 4 | 5 | In most cases, the quickest way to install Jagr is by downloading the latest binary on the 6 | [GitHub releases page](https://github.com/sourcegrade/jagr/releases). 7 | 8 | ## Arch-based Linux 9 | 10 | The AUR package [jagr-bin](https://aur.archlinux.org/packages/jagr-bin) is available and may be installed with: 11 | 12 | ```shell 13 | yay -S jagr-bin 14 | ``` 15 | 16 | ## Build from source 17 | 18 | In order to build Jagr from source, you must have git and Java version 11 or later installed. 19 | 20 | ### Clone the repository 21 | 22 |
23 | Via SSH 24 | 25 | ```shell 26 | git clone git@github.com:sourcegrade/jagr.git 27 | ``` 28 |
29 | 30 |
31 | Via HTTPS 32 | 33 | ```shell 34 | git clone https://github.com/sourcegrade/jagr.git 35 | ``` 36 |
37 | 38 | ### Build the project 39 | 40 | ```shell 41 | cd jagr 42 | ./gradlew build 43 | ``` 44 | 45 | The compiled jar will be located in `./build/libs/`. 46 | -------------------------------------------------------------------------------- /grader-api/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `java-library` 3 | id("jagr-publish") 4 | id("jagr-sign") 5 | } 6 | 7 | tasks { 8 | withType { 9 | // check if rootProject version ends with .0 or .0-SNAPSHOT 10 | onlyIf { rootProject.version.toString().matches(".*\\.0(-SNAPSHOT)?\$".toRegex()) } 11 | } 12 | } 13 | 14 | val apiVersion: String by rootProject 15 | version = apiVersion 16 | 17 | dependencies { 18 | api(libs.asm.core) 19 | api(libs.guice) 20 | api(libs.logging.api) 21 | api(libs.bundles.junit) 22 | implementation(libs.annotations) 23 | } 24 | -------------------------------------------------------------------------------- /grader-api/src/main/java/org/sourcegrade/jagr/api/rubric/CriterionHolder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.api.rubric; 21 | 22 | import org.jetbrains.annotations.ApiStatus; 23 | 24 | import java.util.List; 25 | 26 | /** 27 | * Something that has criterion children. 28 | * 29 | * @see Criterion 30 | * @see Rubric 31 | */ 32 | @ApiStatus.NonExtendable 33 | public interface CriterionHolder { 34 | 35 | /** 36 | * The criterion children of this CriterionHolder. 37 | * 38 | * @return The criterion children of this CriterionHolder 39 | */ 40 | List getChildCriteria(); 41 | } 42 | -------------------------------------------------------------------------------- /grader-api/src/main/java/org/sourcegrade/jagr/api/rubric/Gradable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.api.rubric; 21 | 22 | import org.jetbrains.annotations.ApiStatus; 23 | import org.sourcegrade.jagr.api.testing.TestCycle; 24 | 25 | /** 26 | * Something that can be graded via a {@link TestCycle}. 27 | * 28 | * @param The resulting graded type 29 | */ 30 | @ApiStatus.NonExtendable 31 | public interface Gradable extends PointRange { 32 | 33 | /** 34 | * The minimum achievable number of points for this Gradable. 35 | * 36 | * @return The minimum achievable number of points. 37 | */ 38 | @Override 39 | int getMinPoints(); 40 | 41 | /** 42 | * The maximum achievable number of points for this Gradable. 43 | * 44 | * @return The maximum achievable number of points. 45 | */ 46 | @Override 47 | int getMaxPoints(); 48 | 49 | /** 50 | * Grade the provided {@link TestCycle}. 51 | */ 52 | G grade(TestCycle testCycle); 53 | } 54 | -------------------------------------------------------------------------------- /grader-api/src/main/java/org/sourcegrade/jagr/api/rubric/Graded.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.api.rubric; 21 | 22 | import org.jetbrains.annotations.ApiStatus; 23 | import org.sourcegrade.jagr.api.testing.TestCycle; 24 | 25 | /** 26 | * Something that has been graded. 27 | */ 28 | @ApiStatus.NonExtendable 29 | public interface Graded { 30 | 31 | /** 32 | * Returns the {@link TestCycle} that was used in the grading process. 33 | * 34 | * @return The {@link TestCycle} that was used in the grading process 35 | */ 36 | TestCycle getTestCycle(); 37 | 38 | /** 39 | * Returns the {@link GradeResult} for this graded object. 40 | * 41 | *

42 | * If this graded object has children, this grade result is the sum of all child grade results. 43 | *

44 | * 45 | * @return The {@link GradeResult} for this graded object 46 | */ 47 | GradeResult getGrade(); 48 | } 49 | -------------------------------------------------------------------------------- /grader-api/src/main/java/org/sourcegrade/jagr/api/rubric/GradedCriterion.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.api.rubric; 21 | 22 | import org.jetbrains.annotations.ApiStatus; 23 | 24 | /** 25 | * A {@link Criterion} that has been graded. 26 | */ 27 | @ApiStatus.NonExtendable 28 | public interface GradedCriterion extends Graded, CriterionHolder { 29 | 30 | /** 31 | * Returns the {@link Criterion} that was used to create this graded criterion. 32 | * 33 | * @return The {@link Criterion} that was used to create this graded criterion 34 | */ 35 | Criterion getCriterion(); 36 | } 37 | -------------------------------------------------------------------------------- /grader-api/src/main/java/org/sourcegrade/jagr/api/rubric/GradedRubric.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.api.rubric; 21 | 22 | import org.jetbrains.annotations.ApiStatus; 23 | 24 | /** 25 | * A {@link Rubric} that has been graded. 26 | */ 27 | @ApiStatus.NonExtendable 28 | public interface GradedRubric extends Graded, CriterionHolder { 29 | 30 | /** 31 | * Returns the {@link Rubric} that was used to create this graded rubric. 32 | * 33 | * @return The {@link Rubric} that was used to create this graded rubric 34 | */ 35 | Rubric getRubric(); 36 | } 37 | -------------------------------------------------------------------------------- /grader-api/src/main/java/org/sourcegrade/jagr/api/rubric/RubricProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.api.rubric; 21 | 22 | import org.jetbrains.annotations.Nullable; 23 | import org.sourcegrade.jagr.api.testing.RubricConfiguration; 24 | import org.sourcegrade.jagr.api.testing.Submission; 25 | 26 | /** 27 | * Used to provide a {@link Rubric} for a submission. 28 | */ 29 | public interface RubricProvider { 30 | 31 | /** 32 | * The filename to use when writing this rubric to disk. By default, it is: 33 | * 34 | *

35 |      * assignmentId + "_Rubric_" + studentLastName + "_" + studentFirstName
36 |      * 
37 | */ 38 | default @Nullable String getOutputFileName(Submission submission) { 39 | return null; 40 | } 41 | 42 | default void configure(RubricConfiguration configuration) { 43 | } 44 | 45 | Rubric getRubric(); 46 | } 47 | -------------------------------------------------------------------------------- /grader-api/src/main/java/org/sourcegrade/jagr/api/rubric/TestForSubmission.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.api.rubric; 21 | 22 | import org.jetbrains.annotations.ApiStatus; 23 | 24 | import java.lang.annotation.ElementType; 25 | import java.lang.annotation.Retention; 26 | import java.lang.annotation.RetentionPolicy; 27 | import java.lang.annotation.Target; 28 | 29 | /** 30 | * This annotation is used to mark a class as a test for a submission. 31 | */ 32 | @ApiStatus.NonExtendable 33 | @Retention(RetentionPolicy.RUNTIME) 34 | @Target(ElementType.TYPE) 35 | public @interface TestForSubmission { 36 | } 37 | -------------------------------------------------------------------------------- /grader-api/src/main/java/org/sourcegrade/jagr/api/testing/ClassTransformerOrder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.api.testing; 21 | 22 | /** 23 | * The order in which to apply a {@link ClassTransformer}. 24 | */ 25 | public enum ClassTransformerOrder { 26 | 27 | /** 28 | * Before submission verification. 29 | */ 30 | PRE, 31 | 32 | /** 33 | * After submission verification. 34 | */ 35 | DEFAULT, 36 | } 37 | -------------------------------------------------------------------------------- /grader-api/src/main/java/org/sourcegrade/jagr/api/testing/CompileResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.api.testing; 21 | 22 | import org.jetbrains.annotations.ApiStatus; 23 | 24 | import java.util.List; 25 | 26 | /** 27 | * The result from the compilation of a container. 28 | */ 29 | @ApiStatus.NonExtendable 30 | public interface CompileResult { 31 | 32 | /** 33 | * The messages produced by the compiler. 34 | * 35 | * @return The messages produced by the compiler 36 | */ 37 | List getMessages(); 38 | 39 | /** 40 | * The number of warnings that were produced by the compiler. 41 | * 42 | * @return The number of warnings that were produced by the compiler 43 | */ 44 | int getWarningCount(); 45 | 46 | /** 47 | * The number of errors that were produced by the compiler. 48 | * 49 | * @return The number of errors that were produced by the compiler 50 | */ 51 | int getErrorCount(); 52 | 53 | /** 54 | * The number of messages that are neither warnings nor errors produced by the compiler. 55 | * 56 | * @return The number of messages that are neither warnings nor errors produced by the compiler 57 | */ 58 | int getOtherCount(); 59 | } 60 | -------------------------------------------------------------------------------- /grader-api/src/main/java/org/sourcegrade/jagr/api/testing/SourceFile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.api.testing; 21 | 22 | import org.jetbrains.annotations.ApiStatus; 23 | import org.jetbrains.annotations.Nullable; 24 | 25 | /** 26 | * A java source file. 27 | */ 28 | @ApiStatus.NonExtendable 29 | public interface SourceFile { 30 | 31 | /** 32 | * The file name. 33 | * 34 | * @return The file name 35 | */ 36 | String getFileName(); 37 | 38 | /** 39 | * The content of the file. 40 | * 41 | * @return The content of the file 42 | */ 43 | String getContent(); 44 | 45 | /** 46 | * The top-level class name of this file, if present. 47 | * 48 | * @return The top-level class name of this file, if present 49 | */ 50 | @Nullable String getClassName(); 51 | } 52 | -------------------------------------------------------------------------------- /grader-api/src/main/java/org/sourcegrade/jagr/api/testing/Submission.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.api.testing; 21 | 22 | import org.jetbrains.annotations.ApiStatus; 23 | import org.jetbrains.annotations.Nullable; 24 | 25 | import java.util.Set; 26 | 27 | /** 28 | * A student submission to be graded. 29 | */ 30 | @ApiStatus.NonExtendable 31 | public interface Submission { 32 | 33 | /** 34 | * The info of the submission. 35 | * 36 | * @return The info of the submission 37 | */ 38 | String getInfo(); 39 | 40 | /** 41 | * The {@link CompileResult} of the submission. 42 | * 43 | * @return The {@link CompileResult} of the submission 44 | */ 45 | CompileResult getCompileResult(); 46 | 47 | /** 48 | * The {@link SourceFile} for the given file name. 49 | * 50 | * @param fileName The name of the file to return 51 | * @return The {@link SourceFile} for the given file name 52 | */ 53 | @Nullable SourceFile getSourceFile(String fileName); 54 | 55 | /** 56 | * Experimental API. May be moved in a future release. 57 | * 58 | *

59 | * Deprecated in favor of {@link TestCycle#getClassLoader()} and {@link RuntimeClassLoader#getClassNames()}. 60 | *

61 | * 62 | * @return An immutable set of Java class names from this submission. 63 | * @deprecated Use {@link RuntimeClassLoader#getClassNames()} instead. 64 | */ 65 | @Deprecated(forRemoval = true) 66 | Set getClassNames(); 67 | } 68 | -------------------------------------------------------------------------------- /grader-api/src/main/java/org/sourcegrade/jagr/api/testing/TestStatusListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.api.testing; 21 | 22 | import org.junit.platform.engine.TestExecutionResult; 23 | import org.junit.platform.launcher.TestIdentifier; 24 | 25 | import java.util.Map; 26 | 27 | /** 28 | * Used to store test results from JUnit. 29 | */ 30 | public interface TestStatusListener { 31 | 32 | /** 33 | * Returns the test results that were collected from JUnit. 34 | * 35 | * @return The test results that were collected from JUnit 36 | */ 37 | Map getTestResults(); 38 | } 39 | -------------------------------------------------------------------------------- /grader-api/src/main/java/org/sourcegrade/jagr/api/testing/extension/JagrExecutionCondition.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2024 Alexander Städing 4 | * Copyright (C) 2021-2024 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.api.testing.extension; 21 | 22 | import org.junit.jupiter.api.extension.ConditionEvaluationResult; 23 | import org.junit.jupiter.api.extension.ExecutionCondition; 24 | import org.junit.jupiter.api.extension.ExtensionContext; 25 | 26 | /** 27 | * Checks whether Jagr is currently being used and skips the target test if it is not. 28 | * 29 | *

Inverse of {@link NonJagrExecutionCondition}. 30 | */ 31 | public final class JagrExecutionCondition implements ExecutionCondition { 32 | @Override 33 | public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { 34 | if (TestCycleResolver.Provider.parameterResolver == null) { 35 | return ConditionEvaluationResult.disabled("Jagr is not present, disabled"); 36 | } else { 37 | return ConditionEvaluationResult.enabled("Jagr is present, enabled"); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /grader-api/src/main/java/org/sourcegrade/jagr/api/testing/extension/NonJagrExecutionCondition.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2024 Alexander Städing 4 | * Copyright (C) 2021-2024 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.api.testing.extension; 21 | 22 | import org.junit.jupiter.api.extension.ConditionEvaluationResult; 23 | import org.junit.jupiter.api.extension.ExecutionCondition; 24 | import org.junit.jupiter.api.extension.ExtensionContext; 25 | 26 | /** 27 | * Checks whether Jagr is currently *not* being used and skips the target test if it is. 28 | * 29 | *

Inverse of {@link JagrExecutionCondition}. 30 | */ 31 | public final class NonJagrExecutionCondition implements ExecutionCondition { 32 | @Override 33 | public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { 34 | if (TestCycleResolver.Provider.parameterResolver == null) { 35 | return ConditionEvaluationResult.enabled("Jagr is not present, enabled"); 36 | } else { 37 | return ConditionEvaluationResult.disabled("Jagr is present, disabled"); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | asm = "9.8" 3 | configurate = "4.2.0" 4 | junit = "5.12.2" 5 | kotlin = "2.1.20" 6 | log4j = "2.24.3" 7 | 8 | [libraries] 9 | annotations = "org.jetbrains:annotations:26.0.2" 10 | apiguardian = "org.apiguardian:apiguardian-api:1.1.2" 11 | asm-core = { module = "org.ow2.asm:asm", version.ref = "asm" } 12 | asm-util = { module = "org.ow2.asm:asm-util", version.ref = "asm" } 13 | clikt = "com.github.ajalt.clikt:clikt:5.0.3" 14 | configurate-core = { module = "org.spongepowered:configurate-extra-kotlin", version.ref = "configurate" } 15 | configurate-hocon = { module = "org.spongepowered:configurate-hocon", version.ref = "configurate" } 16 | configurate-kotlin = { module = "org.spongepowered:configurate-extra-kotlin", version.ref = "configurate" } 17 | coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2" 18 | csv = "org.apache.commons:commons-csv:1.14.0" 19 | guice = "com.google.inject:guice:5.1.0" 20 | jansi = "org.fusesource.jansi:jansi:2.4.1" 21 | junit-core = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } 22 | junit-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" } 23 | junit-launcher = "org.junit.platform:junit-platform-launcher:1.12.2" 24 | juniversalchardet = "com.github.albfernandez:juniversalchardet:2.5.0" 25 | logging-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" } 26 | logging-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } 27 | serialization = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1" 28 | 29 | [bundles] 30 | junit = ["junit-core", "junit-engine", "junit-launcher"] 31 | 32 | [plugins] 33 | download = "de.undercouch.download:5.6.0" 34 | gradle-publish = "com.gradle.plugin-publish:1.3.1" 35 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 36 | kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } 37 | kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } 38 | shadow = { id = "com.github.johnrengelman.shadow", version = "8.1.1" } 39 | style = "org.sourcegrade.style:3.0.0" 40 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegrade/jagr/4aefdeb3fb257fbd8a5a05a3f238ae675be19ae1/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /launcher/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.sourcegrade.jagr.script.apiProject 2 | 3 | plugins { 4 | kotlin("jvm") 5 | kotlin("kapt") 6 | kotlin("plugin.serialization") 7 | id("jagr-publish") 8 | id("jagr-sign") 9 | } 10 | 11 | dependencies { 12 | apiProject(project, "jagr-grader-api") 13 | api(libs.coroutines) { 14 | exclude("org.jetbrains", "annotations") 15 | } 16 | implementation(kotlin("reflect")) 17 | implementation(libs.configurate.hocon) 18 | implementation(libs.configurate.kotlin) 19 | implementation(libs.annotations) 20 | implementation(libs.serialization) 21 | implementation(libs.logging.core) 22 | kapt(libs.logging.core) 23 | } 24 | 25 | tasks { 26 | withType { 27 | from(rootProject.file("version")) { 28 | into("org/sourcegrade/jagr/") 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /launcher/gradle-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `java-gradle-plugin` 3 | alias(libs.plugins.gradle.publish) 4 | alias(libs.plugins.kotlin.jvm) 5 | alias(libs.plugins.kotlin.serialization) 6 | id("jagr-publish") 7 | // signing done by gradle-publish plugin 8 | } 9 | 10 | tasks { 11 | withType { 12 | onlyIf { project.version.toString().endsWith("-SNAPSHOT") } 13 | } 14 | } 15 | 16 | dependencies { 17 | implementation(gradleKotlinDsl()) 18 | implementation(libs.serialization) 19 | implementation(libs.logging.core) 20 | implementation("de.undercouch:gradle-download-task:${libs.plugins.download.get().version}") 21 | runtimeOnly(project(":jagr-core")) 22 | implementation(project(":jagr-launcher")) 23 | } 24 | 25 | gradlePlugin { 26 | plugins { 27 | register("jagr-gradle") { 28 | id = "org.sourcegrade.jagr-gradle" 29 | displayName = "Jagr Gradle Plugin" 30 | description = "Gradle plugin for running the Jagr AutoGrader" 31 | implementationClass = "org.sourcegrade.jagr.gradle.JagrGradlePlugin" 32 | tags.set(listOf("jagr", "assignment", "submission", "grading")) 33 | } 34 | } 35 | website.set("https://www.sourcegrade.org") 36 | vcsUrl.set("https://github.com/sourcegrade/jagr") 37 | } 38 | -------------------------------------------------------------------------------- /launcher/gradle-plugin/src/main/kotlin/org/sourcegrade/jagr/gradle/JagrGradlePlugin.kt: -------------------------------------------------------------------------------- 1 | package org.sourcegrade.jagr.gradle 2 | 3 | import org.gradle.api.Plugin 4 | import org.gradle.api.Project 5 | import org.gradle.kotlin.dsl.create 6 | import org.gradle.kotlin.dsl.get 7 | import org.gradle.kotlin.dsl.register 8 | import org.sourcegrade.jagr.gradle.extension.JagrDownloadExtension 9 | import org.sourcegrade.jagr.gradle.extension.JagrExtension 10 | import org.sourcegrade.jagr.gradle.task.JagrDownloadTask 11 | import org.sourcegrade.jagr.gradle.task.grader.GraderBuildTask 12 | import org.sourcegrade.jagr.gradle.task.grader.GraderLibsTask 13 | import org.sourcegrade.jagr.gradle.task.grader.GraderRunTask 14 | import org.sourcegrade.jagr.gradle.task.grader.GraderWriteInfoTask 15 | import org.sourcegrade.jagr.gradle.task.grader.registerTask 16 | import org.sourcegrade.jagr.gradle.task.submission.SubmissionBuildTask 17 | import org.sourcegrade.jagr.gradle.task.submission.SubmissionWriteInfoTask 18 | import org.sourcegrade.jagr.gradle.task.submission.registerTask 19 | 20 | @Suppress("unused") 21 | class JagrGradlePlugin : Plugin { 22 | 23 | override fun apply(target: Project) { 24 | // create extensions 25 | val jagr = target.extensions.create("jagr") 26 | val jagrDownload = jagr.extensions.create("download") 27 | // register tasks 28 | target.tasks.register("jagrDownload") { 29 | sourceUrl.set(jagrDownload.sourceUrl) 30 | destName.set(jagrDownload.destName) 31 | } 32 | target.afterEvaluate { registerTasks(jagr, it) } 33 | } 34 | 35 | private fun registerTasks(jagr: JagrExtension, target: Project) { 36 | val checkTask = target.tasks["check"] 37 | for (grader in jagr.graders) { 38 | GraderBuildTask.Factory.registerTask(target, grader) 39 | GraderLibsTask.Factory.registerTask(target, grader) 40 | GraderWriteInfoTask.Factory.registerTask(target, grader) 41 | GraderRunTask.Factory.registerTask(target, grader) 42 | checkTask.dependsOn(GraderRunTask.Factory.determineTaskName(grader.name)) 43 | } 44 | for (submission in jagr.submissions) { 45 | SubmissionBuildTask.Factory.registerTask(target, submission) 46 | SubmissionWriteInfoTask.Factory.registerTask(target, submission) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /launcher/gradle-plugin/src/main/kotlin/org/sourcegrade/jagr/gradle/SourceSetExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.gradle 21 | 22 | import org.gradle.api.tasks.SourceSet 23 | 24 | internal fun SourceSet.forEachFile(action: (directorySet: String, fileName: String) -> Unit) { 25 | for (directorySet in allSource.sourceDirectories) { 26 | for (file in directorySet.walkTopDown()) { 27 | if (file.isFile) { 28 | action(directorySet.name, file.relativeTo(directorySet).invariantSeparatorsPath) 29 | } 30 | } 31 | } 32 | } 33 | 34 | internal fun SourceSet.getFiles(): Map> { 35 | val result = mutableMapOf>() 36 | forEachFile { directorySet, fileName -> result.computeIfAbsent(directorySet) { mutableSetOf() }.add(fileName) } 37 | return result 38 | } 39 | 40 | fun List.mergeSourceSets(): Map>> { 41 | return asSequence() 42 | .map { it.name to it.getFiles() } 43 | .fold(mutableMapOf()) { acc, (sourceSetName, sourceSetDir) -> 44 | acc.merge(sourceSetName, sourceSetDir) { a, b -> 45 | (a.asSequence() + b.asSequence()).fold(mutableMapOf()) { map, (name, files) -> 46 | map.merge(name, files) { x, y -> x + y } 47 | map 48 | } 49 | } 50 | acc 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /launcher/gradle-plugin/src/main/kotlin/org/sourcegrade/jagr/gradle/extension/DependencyConfiguration.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.gradle.extension 21 | 22 | class DependencyConfiguration { 23 | 24 | internal val dependencies = mutableMapOf>() 25 | 26 | operator fun String.invoke(dependencyNotation: Any) { 27 | dependencies.computeIfAbsent(this) { mutableListOf() }.add(dependencyNotation) 28 | } 29 | 30 | fun api(dependencyNotation: Any) = "api"(dependencyNotation) 31 | fun compileOnly(dependencyNotation: Any) = "compileOnly"(dependencyNotation) 32 | fun compileOnlyApi(dependencyNotation: Any) = "compileOnlyApi"(dependencyNotation) 33 | fun implementation(dependencyNotation: Any) = "implementation"(dependencyNotation) 34 | fun runtimeOnly(dependencyNotation: Any) = "runtimeOnly"(dependencyNotation) 35 | } 36 | -------------------------------------------------------------------------------- /launcher/gradle-plugin/src/main/kotlin/org/sourcegrade/jagr/gradle/extension/JagrDownloadExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.gradle.extension 21 | 22 | import com.google.inject.Inject 23 | import org.gradle.api.model.ObjectFactory 24 | import org.gradle.api.provider.Property 25 | import org.gradle.kotlin.dsl.property 26 | import org.sourcegrade.jagr.launcher.env.Jagr 27 | 28 | abstract class JagrDownloadExtension @Inject constructor( 29 | objectFactory: ObjectFactory, 30 | ) { 31 | val jagrVersion: Property = objectFactory.property() 32 | .convention(Jagr.version) 33 | val sourceUrl: Property = objectFactory.property() 34 | .convention(jagrVersion.map { "https://github.com/sourcegrade/jagr/releases/download/v$it/Jagr-$it.jar" }) 35 | val destName: Property = objectFactory.property() 36 | .convention(jagrVersion.map { "Jagr-$it.jar" }) 37 | } 38 | -------------------------------------------------------------------------------- /launcher/gradle-plugin/src/main/kotlin/org/sourcegrade/jagr/gradle/extension/JagrExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.gradle.extension 21 | 22 | import org.gradle.api.NamedDomainObjectContainer 23 | import org.gradle.api.plugins.ExtensionAware 24 | import org.gradle.api.provider.Property 25 | 26 | abstract class JagrExtension : ExtensionAware { 27 | abstract val assignmentId: Property 28 | abstract val graders: NamedDomainObjectContainer 29 | abstract val submissions: NamedDomainObjectContainer 30 | fun graders(configure: NamedDomainObjectContainer.() -> Unit) = graders.configure() 31 | fun submissions(configure: NamedDomainObjectContainer.() -> Unit) = submissions.configure() 32 | } 33 | -------------------------------------------------------------------------------- /launcher/gradle-plugin/src/main/kotlin/org/sourcegrade/jagr/gradle/extension/ProjectSourceSetTuple.kt: -------------------------------------------------------------------------------- 1 | package org.sourcegrade.jagr.gradle.extension 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.api.tasks.SourceSet 5 | import org.gradle.api.tasks.SourceSetContainer 6 | import org.gradle.kotlin.dsl.getByType 7 | import java.io.Serializable 8 | 9 | data class ProjectSourceSetTuple( 10 | val projectPath: String, 11 | val sourceSetName: String, 12 | ) : Serializable { 13 | companion object { 14 | fun fromSourceSetNames(projectPath: String, sourceSetNames: Sequence) = 15 | sourceSetNames.map { ProjectSourceSetTuple(projectPath, it) }.toSet() 16 | 17 | fun fromSourceSetNames(projectPath: String, sourceSetNames: Iterable) = 18 | fromSourceSetNames(projectPath, sourceSetNames.asSequence()) 19 | } 20 | } 21 | 22 | fun ProjectSourceSetTuple.getSourceSet(rootProject: Project): SourceSet = 23 | rootProject.relative(projectPath).extensions.getByType().getByName(sourceSetName) 24 | -------------------------------------------------------------------------------- /launcher/gradle-plugin/src/main/kotlin/org/sourcegrade/jagr/gradle/extension/RelativeFiles.kt: -------------------------------------------------------------------------------- 1 | package org.sourcegrade.jagr.gradle.extension 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.api.file.Directory 5 | import org.gradle.api.file.DirectoryProperty 6 | import org.gradle.api.file.RegularFile 7 | import org.gradle.api.file.RegularFileProperty 8 | import org.gradle.api.provider.Provider 9 | import org.sourcegrade.jagr.gradle.task.TargetSourceSetsTask 10 | 11 | internal fun Project.relative(path: String): Project { 12 | if (path.isEmpty()) return this 13 | return project.project(path) 14 | } 15 | 16 | internal fun TargetSourceSetsTask.resolveBuildFile( 17 | configurationNameProvider: Provider, 18 | pathResolver: (configurationName: String) -> String, 19 | ): Provider { 20 | return configurationNameProvider.flatMap { configurationName -> 21 | project.layout.buildDirectory.map { it.file(pathResolver(configurationName)) } 22 | } 23 | } 24 | 25 | internal fun TargetSourceSetsTask.resolveBuildDirectory( 26 | configurationNameProvider: Provider, 27 | pathResolver: (configurationName: String) -> String, 28 | ): Provider { 29 | return configurationNameProvider.flatMap { configurationName -> 30 | project.layout.buildDirectory.map { it.dir(pathResolver(configurationName)) } 31 | } 32 | } 33 | 34 | internal fun TargetSourceSetsTask.createSubmissionInfoFileProperty( 35 | configurationNameProvider: Provider, 36 | ): RegularFileProperty { 37 | return project.objects.fileProperty() 38 | .value(resolveBuildFile(configurationNameProvider) { "resources/jagr/$it/submission-info.json" }) 39 | } 40 | 41 | internal fun TargetSourceSetsTask.createGraderInfoFileProperty(): RegularFileProperty { 42 | return project.objects.fileProperty() 43 | .value(resolveBuildFile(configurationName) { "resources/jagr/$it/grader-info.json" }) 44 | } 45 | 46 | internal fun TargetSourceSetsTask.createRubricOutputDirectoryProperty(): DirectoryProperty { 47 | return project.objects.directoryProperty() 48 | .value(resolveBuildDirectory(configurationName) { "resources/jagr/$it/rubric" }) 49 | } 50 | -------------------------------------------------------------------------------- /launcher/gradle-plugin/src/main/kotlin/org/sourcegrade/jagr/gradle/extension/SubmissionConfiguration.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.gradle.extension 21 | 22 | import org.gradle.api.Project 23 | import org.gradle.api.provider.Property 24 | import org.gradle.kotlin.dsl.property 25 | 26 | abstract class SubmissionConfiguration( 27 | name: String, 28 | project: Project, 29 | ) : AbstractConfiguration(name, project, setOf("main", "test")) { 30 | abstract val studentId: Property 31 | abstract val firstName: Property 32 | abstract val lastName: Property 33 | val checkCompilation: Property = project.objects.property().convention(true) 34 | 35 | init { 36 | project.afterEvaluate { 37 | configureProject() 38 | } 39 | } 40 | 41 | fun skipCompilationCheck() { 42 | checkCompilation.set(false) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /launcher/gradle-plugin/src/main/kotlin/org/sourcegrade/jagr/gradle/task/JagrDownloadTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.gradle.task 21 | 22 | import de.undercouch.gradle.tasks.download.Download 23 | import org.gradle.api.provider.Property 24 | import org.gradle.api.tasks.Input 25 | import java.nio.file.Path 26 | import kotlin.io.path.Path 27 | import kotlin.io.path.createDirectories 28 | import kotlin.io.path.pathString 29 | 30 | @Suppress("LeakingThis") 31 | abstract class JagrDownloadTask : Download() { 32 | 33 | @get:Input 34 | abstract val sourceUrl: Property 35 | 36 | @get:Input 37 | abstract val destName: Property 38 | 39 | init { 40 | group = "jagr resources" 41 | src(sourceUrl) 42 | dest(destName.map { JAGR_CACHE.resolve(it).pathString }) 43 | overwrite(false) 44 | } 45 | 46 | companion object { 47 | internal val JAGR_HOME: Path = Path(System.getProperty("user.home")).resolve(".jagr").createDirectories() 48 | internal val JAGR_CACHE: Path = JAGR_HOME.resolve("cache").createDirectories() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /launcher/gradle-plugin/src/main/kotlin/org/sourcegrade/jagr/gradle/task/JagrTaskFactory.kt: -------------------------------------------------------------------------------- 1 | package org.sourcegrade.jagr.gradle.task 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.api.Task 5 | import org.sourcegrade.jagr.gradle.extension.AbstractConfiguration 6 | 7 | internal interface JagrTaskFactory { 8 | fun determineTaskName(name: String): String 9 | fun configureTask(task: T, project: Project, configuration: C) 10 | } 11 | -------------------------------------------------------------------------------- /launcher/gradle-plugin/src/main/kotlin/org/sourcegrade/jagr/gradle/task/TargetAssignmentTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.gradle.task 21 | 22 | import org.gradle.api.Task 23 | import org.gradle.api.provider.Property 24 | import org.gradle.api.tasks.Input 25 | 26 | interface TargetAssignmentTask : Task { 27 | 28 | @get:Input 29 | val assignmentId: Property 30 | } 31 | -------------------------------------------------------------------------------- /launcher/gradle-plugin/src/main/kotlin/org/sourcegrade/jagr/gradle/task/TargetSourceSetsTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.gradle.task 21 | 22 | import org.gradle.api.Task 23 | import org.gradle.api.provider.Property 24 | import org.gradle.api.provider.SetProperty 25 | import org.gradle.api.tasks.Input 26 | import org.sourcegrade.jagr.gradle.extension.ProjectSourceSetTuple 27 | 28 | interface TargetSourceSetsTask : Task { 29 | 30 | @get:Input 31 | val configurationName: Property 32 | 33 | @get:Input 34 | val sourceSetNames: SetProperty 35 | } 36 | -------------------------------------------------------------------------------- /launcher/gradle-plugin/src/main/kotlin/org/sourcegrade/jagr/gradle/task/WriteInfoTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.gradle.task 21 | 22 | import org.gradle.api.DefaultTask 23 | import org.gradle.api.artifacts.dsl.RepositoryHandler 24 | import org.gradle.api.artifacts.repositories.MavenArtifactRepository 25 | import org.gradle.api.internal.GradleInternal 26 | import org.gradle.api.provider.ListProperty 27 | import org.gradle.api.provider.Provider 28 | import org.gradle.api.tasks.Input 29 | import org.gradle.kotlin.dsl.listProperty 30 | import org.sourcegrade.jagr.gradle.extension.SubmissionConfiguration 31 | 32 | abstract class WriteInfoTask : DefaultTask(), TargetSourceSetsTask { 33 | 34 | @get:Input 35 | @Suppress("UnstableApiUsage") 36 | val repositories: ListProperty> = project.objects.listProperty>().value( 37 | (project.gradle as GradleInternal).settings.dependencyResolutionManagement.repositories.mapToPairs() + 38 | project.repositories.mapToPairs(), 39 | ) 40 | 41 | private fun RepositoryHandler.mapToPairs(): List> = 42 | filterIsInstance().map { it.name to it.url.toString() } 43 | 44 | protected fun configureSubmissionCompilationDependency(submissionContainerProvider: Provider) { 45 | dependsOn( 46 | submissionContainerProvider 47 | .let { provider -> provider.zip(provider.flatMap { it.checkCompilation }, ::Pair) } 48 | .map { (configuration, checkCompilation) -> 49 | if (checkCompilation) configuration.getCompileJavaTaskNames() else emptyList() 50 | }, 51 | ) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /launcher/gradle-plugin/src/main/kotlin/org/sourcegrade/jagr/gradle/task/grader/GraderBuildTask.kt: -------------------------------------------------------------------------------- 1 | package org.sourcegrade.jagr.gradle.task.grader 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.api.file.DuplicatesStrategy 5 | import org.gradle.api.file.RegularFileProperty 6 | import org.gradle.api.file.SourceDirectorySet 7 | import org.gradle.api.tasks.InputFile 8 | import org.gradle.jvm.tasks.Jar 9 | import org.gradle.kotlin.dsl.get 10 | import org.gradle.kotlin.dsl.getByType 11 | import org.sourcegrade.jagr.gradle.extension.GraderConfiguration 12 | import org.sourcegrade.jagr.gradle.extension.JagrExtension 13 | import org.sourcegrade.jagr.gradle.extension.createGraderInfoFileProperty 14 | import org.sourcegrade.jagr.gradle.task.JagrTaskFactory 15 | 16 | @Suppress("LeakingThis") 17 | abstract class GraderBuildTask : Jar(), GraderTask { 18 | 19 | @get:InputFile 20 | val graderInfoFile: RegularFileProperty = createGraderInfoFileProperty() 21 | 22 | init { 23 | group = "build" 24 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 25 | dependsOn(configurationName.map(GraderWriteInfoTask.Factory::determineTaskName)) 26 | archiveFileName.set(graderName.map { "$it-${project.version}.jar" }) 27 | from(graderInfoFile) 28 | from(configurationName.map { project.extensions.getByType().graders[it].getSourceDirectoriesRecursive() }) 29 | } 30 | 31 | private fun GraderConfiguration.getSourceDirectoriesRecursive(): List { 32 | val result = mutableListOf() 33 | sourceSets.forEach { result.add(it.allSource) } 34 | // technically this is a race condition, but we can't use Provider.zip because the value is not always configured 35 | if (solutionConfiguration.isPresent) { 36 | solutionConfiguration.get().sourceSets.asSequence() 37 | .map { it.allSource } 38 | .forEach { result.add(it) } 39 | } 40 | if (parentConfiguration.isPresent) { 41 | result.addAll(parentConfiguration.get().getSourceDirectoriesRecursive()) 42 | } 43 | return result 44 | } 45 | 46 | internal object Factory : JagrTaskFactory { 47 | override fun determineTaskName(name: String) = "${name}BuildGrader" 48 | override fun configureTask(task: GraderBuildTask, project: Project, configuration: GraderConfiguration) { 49 | task.description = "Builds the grader jar for ${task.sourceSetNames.get()}" 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /launcher/gradle-plugin/src/main/kotlin/org/sourcegrade/jagr/gradle/task/grader/GradleLaunchConfiguration.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2025 Alexander Städing 4 | * Copyright (C) 2021-2025 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.gradle.task.grader 21 | 22 | import org.apache.logging.log4j.Logger 23 | import org.apache.logging.log4j.core.config.Configurator 24 | import org.sourcegrade.jagr.launcher.env.Config 25 | import org.sourcegrade.jagr.launcher.env.LaunchConfiguration 26 | import org.sourcegrade.jagr.launcher.executor.RuntimeInvoker 27 | import org.sourcegrade.jagr.launcher.executor.RuntimeJarInvoker 28 | import java.nio.file.Path 29 | 30 | internal class GradleLaunchConfiguration( 31 | override val config: Config, 32 | jagrJar: Path, 33 | ) : LaunchConfiguration { 34 | override val logger: Logger by lazy { 35 | Configurator.initialize( 36 | "console-only", 37 | "log4j2-console-only.xml", 38 | ).getLogger("jagr") 39 | } 40 | 41 | override val runtimeInvokerFactory: RuntimeInvoker.Factory = RuntimeInvoker.Factory { jagr -> 42 | RuntimeJarInvoker(jagr, jagrJar, config.executor.jvmArgs) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /launcher/gradle-plugin/src/main/kotlin/org/sourcegrade/jagr/gradle/task/submission/SubmissionBuildTask.kt: -------------------------------------------------------------------------------- 1 | package org.sourcegrade.jagr.gradle.task.submission 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.api.file.RegularFileProperty 5 | import org.gradle.api.tasks.InputFile 6 | import org.gradle.jvm.tasks.Jar 7 | import org.sourcegrade.jagr.gradle.extension.SubmissionConfiguration 8 | import org.sourcegrade.jagr.gradle.extension.createSubmissionInfoFileProperty 9 | import org.sourcegrade.jagr.gradle.extension.getSourceSet 10 | import org.sourcegrade.jagr.gradle.task.JagrTaskFactory 11 | 12 | @Suppress("LeakingThis") 13 | abstract class SubmissionBuildTask : Jar(), SubmissionTask { 14 | 15 | @get:InputFile 16 | val submissionInfoFile: RegularFileProperty = createSubmissionInfoFileProperty(configurationName) 17 | 18 | init { 19 | group = "build" 20 | dependsOn(configurationName.map(SubmissionWriteInfoTask.Factory::determineTaskName)) 21 | from(submissionInfoFile) 22 | from(sourceSetNames.map { all -> all.map { it.getSourceSet(project).allSource } }) 23 | archiveFileName.set( 24 | assignmentId.zip(studentId) { assignmentId, studentId -> 25 | "$assignmentId-$studentId" 26 | }.zip(firstName) { left, firstName -> 27 | "$left-$firstName" 28 | }.zip(lastName) { left, lastName -> 29 | "$left-$lastName-submission" 30 | }.zip(archiveExtension) { left, extension -> 31 | "$left.$extension" 32 | }, 33 | ) 34 | } 35 | 36 | internal object Factory : JagrTaskFactory { 37 | override fun determineTaskName(name: String) = "${name}BuildSubmission" 38 | 39 | override fun configureTask(task: SubmissionBuildTask, project: Project, configuration: SubmissionConfiguration) { 40 | task.description = "Builds the submission for ${configuration.name}" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/env/CopyConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2023 Alexander Städing 4 | * Copyright (C) 2021-2023 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.env 21 | 22 | fun Config.copyDir(block: Dir.() -> Dir) = copy(dir = block(dir)) 23 | 24 | fun Config.copyExecutor(block: Executor.() -> Executor) = copy(executor = block(executor)) 25 | 26 | fun Config.copyExtras(block: Extras.() -> Extras) = copy(extras = block(extras)) 27 | fun Extras.copyMoodleUnpack(block: Extras.MoodleUnpack.() -> Extras.MoodleUnpack) = copy(moodleUnpack = block(moodleUnpack)) 28 | 29 | fun Config.copyTransformers(block: Transformers.() -> Transformers) = copy(transformers = block(transformers)) 30 | fun Transformers.copyTimeout(block: Transformers.TimeoutTransformer.() -> Transformers.TimeoutTransformer) = copy(timeout = block(timeout)) 31 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/env/Environment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.env 21 | 22 | import org.jetbrains.annotations.ApiStatus 23 | import org.sourcegrade.jagr.launcher.io.ProgressAwareOutputStream 24 | import java.io.OutputStream 25 | import java.io.PrintStream 26 | 27 | @ApiStatus.Internal 28 | object Environment { 29 | private val actualStdOut = System.out 30 | var stdOut: PrintStream = actualStdOut 31 | private set 32 | 33 | fun initializeChildProcess() { 34 | Jagr.logger 35 | setWasteBasket() 36 | } 37 | 38 | fun initializeMainProcess() { 39 | stdOut = PrintStream(ProgressAwareOutputStream(stdOut)) 40 | System.setOut(stdOut) 41 | Jagr.logger 42 | setWasteBasket() 43 | } 44 | 45 | private fun setWasteBasket() { 46 | val wasteBasket = PrintStream(OutputStream.nullOutputStream()) 47 | System.setOut(wasteBasket) 48 | System.setErr(wasteBasket) 49 | } 50 | 51 | fun cleanupMainProcess() { 52 | stdOut = actualStdOut 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/env/JagrImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.env 21 | 22 | import com.google.inject.Guice 23 | import com.google.inject.Injector 24 | import kotlinx.serialization.Serializable 25 | import kotlin.reflect.full.primaryConstructor 26 | 27 | internal class DeferredJagr(val json: JagrJson, val configuration: LaunchConfiguration) : Jagr { 28 | override val injector: Injector by lazy { 29 | val modules = json.moduleFactories.map { 30 | coerceClass(it).kotlin.run { 31 | (objectInstance ?: primaryConstructor!!.call()).create(configuration) 32 | } 33 | }.toTypedArray() 34 | Guice.createInjector(*modules) 35 | } 36 | 37 | override fun toString(): String = "DeferredJagr" 38 | } 39 | 40 | @Serializable 41 | internal data class JagrJson( 42 | /** 43 | * guice modules to bind 44 | */ 45 | val moduleFactories: List, 46 | ) 47 | 48 | private inline fun coerceClass(implementation: String): Class { 49 | return Class.forName(implementation).asSubclass(T::class.java) 50 | } 51 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/env/ModuleFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.env 21 | 22 | import com.google.inject.Module 23 | 24 | fun interface ModuleFactory { 25 | fun create(configuration: LaunchConfiguration): Module 26 | } 27 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/env/SystemResourceJagrFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2025 Alexander Städing 4 | * Copyright (C) 2021-2025 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.env 21 | 22 | import kotlinx.serialization.json.Json 23 | 24 | object SystemResourceJagrFactory : Jagr.Factory { 25 | private const val RESOURCE_NAME = "/jagr.json" 26 | 27 | override fun create(configuration: LaunchConfiguration): Jagr { 28 | javaClass.getResourceAsStream(RESOURCE_NAME).use { 29 | checkNotNull(it) { "Could not find resource $RESOURCE_NAME" } 30 | val json = Json.decodeFromString(it.bufferedReader().readText()) 31 | return DeferredJagr(json, configuration) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/executor/Executor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.executor 21 | 22 | import org.sourcegrade.jagr.launcher.env.Jagr 23 | 24 | interface Executor { 25 | suspend fun schedule(queue: GradingQueue) 26 | suspend fun start(rubricCollector: MutableRubricCollector) 27 | interface Factory { 28 | fun create(jagr: Jagr): Executor 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/executor/GradingJob.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.executor 21 | 22 | import kotlinx.coroutines.CompletableDeferred 23 | 24 | data class GradingJob(val request: GradingRequest) { 25 | val result: CompletableDeferred = CompletableDeferred() 26 | } 27 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/executor/GradingRequest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.executor 21 | 22 | import org.sourcegrade.jagr.api.testing.Submission 23 | import org.sourcegrade.jagr.launcher.io.GraderJar 24 | 25 | interface GradingRequest { 26 | val submission: Submission 27 | 28 | val graders: List 29 | } 30 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/executor/GradingResult.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.executor 21 | 22 | import org.sourcegrade.jagr.api.rubric.GradedRubric 23 | import org.sourcegrade.jagr.launcher.io.SerializationScope 24 | import org.sourcegrade.jagr.launcher.io.SerializerFactory 25 | import org.sourcegrade.jagr.launcher.io.get 26 | import org.sourcegrade.jagr.launcher.io.readInstant 27 | import org.sourcegrade.jagr.launcher.io.readMap 28 | import org.sourcegrade.jagr.launcher.io.writeInstant 29 | import org.sourcegrade.jagr.launcher.io.writeMap 30 | import java.time.Instant 31 | 32 | data class GradingResult( 33 | val startedUtc: Instant, 34 | val finishedUtc: Instant, 35 | val request: GradingRequest, 36 | val rubrics: Map, 37 | ) { 38 | companion object Factory : SerializerFactory { 39 | override fun read(scope: SerializationScope.Input) = GradingResult( 40 | scope.input.readInstant(), 41 | scope.input.readInstant(), 42 | scope[GradingRequest::class], 43 | scope.readMap(), 44 | ) 45 | 46 | override fun write(obj: GradingResult, scope: SerializationScope.Output) { 47 | scope.output.writeInstant(obj.startedUtc) 48 | scope.output.writeInstant(obj.finishedUtc) 49 | // skip request because parent process already has it 50 | scope.writeMap(obj.rubrics) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/executor/MutableRubricCollector.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.executor 21 | 22 | import org.sourcegrade.jagr.launcher.env.Jagr 23 | 24 | sealed interface MutableRubricCollector : RubricCollector { 25 | suspend fun allocate(queue: GradingQueue) 26 | suspend fun start(request: GradingRequest): GradingJob 27 | suspend fun startBlock(block: suspend (StartBlock) -> T): T 28 | 29 | interface StartBlock { 30 | fun start(request: GradingRequest): GradingJob 31 | } 32 | } 33 | 34 | fun emptyCollector(jagr: Jagr = Jagr): MutableRubricCollector = RubricCollectorImpl(jagr) 35 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/executor/ProgressBarProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.executor 21 | 22 | fun interface ProgressBarProvider { 23 | 24 | fun transformProgressBar(sb: StringBuilder): StringBuilder 25 | 26 | companion object { 27 | const val BAR_CHAR = '=' 28 | const val TIP_CHAR = '>' 29 | const val INNER_WIDTH = 50 30 | const val MAX_WIDTH = 120 31 | val CLEAR_TEXT = " ".repeat(MAX_WIDTH) + '\r' 32 | } 33 | 34 | object Default : ProgressBarProvider { 35 | override fun transformProgressBar(sb: StringBuilder): StringBuilder = sb 36 | } 37 | } 38 | 39 | fun createProgressBarProvider(name: String?): ProgressBarProvider = when (name) { 40 | "rainbow" -> RotationProgressBar.Rainbow() 41 | "xmas" -> RotationProgressBar.XMas() 42 | null -> ProgressBarProvider.Default 43 | else -> error("Could not find progress bar provider $name") 44 | } 45 | 46 | fun ProgressBarProvider.createProgressBar(progressDecimal: Double): String = 47 | transformProgressBar(createBasicProgressBar(progressDecimal)).toString() 48 | 49 | private fun createBasicProgressBar(progressDecimal: Double): StringBuilder { 50 | val barCount = (ProgressBarProvider.INNER_WIDTH * progressDecimal).toInt() 51 | val sb = StringBuilder(30) 52 | for (i in 0 until barCount) { 53 | sb.append(ProgressBarProvider.BAR_CHAR) 54 | } 55 | if (progressDecimal < 1.0) { 56 | sb.append(ProgressBarProvider.TIP_CHAR) 57 | } 58 | return sb 59 | } 60 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/executor/RotationProgressBar.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.executor 21 | 22 | open class RotationProgressBar(private vararg val rotationColors: String) : ProgressBarProvider { 23 | 24 | init { 25 | require(rotationColors.isNotEmpty()) { "rotationColors may not be empty" } 26 | } 27 | 28 | private val reset = "\u001b[0m" 29 | 30 | private var startIndex = 0 31 | 32 | override fun transformProgressBar(sb: StringBuilder): StringBuilder { 33 | val tmp = StringBuilder(6 * sb.length) 34 | for ((i, elem) in sb.withIndex()) { 35 | tmp.append(rotationColors[(i + rotationColors.size - startIndex) % rotationColors.size]) 36 | tmp.append(elem) 37 | } 38 | tmp.append(reset) 39 | startIndex = (startIndex + 1) % rotationColors.size 40 | return tmp 41 | } 42 | 43 | // red, purple, blue, cyan, green, yellow 44 | class Rainbow : RotationProgressBar("\u001b[31m", "\u001B[35m", "\u001B[34m", "\u001B[36m", "\u001B[32m", "\u001B[33m") 45 | 46 | // red, green 47 | class XMas : RotationProgressBar("\u001b[31m", "\u001B[32m") 48 | } 49 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/executor/RubricCollector.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.executor 21 | 22 | sealed interface RubricCollector { 23 | suspend fun withGradingScheduled(block: suspend (List) -> T): T 24 | suspend fun withGradingRunning(block: suspend (List) -> T): T 25 | suspend fun withGradingFinished(block: suspend (List) -> T): T 26 | suspend fun setListener(listener: (GradingResult) -> Unit) 27 | suspend fun getTotal(): Int 28 | suspend fun getRemaining(): Int 29 | suspend fun toSnapshot(): Snapshot 30 | suspend fun withSnapshot(block: suspend (Snapshot) -> T): T 31 | 32 | data class Snapshot( 33 | val gradingScheduled: List, 34 | val gradingRunning: List, 35 | val gradingFinished: List, 36 | val total: Int, 37 | val remaining: Int, 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/executor/RuntimeInvoker.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2025 Alexander Städing 4 | * Copyright (C) 2021-2025 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.executor 21 | 22 | import org.apache.logging.log4j.Logger 23 | 24 | interface RuntimeInvoker { 25 | // TODO: For now use Process, later use a custom interface to abstract communication 26 | fun createRuntime(): Process 27 | 28 | fun interface Factory { 29 | fun create(logger: Logger): RuntimeInvoker 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/executor/RuntimeJarInvoker.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2025 Alexander Städing 4 | * Copyright (C) 2021-2025 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.executor 21 | 22 | import org.apache.logging.log4j.Logger 23 | import java.nio.file.Path 24 | import java.nio.file.Paths 25 | import kotlin.io.path.pathString 26 | 27 | class RuntimeJarInvoker( 28 | private val logger: Logger, 29 | private val jagrLocation: Path = Paths.get(RuntimeJarInvoker::class.java.protectionDomain.codeSource.location.toURI()), 30 | private val jvmArgs: List = emptyList(), 31 | ) : RuntimeInvoker { 32 | 33 | private val commands: List = buildList(5 + jvmArgs.size) { 34 | add("java") 35 | add("-Dlog4j.configurationFile=log4j2-child.xml") 36 | addAll(jvmArgs) 37 | add("-jar") 38 | add(jagrLocation.pathString) 39 | add("--child") 40 | } 41 | 42 | override fun createRuntime(): Process { 43 | logger.info("Starting process $commands") 44 | return ProcessBuilder().command(commands).start() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/executor/SyncExecutor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.executor 21 | 22 | import kotlinx.coroutines.sync.Mutex 23 | import kotlinx.coroutines.sync.withLock 24 | import org.sourcegrade.jagr.launcher.env.Jagr 25 | 26 | class SyncExecutor constructor( 27 | private val jagr: Jagr, 28 | ) : Executor { 29 | private val mutex = Mutex() 30 | private val scheduled = mutableListOf() 31 | 32 | override suspend fun schedule(queue: GradingQueue) = mutex.withLock { 33 | scheduled += queue 34 | } 35 | 36 | override suspend fun start(rubricCollector: MutableRubricCollector) = mutex.withLock { 37 | while (scheduled.isNotEmpty()) { 38 | val next = scheduled.next() ?: break 39 | rubricCollector.start(next).gradeCatching(jagr) 40 | } 41 | scheduled.clear() 42 | } 43 | 44 | object Factory : Executor.Factory { 45 | override fun create(jagr: Jagr): Executor = SyncExecutor(jagr) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/executor/ThreadWorker.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.executor 21 | 22 | import kotlinx.coroutines.runBlocking 23 | import org.sourcegrade.jagr.launcher.env.Jagr 24 | import kotlin.concurrent.thread 25 | 26 | class ThreadWorker( 27 | private val jagr: Jagr, 28 | private val removeActive: suspend (Worker) -> Unit, 29 | ) : Worker { 30 | override var job: GradingJob? = null 31 | override var status: WorkerStatus = WorkerStatus.READY 32 | override var userTime: Long = 0 33 | 34 | private lateinit var thread: Thread 35 | 36 | override fun assignJob(job: GradingJob) { 37 | check(this.job == null) { "Worker already has a job!" } 38 | status = WorkerStatus.RUNNING 39 | this.job = job 40 | thread( 41 | isDaemon = true, 42 | name = GRADING_THREAD_PREFIX + job.request.submission.info.toString(), 43 | priority = 3, 44 | ) { 45 | runBlocking { 46 | job.gradeCatching(jagr) 47 | status = WorkerStatus.FINISHED 48 | removeActive(this@ThreadWorker) 49 | } 50 | } 51 | } 52 | 53 | override fun kill() { 54 | thread.stop() 55 | runBlocking { 56 | removeActive(this@ThreadWorker) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/executor/Worker.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.executor 21 | 22 | /** 23 | * A worker is essentially a grading-specialized [Runnable] that provides 24 | * 25 | * A worker may only be used once nad u 26 | */ 27 | interface Worker { 28 | val job: GradingJob? 29 | val status: WorkerStatus 30 | val userTime: Long // TODO: maybe elapsedTime instead? 31 | 32 | fun assignJob(job: GradingJob) 33 | 34 | /** 35 | * Attempts to kill this worker and stop grading this job to free up resources. 36 | * 37 | * The goal of this method is to free up system resources from grading futile submissions. While a worker is normally able to 38 | * stop properly, there are unfortunately cases where this will not work. 39 | */ 40 | fun kill() 41 | } 42 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/executor/WorkerPool.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.executor 21 | 22 | import org.sourcegrade.jagr.launcher.env.Jagr 23 | import java.io.Closeable 24 | 25 | /** 26 | * A resource which may, or may not give you another [Worker] depending on available resources. 27 | */ 28 | interface WorkerPool : Closeable { 29 | 30 | suspend fun withActiveWorkers(block: suspend (List) -> T): T 31 | 32 | /** 33 | * Creates up to [maxCount] workers depending on availability. 34 | */ 35 | suspend fun createWorkers(maxCount: Int): List 36 | 37 | /** 38 | * Closes this [WorkerPool] and any resources associated with it. (e.g. Extra monitor/IO threads) 39 | */ 40 | override fun close() 41 | 42 | interface Factory { 43 | fun create(jagr: Jagr): WorkerPool 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/executor/WorkerStatus.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.executor 21 | 22 | enum class WorkerStatus { 23 | /** 24 | * Not ready to receive job 25 | */ 26 | PREPARING, 27 | 28 | /** 29 | * Ready to receive job 30 | */ 31 | READY, 32 | 33 | /** 34 | * Processing a job 35 | */ 36 | RUNNING, 37 | 38 | /** 39 | * Finished processing a job 40 | */ 41 | FINISHED, 42 | } 43 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/io/AbstractSerializationScope.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.io 21 | 22 | import com.google.inject.Key 23 | 24 | internal abstract class AbstractSerializationScope : SerializationScope { 25 | abstract val parent: SerializationScope? 26 | protected val backing = mutableMapOf, Any>() 27 | private val proxy = mutableMapOf, SerializationScope.Key<*>>() 28 | private fun getProxy(key: SerializationScope.Key): SerializationScope.Key? { 29 | return proxy[key] as SerializationScope.Key? 30 | } 31 | 32 | private fun getInjected(key: SerializationScope.Key): T? { 33 | return if (key.name == null) { 34 | jagr.injector.getExistingBinding(Key.get(key.type.java))?.run { provider.get() } 35 | } else { 36 | null 37 | } 38 | } 39 | 40 | override fun get(key: SerializationScope.Key): T { 41 | val fromBacking = backing[key] 42 | return if (fromBacking == null) { 43 | parent?.getOrNull(key) 44 | ?: getInjected(key) 45 | ?: getProxy(key)?.let { get(it) } 46 | ?: error("Key $key not found in scope") 47 | } else { 48 | fromBacking as T 49 | } 50 | } 51 | 52 | override fun getOrNull(key: SerializationScope.Key): T? { 53 | return backing[key] as T? 54 | } 55 | 56 | override fun set(key: SerializationScope.Key, obj: T) { 57 | backing[key] = obj 58 | } 59 | 60 | override fun proxy(key: SerializationScope.Key, from: SerializationScope.Key) { 61 | proxy[key] = from 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/io/AssignmentArtifactInfo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.io 21 | 22 | interface AssignmentArtifactInfo { 23 | val assignmentId: String 24 | val jagrVersion: String 25 | val sourceSets: List 26 | val dependencyConfigurations: Map> 27 | val repositoryConfigurations: List 28 | } 29 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/io/DataUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package org.sourcegrade.jagr.launcher.io 20 | 21 | import com.google.common.io.ByteArrayDataInput 22 | import com.google.common.io.ByteArrayDataOutput 23 | import java.time.Instant 24 | import kotlin.reflect.KClass 25 | 26 | fun ByteArrayDataInput.readKClass(): KClass = Class.forName(readUTF()).kotlin as KClass 27 | 28 | fun ByteArrayDataOutput.writeKClass(type: KClass<*>) { 29 | writeUTF(requireNotNull(type.qualifiedName) { "$type must have a qualified name" }) 30 | } 31 | 32 | fun ByteArrayDataInput.readByteArray(): ByteArray = ByteArray(readInt()) { readByte() } 33 | 34 | fun ByteArrayDataOutput.writeByteArray(array: ByteArray) { 35 | writeInt(array.size) 36 | write(array) 37 | } 38 | 39 | fun ByteArrayDataInput.readInstant(): Instant = Instant.ofEpochSecond(readLong(), readInt().toLong()) 40 | 41 | fun ByteArrayDataOutput.writeInstant(instant: Instant) { 42 | writeLong(instant.epochSecond) 43 | writeInt(instant.nano) 44 | } 45 | 46 | fun ByteArrayDataInput.readNull(): Boolean = readByte() == 0.toByte() 47 | 48 | fun ByteArrayDataOutput.writeNull() { 49 | writeByte(0) 50 | } 51 | 52 | fun ByteArrayDataOutput.writeNotNull() { 53 | writeByte(1) 54 | } 55 | 56 | /** 57 | * Reads in a string that is longer than 65535 characters (the maximum supported by readUTF). 58 | * 59 | * readUTF and writeUTF allocate the first 2 bytes for the length of the string, whereas this method allocates 4 bytes. 60 | */ 61 | fun ByteArrayDataInput.readText(): String = readByteArray().decodeToString() 62 | fun ByteArrayDataOutput.writeText(text: String) = writeByteArray(text.toByteArray()) 63 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/io/ExtrasManager.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.io 21 | 22 | interface ExtrasManager { 23 | fun runExtras() 24 | } 25 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/io/GradedRubricExporter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.io 21 | 22 | import org.sourcegrade.jagr.api.rubric.GradedRubric 23 | 24 | interface GradedRubricExporter { 25 | fun export(gradedRubric: GradedRubric): Resource 26 | interface CSV : GradedRubricExporter 27 | interface HTML : GradedRubricExporter 28 | interface Moodle : GradedRubricExporter 29 | } 30 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/io/GraderJar.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.io 21 | 22 | import org.sourcegrade.jagr.api.testing.RubricConfiguration 23 | 24 | interface GraderJar { 25 | val info: GraderInfo 26 | val configuration: RubricConfiguration 27 | val testClassNames: List 28 | } 29 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/io/InputSerializationScopeImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.io 21 | 22 | import com.google.common.io.ByteArrayDataInput 23 | import org.sourcegrade.jagr.launcher.env.Jagr 24 | import org.sourcegrade.jagr.launcher.env.serializerFactoryLocator 25 | 26 | fun createScope(input: ByteArrayDataInput, jagr: Jagr, parent: SerializationScope.Input? = null): SerializationScope.Input { 27 | return InputSerializationScopeImpl(input, jagr, parent) 28 | } 29 | 30 | inline fun openScope(input: ByteArrayDataInput, jagr: Jagr, block: SerializationScope.Input.() -> T): T { 31 | return createScope(input, jagr).block() 32 | } 33 | 34 | inline fun SerializationScope.Input.openScope(block: SerializationScope.Input.() -> T): T { 35 | return createScope(input, jagr, this).block() 36 | } 37 | 38 | private class InputSerializationScopeImpl( 39 | override val input: ByteArrayDataInput, 40 | override val jagr: Jagr, 41 | override val parent: SerializationScope?, 42 | ) : AbstractSerializationScope(), SerializationScope.Input { 43 | override fun readScoped(key: SerializationScope.Key): T { 44 | val obj = SerializerFactory[key.type, jagr.serializerFactoryLocator].read(this) 45 | backing[key] = obj 46 | return obj 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/io/OutputSerializationScopeImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.io 21 | 22 | import com.google.common.io.ByteArrayDataOutput 23 | import org.sourcegrade.jagr.launcher.env.Jagr 24 | import org.sourcegrade.jagr.launcher.env.serializerFactoryLocator 25 | 26 | fun createScope(output: ByteArrayDataOutput, jagr: Jagr, parent: SerializationScope.Output? = null): SerializationScope.Output { 27 | return OutputSerializationScopeImpl(output, jagr, parent) 28 | } 29 | 30 | inline fun openScope(output: ByteArrayDataOutput, jagr: Jagr, block: SerializationScope.Output.() -> T): T { 31 | return createScope(output, jagr).block() 32 | } 33 | 34 | inline fun SerializationScope.Output.openScope(block: SerializationScope.Output.() -> T): T { 35 | return createScope(output, jagr, this).block() 36 | } 37 | 38 | private class OutputSerializationScopeImpl( 39 | override val output: ByteArrayDataOutput, 40 | override val jagr: Jagr, 41 | override val parent: SerializationScope.Output?, 42 | ) : AbstractSerializationScope(), SerializationScope.Output { 43 | override fun writeScoped(obj: T, key: SerializationScope.Key) { 44 | SerializerFactory[key.type, jagr.serializerFactoryLocator].write(obj, this) 45 | backing[key] = obj 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/io/ProgressAwareOutputStream.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.io 21 | 22 | import kotlinx.coroutines.runBlocking 23 | import org.sourcegrade.jagr.launcher.executor.ProgressBar 24 | import java.io.OutputStream 25 | import java.io.PrintStream 26 | import java.util.concurrent.locks.ReentrantLock 27 | import kotlin.concurrent.withLock 28 | 29 | class ProgressAwareOutputStream(private val delegate: PrintStream) : OutputStream() { 30 | 31 | private val lock = ReentrantLock() 32 | 33 | override fun write(b: Int) = lock.withLock { 34 | progressBar?.let { writeWithProgress(it, b) } ?: delegate.write(b) 35 | } 36 | 37 | private fun writeWithProgress(progressBar: ProgressBar, b: Int) { 38 | delegate.write(b) 39 | if (enabled && b == newLine) { 40 | runBlocking { 41 | progressBar.print(delegate) 42 | } 43 | } 44 | } 45 | 46 | companion object { 47 | const val newLine = '\n'.code 48 | var progressBar: ProgressBar? = null 49 | var enabled = true 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/io/RepositoryConfiguration.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.io 21 | 22 | import kotlinx.serialization.Serializable 23 | 24 | @Serializable 25 | data class RepositoryConfiguration( 26 | val name: String, 27 | val url: String, 28 | ) { 29 | 30 | companion object Factory : SerializerFactory { 31 | override fun read(scope: SerializationScope.Input) = RepositoryConfiguration( 32 | scope.input.readUTF(), 33 | scope.input.readUTF(), 34 | ) 35 | 36 | override fun write(obj: RepositoryConfiguration, scope: SerializationScope.Output) { 37 | scope.output.writeUTF(obj.name) 38 | scope.output.writeUTF(obj.url) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/io/Resource.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.io 21 | 22 | import java.io.ByteArrayInputStream 23 | import java.io.ByteArrayOutputStream 24 | import java.io.File 25 | import java.io.InputStream 26 | 27 | interface Resource { 28 | val name: String 29 | val size: Int 30 | fun getInputStream(): InputStream 31 | interface Builder { 32 | var name: String 33 | val outputStream: ByteArrayOutputStream 34 | fun build(): Resource 35 | } 36 | } 37 | 38 | inline fun buildResource(configure: Resource.Builder.() -> Unit): Resource = createResourceBuilder().apply(configure).build() 39 | 40 | fun createResourceBuilder(): Resource.Builder = ResourceBuilderImpl() 41 | 42 | fun Resource.writeIn(dir: File, name: String? = null): File { 43 | val file = dir.resolve(name ?: this.name) 44 | with(file.parentFile) { 45 | check(exists() || mkdirs()) { "Unable to create directory $this" } 46 | } 47 | file.outputStream().buffered().use { getInputStream().copyTo(it) } 48 | return file 49 | } 50 | 51 | private class ResourceBuilderImpl : Resource.Builder { 52 | override lateinit var name: String 53 | override val outputStream = ReadableByteArrayOutputStream() 54 | override fun build(): Resource = outputStream.toResource(name) 55 | } 56 | 57 | private class ReadableByteArrayOutputStream : ByteArrayOutputStream() { 58 | fun toResource(name: String): Resource = ByteArrayResource(name, buf, count) 59 | } 60 | 61 | internal class ByteArrayResource( 62 | override val name: String, 63 | private val buf: ByteArray, 64 | override val size: Int = buf.size, 65 | ) : Resource { 66 | override fun getInputStream(): InputStream = ByteArrayInputStream(buf, 0, size) 67 | } 68 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/io/SafeStringSerializer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | @file:UseSerializers(serializerClasses = [SafeStringSerializer::class]) 20 | 21 | package org.sourcegrade.jagr.launcher.io 22 | 23 | import kotlinx.serialization.KSerializer 24 | import kotlinx.serialization.UseSerializers 25 | import kotlinx.serialization.descriptors.PrimitiveKind 26 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor 27 | import kotlinx.serialization.descriptors.SerialDescriptor 28 | import kotlinx.serialization.encoding.Decoder 29 | import kotlinx.serialization.encoding.Encoder 30 | 31 | object SafeStringSerializer : KSerializer { 32 | override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("SafeString", PrimitiveKind.STRING) 33 | override fun deserialize(decoder: Decoder): String = decoder.decodeString().normalized() 34 | override fun serialize(encoder: Encoder, value: String) = encoder.encodeString(value.normalized()) 35 | 36 | private fun String.normalized() = this 37 | .trim() 38 | .replace('\\', '/') 39 | } 40 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/io/SourceSetInfo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.io 21 | 22 | import kotlinx.serialization.Serializable 23 | 24 | @Serializable 25 | data class SourceSetInfo( 26 | val name: String, 27 | val files: Map>, 28 | ) { 29 | companion object Factory : SerializerFactory { 30 | override fun read(scope: SerializationScope.Input) = SourceSetInfo( 31 | scope.input.readUTF(), 32 | scope.readMap(valueSerializer = SetSerializerFactory(SerializerFactory.get(scope.jagr))), 33 | ) 34 | 35 | override fun write(obj: SourceSetInfo, scope: SerializationScope.Output) { 36 | scope.output.writeUTF(obj.name) 37 | scope.writeMap(obj.files, valueSerializer = SetSerializerFactory(SerializerFactory.get(scope.jagr))) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/io/SubmissionExporter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.io 21 | 22 | import org.sourcegrade.jagr.api.testing.Submission 23 | import org.sourcegrade.jagr.launcher.executor.GradingQueue 24 | 25 | interface SubmissionExporter { 26 | /** 27 | * Creates a list of [ResourceContainer] for every combination of [Submission] + [GraderJar]. 28 | * 29 | * The resulting list has [GraderJar].size + 1 entries. (The first entry is the default export without 30 | * a combined [GraderJar]) 31 | */ 32 | fun export(graders: List, submissions: List): List 33 | interface Gradle : SubmissionExporter 34 | interface Eclipse : SubmissionExporter 35 | } 36 | 37 | fun SubmissionExporter.export(queue: GradingQueue) = export(queue.graders, queue.submissions) 38 | -------------------------------------------------------------------------------- /launcher/src/main/kotlin/org/sourcegrade/jagr/launcher/io/ZipResourceContainer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr.launcher.io 21 | 22 | import java.io.File 23 | import java.io.InputStream 24 | import java.util.zip.ZipEntry 25 | import java.util.zip.ZipInputStream 26 | 27 | internal class ZipResourceContainer( 28 | override val info: ResourceContainerInfo, 29 | private val input: InputStream, 30 | ) : ResourceContainer { 31 | constructor(name: String, input: InputStream) : this(ResourceContainerInfoImpl(name), input) 32 | constructor(file: File) : this(file.name, file.inputStream().buffered()) 33 | 34 | override fun iterator(): Iterator = ZipResourceIterator(ZipInputStream(input)) 35 | override fun toString(): String = info.toString() 36 | } 37 | 38 | private class ZipResourceIterator(private val zip: ZipInputStream) : Iterator { 39 | private var next: ZipEntry? = null 40 | 41 | private fun calculateNext(): ZipEntry? { 42 | // skip directory entries 43 | do { 44 | next = zip.nextEntry 45 | } while (next?.isDirectory == true) 46 | return next 47 | } 48 | 49 | override fun hasNext(): Boolean { 50 | if (next == null) { 51 | calculateNext() 52 | return next != null 53 | } 54 | return true 55 | } 56 | 57 | override fun next(): Resource { 58 | return next?.let { 59 | // return cached next from hasNext() and reset it 60 | ByteArrayResource(it.name, zip.readBytes()).also { next = null } 61 | } // no cached next, calculate and return 62 | ?: ByteArrayResource(requireNotNull(calculateNext()) { "No next entry!" }.name, zip.readBytes()) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /launcher/src/main/resources/log4j2-child.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /launcher/src/main/resources/log4j2-console-only.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %highlight{[%d{yy-MMM-dd HH:mm:ss}] [%p] - %m%n%throwable}{INFO=white} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /launcher/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %highlight{[%d{yy-MMM-dd HH:mm:ss}] [%p] - %m%n%throwable}{INFO=white} 7 | 8 | 9 | 12 | 13 | [%d{yy-MMM-dd HH:mm:ss}] [%p] - %m%n%throwable 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json 2 | 3 | site_name: Jagr Docs 4 | repo_name: sourcegrade/jagr 5 | repo_url: https://github.com/sourcegrade/jagr 6 | # Custom edit_uri because repo_url points to the main project, not the docs 7 | edit_uri: https://github.com/sourcegrade/jagr-docs/edit/master/docs/ 8 | theme: 9 | name: material 10 | favicon: assets/favicon.svg 11 | palette: 12 | # Palette toggle for light mode 13 | - media: "(prefers-color-scheme: light)" 14 | scheme: default 15 | primary: indigo 16 | accent: deep orange 17 | toggle: 18 | icon: material/lightbulb 19 | name: Switch to dark mode 20 | 21 | # Palette toggle for dark mode 22 | - media: "(prefers-color-scheme: dark)" 23 | scheme: slate 24 | primary: indigo 25 | accent: amber 26 | toggle: 27 | icon: material/lightbulb-outline 28 | name: Switch to light mode 29 | features: 30 | - navigation.instant 31 | - navigation.tabs 32 | - content.code.annotate 33 | markdown_extensions: 34 | - admonition 35 | - pymdownx.highlight: 36 | anchor_linenums: true 37 | auto_title: true 38 | - pymdownx.superfences 39 | - pymdownx.details 40 | - pymdownx.inlinehilite 41 | - pymdownx.snippets 42 | - pymdownx.tabbed: 43 | alternate_style: true 44 | 45 | nav: 46 | - Welcome to Jagr: index.md 47 | - Usage: 48 | - Getting Started: 49 | - Installation: usage/getting-started/installation.md 50 | - Command Line: 51 | - Basics: usage/command-line/basics.md 52 | - Options: usage/command-line/options.md 53 | - Development: 54 | - Getting Started: 55 | - Gradle Setup: development/getting-started/gradle-setup.md 56 | - Grader API: 57 | - Criterion: development/grader-api/criterion.md 58 | - Rubric: development/grader-api/rubric.md 59 | - Architecture: 60 | - Grader: architecture/grader.md 61 | - Submission: architecture/submission.md 62 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 3 | repositories { 4 | gradlePluginPortal() 5 | mavenCentral() 6 | maven("https://repo.spongepowered.org/repository/maven-public/") 7 | } 8 | } 9 | 10 | pluginManagement { 11 | includeBuild("build-logic") 12 | repositories { 13 | mavenCentral() 14 | gradlePluginPortal() 15 | } 16 | } 17 | 18 | rootProject.name = "jagr" 19 | 20 | include(":jagr-launcher-gradle-plugin") 21 | project(":jagr-launcher-gradle-plugin").projectDir = file("launcher/gradle-plugin") 22 | 23 | sequenceOf( 24 | "core", 25 | "grader-api", 26 | "launcher", 27 | ).forEach { 28 | val project = ":jagr-$it" 29 | include(project) 30 | project(project).projectDir = file(it) 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/org/sourcegrade/jagr/Files.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Jagr - SourceGrade.org 3 | * Copyright (C) 2021-2022 Alexander Staeding 4 | * Copyright (C) 2021-2022 Contributors 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | package org.sourcegrade.jagr 21 | 22 | import org.apache.logging.log4j.Logger 23 | import java.io.File 24 | 25 | fun File.ensure(logger: Logger? = null, logInfo: Boolean = true): File? { 26 | if (!exists()) { 27 | if (logInfo) { 28 | logger?.info("No $this dir! Creating...") 29 | } 30 | try { 31 | if (!mkdirs()) { 32 | logger?.error("Unable to create $absolutePath dir") 33 | return null 34 | } 35 | } catch (e: SecurityException) { 36 | logger?.error("Unable to create $absolutePath dir", e) 37 | return null 38 | } 39 | } 40 | return this 41 | } 42 | -------------------------------------------------------------------------------- /version: -------------------------------------------------------------------------------- 1 | 0.11.1-SNAPSHOT 2 | --------------------------------------------------------------------------------