Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Embedding contents of a method in a log or report

In Java, is there a generic way to embed the code of a method in a log by any means? I am working in Cucumber and altough its tending towards (or is?) bad practice, the compliance department wants to see the assertions behind a "Then" statement printed out in the report (they cant access the source code). Writing to a log is the easy part, but how can i read everything in a method behind a "@Then" in a generic way?

So just to give you an idea of an example method:

 @Then("^my profile information is retrieved with success")
    public void validateProfileInformation() {
     assertThat(..).isEqualTo(..);
     assertThat(..).isEqualTo(..);
     assertThat(..).isEqualTo(..);
     assertThat(..).isEqualTo(..);
     assertThat(..).isEqualTo(..);
     assertThat(..).isEqualTo(..);
     if (this){
        x = y;
     }
}

What needs to be written in the log:

 SOME HEADER TEXT
 assertThat(..).isEqualTo(..);
 assertThat(..).isEqualTo(..);
 assertThat(..).isEqualTo(..);
 assertThat(..).isEqualTo(..);
 assertThat(..).isEqualTo(..);
 assertThat(..).isEqualTo(..);
 **Optionally:**
 if (this){
    x = y;
 }

So i really need the unexecuted code (both when tests fail and pass), dont need the outcome. Preferably like a generic method or interceptor, so that it can be applied accross the project, rather than having to add it to each method. A push in the right direction would be appreciated.

like image 421
Neruul Avatar asked Nov 06 '22 06:11

Neruul


1 Answers

