diff --git a/src/main/java/org/asciidoctor/maven/AsciidoctorHttpMojo.java b/src/main/java/org/asciidoctor/maven/AsciidoctorHttpMojo.java index 91232c61..207cfa2f 100644 --- a/src/main/java/org/asciidoctor/maven/AsciidoctorHttpMojo.java +++ b/src/main/java/org/asciidoctor/maven/AsciidoctorHttpMojo.java @@ -46,6 +46,7 @@ protected void doWork() throws MojoFailureException, MojoExecutionException { server.start(); super.doWork(); + super.doWait(); server.stop(); } diff --git a/src/main/java/org/asciidoctor/maven/AsciidoctorRefreshMojo.java b/src/main/java/org/asciidoctor/maven/AsciidoctorRefreshMojo.java index 69e55a7a..ce792b61 100644 --- a/src/main/java/org/asciidoctor/maven/AsciidoctorRefreshMojo.java +++ b/src/main/java/org/asciidoctor/maven/AsciidoctorRefreshMojo.java @@ -48,6 +48,7 @@ public class AsciidoctorRefreshMojo extends AsciidoctorMojo { public void execute() throws MojoExecutionException, MojoFailureException { startPolling(); doWork(); + doWait(); stopMonitors(); } @@ -60,7 +61,6 @@ protected void doWork() throws MojoFailureException, MojoExecutionException { } }); getLog().info("Converted document(s) in " + timeInMillis + "ms"); - doWait(); } protected void doWait() throws MojoExecutionException, MojoFailureException { @@ -71,7 +71,7 @@ protected void doWait() throws MojoExecutionException, MojoFailureException { while ((line = scanner.nextLine()) != null) { line = line.trim(); if ("exit".equalsIgnoreCase(line) || "quit".equalsIgnoreCase(line)) { - break; + return; } if ("refresh".equalsIgnoreCase(line)) { diff --git a/src/test/java/org/asciidoctor/maven/AsciidoctorRefreshMojoTest.java b/src/test/java/org/asciidoctor/maven/AsciidoctorRefreshMojoTest.java index 3145267f..93eb69b7 100644 --- a/src/test/java/org/asciidoctor/maven/AsciidoctorRefreshMojoTest.java +++ b/src/test/java/org/asciidoctor/maven/AsciidoctorRefreshMojoTest.java @@ -18,6 +18,7 @@ import java.io.File; import java.io.IOException; import java.util.Arrays; +import java.util.UUID; import java.util.function.Consumer; import static java.nio.charset.StandardCharsets.UTF_8; @@ -55,11 +56,115 @@ public AsciidoctorRefreshMojo newFakeRefreshMojo() { return mojo; } + @Test - public void should_auto_convert_file_with_custom_file_extension_when_source_is_updated() throws IOException { + public void should_stop_with_exit_command() { + // given + final ConsoleHolder consoleHolder = ConsoleHolder.start(); + + final File srcDir = newOutputTestDirectory("refresh-mojo"); + final File outputDir = newOutputTestDirectory("refresh-mojo"); + + Thread mojoThread = runMojoAsynchronously(srcDir, outputDir); + + // when + consoleHolder.input("exit"); + + // then + consoleHolder.awaitProcessingAllSources(); + consoleHolder.release(); + awaitTermination(mojoThread); + } + @Test + public void should_stop_with_quit_command() { // given - final ConsoleHolder consoleHolder = ConsoleHolder.hold(); + final ConsoleHolder consoleHolder = ConsoleHolder.start(); + + final File srcDir = newOutputTestDirectory("refresh-mojo"); + final File outputDir = newOutputTestDirectory("refresh-mojo"); + + Thread mojoThread = runMojoAsynchronously(srcDir, outputDir); + + // when + consoleHolder.input("quit"); + + // then + consoleHolder.awaitProcessingAllSources(); + consoleHolder.release(); + awaitTermination(mojoThread); + } + + @Test + public void should_show_tip_when_command_is_not_valid() { + // given + final ConsoleHolder consoleHolder = ConsoleHolder.start(); + + final File srcDir = newOutputTestDirectory("refresh-mojo"); + final File outputDir = newOutputTestDirectory("refresh-mojo"); + + Thread mojoThread = runMojoAsynchronously(srcDir, outputDir); + consoleHolder.awaitProcessingAllSources(); + // when + consoleHolder.input("not_a_command"); + // then + consoleHolder.awaitForMessage("'not_a_command' not understood, available commands are [quit, exit, refresh]"); + // then + consoleHolder.input("exit"); + consoleHolder.release(); + awaitTermination(mojoThread); + } + + @Test + public void should_only_auto_convert_file_with_custom_sourceDocumentName_when_source_is_updated() throws IOException { + // given + final ConsoleHolder consoleHolder = ConsoleHolder.start(); + + final File srcDir = newOutputTestDirectory("refresh-mojo"); + final File outputDir = newOutputTestDirectory("refresh-mojo"); + + final String fileExtension = "adoc"; + final File sourceFile = new File(srcDir, "my-sourceFile-" + UUID.randomUUID() + "." + fileExtension); + final File ignoredFile = new File(srcDir, "extra-sourceFile." + fileExtension); + + // when + FileUtils.write(sourceFile, "= Document Title\n\nThis is test, only a test.", UTF_8); + Thread mojoThread = runMojoAsynchronously(mojo -> { + mojo.backend = "html5"; + mojo.sourceDirectory = srcDir; + mojo.outputDirectory = outputDir; + mojo.sourceDocumentName = sourceFile.getName(); + }); + + // then + final File target = new File(outputDir, sourceFile.getName().replace(fileExtension, "html")); + consoleHolder.awaitProcessingAllSources(); + assertThat(FileUtils.readFileToString(target, UTF_8)) + .contains("This is test, only a test"); + final File ignoredTarget = new File(outputDir, ignoredFile.getName().replace(fileExtension, "html")); + assertThat(ignoredTarget) + .doesNotExist(); + + // and when + FileUtils.write(sourceFile, "= Document Title\n\nWow, this will be auto refreshed !", UTF_8); + + // then + consoleHolder.awaitProcessingSource(); + assertThat(FileUtils.readFileToString(target, UTF_8)) + .contains("Wow, this will be auto refreshed"); + assertThat(ignoredTarget) + .doesNotExist(); + + // cleanup + consoleHolder.input("exit"); + consoleHolder.release(); + awaitTermination(mojoThread); + } + + @Test + public void should_auto_convert_file_with_custom_file_extension_when_source_is_updated() throws IOException { + // given + final ConsoleHolder consoleHolder = ConsoleHolder.start(); final File srcDir = newOutputTestDirectory("refresh-mojo"); final File outputDir = newOutputTestDirectory("refresh-mojo"); @@ -69,7 +174,7 @@ public void should_auto_convert_file_with_custom_file_extension_when_source_is_u // when FileUtils.write(sourceFile, "= Document Title\n\nThis is test, only a test.", UTF_8); - runMojoAsynchronously(mojo -> { + Thread mojoThread = runMojoAsynchronously(mojo -> { mojo.backend = "html5"; mojo.sourceDirectory = srcDir; mojo.outputDirectory = outputDir; @@ -91,14 +196,15 @@ public void should_auto_convert_file_with_custom_file_extension_when_source_is_u .contains("Wow, this will be auto refreshed"); // cleanup + consoleHolder.input("exit"); consoleHolder.release(); + awaitTermination(mojoThread); } @Test public void should_auto_convert_file_in_root_when_source_is_updated() throws IOException { - // given - final ConsoleHolder consoleHolder = ConsoleHolder.hold(); + final ConsoleHolder consoleHolder = ConsoleHolder.start(); final File srcDir = newOutputTestDirectory("refresh-mojo"); final File outputDir = newOutputTestDirectory("refresh-mojo"); @@ -107,7 +213,7 @@ public void should_auto_convert_file_in_root_when_source_is_updated() throws IOE // when FileUtils.write(sourceFile, "= Document Title\n\nThis is test, only a test.", UTF_8); - runMojoAsynchronously(srcDir, outputDir); + Thread mojoThread = runMojoAsynchronously(srcDir, outputDir); // then final File target = new File(outputDir, sourceFile.getName().replace(".asciidoc", ".html")); @@ -124,14 +230,15 @@ public void should_auto_convert_file_in_root_when_source_is_updated() throws IOE .contains("Wow, this will be auto refreshed"); // cleanup + consoleHolder.input("exit"); consoleHolder.release(); + awaitTermination(mojoThread); } @Test public void should_auto_convert_file_in_subDir_when_source_is_updated() throws IOException { - // given - final ConsoleHolder consoleHolder = ConsoleHolder.hold(); + final ConsoleHolder consoleHolder = ConsoleHolder.start(); final File srcDir = newOutputTestDirectory("refresh-mojo"); final File outputDir = newOutputTestDirectory("refresh-mojo"); @@ -140,7 +247,7 @@ public void should_auto_convert_file_in_subDir_when_source_is_updated() throws I // when FileUtils.write(sourceFile, "= Document Title\n\nThis is test, only a test.", UTF_8); - runMojoAsynchronously(srcDir, outputDir); + Thread mojoThread = runMojoAsynchronously(srcDir, outputDir); // then File target = new File(outputDir, sourceFile.getName().replace(".asciidoc", ".html")); @@ -157,14 +264,15 @@ public void should_auto_convert_file_in_subDir_when_source_is_updated() throws I .contains("Wow, this will be auto refreshed"); // cleanup + consoleHolder.input("exit"); consoleHolder.release(); + awaitTermination(mojoThread); } @Test public void should_auto_convert_file_when_new_source_is_created() throws IOException { - // given - final ConsoleHolder consoleHolder = ConsoleHolder.hold(); + final ConsoleHolder consoleHolder = ConsoleHolder.start(); final File srcDir = newOutputTestDirectory("refresh-mojo"); final File outputDir = newOutputTestDirectory("refresh-mojo"); @@ -173,7 +281,7 @@ public void should_auto_convert_file_when_new_source_is_created() throws IOExcep // when FileUtils.write(sourceFile, "= Document Title\n\nThis is test, only a test.", UTF_8); - runMojoAsynchronously(srcDir, outputDir); + Thread mojoThread = runMojoAsynchronously(srcDir, outputDir); // then File target = new File(outputDir, sourceFile.getName().replace(".asciidoc", ".html")); @@ -194,14 +302,15 @@ public void should_auto_convert_file_when_new_source_is_created() throws IOExcep .contains("This is test, only a test"); // cleanup + consoleHolder.input("exit"); consoleHolder.release(); + awaitTermination(mojoThread); } @Test public void should_copy_resources_when_updated_but_not_on_start_when_there_are_no_sources() throws IOException { - // given - final ConsoleHolder consoleHolder = ConsoleHolder.hold(); + final ConsoleHolder consoleHolder = ConsoleHolder.start(); final File srcDir = newOutputTestDirectory("refresh-mojo"); final File outputDir = newOutputTestDirectory("refresh-mojo"); @@ -210,7 +319,7 @@ public void should_copy_resources_when_updated_but_not_on_start_when_there_are_n // when FileUtils.write(resourceFile, "Supposedly image content", UTF_8); - runMojoAsynchronously(srcDir, outputDir); + Thread mojoThread = runMojoAsynchronously(srcDir, outputDir); // then final File target = new File(outputDir, resourceFile.getName()); @@ -227,14 +336,15 @@ public void should_copy_resources_when_updated_but_not_on_start_when_there_are_n .isEqualTo("Supposedly image content UPDATED!"); // cleanup + consoleHolder.input("exit"); consoleHolder.release(); + awaitTermination(mojoThread); } @Test public void should_copy_resource_in_root_when_resource_is_updated() throws IOException { - // given - final ConsoleHolder consoleHolder = ConsoleHolder.hold(); + final ConsoleHolder consoleHolder = ConsoleHolder.start(); final File srcDir = newOutputTestDirectory("refresh-mojo"); final File outputDir = newOutputTestDirectory("refresh-mojo"); @@ -245,7 +355,7 @@ public void should_copy_resource_in_root_when_resource_is_updated() throws IOExc // when FileUtils.write(resourceFile, "Supposedly image content", UTF_8); - runMojoAsynchronously(srcDir, outputDir); + Thread mojoThread = runMojoAsynchronously(srcDir, outputDir); // then final File target = new File(outputDir, resourceFile.getName()); @@ -262,14 +372,15 @@ public void should_copy_resource_in_root_when_resource_is_updated() throws IOExc .isEqualTo("Supposedly image content UPDATED!"); // cleanup + consoleHolder.input("exit"); consoleHolder.release(); + awaitTermination(mojoThread); } @Test public void should_copy_resource_in_subDir_when_resource_is_updated() throws IOException { - // given - final ConsoleHolder consoleHolder = ConsoleHolder.hold(); + final ConsoleHolder consoleHolder = ConsoleHolder.start(); final File srcDir = newOutputTestDirectory("refresh-mojo"); final File outputDir = newOutputTestDirectory("refresh-mojo"); @@ -281,7 +392,7 @@ public void should_copy_resource_in_subDir_when_resource_is_updated() throws IOE // when FileUtils.write(resourceFile, "Supposedly image content", UTF_8); - runMojoAsynchronously(srcDir, outputDir); + Thread mojoThread = runMojoAsynchronously(srcDir, outputDir); // then final File target = new File(subDirectory, resourceFile.getName()); @@ -298,14 +409,15 @@ public void should_copy_resource_in_subDir_when_resource_is_updated() throws IOE .isEqualTo("Supposedly image content UPDATED!"); // cleanup + consoleHolder.input("exit"); consoleHolder.release(); + awaitTermination(mojoThread); } @Test public void should_copy_resource_when_new_resource_is_created() throws IOException { - // given - final ConsoleHolder consoleHolder = ConsoleHolder.hold(); + final ConsoleHolder consoleHolder = ConsoleHolder.start(); final File srcDir = newOutputTestDirectory("refresh-mojo"); final File outputDir = newOutputTestDirectory("refresh-mojo"); @@ -317,7 +429,7 @@ public void should_copy_resource_when_new_resource_is_created() throws IOExcepti // when FileUtils.write(resourceFile, "Supposedly image content", UTF_8); - runMojoAsynchronously(srcDir, outputDir); + Thread mojoThread = runMojoAsynchronously(srcDir, outputDir); // then final File target = new File(subDirectory, resourceFile.getName()); @@ -337,10 +449,41 @@ public void should_copy_resource_when_new_resource_is_created() throws IOExcepti .exists(); // cleanup + consoleHolder.input("exit"); consoleHolder.release(); + awaitTermination(mojoThread); } - private void runMojoAsynchronously(Consumer mojoConfigurator) { + @Test + public void should_run_full_convert_with_refresh_command() throws IOException { + // given + final ConsoleHolder consoleHolder = ConsoleHolder.start(); + + final File srcDir = newOutputTestDirectory("refresh-mojo"); + final File outputDir = newOutputTestDirectory("refresh-mojo"); + + final File sourceFile = new File(srcDir, "sourceFile.asciidoc"); + + // when + FileUtils.write(sourceFile, "= Document Title\n\nThis is test, only a test.", UTF_8); + Thread mojoThread = runMojoAsynchronously(srcDir, outputDir); + + // then + consoleHolder.awaitProcessingAllSources(); + + // and when + consoleHolder.input("refresh"); + + // then + consoleHolder.awaitProcessingAllSources(); + + // cleanup + consoleHolder.input("exit"); + consoleHolder.release(); + awaitTermination(mojoThread); + } + + private Thread runMojoAsynchronously(Consumer mojoConfigurator) { final AsciidoctorRefreshMojo mojo = newFakeRefreshMojo(); mojoConfigurator.accept(mojo); Thread mojoThread = new Thread(() -> { @@ -348,17 +491,30 @@ private void runMojoAsynchronously(Consumer mojoConfigur mojo.execute(); } catch (MojoExecutionException | MojoFailureException e) { } - System.out.println("end"); }); mojoThread.start(); + return mojoThread; } - private void runMojoAsynchronously(File srcDir, File outputDir) { - runMojoAsynchronously(mojo -> { + private Thread runMojoAsynchronously(File srcDir, File outputDir) { + return runMojoAsynchronously(mojo -> { mojo.backend = "html5"; mojo.sourceDirectory = srcDir; mojo.outputDirectory = outputDir; }); } + @SneakyThrows + private void awaitTermination(Thread thread) { + int pollTime = 250; + int ticks = (10 * 1000 / pollTime); + while (thread.isAlive()) { + ticks--; + if (ticks == 0) + throw new InterruptedException("Max wait time reached"); + else + Thread.sleep(pollTime); + } + } + } diff --git a/src/test/java/org/asciidoctor/maven/io/ConsoleHolder.java b/src/test/java/org/asciidoctor/maven/io/ConsoleHolder.java index 5f2742de..52a23ff9 100644 --- a/src/test/java/org/asciidoctor/maven/io/ConsoleHolder.java +++ b/src/test/java/org/asciidoctor/maven/io/ConsoleHolder.java @@ -5,7 +5,6 @@ import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.PrintStream; -import java.util.concurrent.CountDownLatch; /** @@ -15,8 +14,6 @@ */ public class ConsoleHolder { - private final CountDownLatch inputLatch = new CountDownLatch(1); - private PrintStream originalOut; private InputStream originalIn; @@ -26,15 +23,14 @@ public class ConsoleHolder { private ConsoleHolder() { } - public static ConsoleHolder hold() { + public static ConsoleHolder start() { final ConsoleHolder holder = new ConsoleHolder(); holder.originalOut = System.out; holder.originalIn = System.in; holder.newOut = new DoubleOutputStream(holder.originalOut); - holder.newIn = new PrefilledInputStream("exit\r\n".getBytes(), holder.inputLatch); - + holder.newIn = new StringsCollectionsInputStream(); System.setOut(new PrintStream(holder.newOut)); System.setIn(holder.newIn); @@ -53,12 +49,18 @@ public void awaitProcessingResource() { awaitForMessage("Copied resource in"); } + int cursor = 0; + @SneakyThrows - private void awaitForMessage(String message) { + public void awaitForMessage(String message) { int pollTime = 300; int ticks = (10 * 1000 / pollTime); while (true) { - if (!!new String(newOut.toByteArray()).contains(message)) break; + int pos = new String(newOut.toByteArray()).indexOf(message, cursor); + if (pos > 0) { + cursor = pos + message.length(); + break; + } ticks--; if (ticks == 0) throw new InterruptedException("Max wait time reached"); @@ -67,9 +69,13 @@ private void awaitForMessage(String message) { } } + @SneakyThrows + public void input(String command) { + ((StringsCollectionsInputStream) newIn).publishLine(command); + } + public void release() { System.setOut(originalOut); - inputLatch.countDown(); System.setIn(originalIn); } diff --git a/src/test/java/org/asciidoctor/maven/io/StringsCollectionsInputStream.java b/src/test/java/org/asciidoctor/maven/io/StringsCollectionsInputStream.java new file mode 100644 index 00000000..5f92d311 --- /dev/null +++ b/src/test/java/org/asciidoctor/maven/io/StringsCollectionsInputStream.java @@ -0,0 +1,64 @@ +package org.asciidoctor.maven.io; + +import lombok.SneakyThrows; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Reads from a collection of strings to simulate command line inputs. + * {@link InputStream#read(byte[], int, int)} will wait indefinitely + * until a new line is published with {@link #publishLine(String)}. + */ +class StringsCollectionsInputStream extends InputStream { + + private static final String LINEBREAK = "\r\n"; + + private AtomicInteger index = new AtomicInteger(0); + private final List lines = new ArrayList<>(); + + private volatile Semaphore mutex = new Semaphore(1); + + @SneakyThrows + public StringsCollectionsInputStream() { + mutex.acquire(); + } + + @SneakyThrows + @Override + public int read() throws IOException { + mutex.acquire(); + int indexValue = index.get(); + return indexValue >= lines.size() ? -1 : lines.get(indexValue).charAt(0); + + } + + @SneakyThrows + @Override + public int read(byte[] b, final int off, final int len) { + mutex.acquire(); + + if (lines.isEmpty()) { + return copyBytesToBuffer(LINEBREAK, b, off); + } else { + return copyBytesToBuffer(lines.get(index.getAndIncrement()), b, off); + } + } + + private int copyBytesToBuffer(String line, byte[] buffer, int off) { + byte[] bytes = (line + LINEBREAK).getBytes(); + for (int i = 0; i < bytes.length; i++) { + buffer[off + i] = bytes[i]; + } + return bytes.length; + } + + public void publishLine(String line) { + lines.add(line); + mutex.release(); + } +}