-
Notifications
You must be signed in to change notification settings - Fork 8
WIP Automatically fill in build rubric, fixes #277 #419
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| package nl.tudelft.ewi.devhub.server.backend; | ||
|
|
||
| import com.google.common.collect.Lists; | ||
| import com.google.inject.Inject; | ||
| import nl.tudelft.ewi.devhub.server.database.controllers.Commits; | ||
| import nl.tudelft.ewi.devhub.server.database.controllers.Deliveries; | ||
| import nl.tudelft.ewi.devhub.server.database.entities.Assignment; | ||
| import nl.tudelft.ewi.devhub.server.database.entities.Commit; | ||
| import nl.tudelft.ewi.devhub.server.database.entities.Delivery; | ||
| import nl.tudelft.ewi.devhub.server.database.entities.Group; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
|
|
||
| import static java.util.stream.Collectors.toList; | ||
|
|
||
| /** | ||
| * Checks whether between the previous assignments delivery and the current deliver, or the start of time if it's the first assignment | ||
| * any commits through any branches that are reachable from the commit that has been handed in contained 4 or more consecutive build failures. | ||
| */ | ||
| public class ConsecutiveBuildFailureBackend { | ||
| public static final int MAX_CONSECUTIVE_BUILD_FAILURES = 4; | ||
| private Commits commits; | ||
| private Deliveries deliveries; | ||
|
|
||
| @Inject | ||
| public ConsecutiveBuildFailureBackend(Commits commits, Deliveries deliveries) { | ||
| this.commits = commits; | ||
| this.deliveries = deliveries; | ||
| } | ||
|
|
||
| public boolean hasConsecutiveBuildFailures(Group group, Delivery delivery) { | ||
| final List<List<Commit>> commitsToCheck = commitsToCheckForBuildFailures(group, delivery); | ||
| return consecutiveBuildFailures(commitsToCheck); | ||
| } | ||
|
|
||
| protected static boolean consecutiveBuildFailures(List<List<Commit>> commitsToCheck) { | ||
| return commitsToCheck.stream().anyMatch(ConsecutiveBuildFailureBackend::hasFourFailingCommits); | ||
| } | ||
|
|
||
|
|
||
| protected static boolean hasFourFailingCommits(List<Commit> commits) { | ||
| int consecutive = 0; | ||
|
|
||
| for (Commit commit : commits) { | ||
| if (commit.getBuildResult().hasFailed()) { | ||
| consecutive++; | ||
| if (consecutive == MAX_CONSECUTIVE_BUILD_FAILURES) { | ||
| return true; | ||
| } | ||
| } else { | ||
| consecutive = 0; | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| protected Optional<Commit> findRootCommit(Group group, Delivery delivery) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't the |
||
| final List<Assignment> assignments = group.getCourseEdition().getAssignments().stream() | ||
| .sorted(Assignment::compareTo) | ||
| .collect(toList()); | ||
|
|
||
| final int index = assignments.indexOf(delivery.getAssignment()); | ||
| final boolean firstAssignment = index < 1; | ||
|
|
||
| if (firstAssignment) { | ||
| return commits.firstCommitInRepo(group.getRepository()); | ||
| } else { | ||
| final Assignment previousAssignment = assignments.get(index - 1); | ||
| return deliveries.getLastDelivery(previousAssignment, group).map(Delivery::getCommit); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the commit for a delivery is nullable, so we should handle that case. |
||
| } | ||
| } | ||
|
|
||
| protected List<List<Commit>> commitsToCheckForBuildFailures(Group group, Delivery delivery) { | ||
| final Commit deliveryCommit = delivery.getCommit(); | ||
| Optional<List<List<Commit>>> commits = findRootCommit(group, delivery).map(prevCommit -> commitsTillRoot(deliveryCommit, prevCommit)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is unfortunately very intensive as it has to query for the commit parents for every commit it traverses. For SKT this usually does not exceed 200 commits and thus 200 queries (with 20ms round trip that would take 4 seconds), but for bigger repositories this could potentially hang the Devhub server, which was also the reason why I limited the traversal that looks for unbuilt commits to 20 in the There are a couple of things we can do here:
|
||
|
|
||
| return commits.orElse(Lists.newArrayList()); | ||
| } | ||
|
|
||
| protected static List<List<Commit>> commitsTillRoot(Commit commit, Commit root) { | ||
| List<List<Commit>> commits = Lists.newArrayList(); | ||
| commitsBetween(commit, root, commits, new ArrayList<>()); | ||
|
|
||
| return commits; | ||
| } | ||
|
|
||
| private static void commitsBetween(Commit current, Commit root, List<List<Commit>> allCommitPaths, List<Commit> currentCommits) { | ||
| currentCommits.add(current); | ||
|
|
||
| if (isPastEnd(current, root)) { | ||
| allCommitPaths.add(currentCommits); | ||
| } else { | ||
| List<Commit> parents = current.getParents(); | ||
| if (parents.size() == 1) { | ||
| commitsBetween(parents.get(0), root, allCommitPaths, currentCommits); | ||
| } else { | ||
| for (Commit commit : parents) { | ||
| List<Commit> currentCopy = Lists.newArrayList(currentCommits); | ||
| commitsBetween(commit, root, allCommitPaths, currentCopy); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * We are at the end of commits to consider for consecutive build failures if we either rejoined the last commit or are past it in time. | ||
| * | ||
| * @param end the commit of the last delivery for the previous assignment. | ||
| * @param commit the commit we are considering right now. | ||
| * @return true if the current commit shouldn't be considered anymore. | ||
| */ | ||
| private static boolean isPastEnd(Commit end, Commit commit) { | ||
| return commit.getCommitTime().before(end.getCommitTime()) || commit.equals(end); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,6 @@ | |
| import nl.tudelft.ewi.devhub.server.database.entities.rubrics.Mastery; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonBackReference; | ||
| import com.fasterxml.jackson.annotation.JsonIdentityInfo; | ||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||
| import com.fasterxml.jackson.annotation.JsonManagedReference; | ||
|
|
||
|
|
@@ -43,11 +42,7 @@ | |
| import javax.persistence.TemporalType; | ||
| import javax.validation.constraints.NotNull; | ||
| import java.net.URI; | ||
| import java.util.Collection; | ||
| import java.util.Comparator; | ||
| import java.util.Date; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.*; | ||
|
|
||
| /** | ||
| * Delivery for an assignment | ||
|
|
@@ -239,6 +234,28 @@ public boolean isLate() { | |
| return dueDate != null && getTimestamp().after(dueDate); | ||
| } | ||
|
|
||
| @JsonIgnore | ||
| public void setBuildFailed() { | ||
| Optional<Mastery> buildFailureMastery = findMasteryByDescription("Build for submitted commit fails"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My original thought was to create |
||
| buildFailureMastery.ifPresent(mastery -> this.getRubrics().put(mastery.getCharacteristic(), mastery)); | ||
| } | ||
|
|
||
| @JsonIgnore | ||
| public void setConsecutiveBuildFailures() { | ||
| Optional<Mastery> buildFailureMastery = findMasteryByDescription("Four subsequent failing"); | ||
| buildFailureMastery.ifPresent(mastery -> this.getRubrics().put(mastery.getCharacteristic(), mastery)); | ||
| } | ||
|
|
||
| @JsonIgnore | ||
| private Optional<Mastery> findMasteryByDescription(String description) { | ||
| return this.getAssignment().getTasks().stream() | ||
| .flatMap(tasks -> tasks.getCharacteristics().stream()) | ||
| .filter(mastery -> mastery.getDescription().equals("Build failure penalty")) | ||
| .findAny().flatMap(characteristic -> characteristic.getLevels().stream() | ||
| .filter(mastery -> mastery.getDescription().contains(description)) | ||
| .findAny()); | ||
| } | ||
|
|
||
| @Override | ||
| public URI getURI() { | ||
| return getGroup().getURI() | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This traversal treats merge commits invalidly. A failing merge commit for which the first parent commit actually succeeded will break this chain. We have to treat this chain as a graph during traversal and look for any failing parent commit instead, which is why I would look into the option of traversing generated
SuccessiveBuildFailureWarningsinstead.