All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.camunda.zeebe.ZeebeConsoleOutputFileReporter Maven / Gradle / Ivy

The newest version!
/*
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
 * one or more contributor license agreements. See the NOTICE file distributed
 * with this work for additional information regarding copyright ownership.
 * Licensed under the Camunda License 1.0. You may not use this file
 * except in compliance with the Camunda License 1.0.
 */
package io.camunda.zeebe;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.maven.plugin.surefire.booterclient.output.InPluginProcessDumpSingleton;
import org.apache.maven.plugin.surefire.report.ConsoleOutputFileReporter;
import org.apache.maven.plugin.surefire.report.FileReporter;
import org.apache.maven.surefire.api.report.TestOutputReportEntry;
import org.apache.maven.surefire.api.report.TestSetReportEntry;
import org.apache.maven.surefire.extensions.ConsoleOutputReportEventListener;

/**
 * A {@link ConsoleOutputReportEventListener} which will save a test's previous run's output instead
 * of overwriting it. This is somewhat hacky, as the canonical implementation {@link
 * ConsoleOutputFileReporter} is not really extensible. So instead we "predict" the output path that
 * it will use, and if it was present, we copy it in order to preserve it. If we see that the
 * canonical implementation changes too much, then we could simply make our own to stabilize things,
 * but for now I think this is acceptable.
 *
 * 

By using this implementation, it now works like this: * *

    *
  • All calls are delegated to an instance of the canonical implementation *
  • When {@link #writeTestOutput(TestOutputReportEntry)} is called, we predict the path by * calling {@link FileReporter#getReportFile(File, String, String, String)} via reflection * just like the canonical implementation does *
  • If a file exists at this path, extract the latest run count from the other existing files; * we opted against keeping an in-memory cache, as on long builds this could use too much * memory */ public final class ZeebeConsoleOutputFileReporter implements ConsoleOutputReportEventListener { private static final Pattern RUN_COUNT_MATCHER = Pattern.compile("^.+?-(\\d)-output\\.txt$"); private static final String OUTPUT_FILE_EXTENSION = "-output.txt"; private final File reportsDirectory; private final String reportNameSuffix; private final boolean usePhrasedFileName; private final Integer forkNumber; private final ConsoleOutputReportEventListener delegate; private volatile String reportEntryName; public ZeebeConsoleOutputFileReporter( final File reportsDirectory, final String reportNameSuffix, final boolean usePhrasedFileName, final Integer forkNumber, final ConsoleOutputReportEventListener delegate) { this.reportsDirectory = reportsDirectory; this.reportNameSuffix = reportNameSuffix; this.usePhrasedFileName = usePhrasedFileName; this.forkNumber = forkNumber; this.delegate = delegate; } @Override public synchronized void testSetStarting(final TestSetReportEntry reportEntry) { reportEntryName = usePhrasedFileName ? reportEntry.getSourceText() : reportEntry.getSourceName(); delegate.testSetStarting(reportEntry); } @Override public synchronized void testSetCompleted(final TestSetReportEntry report) { delegate.testSetStarting(report); } @Override public synchronized void close() { delegate.close(); } @Override public synchronized void writeTestOutput(final TestOutputReportEntry reportEntry) { try { final Path reportPath = getReportPath(); final String fileName = reportPath.getFileName().toString().replace(OUTPUT_FILE_EXTENSION, ""); final Path backupPath = reportPath.resolveSibling( fileName + "-" + computeRunCount(reportPath, fileName) + OUTPUT_FILE_EXTENSION); // write the report first; if anything was written, we then move it with the appropriate run // count in its name delegate.writeTestOutput(reportEntry); if (Files.exists(reportPath)) { Files.move(reportPath, backupPath); } } catch (final Exception e) { dumpException(e); sneakyThrow(e); } } private void dumpException(final Exception e) { if (forkNumber == null) { InPluginProcessDumpSingleton.getSingleton() .dumpException(e, e.getLocalizedMessage(), reportsDirectory); } else { InPluginProcessDumpSingleton.getSingleton() .dumpException(e, e.getLocalizedMessage(), reportsDirectory, forkNumber); } } private int computeRunCount(final Path path, final String fileName) throws IOException { final Path directory = path.getParent(); int latestRunCount = 0; try (final DirectoryStream files = Files.newDirectoryStream(directory, p -> filterTestOutputFiles(fileName, p))) { for (final Path file : files) { final Matcher matcher = RUN_COUNT_MATCHER.matcher(file.getFileName().toString()); if (matcher.find()) { final String runCount = matcher.group(1); latestRunCount = Math.max(latestRunCount, Integer.parseInt(runCount)); } } } return latestRunCount + 1; } private Path getReportPath() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { final Method delegate = FileReporter.class.getDeclaredMethod( "getReportFile", File.class, String.class, String.class, String.class); delegate.setAccessible(true); final File file = (File) delegate.invoke( null, reportsDirectory, reportEntryName, reportNameSuffix, OUTPUT_FILE_EXTENSION); return file.toPath(); } private boolean filterTestOutputFiles(final String fileName, final Path path) { return path.getFileName().toString().startsWith(fileName) && path.getFileName().toString().endsWith(OUTPUT_FILE_EXTENSION); } @SuppressWarnings("unchecked") private void sneakyThrow(final Throwable t) throws T { throw (T) t; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy