Skip to content

Commit

Permalink
merge
Browse files Browse the repository at this point in the history
  • Loading branch information
Feuermagier committed Jul 15, 2024
1 parent 4fa63bd commit 49e97e7
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 36 deletions.
23 changes: 19 additions & 4 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
# Artemis4J
A Java client for the Artemis API, and a wrapper around it to simplify grading of programming exercises.
A Java client for the [Artemis](https://github.com/ls1intum/Artemis) API, and a wrapper around it to simplify grading of programming exercises.
The project is developed and used as part of the [KIT](https://www.kit.edu)'s programming lecture, with the main user being [IntelliGrade](https://github.com/kit-sdq/intelligrade).

Features include:
- A stateless `client` API that maps 1:1 to the Artemis REST API.
- A stateful `grading` API for programming exercises built on top of the API. This API uses a simplified model of the Artemis data model.
- (Partially) automatic grading of programming exercises via the [Autograder](https://github.com/Feuermagier/autograder).



## Usage
The entry point for the `client` API is the [ArtemisClient](src/main/java/edu/kit/kastel/sdq/artemis4j/client/ArtemisClient.java) class, with which the various DTOs can be used.
The [UtilitiesTest](src/test/java/edu/kit/kastel/sdq/artemis4j/UtilitiesTest.java) class demonstrates this.

The entry point for the `grading` API is the [ArtemisConnection](src/main/java/edu/kit/kastel/sdq/artemis4j/grading/ArtemisConnection.java) class.
The [API example](src/test/java/edu/kit/kastel/sdq/artemis4j/APIExampleTest.java) demonstrates the intended usage of the `grading` API.

## Architecture

Artemis4J is split into two main parts: A stateless "client" part that maps 1:1 to the Artemis API, and a stateful "grading" part built on top of it.
Artemis4J is split into two main parts: A stateless `client` part that maps 1:1 to the Artemis API, and a stateful `grading` part built on top of it.

The "client" part is entirely stateless.
The [client](src/main/java/edu/kit/kastel/sdq/artemis4j/client) part is entirely stateless.
It can be used without everything else, e.g. for one-off scripts or tools.
The client is mainly structured around DTOs, where each DTO describes a single request/response entity of Artemis.
DTOs contain static methods that describe associated API endpoints.

The "grading" part is stateful and provides a higher-level API for grading of programming exercises.
The [grading](src/main/java/edu/kit/kastel/sdq/artemis4j/grading) part is stateful and provides a higher-level API for grading of programming exercises.
It is designed to suit the needs of KIT's programming lecture.
It parses grading configs, calculates points, and provides a simplified API for grading tools.
It also provides means to clone student's Git repositories.
Expand Down
7 changes: 1 addition & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,7 @@
<!-- Autograder -->
<dependency>
<groupId>de.firemage.autograder</groupId>
<artifactId>autograder-core</artifactId>
<version>${autograder.version}</version>
</dependency>
<dependency>
<groupId>de.firemage.autograder</groupId>
<artifactId>autograder-extra</artifactId>
<artifactId>autograder-api</artifactId>
<version>${autograder.version}</version>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@
import edu.kit.kastel.sdq.artemis4j.ArtemisClientException;

public class AutograderFailedException extends ArtemisClientException {
public AutograderFailedException(String message) {
super(message);
}

public AutograderFailedException(Throwable cause) {
super("Autograder failed", cause);
}

public AutograderFailedException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@
package edu.kit.kastel.sdq.artemis4j.grading.autograder;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import de.firemage.autograder.core.CheckConfiguration;
import de.firemage.autograder.core.Linter;
import de.firemage.autograder.core.LinterException;
import de.firemage.autograder.core.LinterStatus;
import de.firemage.autograder.core.Problem;
import de.firemage.autograder.core.compiler.JavaVersion;
import de.firemage.autograder.core.file.TempLocation;
import de.firemage.autograder.core.file.UploadedFile;
import de.firemage.autograder.api.AbstractLinter;
import de.firemage.autograder.api.CheckConfiguration;
import de.firemage.autograder.api.JavaVersion;
import de.firemage.autograder.api.LinterException;
import de.firemage.autograder.api.Translatable;
import de.firemage.autograder.api.loader.AutograderLoader;
import edu.kit.kastel.sdq.artemis4j.grading.Assessment;
import edu.kit.kastel.sdq.artemis4j.grading.ClonedProgrammingSubmission;

Expand All @@ -30,25 +26,33 @@ public static AutograderStats runAutograder(Assessment assessment, ClonedProgram
throw new IllegalArgumentException("The assessment and submission do not match");
}

try {
if (!AutograderLoader.isAutograderLoaded()) {
statusConsumer.accept("Downloading the latest Autograder release");
AutograderLoader.loadFromGithubWithExtraChecks();
} else if (!AutograderLoader.isCurrentVersionLoaded()) {
throw new AutograderFailedException("There is a more recent version of the Autograder available");
}
} catch (IOException e) {
throw new AutograderFailedException("Failed to check for or download the latest Autograder release", e);
}

var problemTypesMap = assessment.getConfig().getMistakeTypes().stream().flatMap(m -> m.getAutograderProblemTypes().stream().map(p -> Map.entry(p, m)))
.distinct().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
var checkConfiguration = CheckConfiguration.fromProblemTypes(new ArrayList<>(problemTypesMap.keySet()));
var checkConfiguration = CheckConfiguration.fromProblemTypes(problemTypesMap.keySet().stream().map(AutograderLoader::convertProblemType).toList());

try (TempLocation tempLocation = TempLocation.random()) {
Linter autograder = Linter.builder(locale).threads(threads).tempLocation(tempLocation).maxProblemsPerCheck(-1).build();
try (var tempLocation = AutograderLoader.instantiateTempLocation()) {
var autograderBuilder = AbstractLinter.builder(locale).threads(threads).tempLocation(tempLocation).maxProblemsPerCheck(-1);
var autograder = AutograderLoader.instantiateLinter(autograderBuilder);

Consumer<LinterStatus> statusConsumerWrapper = status -> statusConsumer.accept(autograder.translateMessage(status.getMessage()));
Consumer<Translatable> statusConsumerWrapper = status -> statusConsumer.accept(autograder.translateMessage(status));

List<Problem> problems;
try (UploadedFile uploadedFile = UploadedFile.build(submission.getSubmissionSourcePath(), JavaVersion.JAVA_17, tempLocation, statusConsumerWrapper,
null)) {
problems = autograder.checkFile(uploadedFile, checkConfiguration, statusConsumerWrapper);
}
var problems = autograder.checkFile(submission.getSubmissionSourcePath(), JavaVersion.JAVA_21, checkConfiguration, statusConsumerWrapper);

for (var problem : problems) {
var mistakeType = problemTypesMap.get(problem.getProblemType());
var mistakeType = problemTypesMap.get(problem.getType());
var position = problem.getPosition();
assessment.addAutograderAnnotation(mistakeType, position.file().toString(), position.startLine(), position.endLine(),
assessment.addAutograderAnnotation(mistakeType, position.path().toString(), position.startLine(), position.endLine(),
autograder.translateMessage(problem.getExplanation()));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import java.util.Map;
import java.util.Objects;

import de.firemage.autograder.core.ProblemType;
import edu.kit.kastel.sdq.artemis4j.i18n.FormatString;
import edu.kit.kastel.sdq.artemis4j.i18n.TranslatableString;

Expand All @@ -17,7 +16,7 @@ public final class MistakeType {
private final FormatString message;
private final FormatString buttonTexts;
private final MistakeReportingState reporting;
private final List<ProblemType> autograderProblemTypes;
private final List<String> autograderProblemTypes;

static void createAndAddToGroup(MistakeTypeDTO dto, boolean shouldScore, RatingGroup ratingGroup) {
var mistakeType = new MistakeType(dto, shouldScore, ratingGroup);
Expand Down Expand Up @@ -72,7 +71,7 @@ public boolean isCustomAnnotation() {
return this.rule instanceof CustomPenaltyRule;
}

public List<ProblemType> getAutograderProblemTypes() {
public List<String> getAutograderProblemTypes() {
return Collections.unmodifiableList(autograderProblemTypes);
}

Expand All @@ -93,6 +92,6 @@ public int hashCode() {

/* package-private */ record MistakeTypeDTO(String shortName, String message, String button, PenaltyRule penaltyRule, String appliesTo,
String enabledForExercises, String enabledPenaltyForExercises, Map<String, String> additionalButtonTexts, Map<String, String> additionalMessages,
List<ProblemType> autograderProblemTypes) {
List<String> autograderProblemTypes) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* This test demonstrates the intended use of the new version of artemis4j
*/
@Disabled
public class NewAPITest {
public class APIExampleTest {
private static final String ARTEMIS_URL = System.getenv("ARTEMIS_URL");
private static final String ARTEMIS_USERNAME = System.getenv("ARTEMIS_USER");
private static final String ARTEMIS_PASSWORD = System.getenv("ARTEMIS_PASSWORD");
Expand Down

0 comments on commit 49e97e7

Please sign in to comment.