You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by sl...@apache.org on 2020/06/05 18:18:38 UTC

[maven-script-interpreter] branch MSHARED-907 created (now 9f62f60)

This is an automated email from the ASF dual-hosted git repository.

slachiewicz pushed a change to branch MSHARED-907
in repository https://gitbox.apache.org/repos/asf/maven-script-interpreter.git.


      at 9f62f60  [MSHARED-907] Output build log from script to application

This branch includes the following new commits:

     new 9f62f60  [MSHARED-907] Output build log from script to application

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[maven-script-interpreter] 01/01: [MSHARED-907] Output build log from script to application

Posted by sl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

slachiewicz pushed a commit to branch MSHARED-907
in repository https://gitbox.apache.org/repos/asf/maven-script-interpreter.git

commit 9f62f6084299a4e3048f535356da94551725c70d
Author: Slawomir Jaranowski <sl...@payu.pl>
AuthorDate: Tue Jun 2 21:05:52 2020 +0200

    [MSHARED-907] Output build log from script to application
    
    Closes #4
---
 pom.xml                                            |  20 ++-
 .../maven/shared/scriptinterpreter/FileLogger.java | 151 +++++++++++++--------
 .../scriptinterpreter/FileLoggerMirrorHandler.java |  34 +++++
 .../shared/scriptinterpreter/ScriptRunner.java     |  70 ++++------
 src/site/apt/index.apt.vm                          |  29 +++-
 .../shared/scriptinterpreter/FileLoggerTest.java   | 129 ++++++++++++++++++
 .../shared/scriptinterpreter/ScriptRunnerTest.java | 141 ++++++++++++++-----
 .../scriptinterpreter/TestMirrorHandler.java       |  48 +++++++
 src/test/resources/bsh-test/failed.bsh             |  20 +++
 src/test/resources/groovy-test/failed.groovy       |  25 ++++
 10 files changed, 524 insertions(+), 143 deletions(-)

diff --git a/pom.xml b/pom.xml
index b78ae5b..3eb16b8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,15 +56,11 @@
   <properties>
     <javaVersion>7</javaVersion>
     <project.build.outputTimestamp>2020-04-04T09:03:59Z</project.build.outputTimestamp>
+    <slf4j.version>1.7.30</slf4j.version>
   </properties>
 
   <dependencies>
     <dependency>
-      <groupId>org.apache.maven</groupId>
-      <artifactId>maven-plugin-api</artifactId>
-      <version>2.0.6</version>
-    </dependency>
-    <dependency>
       <groupId>org.apache.maven.shared</groupId>
       <artifactId>maven-shared-utils</artifactId>
       <version>3.2.1</version>
@@ -107,6 +103,20 @@
         </exclusion>
       </exclusions>
     </dependency>
