From 7c801c0e29730fa0f73de0679c2a2a630ffc743d Mon Sep 17 00:00:00 2001 From: Abel Salgado Romero Date: Sat, 8 Mar 2025 21:26:46 +0100 Subject: [PATCH] Capture current file being converted and used if logRecord.cursor is null --- .../maven/log/CapturedLogRecord.java | 66 ++++++++++ .../maven/log/MemoryLogHandler.java | 19 ++- .../maven/log/LogRecordsProcessorsTest.java | 14 +-- .../maven/log/MemoryLogHandlerTest.java | 12 +- .../asciidoctor/maven/log/TestLogRecords.java | 43 +++++++ .../asciidoctor/maven/AsciidoctorMojo.java | 10 +- .../maven/AsciidoctorMojoLogHandlerTest.java | 116 ++++++++++++++---- 7 files changed, 226 insertions(+), 54 deletions(-) create mode 100644 asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/log/CapturedLogRecord.java create mode 100644 asciidoctor-maven-commons/src/test/java/org/asciidoctor/maven/log/TestLogRecords.java diff --git a/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/log/CapturedLogRecord.java b/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/log/CapturedLogRecord.java new file mode 100644 index 00000000..c45c96bb --- /dev/null +++ b/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/log/CapturedLogRecord.java @@ -0,0 +1,66 @@ +package org.asciidoctor.maven.log; + +import java.io.File; +import java.util.Optional; + +import org.asciidoctor.ast.Cursor; +import org.asciidoctor.log.LogRecord; + +/** + * {@link LogRecord} proxy that allows capturing the source file being + * processed. + * Important: the {@link #sourceFile} and the actual source where an error is present + * may not be the same. For example if the source is being included. + * + * @since 3.1.2 + */ +final class CapturedLogRecord extends LogRecord { + + private final File sourceFile; + + CapturedLogRecord(LogRecord record, File sourceFile) { + super(record.getSeverity(), record.getCursor(), record.getMessage(), record.getSourceFileName(), record.getSourceMethodName()); + this.sourceFile = sourceFile; + } + + public Cursor getCursor() { + if (sourceFile == null) + return null; + + return Optional.ofNullable(super.getCursor()) + .orElse(new FileCursor(sourceFile)); + } + + public File getSourceFile() { + return sourceFile; + } + + class FileCursor implements Cursor { + + private final File file; + + public FileCursor(File file) { + this.file = file; + } + + @Override + public int getLineNumber() { + return 0; + } + + @Override + public String getPath() { + return file.getName(); + } + + @Override + public String getDir() { + return file.getParent(); + } + + @Override + public String getFile() { + return file.getAbsolutePath(); + } + } +} diff --git a/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/log/MemoryLogHandler.java b/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/log/MemoryLogHandler.java index 13293aaf..6d109463 100644 --- a/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/log/MemoryLogHandler.java +++ b/asciidoctor-maven-commons/src/main/java/org/asciidoctor/maven/log/MemoryLogHandler.java @@ -1,5 +1,6 @@ package org.asciidoctor.maven.log; +import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; @@ -24,6 +25,14 @@ public class MemoryLogHandler implements LogHandler { private final Boolean outputToConsole; private final Consumer recordConsumer; + /** + * Provides simple way to inject the current file being processes. + * Will need re-work in concurrent scenarios. + * + * @since 3.1.2 + */ + private File currentFile; + public MemoryLogHandler(Boolean outputToConsole, Consumer recordConsumer) { this.outputToConsole = outputToConsole == null ? Boolean.FALSE : outputToConsole; this.recordConsumer = recordConsumer; @@ -31,9 +40,11 @@ public MemoryLogHandler(Boolean outputToConsole, Consumer recordConsu @Override public void log(LogRecord logRecord) { - records.add(logRecord); + final CapturedLogRecord record = new CapturedLogRecord(logRecord, currentFile); + + records.add(record); if (outputToConsole) - recordConsumer.accept(logRecord); + recordConsumer.accept(record); } public void clear() { @@ -108,4 +119,8 @@ private static boolean messageContains(LogRecord record, String text) { } } + public void setCurrentFile(File currentFile) { + this.currentFile = currentFile; + } + } diff --git a/asciidoctor-maven-commons/src/test/java/org/asciidoctor/maven/log/LogRecordsProcessorsTest.java b/asciidoctor-maven-commons/src/test/java/org/asciidoctor/maven/log/LogRecordsProcessorsTest.java index e41a30ae..490d4dd6 100644 --- a/asciidoctor-maven-commons/src/test/java/org/asciidoctor/maven/log/LogRecordsProcessorsTest.java +++ b/asciidoctor-maven-commons/src/test/java/org/asciidoctor/maven/log/LogRecordsProcessorsTest.java @@ -4,10 +4,10 @@ import java.util.List; import static org.asciidoctor.log.Severity.*; +import static org.asciidoctor.maven.log.TestLogRecords.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.catchThrowable; -import org.asciidoctor.log.LogRecord; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -256,16 +256,4 @@ private static MemoryLogHandler testMemoryLogHandler() { return memoryLogHandler; } - private static LogRecord errorMessage(int index) { - return new LogRecord(ERROR, "error message " + index); - } - - private static LogRecord getInfoMessage(int index) { - return new LogRecord(INFO, "info message " + index); - } - - private static LogRecord warningMessage(int index) { - return new LogRecord(WARN, "warning message " + index); - } - } diff --git a/asciidoctor-maven-commons/src/test/java/org/asciidoctor/maven/log/MemoryLogHandlerTest.java b/asciidoctor-maven-commons/src/test/java/org/asciidoctor/maven/log/MemoryLogHandlerTest.java index 4314ae96..10c58b52 100644 --- a/asciidoctor-maven-commons/src/test/java/org/asciidoctor/maven/log/MemoryLogHandlerTest.java +++ b/asciidoctor-maven-commons/src/test/java/org/asciidoctor/maven/log/MemoryLogHandlerTest.java @@ -3,6 +3,7 @@ import java.util.List; import static org.asciidoctor.log.Severity.*; +import static org.asciidoctor.maven.log.TestLogRecords.*; import static org.assertj.core.api.Assertions.assertThat; import org.asciidoctor.log.LogRecord; @@ -162,15 +163,4 @@ private static MemoryLogHandler testMemoryLogHandler() { return memoryLogHandler; } - private static LogRecord errorMessage() { - return new LogRecord(ERROR, "error message"); - } - - private static LogRecord getInfoMessage() { - return new LogRecord(INFO, "info message"); - } - - private static LogRecord warningMessage() { - return new LogRecord(WARN, "warning message"); - } } diff --git a/asciidoctor-maven-commons/src/test/java/org/asciidoctor/maven/log/TestLogRecords.java b/asciidoctor-maven-commons/src/test/java/org/asciidoctor/maven/log/TestLogRecords.java new file mode 100644 index 00000000..26dc8ce4 --- /dev/null +++ b/asciidoctor-maven-commons/src/test/java/org/asciidoctor/maven/log/TestLogRecords.java @@ -0,0 +1,43 @@ +package org.asciidoctor.maven.log; + +import java.util.Optional; + +import static org.asciidoctor.log.Severity.*; + +import org.asciidoctor.log.LogRecord; + +class TestLogRecords { + + static LogRecord errorMessage() { + return errorMessage(null); + } + + static LogRecord errorMessage(Integer index) { + LogRecord logRecord = new LogRecord(ERROR, buildMessage("error", index)); + return new CapturedLogRecord(logRecord, null); + } + + static LogRecord getInfoMessage() { + return getInfoMessage(null); + } + + static LogRecord getInfoMessage(Integer index) { + LogRecord logRecord = new LogRecord(INFO, buildMessage("info", index)); + return new CapturedLogRecord(logRecord, null); + } + + static LogRecord warningMessage() { + return warningMessage(null); + } + + static LogRecord warningMessage(Integer index) { + LogRecord logRecord = new LogRecord(WARN, buildMessage("warning", index)); + return new CapturedLogRecord(logRecord, null); + } + + private static String buildMessage(String type, Integer index) { + return Optional.ofNullable(index) + .map(i -> type + " message " + index) + .orElse(type + " message"); + } +} diff --git a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorMojo.java b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorMojo.java index 208ead00..34ad57dd 100644 --- a/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorMojo.java +++ b/asciidoctor-maven-plugin/src/main/java/org/asciidoctor/maven/AsciidoctorMojo.java @@ -242,7 +242,6 @@ public void processSources(List sourceFiles, ResourcesProcessor resourcesP final Set uniquePaths = new HashSet<>(); for (int i = 0; i < sourceFiles.size(); i++) { -// for (final File source : sourceFiles) { final File source = sourceFiles.get(i); final Destination destination = setDestinationPaths(source, optionsBuilder, sourceDir, this); final File destinationPath = destination.path; @@ -255,8 +254,8 @@ public void processSources(List sourceFiles, ResourcesProcessor resourcesP getLog().warn("Duplicated destination found: overwriting file: " + destinationFile); } - boolean lastFile = i == (sourceFiles.size() - 1); - convertFile(asciidoctor, optionsBuilder.build(), source, sourceDir, memoryLogHandler, lastFile); + boolean processLogRecords = logHandler.getFailFast() || (i == (sourceFiles.size() - 1)); + convertFile(asciidoctor, optionsBuilder.build(), source, sourceDir, memoryLogHandler, processLogRecords); } } @@ -354,10 +353,11 @@ protected List findSourceFiles(File sourceDirectory) { finder.find(sourceDirectoryPath, sourceDocumentExtensions); } - private void convertFile(Asciidoctor asciidoctor, Options options, File f, File sourceDir, MemoryLogHandler memoryLogHandler, boolean lastFile) throws MojoExecutionException { + private void convertFile(Asciidoctor asciidoctor, Options options, File f, File sourceDir, MemoryLogHandler memoryLogHandler, boolean processLogRecords) throws MojoExecutionException { + memoryLogHandler.setCurrentFile(f); asciidoctor.convertFile(f, options); logConvertedFile(f); - if (logHandler.getFailFast() || lastFile) { + if (processLogRecords) { processLogRecords(sourceDir, memoryLogHandler); } } diff --git a/asciidoctor-maven-plugin/src/test/java/org/asciidoctor/maven/AsciidoctorMojoLogHandlerTest.java b/asciidoctor-maven-plugin/src/test/java/org/asciidoctor/maven/AsciidoctorMojoLogHandlerTest.java index 1a2ab457..0906a3fc 100644 --- a/asciidoctor-maven-plugin/src/test/java/org/asciidoctor/maven/AsciidoctorMojoLogHandlerTest.java +++ b/asciidoctor-maven-plugin/src/test/java/org/asciidoctor/maven/AsciidoctorMojoLogHandlerTest.java @@ -6,23 +6,26 @@ import java.util.Map; import java.util.stream.Collectors; +import static org.asciidoctor.log.Severity.*; +import static org.asciidoctor.maven.io.TestFilesHelper.newOutputTestDirectory; +import static org.asciidoctor.maven.test.TestUtils.mockAsciidoctorMojo; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; + +import lombok.extern.slf4j.Slf4j; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; +import org.asciidoctor.log.Severity; import org.asciidoctor.maven.io.ConsoleHolder; import org.asciidoctor.maven.log.FailIf; import org.asciidoctor.maven.log.LogHandler; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import static org.asciidoctor.log.Severity.ERROR; -import static org.asciidoctor.log.Severity.WARN; -import static org.asciidoctor.maven.io.TestFilesHelper.newOutputTestDirectory; -import static org.asciidoctor.maven.test.TestUtils.mockAsciidoctorMojo; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowable; - +@Slf4j class AsciidoctorMojoLogHandlerTest { private static final String DEFAULT_SOURCE_DIRECTORY = "target/test-classes/src/asciidoctor"; @@ -212,9 +215,7 @@ void should_fail_when_logHandler_failIf_is_WARNING() { File srcDir = new File(DEFAULT_SOURCE_DIRECTORY); File outputDir = newOutputTestDirectory("logHandler"); final LogHandler logHandler = new LogHandler(); - FailIf failIf = new FailIf(); - failIf.setSeverity(WARN); - logHandler.setFailIf(failIf); + logHandler.setFailIf(failIf(WARN)); // when AsciidoctorMojo mojo = mockAsciidoctorMojo(logHandler); @@ -238,9 +239,7 @@ void should_fail_when_logHandler_failIf_is_ERROR() { File srcDir = new File(DEFAULT_SOURCE_DIRECTORY); File outputDir = newOutputTestDirectory("logHandler"); final LogHandler logHandler = new LogHandler(); - FailIf failIf = new FailIf(); - failIf.setSeverity(ERROR); - logHandler.setFailIf(failIf); + logHandler.setFailIf(failIf(ERROR)); // when AsciidoctorMojo mojo = mockAsciidoctorMojo(logHandler); @@ -398,44 +397,115 @@ void should_not_print_default_AsciidoctorJ_messages() throws MojoFailureExceptio class WithFailOn { @Test - void should_print_messages_for_all_sources_when_failFast_is_false() throws MojoFailureException, MojoExecutionException { + void should_fail_and_print_messages_for_all_sources_when_failFast_is_false() { // setup final ConsoleHolder consoleHolder = ConsoleHolder.start(); File srcDir = new File(DEFAULT_SOURCE_DIRECTORY, "errors"); File outputDir = newOutputTestDirectory("logHandler"); LogHandler logHandler = new LogHandler(); - logHandler.setOutputToConsole(true); + logHandler.setOutputToConsole(false); + logHandler.setFailFast(false); + logHandler.setFailIf(failIf(DEBUG)); // when AsciidoctorMojo mojo = mockAsciidoctorMojo(logHandler); mojo.backend = "html"; mojo.sourceDirectory = srcDir; mojo.outputDirectory = outputDir; - mojo.execute(); + Throwable throwable = catchThrowable(mojo::execute); // then - List asciidoctorMessages = Arrays.stream(consoleHolder.getOutput().split("\n")) + assertThat(throwable) + .isInstanceOf(MojoExecutionException.class) + .hasMessageContaining("Found 6 issue(s) of severity DEBUG or higher during conversion"); + + long convertedFiles = Arrays.stream(consoleHolder.getOutput().split("\n")) + .filter(line -> line.contains("Converted")) + .count(); + assertThat(convertedFiles).isEqualTo(3); + + List errorMessages = Arrays.stream(consoleHolder.getError().split("\n")) .filter(line -> line.contains("asciidoctor:")) .collect(Collectors.toList()); - assertThat(asciidoctorMessages) + assertThat(errorMessages) .hasSize(6); + assertThat(errorMessages.get(0)) + .contains(fixOsSeparator("[error] asciidoctor: ERROR: document-with-missing-include.adoc: line 3: include file not found:")); + assertThat(errorMessages.get(1)) + .contains(fixOsSeparator("[error] asciidoctor: ERROR: document-with-missing-include.adoc: line 5: include file not found:")); + assertThat(errorMessages.get(2)) + .contains(fixOsSeparator("[error] asciidoctor: ERROR: document-with-missing-include.adoc: line 9: include file not found:")); + assertThat(errorMessages.get(3)) + .contains(fixOsSeparator("[error] asciidoctor: WARN: document-with-missing-include.adoc: line 25: no callout found for <1>")); + assertThat(errorMessages.get(4)) + .contains(fixOsSeparator("[error] asciidoctor: INFO: document-with-invalid-reference.adoc: possible invalid reference: ../path/some-file.adoc")); + assertThat(errorMessages.get(5)) + .contains(fixOsSeparator("[error] asciidoctor: INFO: document-with-invalid-reference.adoc: possible invalid reference: section-id")); + // cleanup + consoleHolder.release(); + } + + @Test + void should_fail_and_print_messages_for_all_sources_when_failFast_is_true() { + // setup + final ConsoleHolder consoleHolder = ConsoleHolder.start(); + + File srcDir = new File(DEFAULT_SOURCE_DIRECTORY, "errors"); + File outputDir = newOutputTestDirectory("logHandler"); + LogHandler logHandler = new LogHandler(); + logHandler.setOutputToConsole(false); + logHandler.setFailFast(true); + logHandler.setFailIf(failIf(DEBUG)); + + // when + AsciidoctorMojo mojo = mockAsciidoctorMojo(logHandler); + mojo.backend = "html"; + mojo.sourceDirectory = srcDir; + mojo.outputDirectory = outputDir; + Throwable throwable = catchThrowable(mojo::execute); + + // then + assertThat(throwable) + .isInstanceOf(MojoExecutionException.class) + .hasMessageContaining("Found 4 issue(s) of severity DEBUG or higher during conversion"); + + long convertedFiles = Arrays.stream(consoleHolder.getOutput().split("\n")) + .filter(line -> line.contains("Converted")) + .count(); + assertThat(convertedFiles).isEqualTo(1); + + List asciidoctorMessages = Arrays.stream(consoleHolder.getError().split("\n")) + .filter(line -> line.contains("asciidoctor:")) + .collect(Collectors.toList()); + + assertThat(asciidoctorMessages) + .hasSize(4); assertThat(asciidoctorMessages.get(0)) - .contains(fixOsSeparator("[info] asciidoctor: ERROR: errors/document-with-missing-include.adoc: line 3: include file not found:")); + .contains(fixOsSeparator("[error] asciidoctor: ERROR: document-with-missing-include.adoc: line 3: include file not found:")); assertThat(asciidoctorMessages.get(1)) - .contains(fixOsSeparator("[info] asciidoctor: ERROR: errors/document-with-missing-include.adoc: line 5: include file not found:")); + .contains(fixOsSeparator("[error] asciidoctor: ERROR: document-with-missing-include.adoc: line 5: include file not found:")); assertThat(asciidoctorMessages.get(2)) - .contains(fixOsSeparator("[info] asciidoctor: ERROR: errors/document-with-missing-include.adoc: line 9: include file not found:")); + .contains(fixOsSeparator("[error] asciidoctor: ERROR: document-with-missing-include.adoc: line 9: include file not found:")); assertThat(asciidoctorMessages.get(3)) - .contains(fixOsSeparator("[info] asciidoctor: WARN: errors/document-with-missing-include.adoc: line 25: no callout found for <1>")); - + .contains(fixOsSeparator("[error] asciidoctor: WARN: document-with-missing-include.adoc: line 25: no callout found for <1>")); +// assertThat(asciidoctorMessages.get(4)) +// .contains(fixOsSeparator("[error] asciidoctor: INFO: document-with-invalid-reference.adoc: possible invalid reference: ../path/some-file.adoc")); +// assertThat(asciidoctorMessages.get(5)) +// .contains(fixOsSeparator("[error] asciidoctor: INFO: document-with-invalid-reference.adoc: possible invalid reference: section-id")); // cleanup consoleHolder.release(); } } + private static FailIf failIf(Severity severity) { + FailIf failIf = new FailIf(); + failIf.setSeverity(severity); + return failIf; + } + private List getOutputInfoLines(ConsoleHolder consoleHolder) { final String lineSeparator = lineSeparator(); return Arrays.stream(consoleHolder.getOutput().split(lineSeparator))