diff --git a/README.md b/README.md index 3e5c211..35a088d 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,12 @@ The file tnsnames.ora must contain valid TNS entries. -exclude=pckg_list - Comma-separated object list to exclude from the coverage report. Format: [schema.]package[,[schema.]package ...]. See coverage reporting options in framework documentation. + +-q - Does not output the informational messages normally printed to console. + Default: false + +-d - Outputs a load of debug information to console + Default: false ``` Parameters -f, -o, -s are correlated. That is parameters -o and -s are controlling outputs for reporter specified by the preceding -f parameter. diff --git a/pom.xml b/pom.xml index edf96a2..cb09913 100644 --- a/pom.xml +++ b/pom.xml @@ -20,36 +20,42 @@ - - org.utplsql - java-api - 3.1.2 - compile - - - com.oracle.jdbc - ucp - - - - - com.beust - jcommander - 1.72 - compile - - - com.zaxxer - HikariCP - 2.7.2 - compile - - org.slf4j - slf4j-nop - 1.7.25 + org.utplsql + java-api + 3.1.3-SNAPSHOT compile + + + com.oracle.jdbc + ucp + + + + com.beust + jcommander + 1.72 + compile + + + com.zaxxer + HikariCP + 2.7.2 + compile + + + javax.xml.bind + jaxb-api + 2.3.0 + + + ch.qos.logback + logback-classic + 1.2.3 + + + org.junit.jupiter junit-jupiter-api @@ -62,11 +68,7 @@ ${junit.jupiter.version} test - - javax.xml.bind - jaxb-api - 2.3.0 - + diff --git a/src/main/java/org/utplsql/cli/Cli.java b/src/main/java/org/utplsql/cli/Cli.java index a2e9202..f82d80c 100644 --- a/src/main/java/org/utplsql/cli/Cli.java +++ b/src/main/java/org/utplsql/cli/Cli.java @@ -18,6 +18,8 @@ public static void main(String[] args) { } static int runWithExitCode( String[] args ) { + + LoggerConfiguration.configure(LoggerConfiguration.ConfigLevel.NONE); LocaleInitializer.initLocale(); JCommander jc = new JCommander(); diff --git a/src/main/java/org/utplsql/cli/LoggerConfiguration.java b/src/main/java/org/utplsql/cli/LoggerConfiguration.java new file mode 100644 index 0000000..a10d8e6 --- /dev/null +++ b/src/main/java/org/utplsql/cli/LoggerConfiguration.java @@ -0,0 +1,79 @@ +package org.utplsql.cli; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.encoder.PatternLayoutEncoder; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.ConsoleAppender; +import com.zaxxer.hikari.HikariDataSource; +import org.slf4j.LoggerFactory; + +class LoggerConfiguration { + + public enum ConfigLevel { + BASIC, NONE, DEBUG + } + + private LoggerConfiguration() { + throw new UnsupportedOperationException(); + } + + static void configure(ConfigLevel level) { + switch ( level ) { + case BASIC: + configureInfo(); + break; + case NONE: + configureSilent(); + break; + case DEBUG: + configureDebug(); + break; + } + } + + private static void configureSilent() { + setRootLoggerLevel(Level.OFF); + } + + private static void configureInfo() { + setRootLoggerLevel(Level.INFO); + muteHikariLogger(); + setSingleConsoleAppenderWithLayout("%msg%n"); + } + + private static void configureDebug() { + setRootLoggerLevel(Level.DEBUG); + setSingleConsoleAppenderWithLayout("%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"); + } + + private static void setRootLoggerLevel( Level level ) { + Logger root = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + root.setLevel(level); + } + + private static void muteHikariLogger() { + ((Logger) LoggerFactory.getLogger(HikariDataSource.class)).setLevel(Level.OFF); + } + + private static void setSingleConsoleAppenderWithLayout( String patternLayout ) { + Logger logger = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); + + PatternLayoutEncoder ple = new PatternLayoutEncoder(); + ple.setPattern(patternLayout); + + ple.setContext(lc); + ple.start(); + + ConsoleAppender consoleAppender = new ConsoleAppender<>(); + consoleAppender.setEncoder(ple); + consoleAppender.setContext(lc); + consoleAppender.start(); + + logger.detachAndStopAllAppenders(); + logger.setAdditive(false); + logger.addAppender(consoleAppender); + } +} diff --git a/src/main/java/org/utplsql/cli/ReportersCommand.java b/src/main/java/org/utplsql/cli/ReportersCommand.java index 6d297a4..0c3560a 100644 --- a/src/main/java/org/utplsql/cli/ReportersCommand.java +++ b/src/main/java/org/utplsql/cli/ReportersCommand.java @@ -47,6 +47,7 @@ public int run() { } catch ( DatabaseNotCompatibleException | UtPLSQLNotInstalledException | DatabaseConnectionFailed | IllegalArgumentException e ) { System.out.println(e.getMessage()); + return 1; } catch (Exception e) { e.printStackTrace(); diff --git a/src/main/java/org/utplsql/cli/RunCommand.java b/src/main/java/org/utplsql/cli/RunCommand.java index 01ecdbd..4142f9a 100644 --- a/src/main/java/org/utplsql/cli/RunCommand.java +++ b/src/main/java/org/utplsql/cli/RunCommand.java @@ -2,17 +2,18 @@ import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; -import org.utplsql.api.FileMapperOptions; -import org.utplsql.api.KeyValuePair; -import org.utplsql.api.TestRunner; -import org.utplsql.api.Version; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.utplsql.api.*; import org.utplsql.api.compatibility.CompatibilityProxy; +import org.utplsql.api.db.DefaultDatabaseInformation; import org.utplsql.api.exception.DatabaseNotCompatibleException; import org.utplsql.api.exception.SomeTestsFailedException; import org.utplsql.api.exception.UtPLSQLNotInstalledException; import org.utplsql.api.reporter.Reporter; import org.utplsql.api.reporter.ReporterFactory; import org.utplsql.cli.exception.DatabaseConnectionFailed; +import org.utplsql.cli.log.StringBlockFormatter; import javax.sql.DataSource; import java.io.File; @@ -34,6 +35,8 @@ @Parameters(separators = "=", commandDescription = "run tests") public class RunCommand implements ICommand { + private static final Logger logger = LoggerFactory.getLogger(RunCommand.class); + @Parameter( required = true, converter = ConnectionInfo.ConnectionStringConverter.class, @@ -99,6 +102,15 @@ public class RunCommand implements ICommand { ) private String excludeObjects = null; + @Parameter( + names = {"-q", "--quiet"}, + description = "Does not output the informational messages normally printed to console") + private boolean logSilent = false; + + @Parameter( + names = {"-d", "--debug"}, + description = "Outputs a load of debug information to console") + private boolean logDebug = false; private CompatibilityProxy compatibilityProxy; private ReporterFactory reporterFactory; @@ -112,7 +124,22 @@ public List getTestPaths() { return testPaths; } + void init() { + + LoggerConfiguration.ConfigLevel level = LoggerConfiguration.ConfigLevel.BASIC; + if ( logSilent ) { + level = LoggerConfiguration.ConfigLevel.NONE; + } + else if ( logDebug ) { + level = LoggerConfiguration.ConfigLevel.DEBUG; + } + + LoggerConfiguration.configure(level); + } + public int run() { + init(); + outputMainInformation(); try { @@ -148,25 +175,8 @@ public int run() { final DataSource dataSource = DataSourceProvider.getDataSource(getConnectionInfo(), getReporterManager().getNumberOfReporters() + 1); - // Do the reporters initialization, so we can use the id to run and gather results. - try (Connection conn = dataSource.getConnection()) { - - // Check if orai18n exists if database version is 11g - RunCommandChecker.checkOracleI18nExists(conn); - - // First of all do a compatibility check and fail-fast - compatibilityProxy = checkFrameworkCompatibility(conn); - reporterFactory = ReporterFactoryProvider.createReporterFactory(compatibilityProxy); - - reporterList = getReporterManager().initReporters(conn, reporterFactory, compatibilityProxy); - - } catch (SQLException e) { - if (e.getErrorCode() == 1017 || e.getErrorCode() == 12514) { - throw new DatabaseConnectionFailed(e); - } else { - throw e; - } - } + initDatabase(dataSource); + reporterList = initReporters(dataSource); // Output a message if --failureExitCode is set but database framework is not capable of String msg = RunCommandChecker.getCheckFailOnErrorMessage(failureExitCode, compatibilityProxy.getDatabaseVersion()); @@ -190,6 +200,8 @@ public int run() { .includeObjects(finalIncludeObjectsList) .excludeObjects(finalExcludeObjectsList); + logger.info("Running tests now."); + logger.info("--------------------------------------"); testRunner.run(conn); } catch (SomeTestsFailedException e) { returnCode[0] = this.failureExitCode; @@ -205,6 +217,10 @@ public int run() { executorService.shutdown(); executorService.awaitTermination(60, TimeUnit.MINUTES); + + logger.info("--------------------------------------"); + logger.info("All tests done."); + return returnCode[0]; } catch ( DatabaseNotCompatibleException | UtPLSQLNotInstalledException | DatabaseConnectionFailed e ) { @@ -221,6 +237,49 @@ public String getCommand() { } + private void outputMainInformation() { + + StringBlockFormatter formatter = new StringBlockFormatter("utPLCSL cli"); + formatter.appendLine(CliVersionInfo.getInfo()); + formatter.appendLine(JavaApiVersionInfo.getInfo()); + formatter.appendLine("Java-Version: " + System.getProperty("java.version")); + formatter.appendLine("ORACLE_HOME: " + EnvironmentVariableUtil.getEnvValue("ORACLE_HOME")); + formatter.appendLine("NLS_LANG: " + EnvironmentVariableUtil.getEnvValue("NLS_LANG")); + formatter.appendLine(""); + formatter.appendLine("Thanks for testing!"); + + logger.info(formatter.toString()); + logger.info(""); + } + + private void initDatabase(DataSource dataSource) throws SQLException { + try (Connection conn = dataSource.getConnection()) { + + // Check if orai18n exists if database version is 11g + RunCommandChecker.checkOracleI18nExists(conn); + + // First of all do a compatibility check and fail-fast + compatibilityProxy = checkFrameworkCompatibility(conn); + + logger.info("Successfully connected to database. UtPLSQL core: {}", compatibilityProxy.getDatabaseVersion()); + logger.info("Oracle-Version: {}", new DefaultDatabaseInformation().getOracleVersion(conn)); + } + catch (SQLException e) { + if (e.getErrorCode() == 1017 || e.getErrorCode() == 12514) { + throw new DatabaseConnectionFailed(e); + } else { + throw e; + } + } + } + + private List initReporters(DataSource dataSource) throws SQLException { + try (Connection conn = dataSource.getConnection()) { + reporterFactory = ReporterFactoryProvider.createReporterFactory(compatibilityProxy); + return getReporterManager().initReporters(conn, reporterFactory, compatibilityProxy); + } + } + /** Returns FileMapperOptions for the first item of a given param list in a baseDir * * @param pathParams diff --git a/src/main/java/org/utplsql/cli/datasource/TestedDataSourceProvider.java b/src/main/java/org/utplsql/cli/datasource/TestedDataSourceProvider.java index f30cd91..a0bc3ab 100644 --- a/src/main/java/org/utplsql/cli/datasource/TestedDataSourceProvider.java +++ b/src/main/java/org/utplsql/cli/datasource/TestedDataSourceProvider.java @@ -1,6 +1,8 @@ package org.utplsql.cli.datasource; import com.zaxxer.hikari.HikariDataSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.utplsql.api.EnvironmentVariableUtil; import org.utplsql.cli.ConnectionConfig; import org.utplsql.cli.exception.DatabaseConnectionFailed; @@ -19,6 +21,7 @@ interface ConnectStringPossibility { String getMaskedConnectString(ConnectionConfig config); } + private static final Logger logger = LoggerFactory.getLogger(TestedDataSourceProvider.class); private final ConnectionConfig config; private List possibilities = new ArrayList<>(); @@ -43,9 +46,14 @@ private void setThickOrThinJdbcUrl(HikariDataSource ds ) throws SQLException { List errors = new ArrayList<>(); Throwable lastException = null; + + ds.setUsername(config.getUser()); + ds.setPassword(config.getPassword()); + for (ConnectStringPossibility possibility : possibilities) { ds.setJdbcUrl(possibility.getConnectString(config)); try (Connection con = ds.getConnection()) { + logger.info("Use connectstring {}", possibility.getMaskedConnectString(config)); return; } catch (UnsatisfiedLinkError | Exception e) { errors.add(possibility.getMaskedConnectString(config) + ": " + e.getMessage()); @@ -78,6 +86,7 @@ private void setInitSqlFrom_NLS_LANG(HikariDataSource ds ) { sb.append(String.format("EXECUTE IMMEDIATE q'[%s]';\n", command)); sb.append("END;"); + logger.debug("NLS settings: {}", sb.toString()); ds.setConnectionInitSql(sb.toString()); } } @@ -87,7 +96,7 @@ private void setInitSqlFrom_NLS_LANG(HikariDataSource ds ) { private static class ThickConnectStringPossibility implements ConnectStringPossibility { @Override public String getConnectString(ConnectionConfig config) { - return "jdbc:oracle:oci8:" + config.getConnectString(); + return "jdbc:oracle:oci8:@" + config.getConnect(); } @Override @@ -99,7 +108,7 @@ public String getMaskedConnectString(ConnectionConfig config) { private static class ThinConnectStringPossibility implements ConnectStringPossibility { @Override public String getConnectString(ConnectionConfig config) { - return "jdbc:oracle:thin:" + config.getConnectString(); + return "jdbc:oracle:thin:@" + config.getConnect(); } @Override diff --git a/src/main/java/org/utplsql/cli/log/StringBlockFormatter.java b/src/main/java/org/utplsql/cli/log/StringBlockFormatter.java new file mode 100644 index 0000000..d73656f --- /dev/null +++ b/src/main/java/org/utplsql/cli/log/StringBlockFormatter.java @@ -0,0 +1,81 @@ +package org.utplsql.cli.log; + +public class StringBlockFormatter { + + private String headline; + private StringBuilder content = new StringBuilder(); + + public StringBlockFormatter() {} + + public StringBlockFormatter(String headline) { + setHeadline(headline); + } + + public void setHeadline(String headline) { + this.headline = headline; + } + + public String getHeadline() { + return headline; + } + + public void append( CharSequence seq ) { + content.append(seq); + } + + public void appendLine( CharSequence seq ) { + content.append(seq).append("\n"); + } + + private int getMaxLength( String[] lines ) { + int len = 0; + for ( String line : lines ) { + if (line.length() > len) + len = line.length(); + } + + if ( headline.length() > (len+6)) + len = headline.length(); + + return len; + } + + public static String getEncapsulatedLine( String line, int maxLength ) { + return String.format("# %-" + maxLength + "s #", line); + } + + public static String getEncapsulatedHeadline( String headline, int maxLength ) { + String content = new String(new char[maxLength+8]).replace("\0", "#"); + if ( headline == null || headline.isEmpty() ) + return content; + + headline = " " + headline + " "; + int start = (int)Math.floor( + (float)content.length()/2f + -(float)headline.length()/2f + ); + int end = start + headline.length(); + + return content.substring(0, start) + + headline + + content.substring(end); + } + + public String toString() { + + String[] lines = content.toString().split("\n"); + int maxLen = getMaxLength(lines); + + StringBuilder sb = new StringBuilder(); + + sb.append(getEncapsulatedHeadline(headline, maxLen)).append("\n"); + sb.append(getEncapsulatedLine("", maxLen)).append("\n"); + for ( String line : lines ) { + sb.append(getEncapsulatedLine(line, maxLen)).append("\n"); + } + sb.append(getEncapsulatedLine("", maxLen)).append("\n"); + sb.append(getEncapsulatedHeadline("", maxLen)); + + return sb.toString(); + } +} diff --git a/src/test/java/org/utplsql/cli/RunCommandConfigLevelTest.java b/src/test/java/org/utplsql/cli/RunCommandConfigLevelTest.java new file mode 100644 index 0000000..411a0e3 --- /dev/null +++ b/src/test/java/org/utplsql/cli/RunCommandConfigLevelTest.java @@ -0,0 +1,39 @@ +package org.utplsql.cli; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class RunCommandConfigLevelTest { + + private Logger getRootLogger() { + return (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + } + + @Test + void defaultIsInfo() { + TestHelper.createRunCommand(TestHelper.getConnectionString()) + .init(); + + assertEquals(Level.INFO, getRootLogger().getLevel()); + } + + @Test + void silentModeSetsLoggerToOff() { + TestHelper.createRunCommand(TestHelper.getConnectionString(), "-q") + .init(); + + assertEquals(Level.OFF, getRootLogger().getLevel()); + } + + @Test + void debugModeSetsLoggerToDebug() { + TestHelper.createRunCommand(TestHelper.getConnectionString(), "-d") + .init(); + + assertEquals(Level.DEBUG, getRootLogger().getLevel()); + } +} diff --git a/src/test/java/org/utplsql/cli/RunCommandIT.java b/src/test/java/org/utplsql/cli/RunCommandIT.java index f03b4d2..fbf35e5 100644 --- a/src/test/java/org/utplsql/cli/RunCommandIT.java +++ b/src/test/java/org/utplsql/cli/RunCommandIT.java @@ -29,6 +29,17 @@ public void run_Default() throws Exception { else assertEquals(0, result); } + + @Test + public void run_Debug() throws Exception { + + int result = TestHelper.runApp("run", + TestHelper.getConnectionString(), + "--debug"); + + assertEquals(1, result); + } + @Test public void run_MultipleReporters() throws Exception { diff --git a/src/test/java/org/utplsql/cli/StringBlockFormatterTest.java b/src/test/java/org/utplsql/cli/StringBlockFormatterTest.java new file mode 100644 index 0000000..62fc5f6 --- /dev/null +++ b/src/test/java/org/utplsql/cli/StringBlockFormatterTest.java @@ -0,0 +1,55 @@ +package org.utplsql.cli; + +import org.junit.jupiter.api.Test; +import org.utplsql.cli.log.StringBlockFormatter; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class StringBlockFormatterTest { + + @Test + void getBlockFormattedString() { + + String expected = + "#### Headline ####\n" + + "# #\n" + + "# My value 1 #\n" + + "# My val 2 #\n" + + "# #\n" + + "##################"; + + StringBlockFormatter formatter = new StringBlockFormatter("Headline"); + formatter.appendLine("My value 1"); + formatter.appendLine("My val 2"); + + assertEquals( expected, formatter.toString() ); + } + + @Test + void getEncapsulatedLine() { + + String line = StringBlockFormatter.getEncapsulatedLine("val 1", 20); + + assertEquals("# val 1 #", line); + } + + @Test + void getEncapsulatedHeadline() { + + assertEquals("######### headline #########", + StringBlockFormatter.getEncapsulatedHeadline("headline", 20)); + assertEquals("######### headline ##########", + StringBlockFormatter.getEncapsulatedHeadline("headline", 21)); + assertEquals("######### headline1 #########", + StringBlockFormatter.getEncapsulatedHeadline("headline1", 21)); + assertEquals("######## headline1 #########", + StringBlockFormatter.getEncapsulatedHeadline("headline1", 20)); + } + + @Test + void getEmptyEncapsulatedHeadline() { + + assertEquals("##################", + StringBlockFormatter.getEncapsulatedHeadline("", 10)); + } +} diff --git a/src/test/java/org/utplsql/cli/TestRunCommandChecker.java b/src/test/java/org/utplsql/cli/TestRunCommandChecker.java index fcf2d21..0b1a651 100644 --- a/src/test/java/org/utplsql/cli/TestRunCommandChecker.java +++ b/src/test/java/org/utplsql/cli/TestRunCommandChecker.java @@ -15,7 +15,7 @@ public void getCheckFailOnErrorMessage() assertNotNull(RunCommandChecker.getCheckFailOnErrorMessage(2, new Version("3.0.0"))); assertNotNull(RunCommandChecker.getCheckFailOnErrorMessage(2, new Version("3.0.1"))); assertNotNull(RunCommandChecker.getCheckFailOnErrorMessage(2, new Version("3.0.2"))); - assertNull(RunCommandChecker.getCheckFailOnErrorMessage(2, new Version("3.0.3"))); + assertNull(RunCommandChecker.getCheckFailOnErrorMessage(2, new Version("3.0.3.1266"))); assertNull(RunCommandChecker.getCheckFailOnErrorMessage(2, new Version("3.0.4"))); } }