Verified Commit 657b50aa authored by Lukas Wiest's avatar Lukas Wiest 🚂
Browse files

Merge branch '1-add-unified-ticketing'

closes #1
parents cf70796d b30650fa
......@@ -63,6 +63,17 @@
<artifactId>log4j-core</artifactId>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>de.hftstuttgart</groupId>
<artifactId>unified-ticketing</artifactId>
<version>0.2.0</version>
</dependency>
<!-- somehow for unified-ticketing maven downloads a 3.x version, even though 4.9.0 is set on unified-ticketing pom -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
</dependencies>
<build>
......@@ -74,4 +85,11 @@
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>unified-ticketing</id>
<url>https://transfer.hft-stuttgart.de/gitlab/api/v4/projects/154/packages/maven</url>
</repository>
</repositories>
</project>
......@@ -9,6 +9,7 @@ import de.hftstuttgart.rest.v1.unittest.UnitTestUpload;
import de.hftstuttgart.utils.DockerUtil;
import de.hftstuttgart.utils.FileUtil;
import de.hftstuttgart.utils.JGitUtil;
import de.hftstuttgart.utils.UnifiedTicketingUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.core.env.Environment;
......@@ -201,6 +202,9 @@ public class TaskUpload {
LOG.debug("convert to moodle understandable format");
LegacyMoodleResult moodleResult = LegacyMoodleResult.convertToModdleResult(resultSummary);
LOG.info("check for provided Ticketsystem information");
UnifiedTicketingUtil.reportResults(taskFileRef.getInputStream(), resultSummary);
LOG.info("submission tested successfully");
return moodleResult;
}
......
package de.hftstuttgart.utils;
import de.hftstuttgart.models.ModocotResult;
import de.hftstuttgart.models.ModocotResultSummary;
import de.hftstuttgart.unifiedticketing.core.Filter;
import de.hftstuttgart.unifiedticketing.core.Ticket;
import de.hftstuttgart.unifiedticketing.core.TicketBuilder;
import de.hftstuttgart.unifiedticketing.core.TicketSystem;
import de.hftstuttgart.unifiedticketing.exceptions.UnifiedticketingException;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class UnifiedTicketingUtil {
private final static Logger LOG = LogManager.getLogger(UnifiedTicketingUtil.class);
private final static String MODOCOT_LABEL = "MoDoCoT created";
private final static String MODOCOT_TITLE = " | " + MODOCOT_LABEL;
public static String createTicketDescriptionFromResult(TicketSystem ts, ModocotResult result, boolean compilationError) {
StringBuilder sb = new StringBuilder();
// heading
if (compilationError) {
sb.append(String.format("# %s is not compilable", result.name));
} else {
sb.append(String.format("# Unittest for %s fails", result.name));
}
sb.append("\n\n");
// meta data table
sb.append("|||\n");
sb.append("|:-|:-|\n");
if (compilationError) {
sb.append(String.format("|File|`%s`|\n", result.name));
} else {
sb.append(String.format("|Test|`%s`|\n", result.name));
}
sb.append(String.format("|Reason|`%s`|\n", result.failureReason));
if (compilationError) {
sb.append(String.format("|Line|`%s`|\n", result.lineNumber));
sb.append(String.format("|Column|`%s`|\n", result.columnNumber));
sb.append(String.format("|Position|`%s`|\n", result.position));
} else {
sb.append(String.format("|Type|`%s`|\n", result.failureType));
}
sb.append("\n\n");
// stacktrace
sb.append("<details>\n\n");
sb.append("<summary>show stacktrace</summary>\n\n");
sb.append("```");
sb.append(result.stacktrace);
sb.append("\n```");
sb.append("\n\n</details>\n");
return sb.toString();
}
public static String createTicketTitleFromResult(TicketSystem ts, ModocotResult result, boolean compilationError) {
StringBuilder sb = new StringBuilder();
String separator = " | ";
if (compilationError) sb.append("compilation fails");
else sb.append("test method fails");
sb.append(separator);
sb.append(result.name);
if (!compilationError) {
sb.append(separator);
sb.append(result.failureReason);
}
// if label-support is not present, place global identifier into title
if (!ts.hasLabelSupport()) sb.append(MODOCOT_TITLE);
sb.append(separator);
sb.append(getHashForFailure(result));
return sb.toString();
}
public static Ticket createTicketFromResult(TicketSystem ts, ModocotResult result, boolean compilationError) {
TicketBuilder tb = ts.createTicket()
.title(createTicketTitleFromResult(ts, result, compilationError))
.description(createTicketDescriptionFromResult(ts, result, compilationError));
if (ts.hasLabelSupport()) tb.labels(Collections.singleton(MODOCOT_LABEL));
return tb.create();
}
public static Set<Ticket> fetchExistingModocotTickets(TicketSystem ts) {
Set<Ticket> ret = new HashSet<>();
Filter f = ts.find();
// depending on label support, identify MoDoCoT tickets by label or title containing string
if (ts.hasLabelSupport()) {
LOG.debug(String.format(
"ticketsystem has label support, using label %s to find modocot tickets", MODOCOT_LABEL));
f.withLabel(MODOCOT_LABEL);
}
else {
LOG.debug(String.format(
"ticketsystem without labels, searching for ticket titles containing %s", MODOCOT_TITLE));
f.withTitleContain(MODOCOT_TITLE);
}
LOG.debug("prepare pagination cycling");
// set first page and page size for pagination
int page = 1;
int pageSize = 10;
f.setPageSize(pageSize);
LOG.debug(String.format("using pagination with %s elements per page", pageSize));
// declare list for received tickets
List<Ticket> received;
// go into do-while to evaluate after receiving if another round is needed
do {
LOG.debug(String.format("calling page %s", page));
f.setPage(page++);
received = f.get();
ret.addAll(received);
} while (f.getLastReceivedItemCount() >= pageSize);
return ret;
}
public static String getHashForFailure(ModocotResult result) {
MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA-512");
} catch (NoSuchAlgorithmException e) {
throw new UnifiedticketingException(e);
}
byte[] data = digest.digest(result.name.getBytes(StandardCharsets.UTF_8));
StringBuilder hexString = new StringBuilder(2 * data.length);
for (byte character: data)
{
String hex = Integer.toHexString(0xff & character);
if (hex.length() == 1)
{
hexString.append('0');
}
hexString.append(hex);
}
return hexString.substring(hexString.length() - 9, hexString.length() - 1);
}
public static void processResult(TicketSystem ts, Set<Ticket> tickets, ModocotResult result, boolean compilationError) {
LOG.debug(String.format("retrieving hash for %s", result.name));
String hash = getHashForFailure(result);
// check if corresponding ticket exists yet, otherwise create new one
Ticket ticket = tickets.stream()
.filter(t -> t.getTitle().endsWith(hash))
.findFirst()
.orElse(null);
if (ticket != null) {
// if yet existing, remove from found list
LOG.debug("found ticket with matching hash, removing from collection");
tickets.remove(ticket);
LOG.debug("updating ticket with new result");
updateTicketFromResult(ts, ticket, result, compilationError);
} else {
LOG.debug("no ticket found, creating new one");
createTicketFromResult(ts, result, compilationError);
}
}
/**
* search file for unified-ticketing URI's and report to every set ticket system,
* not waiting for it to finish and catching an eventually interrupted thread.
*
* @param meta student uploaded file
* @param resultSummary summary from the testrunner container
*/
public static void reportResults(InputStream meta, ModocotResultSummary resultSummary) {
try {
reportResults(meta, resultSummary, false);
} catch (InterruptedException e) {
LOG.error(String.format("Unified-Ticketing got interrupted with: %s", e.getMessage()));
}
}
/**
* report all failures and compilation errors to all configured ticket systems.
* You can optionally wait for the ticket creation to finish, which is done in a separate thread.
*
* @param meta student uploaded file
* @param resultSummary summary from the testrunner container
* @param wait if we should block until the ticket creation has finished
* @throws InterruptedException
*/
public static void reportResults(InputStream meta, ModocotResultSummary resultSummary, boolean wait) throws InterruptedException {
LOG.debug("preparing thread for ticket result submitting");
Thread unifiedTicketingUtil = new Thread(() -> {
// read student transmitted file
try (BufferedReader br = new BufferedReader(new InputStreamReader(meta))) {
String line = null;
while ((line = br.readLine()) != null) {
TicketSystem ts = null;
// try each line as URI for a unified-ticketing instantiation
try {
ts = TicketSystem.fromUri(line);
LOG.info(String.format("ticket system for reporting found: %s", ts.baseUrl));
} catch (UnifiedticketingException e) {
// ignore if line didn't match a unified-ticketing URI
}
// if a ticketsystem got instantiated, start the submission.
// If errors occur, log it this time.
try {
if (ts != null) reportToTicketsystem(ts, resultSummary);
} catch (UnifiedticketingException e) {
LOG.warn(String.format(
"reporting fails to ticketsystem %s failed with: ",
ts.baseUrl,
e.getMessage()));
}
}
} catch (IOException e) {
LOG.error(String.format("couldn't read config to find lines for ticket reporting: %s", e.getMessage()));
}
});
LOG.debug("starting ticket submitting thread");
unifiedTicketingUtil.start();
if (wait) {
LOG.debug("wait for ticket submission completion");
unifiedTicketingUtil.join();
} else {
LOG.debug("tickets will be submitted in background");
}
}
public static void reportToTicketsystem(TicketSystem ts, ModocotResultSummary resultSummary) {
// tickets existing yet
LOG.debug("fetching existing tickets");
Set<Ticket> tickets = fetchExistingModocotTickets(ts);
// for each fail or compile error
LOG.debug("start failed tests reporting");
resultSummary.failures.forEach(f -> processResult(ts, tickets, f, false));
LOG.debug("start compilation errors reporting");
resultSummary.compilationErrors.forEach(c -> processResult(ts, tickets, c, true));
LOG.debug("closing all remaining tickets, no longer appeared");
tickets.forEach(ticket -> ticket.close().save());
}
public static Ticket updateTicketFromResult(TicketSystem ts, Ticket ticket, ModocotResult result, boolean compilationError) {
ticket
.open()
.setTitle(createTicketTitleFromResult(ts, result, compilationError))
.setDescription(createTicketDescriptionFromResult(ts, result, compilationError));
if (ts.hasLabelSupport()) ticket.addLabel(MODOCOT_LABEL);
return ticket.save();
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment