Search in sources :

Example 1 with BuildLogEntry

use of de.tum.in.www1.artemis.domain.BuildLogEntry in project Artemis by ls1intum.

the class JenkinsService method onBuildCompleted.

@Override
public Result onBuildCompleted(ProgrammingExerciseParticipation participation, Object requestBody) {
    final var buildResult = TestResultsDTO.convert(requestBody);
    var newResult = createResultFromBuildResult(buildResult, participation);
    // Fetch submission or create a fallback
    var latestSubmission = super.getSubmissionForBuildResult(participation.getId(), buildResult).orElseGet(() -> createAndSaveFallbackSubmission(participation, buildResult));
    latestSubmission.setBuildFailed("No tests found".equals(newResult.getResultString()));
    // Parse, filter, and save the build logs if they exist
    if (buildResult.getLogs() != null) {
        ProgrammingLanguage programmingLanguage = participation.getProgrammingExercise().getProgrammingLanguage();
        List<BuildLogEntry> buildLogEntries = JenkinsBuildLogParseUtils.parseBuildLogsFromJenkinsLogs(buildResult.getLogs());
        buildLogEntries = filterUnnecessaryLogs(buildLogEntries, programmingLanguage);
        buildLogEntries = buildLogService.saveBuildLogs(buildLogEntries, latestSubmission);
        // Set the received logs in order to avoid duplicate entries (this removes existing logs)
        latestSubmission.setBuildLogEntries(buildLogEntries);
    }
    // Note: we only set one side of the relationship because we don't know yet whether the result will actually be saved
    newResult.setSubmission(latestSubmission);
    newResult.setRatedIfNotExceeded(exerciseDateService.getDueDate(participation).orElse(null), latestSubmission);
    return newResult;
}
Also used : ProgrammingLanguage(de.tum.in.www1.artemis.domain.enumeration.ProgrammingLanguage)

Example 2 with BuildLogEntry

use of de.tum.in.www1.artemis.domain.BuildLogEntry in project ArTEMiS by ls1intum.

the class JenkinsService method onBuildCompleted.

@Override
public Result onBuildCompleted(ProgrammingExerciseParticipation participation, Object requestBody) {
    final var buildResult = TestResultsDTO.convert(requestBody);
    var newResult = createResultFromBuildResult(buildResult, participation);
    // Fetch submission or create a fallback
    var latestSubmission = super.getSubmissionForBuildResult(participation.getId(), buildResult).orElseGet(() -> createAndSaveFallbackSubmission(participation, buildResult));
    latestSubmission.setBuildFailed("No tests found".equals(newResult.getResultString()));
    // Parse, filter, and save the build logs if they exist
    if (buildResult.getLogs() != null) {
        ProgrammingLanguage programmingLanguage = participation.getProgrammingExercise().getProgrammingLanguage();
        List<BuildLogEntry> buildLogEntries = JenkinsBuildLogParseUtils.parseBuildLogsFromJenkinsLogs(buildResult.getLogs());
        buildLogEntries = filterUnnecessaryLogs(buildLogEntries, programmingLanguage);
        buildLogEntries = buildLogService.saveBuildLogs(buildLogEntries, latestSubmission);
        // Set the received logs in order to avoid duplicate entries (this removes existing logs)
        latestSubmission.setBuildLogEntries(buildLogEntries);
    }
    // Note: we only set one side of the relationship because we don't know yet whether the result will actually be saved
    newResult.setSubmission(latestSubmission);
    newResult.setRatedIfNotExceeded(exerciseDateService.getDueDate(participation).orElse(null), latestSubmission);
    return newResult;
}
Also used : ProgrammingLanguage(de.tum.in.www1.artemis.domain.enumeration.ProgrammingLanguage)

Example 3 with BuildLogEntry

use of de.tum.in.www1.artemis.domain.BuildLogEntry in project ArTEMiS by ls1intum.

the class JenkinsService method getLatestBuildLogs.

@Override
public List<BuildLogEntry> getLatestBuildLogs(ProgrammingSubmission programmingSubmission) {
    ProgrammingExerciseParticipation programmingExerciseParticipation = (ProgrammingExerciseParticipation) programmingSubmission.getParticipation();
    String projectKey = programmingExerciseParticipation.getProgrammingExercise().getProjectKey();
    String buildPlanId = programmingExerciseParticipation.getBuildPlanId();
    ProgrammingLanguage programmingLanguage = programmingExerciseParticipation.getProgrammingExercise().getProgrammingLanguage();
    try {
        final var build = jenkinsJobService.getJobInFolder(projectKey, buildPlanId).getLastBuild();
        List<BuildLogEntry> buildLogEntries;
        // Attempt to parse pipeline logs
        final String pipelineLogs = build.details().getConsoleOutputText();
        if (pipelineLogs != null && pipelineLogs.contains("[Pipeline] Start of Pipeline")) {
            buildLogEntries = JenkinsBuildLogParseUtils.parseBuildLogsFromJenkinsLogs(List.of(pipelineLogs.split("\n")));
        } else {
            // Fallback to legacy logs
            final var logHtml = Jsoup.parse(build.details().getConsoleOutputHtml()).body();
            buildLogEntries = JenkinsBuildLogParseUtils.parseLogsLegacy(logHtml);
        }
        // Filter and save build logs
        buildLogEntries = filterUnnecessaryLogs(buildLogEntries, programmingLanguage);
        buildLogEntries = buildLogService.saveBuildLogs(buildLogEntries, programmingSubmission);
        programmingSubmission.setBuildLogEntries(buildLogEntries);
        programmingSubmissionRepository.save(programmingSubmission);
        return buildLogEntries;
    } catch (IOException e) {
        log.error(e.getMessage(), e);
        throw new JenkinsException(e.getMessage(), e);
    }
}
Also used : ProgrammingExerciseParticipation(de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseParticipation) JenkinsException(de.tum.in.www1.artemis.exception.JenkinsException) ProgrammingLanguage(de.tum.in.www1.artemis.domain.enumeration.ProgrammingLanguage) IOException(java.io.IOException)

Example 4 with BuildLogEntry

use of de.tum.in.www1.artemis.domain.BuildLogEntry in project ArTEMiS by ls1intum.

the class JenkinsBuildLogParseUtils method parseBuildLogsFromJenkinsLogs.

/**
 * Parses build logs from Jenkins into BuildLogEntry objects. The function reads the list
 * of log strings and tries to parse lines of the following format:
 *
 * [2021-05-10T15:19:49.741Z] [INFO] BUILD FAILURE
 *
 * and extract the timestamp and message.
 *
 * A small snippet of the log format is:
 * [Pipeline] {
 * [Pipeline] sh
 * [2021-05-10T15:19:37.112Z] + mvn clean test -B
 * [2021-05-10T15:19:49.741Z] [INFO] BUILD FAILURE
 * ...
 * [2021-05-10T15:19:49.741Z] [ERROR] BubbleSort.java:[15,9] not a statement
 * [2021-05-10T15:19:49.741Z] [ERROR] BubbleSort.java:[15,10] ';' expected
 * [Pipeline] }
 * @param logLines The lines of the Jenkins log
 * @return a list of BuildLogEntries
 */
public static List<BuildLogEntry> parseBuildLogsFromJenkinsLogs(List<String> logLines) {
    final List<BuildLogEntry> buildLogs = new ArrayList<>();
    for (final var logLine : logLines) {
        // The build logs that we are interested in are the ones that start with a timestamp
        // of format [timestamp] ...
        final String possibleTimestamp = StringUtils.substringBetween(logLine, "[", "]");
        if (possibleTimestamp == null) {
            continue;
        }
        try {
            final ZonedDateTime timestamp = ZonedDateTime.parse(possibleTimestamp);
            // The 2 is used because the timestamp is surrounded with '[' ']'
            final String log = logLine.substring(possibleTimestamp.length() + 2);
            BuildLogEntry buildLogEntry = new BuildLogEntry(timestamp, stripLogEndOfLine(log).trim());
            buildLogs.add(buildLogEntry);
        } catch (DateTimeParseException e) {
        // The log line doesn't contain the timestamp so we ignore it
        }
    }
    return buildLogs;
}
Also used : BuildLogEntry(de.tum.in.www1.artemis.domain.BuildLogEntry) DateTimeParseException(java.time.format.DateTimeParseException) ZonedDateTime(java.time.ZonedDateTime) ArrayList(java.util.ArrayList)

Example 5 with BuildLogEntry