+
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+      <version>${slf4j.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-simple</artifactId>
+      <version>${slf4j.version}</version>
+      <scope>test</scope>
+    </dependency>
+
     <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
diff --git a/src/main/java/org/apache/maven/shared/scriptinterpreter/FileLogger.java b/src/main/java/org/apache/maven/shared/scriptinterpreter/FileLogger.java
index 236a2bb..b915090 100644
--- a/src/main/java/org/apache/maven/shared/scriptinterpreter/FileLogger.java
+++ b/src/main/java/org/apache/maven/shared/scriptinterpreter/FileLogger.java
@@ -19,18 +19,17 @@ package org.apache.maven.shared.scriptinterpreter;
  * under the License.
  */
 
-import org.apache.maven.plugin.logging.Log;
 import org.apache.maven.shared.utils.io.IOUtil;
 
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.io.PrintStream;
 
 /**
  */
-public class FileLogger
-    implements ExecutionLogger
+public class FileLogger implements ExecutionLogger, AutoCloseable
 {
 
     /**
@@ -44,65 +43,52 @@ public class FileLogger
     private PrintStream stream;
 
     /**
-     * A flag whether the output stream should be closed during finalization of this logger.
-     */
-    private boolean shouldFinalize = true;
-
-    /**
-     * The optional mojo logger to additionally write messages to, can be <code>null</code>.
-     */
-    private final Log log;
-
-    /**
      * Creates a new logger that writes to the specified file.
-     * 
-     * @param outputFile The path to the output file, must not be <code>null</code>.
+     *
+     * @param outputFile The path to the output file, if null all message will be discarded.
      * @throws java.io.IOException If the output file could not be created.
      */
-    public FileLogger( File outputFile )
-        throws IOException
+    public FileLogger( File outputFile ) throws IOException
     {
         this( outputFile, null );
     }
 
     /**
-     * Creates a new logger that writes to the specified file and optionally mirrors messages to the given mojo logger.
+     * Creates a new logger that writes to the specified file and optionally mirrors messages.
      *
-     * @param outputFile The path to the output file, must not be <code>null</code>.
-     * @param log The mojo logger to additionally output messages to, may be <code>null</code> if not used.
+     * @param outputFile The path to the output file, if null all message will be discarded.
+     * @param mirrorHandler The class which handle mirrored message, can be <code>null</code>.
      * @throws java.io.IOException If the output file could not be created.
      */
-    public FileLogger( File outputFile, Log log )
-        throws IOException
+    public FileLogger( File outputFile, FileLoggerMirrorHandler mirrorHandler ) throws IOException
     {
         this.file = outputFile;
-        this.log = log;
 
-        outputFile.getParentFile().mkdirs();
-        stream = new PrintStream( new FileOutputStream( outputFile ) );
+        OutputStream outputStream;
 
-        Runnable finalizer = new Runnable()
+        if ( outputFile != null )
         {
-            @Override
-            public void run()
-            {
-                try
-                {
-                    finalize();
-                }
-                catch ( Throwable e )
-                {
-                    // ignore
-                }
-            }
-        };
+            outputFile.getParentFile().mkdirs();
+            outputStream = new FileOutputStream( outputFile );
+        }
+        else
+        {
+            outputStream = new NullOutputStream();
+        }
 
-        Runtime.getRuntime().addShutdownHook( new Thread( finalizer ) );
+        if ( mirrorHandler != null )
+        {
+            stream = new PrintStream( new MirrorStreamWrapper( outputStream, mirrorHandler ) );
+        }
+        else
+        {
+            stream = new PrintStream( outputStream );
+        }
     }
 
     /**
      * Gets the path to the output file.
-     * 
+     *
      * @return The path to the output file, never <code>null</code>.
      */
     public File getOutputFile()
@@ -112,7 +98,7 @@ public class FileLogger
 
     /**
      * Gets the underlying stream used to write message to the log file.
-     * 
+     *
      * @return The underlying stream used to write message to the log file, never <code>null</code>.
      */
     @Override
@@ -122,8 +108,9 @@ public class FileLogger
     }
 
     /**
-     * Writes the specified line to the log file and optionally to the mojo logger.
-     * 
+     * Writes the specified line to the log file
+     * and invoke {@link FileLoggerMirrorHandler#consumeOutput(String)} if is given.
+     *
      * @param line The message to log.
      */
     @Override
@@ -131,11 +118,6 @@ public class FileLogger
     {
         stream.println( line );
         stream.flush();
-
-        if ( log != null )
-        {
-            log.info( line );
-        }
     }
 
     /**
@@ -151,15 +133,72 @@ public class FileLogger
         IOUtil.close( stream );
     }
 
-    /**
-     * Closes the underlying file stream.
-     */
-    @Override
-    protected void finalize()
+    private static class MirrorStreamWrapper extends OutputStream
     {
-        if ( shouldFinalize )
+        private final OutputStream out;
+        private final FileLoggerMirrorHandler mirrorHandler;
+
+        private StringBuilder lineBuffer;
+
+        MirrorStreamWrapper( OutputStream outputStream, FileLoggerMirrorHandler mirrorHandler )
+        {
+            this.out = outputStream;
+            this.mirrorHandler = mirrorHandler;
+            this.lineBuffer = new StringBuilder();
+        }
+
+        @Override
+        public void write( int b ) throws IOException
+        {
+            out.write( b );
+            lineBuffer.append( (char) ( b ) );
+        }
+
+        @Override
+        public void write( byte[] b, int off, int len ) throws IOException
+        {
+            out.write( b, off, len );
+            lineBuffer.append( new String( b, off, len ) );
+        }
+
+        @Override
+        public void flush() throws IOException
+        {
+            out.flush();
+
+            int len = lineBuffer.length();
+            if ( len == 0 )
+            {
+                // nothing to log
+                return;
+            }
+
+            // remove line end for log
+            while ( len > 0 && ( lineBuffer.charAt( len - 1 ) == '\n' || lineBuffer.charAt( len - 1 ) == '\r' ) )
+            {
+                len--;
+            }
+            lineBuffer.setLength( len );
+
+            mirrorHandler.consumeOutput( lineBuffer.toString() );
+
+            // clear buffer
+            lineBuffer = new StringBuilder();
+        }
+    }
+
+    private static class NullOutputStream extends OutputStream
+    {
+        @Override
+        public void write( int b )
+        {
+            // do nothing
+        }
+
+        @Override
+        public void write( byte[] b, int off, int len )
         {
-            close();
+            // do nothing
         }
     }
 }
diff --git a/src/main/java/org/apache/maven/shared/scriptinterpreter/FileLoggerMirrorHandler.java b/src/main/java/org/apache/maven/shared/scriptinterpreter/FileLoggerMirrorHandler.java
new file mode 100644
index 0000000..fcc219e
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/scriptinterpreter/FileLoggerMirrorHandler.java
@@ -0,0 +1,34 @@
+package org.apache.maven.shared.scriptinterpreter;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Handle output form interpreter.
+ */
+public interface FileLoggerMirrorHandler
+{
+    /**
+     * Handle output message generated by script interpreter.
+     * This method is invoked when flush occurs on the underlying stream.
+     *
+     * @param message last message
+     */
+    void consumeOutput( String message );
+}
diff --git a/src/main/java/org/apache/maven/shared/scriptinterpreter/ScriptRunner.java b/src/main/java/org/apache/maven/shared/scriptinterpreter/ScriptRunner.java
index f17b398..d30e8f7 100644
--- a/src/main/java/org/apache/maven/shared/scriptinterpreter/ScriptRunner.java
+++ b/src/main/java/org/apache/maven/shared/scriptinterpreter/ScriptRunner.java
@@ -19,9 +19,10 @@ package org.apache.maven.shared.scriptinterpreter;
  * under the License.
  */
 
-import org.apache.maven.plugin.logging.Log;
 import org.apache.maven.shared.utils.io.FileUtils;
 import org.apache.maven.shared.utils.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.IOException;
@@ -41,10 +42,8 @@ import java.util.Map;
 public class ScriptRunner
 {
 
-    /**
-     * The mojo logger to print diagnostic to, never <code>null</code>.
-     */
-    private Log log;
+
+    private static final Logger LOG = LoggerFactory.getLogger( ScriptRunner.class );
 
     /**
      * The supported script interpreters, indexed by the lower-case file extension of their associated script files,
@@ -69,16 +68,9 @@ public class ScriptRunner
 
     /**
      * Creates a new script runner.
-     *
-     * @param log The mojo logger to print diagnostic to, must not be <code>null</code>.
      */
-    public ScriptRunner( Log log )
+    public ScriptRunner()
     {
-        if ( log == null )
-        {
-            throw new IllegalArgumentException( "missing logger" );
-        }
-        this.log = log;
         scriptInterpreters = new LinkedHashMap<>();
         scriptInterpreters.put( "bsh", new BeanShellScriptInterpreter() );
         scriptInterpreters.put( "groovy", new GroovyScriptInterpreter() );
@@ -92,19 +84,9 @@ public class ScriptRunner
     }
 
     /**
-     * Gets the mojo logger.
-     *
-     * @return The mojo logger, never <code>null</code>.
-     */
-    private Log getLog()
-    {
-        return log;
-    }
-
-    /**
      * Sets a global variable for the script interpreter.
      *
-     * @param name  The name of the variable, must not be <code>null</code>.
+     * @param name The name of the variable, must not be <code>null</code>.
      * @param value The value of the variable, may be <code>null</code>.
      */
     public void setGlobalVariable( String name, Object value )
@@ -117,8 +99,8 @@ public class ScriptRunner
      * will not affect the scripts.
      *
      * @param classPath The additional class path for the script interpreter, may be <code>null</code> or empty if only
-     *            the plugin realm should be used for the script evaluation. If specified, this class path will precede
-     *            the artifacts from the plugin class path.
+     * the plugin realm should be used for the script evaluation. If specified, this class path will precede the
+     * artifacts from the plugin class path.
      */
     public void setClassPath( List<String> classPath )
     {
@@ -160,7 +142,7 @@ public class ScriptRunner
     {
         if ( relativeScriptPath == null )
         {
-            getLog().debug( scriptDescription + ": relativeScriptPath is null, not executing script" );
+            LOG.debug( "{}: relativeScriptPath is null, not executing script", scriptDescription );
             return;
         }
 
@@ -168,13 +150,13 @@ public class ScriptRunner
 
         if ( !scriptFile.exists() )
         {
-            getLog().debug( scriptDescription + ": no script '" + relativeScriptPath + "' found in directory "
-                + basedir.getAbsolutePath() );
+            LOG.debug( "{} : no script '{}' found in directory {}", scriptDescription, relativeScriptPath,
+                    basedir.getAbsolutePath() );
             return;
         }
 
-        getLog().info( "run " + scriptDescription + ' ' + relativeScriptPath + '.'
-            + FileUtils.extension( scriptFile.getAbsolutePath() ) );
+        LOG.info( "run {} {}.{}",
+                scriptDescription, relativeScriptPath, FileUtils.extension( scriptFile.getAbsolutePath() ) );
 
         executeRun( scriptDescription, scriptFile, context, logger, stage, failOnException );
     }
@@ -187,11 +169,10 @@ public class ScriptRunner
      * @param context The key-value storage used to share information between hook scripts, may be <code>null</code>.
      * @param logger The logger to redirect the script output to, may be <code>null</code> to use stdout/stderr.
      * @param stage The stage of the build job the script is invoked in, must not be <code>null</code>. This is for
-     *            logging purpose only.
-     * @param failOnException If <code>true</code> and the script throws an exception, then a
-     *            {@link RunFailureException} will be thrown, otherwise a {@link RunErrorException} will be thrown on
-     *            script exception.
-     * @throws IOException If an I/O error occurred while reading the script file.
+     * logging purpose only.
+     * @param failOnException If <code>true</code> and the script throws an exception, then a {@link
+     * RunFailureException} will be thrown, otherwise a {@link RunErrorException} will be thrown on script exception.
+     * @throws IOException         If an I/O error occurred while reading the script file.
      * @throws RunFailureException If the script did not return <code>true</code> of threw an exception.
      */
     public void run( final String scriptDescription, File scriptFile, final Map<String, ? extends Object> context,
@@ -201,12 +182,11 @@ public class ScriptRunner
 
         if ( !scriptFile.exists() )
         {
-            getLog().debug( scriptDescription + ": script file not found in directory "
-                + scriptFile.getAbsolutePath() );
+            LOG.debug( "{} : script file not found in directory {}", scriptDescription, scriptFile.getAbsolutePath() );
             return;
         }
 
-        getLog().info( "run " + scriptDescription + ' ' + scriptFile.getAbsolutePath() );
+        LOG.info( "run {} {}", scriptDescription, scriptFile.getAbsolutePath() );
 
         executeRun( scriptDescription, scriptFile, context, logger, stage, failOnException );
     }
@@ -221,11 +201,11 @@ public class ScriptRunner
         globalVariables.put( "context", context );
 
         ScriptInterpreter interpreter = getInterpreter( scriptFile );
-        if ( getLog().isDebugEnabled() )
+        if ( LOG.isDebugEnabled() )
         {
             String name = interpreter.getClass().getName();
             name = name.substring( name.lastIndexOf( '.' ) + 1 );
-            getLog().debug( "Running script with " + name + ": " + scriptFile );
+            LOG.debug( "Running script with {} :{}", name, scriptFile );
         }
 
         String script;
@@ -237,9 +217,7 @@ public class ScriptRunner
         {
             String errorMessage =
                 "error reading " + scriptDescription + " " + scriptFile.getPath() + ", " + e.getMessage();
-            IOException ioException = new IOException( errorMessage );
-            ioException.initCause( e );
-            throw ioException;
+            throw new IOException( errorMessage, e );
         }
 
         Object result;
@@ -262,10 +240,10 @@ public class ScriptRunner
         {
             Throwable t = ( e.getCause() != null ) ? e.getCause() : e;
             String msg = ( t.getMessage() != null ) ? t.getMessage() : t.toString();
-            if ( getLog().isDebugEnabled() )
+            if ( LOG.isDebugEnabled() )
             {
                 String errorMessage = "Error evaluating " + scriptDescription + " " + scriptFile.getPath() + ", " + t;
-                getLog().debug( errorMessage, t );
+                LOG.debug( errorMessage, t );
             }
             if ( logger != null )
             {
diff --git a/src/site/apt/index.apt.vm b/src/site/apt/index.apt.vm
index f43ff0f..2dbae50 100644
--- a/src/site/apt/index.apt.vm
+++ b/src/site/apt/index.apt.vm
@@ -73,13 +73,38 @@ ${project.name}
   See {{{./apidocs/org/apache/maven/shared/scriptinterpreter/ScriptRunner.html}javadoc}} for run method.
 
 +---------
-    SystemStreamLog systemStreamLog = new SystemStreamLog();
 
-    ScriptRunner scriptRunner = new ScriptRunner( systemStreamLog );
+    ScriptRunner scriptRunner = new ScriptRunner( );
     scriptRunner.run( "test", new File( "src/test/resources/bsh-test" ), "verify", buildContext(),
                       new FileLogger( logFile ), "foo", true );
 +---------
 
+* Mirror output from script interpreter
+
+  In order to do something more with script output, eg. log by your application you must implement FileLoggerMirrorHandler
+
++---------
+
+class MyMirrorHandler implements FileLoggerMirrorHandler
+{
+    void consumeOutput( String message )
+    {
+        // this method is invoked every time when flush occurs on the underlying stream.
+    }
+}
+
++---------
+
+  Now use it:
+
++---------
+
+      ScriptRunner scriptRunner = new ScriptRunner( );
+      scriptRunner.run( "test", new File( "src/test/resources/bsh-test" ), "verify", buildContext(),
+                        new FileLogger( logFile, new MyMirrorHandler() ), "foo", true );
+
++---------
+
 ** Global variables
 
   Your scripts will have by default two global variables:
diff --git a/src/test/java/org/apache/maven/shared/scriptinterpreter/FileLoggerTest.java b/src/test/java/org/apache/maven/shared/scriptinterpreter/FileLoggerTest.java
new file mode 100644
index 0000000..8eee66c
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/scriptinterpreter/FileLoggerTest.java
@@ -0,0 +1,129 @@
+package org.apache.maven.shared.scriptinterpreter;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.shared.utils.io.FileUtils;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class FileLoggerTest
+{
+
+    public static final String EXPECTED_LOG = "Test1" + System.lineSeparator() + "Test2" + System.lineSeparator();
+
+    @Test
+    public void nullOutputFileNoMirror() throws IOException
+    {
+        try ( FileLogger fileLogger = new FileLogger( null ) )
+        {
+            fileLogger.consumeLine( "Test1" );
+            fileLogger.getPrintStream().println( "Test2" );
+            fileLogger.getPrintStream().flush();
+
+            assertNull( fileLogger.getOutputFile() );
+        }
+    }
+
+    @Test
+    public void nullOutputFileWithMirror() throws IOException
+    {
+        TestMirrorHandler mirrorHandler = new TestMirrorHandler();
+
+        try ( FileLogger fileLogger = new FileLogger( null, mirrorHandler ) )
+        {
+            fileLogger.consumeLine( "Test1" );
+            fileLogger.getPrintStream().println( "Test2" );
+            fileLogger.getPrintStream().flush();
+
+            assertNull( fileLogger.getOutputFile() );
+        }
+
+        assertEquals( EXPECTED_LOG, mirrorHandler.getLoggedMessage() );
+    }
+
+    @Test
+    public void nullOutputFileWithMirrorWriteByte() throws IOException
+    {
+        TestMirrorHandler mirrorHandler = new TestMirrorHandler();
+
+        try ( FileLogger fileLogger = new FileLogger( null, mirrorHandler ) )
+        {
+            fileLogger.getPrintStream().write( 'A' );
+            fileLogger.getPrintStream().flush();
+
+            assertNull( fileLogger.getOutputFile() );
+        }
+
+        assertEquals( "A" + System.lineSeparator(), mirrorHandler.getLoggedMessage() );
+    }
+
+    @Test
+    public void outputFileNoMirror() throws IOException
+    {
+        File outputFile = new File( "target/test.log" );
+        if ( outputFile.exists() )
+        {
+            outputFile.delete();
+        }
+
+        try ( FileLogger fileLogger = new FileLogger( outputFile ) )
+        {
+            fileLogger.consumeLine( "Test1" );
+            fileLogger.getPrintStream().println( "Test2" );
+            fileLogger.getPrintStream().flush();
+
+            assertEquals( outputFile, fileLogger.getOutputFile() );
+        }
+
+        assertTrue( outputFile.exists() );
+        assertEquals( EXPECTED_LOG, FileUtils.fileRead( outputFile ) );
+    }
+
+    @Test
+    public void outputFileWithMirror() throws IOException
+    {
+        File outputFile = new File( "target/test.log" );
+        if ( outputFile.exists() )
+        {
+            outputFile.delete();
+        }
+        TestMirrorHandler mirrorHandler = new TestMirrorHandler();
+
+        try ( FileLogger fileLogger = new FileLogger( outputFile, mirrorHandler ) )
+        {
+            fileLogger.consumeLine( "Test1" );
+            fileLogger.getPrintStream().println( "Test2" );
+            fileLogger.getPrintStream().flush();
+
+            assertEquals( outputFile, fileLogger.getOutputFile() );
+        }
+
+        assertEquals( EXPECTED_LOG, mirrorHandler.getLoggedMessage() );
+
+        assertTrue( outputFile.exists() );
+        assertEquals( EXPECTED_LOG, FileUtils.fileRead( outputFile ) );
+    }
+}
diff --git a/src/test/java/org/apache/maven/shared/scriptinterpreter/ScriptRunnerTest.java b/src/test/java/org/apache/maven/shared/scriptinterpreter/ScriptRunnerTest.java
index d2c1c37..46fc885 100644
--- a/src/test/java/org/apache/maven/shared/scriptinterpreter/ScriptRunnerTest.java
+++ b/src/test/java/org/apache/maven/shared/scriptinterpreter/ScriptRunnerTest.java
@@ -1,4 +1,5 @@
 package org.apache.maven.shared.scriptinterpreter;
+
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -18,7 +19,6 @@ package org.apache.maven.shared.scriptinterpreter;
  * under the License.
  */
 
-import org.apache.maven.plugin.logging.SystemStreamLog;
 import org.apache.maven.shared.utils.io.FileUtils;
 import org.junit.Test;
 
@@ -26,6 +26,7 @@ import java.io.File;
 import java.util.HashMap;
 import java.util.Map;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 /**
@@ -33,67 +34,108 @@ import static org.junit.Assert.assertTrue;
  */
 public class ScriptRunnerTest
 {
+
     @Test
-    public void testBeanshell()
-        throws Exception
+    public void testBeanshell() throws Exception
     {
         File logFile = new File( "target/build.log" );
         if ( logFile.exists() )
         {
             logFile.delete();
         }
-        SystemStreamLog systemStreamLog = new SystemStreamLog();
 
-        ScriptRunner scriptRunner = new ScriptRunner( systemStreamLog );
-        scriptRunner.setGlobalVariable( "globalVar", "Yeah baby it's rocks" );
-        scriptRunner.run( "test", new File( "src/test/resources/bsh-test" ), "verify", buildContext(),
-                          new FileLogger( logFile ), "foo", true );
+        TestMirrorHandler mirrorHandler = new TestMirrorHandler();
+
+        try ( FileLogger fileLogger = new FileLogger( logFile, mirrorHandler ) )
+        {
+            ScriptRunner scriptRunner = new ScriptRunner();
+            scriptRunner.setGlobalVariable( "globalVar", "Yeah baby it's rocks" );
+            scriptRunner.run( "test", new File( "src/test/resources/bsh-test" ), "verify",
+                    buildContext(), fileLogger, "foo", true );
+        }
 
         String logContent = FileUtils.fileRead( logFile );
         assertTrue( logContent.contains( new File( "src/test/resources/bsh-test/verify.bsh" ).getPath() ) );
         assertTrue( logContent.contains( "foo=bar" ) );
         assertTrue( logContent.contains( "globalVar=Yeah baby it's rocks" ) );
 
+        assertEquals( logContent, mirrorHandler.getLoggedMessage() );
+    }
+
+    @Test
+    public void failedBeanshellShouldCreateProperLogsMessage() throws Exception
+    {
+        File logFile = new File( "target/build.log" );
+        if ( logFile.exists() )
+        {
+            logFile.delete();
+        }
+
+        TestMirrorHandler mirrorHandler = new TestMirrorHandler();
+
+        Exception catchedException = null;
+
+        try ( FileLogger fileLogger = new FileLogger( logFile, mirrorHandler ) )
+        {
+            ScriptRunner scriptRunner = new ScriptRunner();
+            scriptRunner.run( "test", new File( "src/test/resources/bsh-test" ), "failed",
+                    buildContext(), fileLogger, "foo", false );
+        }
+        catch ( Exception e )
+        {
+            catchedException = e;
+        }
+
+        assertTrue( catchedException instanceof RunErrorException );
+        String logContent = FileUtils.fileRead( logFile );
+        assertTrue( logContent.contains( new File( "src/test/resources/bsh-test/failed.bsh" ).getPath() ) );
+        assertEquals( logContent, mirrorHandler.getLoggedMessage() );
     }
 
     @Test
-    public void testBeanshellWithFile()
-        throws Exception
+    public void testBeanshellWithFile() throws Exception
     {
         File logFile = new File( "target/build.log" );
         if ( logFile.exists() )
         {
             logFile.delete();
         }
-        SystemStreamLog systemStreamLog = new SystemStreamLog();
 
-        ScriptRunner scriptRunner = new ScriptRunner( systemStreamLog );
-        scriptRunner.setGlobalVariable( "globalVar", "Yeah baby it's rocks" );
-        scriptRunner.run( "test", new File( "src/test/resources/bsh-test/verify.bsh" ), buildContext(),
-                          new FileLogger( logFile ), "foo", true );
+        TestMirrorHandler mirrorHandler = new TestMirrorHandler();
+
+        try ( FileLogger fileLogger = new FileLogger( logFile, mirrorHandler ) )
+        {
+            ScriptRunner scriptRunner = new ScriptRunner();
+            scriptRunner.setGlobalVariable( "globalVar", "Yeah baby it's rocks" );
+            scriptRunner.run( "test", new File( "src/test/resources/bsh-test/verify.bsh" ),
+                    buildContext(), fileLogger, "foo", true );
+        }
 
         String logContent = FileUtils.fileRead( logFile );
         assertTrue( logContent.contains( new File( "src/test/resources/bsh-test/verify.bsh" ).getPath() ) );
         assertTrue( logContent.contains( "foo=bar" ) );
 
-
+        assertEquals( logContent, mirrorHandler.getLoggedMessage() );
     }
 
     @Test
-    public void testGroovy()
-        throws Exception
+    public void testGroovy() throws Exception
     {
         File logFile = new File( "target/build.log" );
         if ( logFile.exists() )
         {
             logFile.delete();
         }
-        SystemStreamLog systemStreamLog = new SystemStreamLog();
 
-        ScriptRunner scriptRunner = new ScriptRunner( systemStreamLog );
-        scriptRunner.setGlobalVariable( "globalVar", "Yeah baby it's rocks" );
-        scriptRunner.run( "test", new File( "src/test/resources/groovy-test" ), "verify", buildContext(),
-                          new FileLogger( logFile ), "foo", true );
+        TestMirrorHandler mirrorHandler = new TestMirrorHandler();
+
+        try ( FileLogger fileLogger = new FileLogger( logFile, mirrorHandler ) )
+        {
+            ScriptRunner scriptRunner = new ScriptRunner();
+            scriptRunner.setGlobalVariable( "globalVar", "Yeah baby it's rocks" );
+            scriptRunner.run( "test", new File( "src/test/resources/groovy-test" ), "verify",
+                    buildContext(), fileLogger, "foo", true );
+        }
 
         String logContent = FileUtils.fileRead( logFile );
         assertTrue(
@@ -101,31 +143,63 @@ public class ScriptRunnerTest
         assertTrue( logContent.contains( "foo=bar" ) );
         assertTrue( logContent.contains( "globalVar=Yeah baby it's rocks" ) );
 
+        assertEquals( logContent, mirrorHandler.getLoggedMessage() );
     }
 
     @Test
-    public void testGroovyWithFile()
-        throws Exception
+    public void failedGroovyShouldCreateProperLogsMessage() throws Exception
     {
         File logFile = new File( "target/build.log" );
         if ( logFile.exists() )
         {
             logFile.delete();
         }
-        SystemStreamLog systemStreamLog = new SystemStreamLog();
 
-        ScriptRunner scriptRunner = new ScriptRunner( systemStreamLog );
-        scriptRunner.run( "test", new File( "src/test/resources/groovy-test/verify.groovy" ), buildContext(),
-                          new FileLogger( logFile ), "foo", true );
+        TestMirrorHandler mirrorHandler = new TestMirrorHandler();
 
-        String logContent = FileUtils.fileRead( logFile );
-        assertTrue(
-                logContent.contains( new File( "src/test/resources/groovy-test/verify.groovy" ).getPath() ) );
-        assertTrue( logContent.contains( "foo=bar" ) );
+        Exception catchedException = null;
 
+        try ( FileLogger fileLogger = new FileLogger( logFile, mirrorHandler ) )
+        {
+            ScriptRunner scriptRunner = new ScriptRunner();
+            scriptRunner.run( "test", new File( "src/test/resources/groovy-test" ), "failed",
+                    buildContext(), fileLogger, "foo", true );
+        }
+        catch ( Exception e )
+        {
+            catchedException = e;
+        }
 
+        assertTrue( catchedException instanceof RunFailureException );
+        String logContent = FileUtils.fileRead( logFile );
+        assertTrue( logContent.contains( new File( "src/test/resources/groovy-test/failed.groovy" ).getPath() ) );
+        assertEquals( logContent, mirrorHandler.getLoggedMessage() );
     }
 
+    @Test
+    public void testGroovyWithFile() throws Exception
+    {
+        File logFile = new File( "target/build.log" );
+        if ( logFile.exists() )
+        {
+            logFile.delete();
+        }
+
+        TestMirrorHandler mirrorHandler = new TestMirrorHandler();
+
+        try ( FileLogger fileLogger = new FileLogger( logFile, mirrorHandler ) )
+        {
+            ScriptRunner scriptRunner = new ScriptRunner();
+            scriptRunner.run( "test", new File( "src/test/resources/groovy-test/verify.groovy" ),
+                    buildContext(), fileLogger, "foo", true );
+        }
+
+        String logContent = FileUtils.fileRead( logFile );
+        assertTrue( logContent.contains( new File( "src/test/resources/groovy-test/verify.groovy" ).getPath() ) );
+        assertTrue( logContent.contains( "foo=bar" ) );
+
+        assertEquals( logContent, mirrorHandler.getLoggedMessage() );
+    }
 
     private Map<String, ? extends Object> buildContext()
     {
@@ -133,5 +207,4 @@ public class ScriptRunnerTest
         context.put( "foo", "bar" );
         return context;
     }
-
 }
diff --git a/src/test/java/org/apache/maven/shared/scriptinterpreter/TestMirrorHandler.java b/src/test/java/org/apache/maven/shared/scriptinterpreter/TestMirrorHandler.java
new file mode 100644
index 0000000..e818d5b
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/scriptinterpreter/TestMirrorHandler.java
@@ -0,0 +1,48 @@
+package org.apache.maven.shared.scriptinterpreter;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+/**
+ * Implementing {@link FileLoggerMirrorHandler} for testing
+ */
+class TestMirrorHandler implements FileLoggerMirrorHandler
+{
+
+    private StringBuilder loggedMessage;
+
+    public TestMirrorHandler() throws IOException
+    {
+        loggedMessage = new StringBuilder();
+    }
+
+    public String getLoggedMessage()
+    {
+        return loggedMessage.toString();
+    }
+
+    @Override
+    public void consumeOutput( String message )
+    {
+        loggedMessage.append( message );
+        loggedMessage.append( System.lineSeparator() );
+    }
+}
diff --git a/src/test/resources/bsh-test/failed.bsh b/src/test/resources/bsh-test/failed.bsh
new file mode 100644
index 0000000..f8226bf
--- /dev/null
+++ b/src/test/resources/bsh-test/failed.bsh
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+throw new Exception( "test exception" )
diff --git a/src/test/resources/groovy-test/failed.groovy b/src/test/resources/groovy-test/failed.groovy
new file mode 100644
index 0000000..b1881f1
--- /dev/null
+++ b/src/test/resources/groovy-test/failed.groovy
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+def message = 'no Te Message'
+
+assert message.contains('Test Message')
+
+return true