Skip to content
This repository was archived by the owner on Feb 22, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Member

@jwgmeligmeyling jwgmeligmeyling Apr 9, 2017

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 SuccessiveBuildFailureWarnings instead.

return true;
}
} else {
consecutive = 0;
}
}

return false;
}

protected Optional<Commit> findRootCommit(Group group, Delivery delivery) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the Group already contained within the Delivery?

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);
Copy link
Member

Choose a reason for hiding this comment

The 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));
Copy link
Member

@jwgmeligmeyling jwgmeligmeyling Apr 9, 2017

Choose a reason for hiding this comment

The 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 HooksResource.

There are a couple of things we can do here:

  1. Ignore the issue (I'd rather not).
  2. Use blaze-persistence so that we can create recursive CTE criteria queries for Hibernate. Pros: great framework, cons: even though we limit the n queries back to 1 query, Postgres is still not the fastest at processing these queries. Plus we would have to host a snapshot of blaze-persistence as it's currently not in Maven Central and under alpha release.
  3. Use a Postgres CTE that selects the commit ids as Hibernate native query, and then find the commits for these ids. Cons: same query performance, error prone native SQL query in code base
  4. Implement git log on the Git server (or see if we can use the existing branch endpoints for it) and use that.


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
Expand Up @@ -36,6 +36,13 @@ public Optional<Commit> retrieve(RepositoryEntity repository, String commitId) {
return Optional.ofNullable(entityManager.find(Commit.class, key));
}

public Optional<Commit> firstCommitInRepo(RepositoryEntity repositoryEntity) {
return Optional.ofNullable(query().from(commit)
.where(commit.repository.eq(repositoryEntity))
.orderBy(commit.commitTime.asc())
.singleResult(commit));
}

/**
* Ensure that a commit exists in the database. Recursively check if the parents exists as well.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package nl.tudelft.ewi.devhub.server.database.controllers;

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 com.google.inject.Inject;
import com.google.inject.persist.Transactional;
import nl.tudelft.ewi.devhub.server.database.entities.rubrics.Mastery;

import javax.persistence.EntityManager;
import java.util.Comparator;
Expand Down Expand Up @@ -111,6 +113,15 @@ public Delivery find(Group group, long deliveryId) {
"No delivery found for id " + deliveryId);
}


public Optional<Delivery> deliveryForCommit(Commit commit) {
return Optional.ofNullable(
query().from(delivery)
.where(delivery.commit.eq(commit))
.singleResult(delivery)
);
}

/**
* Return the most recent deliveries.
* @param groups Groups to search deliveries for.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
import lombok.ToString;
import nl.tudelft.ewi.devhub.server.database.Base;
import nl.tudelft.ewi.devhub.server.database.entities.identity.FKSegmentedIdentifierGenerator;
import nl.tudelft.ewi.devhub.server.database.entities.rubrics.DutchGradingStrategy;
import nl.tudelft.ewi.devhub.server.database.entities.rubrics.GradingStrategy;
import nl.tudelft.ewi.devhub.server.database.entities.rubrics.Task;
import nl.tudelft.ewi.devhub.server.database.entities.rubrics.*;

import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
Expand Down Expand Up @@ -43,6 +41,7 @@
import java.net.URI;
import java.util.Date;
import java.util.List;
import java.util.Optional;

/**
* Created by jgmeligmeyling on 04/03/15.
Expand Down Expand Up @@ -124,6 +123,7 @@ public double getNumberOfAchievablePoints() {
.sum();
}


@JsonIgnore
public GradingStrategy getGradingStrategy() {
return new DutchGradingStrategy();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My original thought was to create Mastery subtypes (or AutogradedMastery subtypes) for this purpose, but I can live with this work around for now.

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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@
import nl.tudelft.ewi.devhub.server.backend.warnings.PMDWarningGenerator;
import nl.tudelft.ewi.devhub.server.backend.warnings.PMDWarningGenerator.PMDReport;
import nl.tudelft.ewi.devhub.server.backend.warnings.SuccessiveBuildFailureGenerator;
import nl.tudelft.ewi.devhub.server.database.controllers.BuildResults;
import nl.tudelft.ewi.devhub.server.database.controllers.Commits;
import nl.tudelft.ewi.devhub.server.database.controllers.PullRequests;
import nl.tudelft.ewi.devhub.server.database.controllers.RepositoriesController;
import nl.tudelft.ewi.devhub.server.database.controllers.Warnings;
import nl.tudelft.ewi.devhub.server.database.controllers.*;
import nl.tudelft.ewi.devhub.server.database.entities.BuildResult;
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.RepositoryEntity;
import nl.tudelft.ewi.devhub.server.database.entities.rubrics.Characteristic;
import nl.tudelft.ewi.devhub.server.database.entities.rubrics.Mastery;
import nl.tudelft.ewi.devhub.server.database.entities.warnings.CheckstyleWarning;
import nl.tudelft.ewi.devhub.server.database.entities.warnings.CommitWarning;
import nl.tudelft.ewi.devhub.server.database.entities.warnings.FindbugsWarning;
Expand Down Expand Up @@ -57,8 +56,11 @@
import javax.ws.rs.core.MediaType;
import java.io.UnsupportedEncodingException;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand All @@ -83,19 +85,20 @@ public class HooksResource extends Resource {
private final FindBugsWarningGenerator findBugsWarningGenerator;
private final SuccessiveBuildFailureGenerator successiveBuildFailureGenerator;
private final ExecutorService executor;
private final Deliveries deliveries;

@Inject
public HooksResource(BuildResults buildResults,
Commits commits,
Warnings warnings,
BuildResultMailer mailer,
ExecutorService executor,
PMDWarningGenerator pmdWarningGenerator,
GitPushHandlerWorkerFactory gitPushHandlerWorkerFactory,
RepositoriesController repositoriesController,
FindBugsWarningGenerator findBugsWarningGenerator,
CheckstyleWarningGenerator checkstyleWarningGenerator,
SuccessiveBuildFailureGenerator successiveBuildFailureGenerator) {
Commits commits,
Warnings warnings,
BuildResultMailer mailer,
ExecutorService executor,
PMDWarningGenerator pmdWarningGenerator,
GitPushHandlerWorkerFactory gitPushHandlerWorkerFactory,
RepositoriesController repositoriesController,
FindBugsWarningGenerator findBugsWarningGenerator,
CheckstyleWarningGenerator checkstyleWarningGenerator,
SuccessiveBuildFailureGenerator successiveBuildFailureGenerator, Deliveries deliveries) {
this.mailer = mailer;
this.commits = commits;
this.warnings = warnings;
Expand All @@ -107,6 +110,7 @@ public HooksResource(BuildResults buildResults,
this.findBugsWarningGenerator = findBugsWarningGenerator;
this.checkstyleWarningGenerator = checkstyleWarningGenerator;
this.successiveBuildFailureGenerator = successiveBuildFailureGenerator;
this.deliveries = deliveries;
}

/**
Expand Down Expand Up @@ -265,6 +269,7 @@ public void onBuildResult(@QueryParam("repository") @NotEmpty String repository,
RepositoryEntity repositoryEntity = repositoriesController.find(repoName);
Commit commit = commits.ensureExists(repositoryEntity, commitId);


BuildResult result;
try {
result = buildResults.find(repositoryEntity, commitId);
Expand All @@ -285,6 +290,7 @@ public void onBuildResult(@QueryParam("repository") @NotEmpty String repository,

if (!result.getSuccess()) {
mailer.sendFailedBuildResult(Lists.newArrayList(Locale.ENGLISH), result);
fillInBuildFailureRubric(commit);
}

try {
Expand All @@ -294,9 +300,15 @@ public void onBuildResult(@QueryParam("repository") @NotEmpty String repository,
catch (Exception e) {
log.warn("Failed to persist sucessive build failure for {}", e, result);
}
}

@Transactional
public void fillInBuildFailureRubric(Commit commit) {
deliveries.deliveryForCommit(commit)
.ifPresent(Delivery::setBuildFailed);
}


@POST
@Path("pmd-result")
@RequireAuthenticatedBuildServer
Expand Down
Loading