Maven POM

    <groupId>igb.so</groupId>
    <artifactId>SO-62882091-log-sourcecode</artifactId>
    <version>20.7.14</version>

    <url>https://stackoverflow.com/a/62904271/1744774</url>
    <developers>
        <developer>
            <name>Gerold 'Geri' Broser</name>
        </developer>
    </developers>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>

        <log4j.version>2.13.3</log4j.version>
        <slf4j.version>1.7.30</slf4j.version>
        <junit.version>5.6.2</junit.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>${log4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>${log4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

AbstractTestSourceErrorLogger

package igb.so.logging;

import java.util.Arrays;

import org.slf4j.Logger;

public abstract class AbstractTestSourceErrorLogger {

    protected static final String LF = System.getProperty( "line.separator" );

    protected final String testSourcePath;
    protected final Class<? extends Object> testClass;
    protected final Logger log;
    protected boolean withLineNumbers;
    
    protected int lineNo;
    protected boolean isInMethod;
    protected int codeBlockLevel;

    protected AbstractTestSourceErrorLogger( final Class<? extends Object> testClass, final Logger logger ) {
        this( "", testClass, logger, false );
    }

    protected AbstractTestSourceErrorLogger(
            final String testSourcePath, final Class<? extends Object> testClass, final Logger logger ) {
        this( testSourcePath, testClass, logger, false );
    }

    protected AbstractTestSourceErrorLogger(
            final Class<? extends Object> testClass, final Logger logger, final boolean withLineNumbers ) {
        this( "", testClass, logger, withLineNumbers );
    }

    protected AbstractTestSourceErrorLogger(
            final String testSourcePath, final Class<? extends Object> testClass,
            final Logger logger, final boolean withLineNumbers ) {
        this.testSourcePath = testSourcePath;
        this.testClass = testClass;
        log = logger;
        this.withLineNumbers = withLineNumbers;
    }

    /** Logs an {@link AssertionError} with the ERROR level. Afterwards logs the method body in which the error occurred.
     *
     * @param error - the error to log
     */
    public void error( final AssertionError error ) {
        error( error, withLineNumbers );
    }

    /** Logs an {@link AssertionError} with the ERROR level. Afterwards logs the method body in which the error occurred with optional line numbers.
     *
     * @param error - the error to log
     * @param withLineNumbers - <code>true</code> for equipping the logged source code with line numbers
     */
    public abstract void error( final AssertionError error, final boolean withLineNumbers );

    protected StackTraceElement failedTest( final AssertionError error ) {
        return Arrays.stream( error.getStackTrace() )
                .filter( e -> e.getClassName().equals( testClass.getName() ) )
                .findFirst().get();
    }

} // AbstractTestSourceLogger

SimpleTestSourceErrorLogger

package igb.so.logging;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.slf4j.Logger;

public class SimpleTestSourceErrorLogger extends AbstractTestSourceErrorLogger {

    public SimpleTestSourceErrorLogger(
            final Class<? extends Object> testClass, final Logger logger ) {
        super( testClass, logger );
    }

    public SimpleTestSourceErrorLogger(
            final String testSourcePath, final Class<? extends Object> testClass, final Logger logger ) {
        super( testSourcePath, testClass, logger, false );
    }

    public SimpleTestSourceErrorLogger(
            final Class<? extends Object> testClass, final Logger logger, final boolean withLineNumbers ) {
        super( "", testClass, logger, withLineNumbers );
    }

    public SimpleTestSourceErrorLogger(
            final String testSourcePath, final Class<? extends Object> testClass,
            final Logger logger, final boolean withLineNumbers ) {
        super( testSourcePath, testClass, logger, withLineNumbers );
    }

    @Override
    public void error( final AssertionError error, final boolean withLineNumbers ) {
        final StringBuilder sb = new StringBuilder();

        sb.append( error ).append( LF );
        final StackTraceElement failedTest = failedTest( error );
        final Path source = Paths.get( testSourcePath, failedTest.getClassName().replaceAll( "\\.", "/" ) + ".java" );
        sb.append( "  in method:" ).append( LF );
        lineNo = 1;
        try {
            Files.lines( source )
                    .forEach( line -> {
                        if ( line.strip().startsWith( String.format( "public void %s()", failedTest.getMethodName() ) ) )
                            isInMethod = true;
                        if ( isInMethod ) {
                            if ( line.contains( "{" ) )
                                codeBlockLevel++;
                            if ( codeBlockLevel > 0 ) {
                                final String format = withLineNumbers ? "  %d: %s%s%n" : "%2$s%3$s%n";
                                sb.append( String.format( format, lineNo, line,
                                        lineNo == failedTest.getLineNumber() ? " // " + error.toString() : "" ) );
                            }
                            if ( line.contains( "}" ) )
                                codeBlockLevel--;
                            if ( codeBlockLevel <= 0 ) {
                                isInMethod = false;
                            }
                        }
                        lineNo++;
                    } );
            log.error( sb.toString() );
        }
        catch (final IOException e) {
            throw new RuntimeException( e );
        }
    } // error()

} // SimpleTestSourceErrorLogger

SimpleLoggerTest

package igb.so;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import igb.so.logging.SimpleTestSourceErrorLogger;

public class SimpleLoggerTest {

    private static Logger log = LoggerFactory.getLogger( SimpleLoggerTest.class );
    private static SimpleTestSourceErrorLogger sLog = //
            new SimpleTestSourceErrorLogger( "src/test/java", SimpleLoggerTest.class, log, true );

    @Test
    public void testTrue() {

        try {
            assertTrue( true );
            assertTrue( false );
        }
        catch (final AssertionError e) {
            sLog.error( e );
            throw e;
        }

    } // testTrue()

    @Test
    public void testNotNull() {

        try {
            assertNotNull( new Object() );
            assertNotNull( null );
        }
        catch (final AssertionError e) {
            sLog.error( e, false );
            throw e;
        }

    } // testNotNull()

} // SimpleLoggerTest

Eclipse JUnit Console output

23:16:02.035 [main] ERROR igb.so.SimpleLoggerTest - org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
  in method:
  18:   public void testTrue() {
  19: 
  20:       try {
  21:           assertTrue( true );
  22:           assertTrue( false ); // org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
  23:       }
  24:       catch (final AssertionError e) {
  25:           sLog.error( e );
  26:           throw e;
  27:       }
  28: 
  29:   } // testTrue()

23:16:02.111 [main] ERROR igb.so.SimpleLoggerTest - org.opentest4j.AssertionFailedError: expected: not <null>
  in method:
    public void testNotNull() {

        try {
            assertNotNull( new Object() );
            assertNotNull( null ); // org.opentest4j.AssertionFailedError: expected: not <null>
        }
        catch (final AssertionError e) {
            sLog.error( e, false );
            throw e;
        }

    } // testNotNull()

mvn test output

...
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running igb.so.SimpleLoggerTest
23:25:25.956 [main] ERROR igb.so.SimpleLoggerTest - org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
  in method:
  18:   public void testTrue() {
  19: 
  20:       try {
  21:           assertTrue( true );
  22:           assertTrue( false ); // org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
  23:       }
  24:       catch (final AssertionError e) {
  25:           sLog.error( e );
  26:           throw e;
  27:       }
  28: 
  29:   } // testTrue()

23:25:25.966 [main] ERROR igb.so.SimpleLoggerTest - org.opentest4j.AssertionFailedError: expected: not <null>
  in method:
    public void testNotNull() {

        try {
            assertNotNull( new Object() );
            assertNotNull( null ); // org.opentest4j.AssertionFailedError: expected: not <null>
        }
        catch (final AssertionError e) {
            sLog.error( e, false );
            throw e;
        }

    } // testNotNull()

Tests run: 2, Failures: 2, Errors: 0, Skipped: 0, Time elapsed: 0.026 sec <<< FAILURE!
igb.so.SimpleLoggerTest.testTrue()  Time elapsed: 0.023 sec  <<< FAILURE!
org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
    at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:55)
    at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:40)
    at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:35)
    at org.junit.jupiter.api.Assertions.assertTrue(Assertions.java:162)
    at igb.so.SimpleLoggerTest.testTrue(SimpleLoggerTest.java:22)

igb.so.SimpleLoggerTest.testNotNull()  Time elapsed: 0.001 sec  <<< FAILURE!
org.opentest4j.AssertionFailedError: expected: not <null>
    at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:39)
    at org.junit.jupiter.api.Assertions.fail(Assertions.java:109)
    at org.junit.jupiter.api.AssertNotNull.failNull(AssertNotNull.java:47)
    at org.junit.jupiter.api.AssertNotNull.assertNotNull(AssertNotNull.java:36)
    at org.junit.jupiter.api.AssertNotNull.assertNotNull(AssertNotNull.java:31)
    at org.junit.jupiter.api.Assertions.assertNotNull(Assertions.java:283)
    at igb.so.SimpleLoggerTest.testNotNull(SimpleLoggerTest.java:36)


Results :

Failed tests:   igb.so.SimpleLoggerTest.testTrue(): expected: <true> but was: <false>
  igb.so.SimpleLoggerTest.testNotNull(): expected: not <null>

Tests run: 2, Failures: 2, Errors: 0, Skipped: 0
...

PS: Using JUnit since I'm not familiar with Cucumber. But you get the idea.

like image 169
Gerold Broser Avatar answered Nov 12 '22 12:11

Gerold Broser