use of de.tum.in.www1.artemis.domain.BuildLogEntry in project ArTEMiS by ls1intum.

the class ProgrammingSubmissionAndResultGitlabJenkinsIntegrationTest method shouldReceiveBuildLogsOnNewStudentParticipationResult.

@Test
@WithMockUser(username = "student1", roles = "USER")
void shouldReceiveBuildLogsOnNewStudentParticipationResult() throws Exception {
    // Precondition: Database has participation and a programming submission.
    String userLogin = "student1";
    database.addCourseWithOneProgrammingExercise(false, false, ProgrammingLanguage.JAVA);
    ProgrammingExercise exercise = programmingExerciseRepository.findAllWithEagerParticipationsAndLegalSubmissions().get(1);
    var participation = database.addStudentParticipationForProgrammingExercise(exercise, userLogin);
    var submission = database.createProgrammingSubmission(participation, false);
    List<String> logs = new ArrayList<>();
    logs.add("[2021-05-10T15:19:49.740Z] [ERROR] BubbleSort.java:[15,9] not a statement");
    logs.add("[2021-05-10T15:19:49.740Z] [ERROR] BubbleSort.java:[15,10] ';' expected");
    var notification = createJenkinsNewResultNotification(exercise.getProjectKey(), userLogin, ProgrammingLanguage.JAVA, List.of());
    notification.setLogs(logs);
    postResult(notification, HttpStatus.OK);
    var submissionWithLogsOptional = submissionRepository.findWithEagerBuildLogEntriesById(submission.getId());
    assertThat(submissionWithLogsOptional).isPresent();
    // Assert that the submission contains build log entries
    ProgrammingSubmission submissionWithLogs = submissionWithLogsOptional.get();
    List<BuildLogEntry> buildLogEntries = submissionWithLogs.getBuildLogEntries();
    assertThat(buildLogEntries).hasSize(2);
    assertThat(buildLogEntries.get(0).getLog()).isEqualTo("[ERROR] BubbleSort.java:[15,9] not a statement");
    assertThat(buildLogEntries.get(1).getLog()).isEqualTo("[ERROR] BubbleSort.java:[15,10] ';' expected");
}
Also used : BuildLogEntry(de.tum.in.www1.artemis.domain.BuildLogEntry) ProgrammingSubmission(de.tum.in.www1.artemis.domain.ProgrammingSubmission) ArrayList(java.util.ArrayList) ProgrammingExercise(de.tum.in.www1.artemis.domain.ProgrammingExercise) WithMockUser(org.springframework.security.test.context.support.WithMockUser) AbstractSpringIntegrationJenkinsGitlabTest(de.tum.in.www1.artemis.AbstractSpringIntegrationJenkinsGitlabTest) Test(org.junit.jupiter.api.Test) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest)

Aggregations

BuildLogEntry (de.tum.in.www1.artemis.domain.BuildLogEntry)8 ArrayList (java.util.ArrayList)6 AbstractSpringIntegrationJenkinsGitlabTest (de.tum.in.www1.artemis.AbstractSpringIntegrationJenkinsGitlabTest)4 ProgrammingSubmission (de.tum.in.www1.artemis.domain.ProgrammingSubmission)4 ProgrammingLanguage (de.tum.in.www1.artemis.domain.enumeration.ProgrammingLanguage)4 ProgrammingExerciseParticipation (de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseParticipation)4 Test (org.junit.jupiter.api.Test)4 WithMockUser (org.springframework.security.test.context.support.WithMockUser)4 IOException (java.io.IOException)3 ZonedDateTime (java.time.ZonedDateTime)3 Logger (ch.qos.logback.classic.Logger)2 ProgrammingExercise (de.tum.in.www1.artemis.domain.ProgrammingExercise)2 JenkinsException (de.tum.in.www1.artemis.exception.JenkinsException)2 ProgrammingExerciseParticipationService (de.tum.in.www1.artemis.service.programming.ProgrammingExerciseParticipationService)2 LocalRepository (de.tum.in.www1.artemis.util.LocalRepository)2 Path (java.nio.file.Path)2 DateTimeParseException (java.time.format.DateTimeParseException)2 LinkedList (java.util.LinkedList)2 TextNode (org.jsoup.nodes.TextNode)2 BeforeEach (org.junit.jupiter.api.BeforeEach)2