diff --git a/README.md b/README.md index bdcb055..a9f691a 100644 --- a/README.md +++ b/README.md @@ -39,17 +39,31 @@ export LC_ALL=en_US.utf-8 The charset-part of LC_ALL is ignored. ## Usage +Currently, utPLSQL-cli supports the following commands: +- run +- info +- reporters -`utplsql run [-p=(ut_path|ut_paths)] [-f=format [-o=output_file] [-s] ...]` +#### \ -``` - - accepted formats: - /@//[:]/ - /@:: - /@ - To connect using TNS, you need to have the ORACLE_HOME environment variable set. - The file tnsnames.ora must exist in path %ORACLE_HOME%/network/admin - The file tnsnames.ora must contain valid TNS entries. +This is used in all commands as first parameter (though it's optional for `info`). + +Accepted formats: + +- `/@//[:]/` +- `/@::` +- `/@` + +To connect using TNS, you need to have the ORACLE_HOME environment variable set. +The file tnsnames.ora must exist in path %ORACLE_HOME%/network/admin +The file tnsnames.ora must contain valid TNS entries. + +### run +`utplsql run []` + + +#### Options +``` -p=suite_path(s) - A suite path or a comma separated list of suite paths for unit test to be executed. The path(s) can be in one of the following formats: schema[.package[.procedure]] @@ -57,69 +71,57 @@ The charset-part of LC_ALL is ignored. Both formats can be mixed in the list. If only schema is provided, then all suites owner by that schema are executed. If -p is omitted, the current schema is used. + -f=format - A reporter to be used for reporting. If no -f option is provided, the default ut_documentation_reporter is used. - Available options: - -f=ut_documentation_reporter - A textual pretty-print of unit test results (usually use for console output) - -f=ut_teamcity_reporter - For reporting live progress of test execution with Teamcity CI. - -f=ut_xunit_reporter - Used for reporting test results with CI servers like Jenkins/Hudson/Teamcity. - -f=ut_coverage_html_reporter - Generates a HTML coverage report with summary and line by line information on code coverage. - Based on open-source simplecov-html coverage reporter for Ruby. - Includes source code in the report. - -f=ut_coveralls_reporter - Generates a JSON coverage report providing information on code coverage with line numbers. - Designed for [Coveralls](https://coveralls.io/). - -f=ut_coverage_sonar_reporter - Generates a JSON coverage report providing information on code coverage with line numbers. - Designed for [SonarQube](https://about.sonarqube.com/) to report coverage. - -f=ut_sonar_test_reporter - Generates a JSON report providing detailed information on test execution. - Designed for [SonarQube](https://about.sonarqube.com/) to report test execution. - - -o=output - Defines file name to save the output from the specified reporter. + See reporters command for possible values + -o=output - Defines file name to save the output from the specified reporter. If defined, the output is not displayed on screen by default. This can be changed with the -s parameter. If not defined, then output will be displayed on screen, even if the parameter -s is not specified. If more than one -o parameter is specified for one -f parameter, the last one is taken into consideration. - -s - Forces putting output to to screen for a given -f parameter. + -s - Forces putting output to to screen for a given -f parameter. + -source_path=source - path to project source files, use the following options to enable custom type mappings: - -owner="app" - -regex_expression="pattern" - -type_mapping="matched_string=TYPE[/matched_string=TYPE]*" - -owner_subexpression=subexpression_number - -type_subexpression=subexpression_number - -name_subexpression=subexpression_number + -owner="app" + -regex_expression="pattern" + -type_mapping="matched_string=TYPE[/matched_string=TYPE]*" + -owner_subexpression=subexpression_number + -type_subexpression=subexpression_number + -name_subexpression=subexpression_number + -test_path=test - path to project test files, use the following options to enable custom type mappings: - -owner="app" - -regex_expression="pattern" - -type_mapping="matched_string=TYPE[/matched_string=TYPE]*" - -owner_subexpression=subexpression_number - -type_subexpression=subexpression_number - -name_subexpression=subexpression_number + -owner="app" + -regex_expression="pattern" + -type_mapping="matched_string=TYPE[/matched_string=TYPE]*" + -owner_subexpression=subexpression_number + -type_subexpression=subexpression_number + -name_subexpression=subexpression_number + -c - If specified, enables printing of test results in colors as defined by ANSICONSOLE standards. Works only on reporeters that support colors (ut_documentation_reporter). + --failure-exit-code - Override the exit code on failure, defaults to 1. You can set it to 0 to always exit with a success status. + -scc - If specified, skips the compatibility-check with the version of the database framework. If you skip compatibility-check, CLI will expect the most actual framework version --include=package_list - Comma-separated object list to include in the coverage report. - Format: [schema.]package[,[schema.]package ...]. - See coverage reporting options in framework documentation. --exclude=package_list - Comma-separated object list to exclude from the coverage report. - Format: [schema.]package[,[schema.]package ...]. - See coverage reporting options in framework documentation. + +-include=pckg_list - Comma-separated object list to include in the coverage report. + Format: [schema.]package[,[schema.]package ...]. + See coverage reporting options in framework documentation. + +-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. ``` Parameters -f, -o, -s are correlated. That is parameters -o and -s are controlling outputs for reporter specified by the preceding -f parameter. Sonar and Coveralls reporter will only provide valid reports, when source_path and/or test_path are provided, and ut_run is executed from your project's root path. -Examples: +#### Examples ``` -utplsql run hr/hr@xe -p=hr_test -f=ut_documentation_reporter -o=run.log -s -f=ut_coverage_html_reporter -o=coverage.html -source_path=source +> utplsql run hr/hr@xe -p=hr_test -f=ut_documentation_reporter -o=run.log -s -f=ut_coverage_html_reporter -o=coverage.html -source_path=source ``` Invokes all Unit tests from schema/package "hr_test" with two reporters: @@ -128,12 +130,87 @@ Invokes all Unit tests from schema/package "hr_test" with two reporters: * ut_coverage_html_reporter - will report only on database objects that are mapping to file structure from "source" folder and save output to file "coverage.html" ``` -utplsql run hr/hr@xe +> utplsql run hr/hr@xe ``` Invokes all unit test suites from schema "hr". Results are displayed to screen using default ut_documentation_reporter. -#### Enabling Color Outputs on Windows +### info +`utplsql info []` + + +#### Examples + +``` +> utplsql info + +cli 3.1.1-SNAPSHOT.local +utPLSQL-java-api 3.1.1-SNAPSHOT.123 +``` +``` +> utplsql info app/app@localhost:1521/ORCLPDB1 + +cli 3.1.1-SNAPSHOT.local +utPLSQL-java-api 3.1.1-SNAPSHOT.123 +utPLSQL 3.1.2.1913 +``` + +### reporters +`utplsql reporters ` + +#### Examples +``` +> utplsql reporters app/app@localhost:1521/ORCLPDB1 + +UT_COVERAGE_COBERTURA_REPORTER: + Generates a Cobertura coverage report providing information on code coverage with line numbers. + Designed for Jenkins and TFS to report coverage. + Cobertura Document Type Definition can be found: http://cobertura.sourceforge.net/xml/coverage-04.dtd. + Sample file: https://github.com/leobalter/testing-examples/blob/master/solutions/3/report/cobertura-coverage.xml. + +UT_COVERAGE_HTML_REPORTER: + Generates a HTML coverage report with summary and line by line information on code coverage. + Based on open-source simplecov-html coverage reporter for Ruby. + Includes source code in the report. + Will copy all necessary assets to a folder named after the Output-File + +UT_COVERAGE_SONAR_REPORTER: + Generates a JSON coverage report providing information on code coverage with line numbers. + Designed for [SonarQube](https://about.sonarqube.com/) to report coverage. + JSON format returned conforms with the Sonar specification: https://docs.sonarqube.org/display/SONAR/Generic+Test+Data + +UT_COVERALLS_REPORTER: + Generates a JSON coverage report providing information on code coverage with line numbers. + Designed for [Coveralls](https://coveralls.io/). + JSON format conforms with specification: https://docs.coveralls.io/api-introduction + +UT_DOCUMENTATION_REPORTER: + A textual pretty-print of unit test results (usually use for console output) + Provides additional properties lvl and failed + +UT_JUNIT_REPORTER: + Provides outcomes in a format conforming with JUnit 4 and above as defined in: https://gist.github.com/kuzuha/232902acab1344d6b578 + +UT_SONAR_TEST_REPORTER: + Generates a JSON report providing detailed information on test execution. + Designed for [SonarQube](https://about.sonarqube.com/) to report test execution. + JSON format returned conforms with the Sonar specification: https://docs.sonarqube.org/display/SONAR/Generic+Test+Data + +UT_TEAMCITY_REPORTER: + Provides the TeamCity (a CI server by jetbrains) reporting-format that allows tracking of progress of a CI step/task as it executes. + https://confluence.jetbrains.com/display/TCD9/Build+Script+Interaction+with+TeamCity + +UT_TFS_JUNIT_REPORTER: + Provides outcomes in a format conforming with JUnit version for TFS / VSTS. + As defined by specs :https://docs.microsoft.com/en-us/vsts/build-release/tasks/test/publish-test-results?view=vsts + Version is based on windy road junit https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd. + +UT_XUNIT_REPORTER: + Depracated reporter. Please use Junit. + Provides outcomes in a format conforming with JUnit 4 and above as defined in: https://gist.github.com/kuzuha/232902acab1344d6b578 +``` + +## Enabling Color Outputs on Windows To enable color outputs on Windows cmd you need to install an open-source utility called [ANSICON](http://adoxa.altervista.org/ansicon/). @@ -143,4 +220,4 @@ Since v3.1.0 you can call custom reporters (PL/SQL) via cli, too. Just call the ``` utplsql run hr/hr@xe -p=hr_test -f=my_custom_reporter -o=run.log -s -``` \ No newline at end of file +``` diff --git a/pom.xml b/pom.xml index 4d1e25b..861937c 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,7 @@ 1.8 1.0.3 5.0.3 + local @@ -88,6 +89,41 @@ + + com.google.code.maven-replacer-plugin + replacer + 1.5.3 + + + replace-version-number + generate-sources + + replace + + + + + ${project.basedir}/src/main/java + + **/CliVersionInfo.java + + true + + + MAVEN_PROJECT_NAME = ".*" + MAVEN_PROJECT_NAME = "${project.name}" + + + MAVEN_PROJECT_VERSION = ".*" + MAVEN_PROJECT_VERSION = "${project.version}" + + + BUILD_NO = ".*" + BUILD_NO = "${travisBuildNumber}" + + + + org.apache.maven.plugins maven-surefire-plugin diff --git a/src/main/java/org/utplsql/cli/Cli.java b/src/main/java/org/utplsql/cli/Cli.java index 9189367..11a445e 100644 --- a/src/main/java/org/utplsql/cli/Cli.java +++ b/src/main/java/org/utplsql/cli/Cli.java @@ -12,28 +12,31 @@ public class Cli { static final int DEFAULT_ERROR_CODE = 1; static final String HELP_CMD = "-h"; - private static final String RUN_CMD = "run"; public static void main(String[] args) { + int exitCode = runWithExitCode(args); + + System.exit(exitCode); + } + + static int runWithExitCode( String[] args ) { LocaleInitializer.initLocale(); JCommander jc = new JCommander(); jc.setProgramName("utplsql"); - // jc.addCommand(HELP_CMD, new HelpCommand()); - RunCommand runCmd = new RunCommand(); - jc.addCommand(RUN_CMD, runCmd); + + CommandProvider cmdProvider = new CommandProvider(); + + cmdProvider.commands().forEach(cmd -> jc.addCommand(cmd.getCommand(), cmd)); int exitCode = DEFAULT_ERROR_CODE; try { jc.parse(args); - if (RUN_CMD.equals(jc.getParsedCommand())) { - exitCode = runCmd.run(); - } else { - throw new ParameterException("Command not specified."); - } + exitCode = cmdProvider.getCommand(jc.getParsedCommand()).run(); + } catch (ParameterException e) { if (jc.getParsedCommand() != null) { System.err.println(e.getMessage()); @@ -41,13 +44,11 @@ public static void main(String[] args) { } else { jc.usage(); } - } catch ( DatabaseNotCompatibleException | UtPLSQLNotInstalledException | DatabaseConnectionFailed e ) { - System.out.println(e.getMessage()); - } catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); } - System.exit(exitCode); + return exitCode; } private static class HelpCommand { diff --git a/src/main/java/org/utplsql/cli/CliVersionInfo.java b/src/main/java/org/utplsql/cli/CliVersionInfo.java new file mode 100644 index 0000000..96fbc4c --- /dev/null +++ b/src/main/java/org/utplsql/cli/CliVersionInfo.java @@ -0,0 +1,20 @@ +package org.utplsql.cli; + +/** This class is getting updated automatically by the build process. + * Please do not update its constants manually cause they will be overwritten. + * + * @author pesse + */ +public class CliVersionInfo { + + private static final String BUILD_NO = "local"; + private static final String MAVEN_PROJECT_NAME = "cli"; + private static final String MAVEN_PROJECT_VERSION = "3.1.1-SNAPSHOT"; + + public static String getVersion() { + return MAVEN_PROJECT_VERSION + "." + BUILD_NO; + } + + public static String getInfo() { return MAVEN_PROJECT_NAME + " " + getVersion(); } + +} diff --git a/src/main/java/org/utplsql/cli/CommandProvider.java b/src/main/java/org/utplsql/cli/CommandProvider.java new file mode 100644 index 0000000..898eb2a --- /dev/null +++ b/src/main/java/org/utplsql/cli/CommandProvider.java @@ -0,0 +1,37 @@ +package org.utplsql.cli; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +public class CommandProvider { + + private Map commands; + + public CommandProvider() { + init(); + } + + private void init() { + commands = new HashMap<>(); + + addCommand(new RunCommand()); + addCommand(new VersionInfoCommand()); + addCommand(new ReportersCommand()); + } + + private void addCommand( ICommand command ) { + commands.put(command.getCommand().toLowerCase(), command); + } + + public ICommand getCommand( String key ) { + if ( commands.containsKey(key)) + return commands.get(key.toLowerCase()); + else + return new HelpCommand("Unknown command: '" + key + "'"); + } + + public Stream commands() { + return commands.values().stream(); + } +} diff --git a/src/main/java/org/utplsql/cli/ConnectionInfo.java b/src/main/java/org/utplsql/cli/ConnectionInfo.java index f1a32a2..7b48e87 100644 --- a/src/main/java/org/utplsql/cli/ConnectionInfo.java +++ b/src/main/java/org/utplsql/cli/ConnectionInfo.java @@ -1,40 +1,18 @@ package org.utplsql.cli; import com.beust.jcommander.IStringConverter; -import com.zaxxer.hikari.HikariDataSource; - -import java.io.File; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; public class ConnectionInfo { - private String databaseVersion; - - static { - String oracleHome = System.getenv("ORACLE_HOME"); - if (oracleHome != null) { - System.setProperty("oracle.net.tns_admin", - String.join(File.separator, oracleHome, "NETWORK", "ADMIN")); - } - } - - private HikariDataSource pds = new HikariDataSource(); + public static final String COMMANDLINE_PARAM_DESCRIPTION = "/@//[:]/ OR /@ OR /@::"; + private String connectionInfo; public ConnectionInfo(String connectionInfo) { - - pds.setJdbcUrl("jdbc:oracle:thin:" + connectionInfo); - pds.setAutoCommit(false); + this.connectionInfo = connectionInfo; } - public void setMaxConnections( int maxConnections ) { - pds.setMaximumPoolSize(maxConnections); - } - - public Connection getConnection() throws SQLException { - return pds.getConnection(); + public String getConnectionString() { + return connectionInfo; } public static class ConnectionStringConverter implements IStringConverter { @@ -44,41 +22,4 @@ public ConnectionInfo convert(String s) { return new ConnectionInfo(s); } } - - public String getOracleDatabaseVersion() throws SQLException - { - try ( Connection conn = getConnection() ) { - return getOracleDatabaseVersion(conn); - } - } - - public String getOracleDatabaseVersion( Connection conn ) throws SQLException - { - if ( databaseVersion == null ) { - databaseVersion = getOracleDatabaseVersionFromConnection( conn ); - } - - return databaseVersion; - } - - /** TODO: Outsource this to Java-API - * - * @param conn - * @return - * @throws SQLException - */ - public static String getOracleDatabaseVersionFromConnection( Connection conn ) throws SQLException { - assert conn != null; - String result = null; - try (PreparedStatement stmt = conn.prepareStatement("select version from product_component_version where product like 'Oracle Database%'")) - { - ResultSet rs = stmt.executeQuery(); - - if ( rs.next() ) - result = rs.getString(1); - } - - return result; - } - } diff --git a/src/main/java/org/utplsql/cli/DataSourceProvider.java b/src/main/java/org/utplsql/cli/DataSourceProvider.java new file mode 100644 index 0000000..ac790f4 --- /dev/null +++ b/src/main/java/org/utplsql/cli/DataSourceProvider.java @@ -0,0 +1,44 @@ +package org.utplsql.cli; + +import com.zaxxer.hikari.HikariDataSource; +import org.utplsql.api.EnvironmentVariableUtil; + +import javax.sql.DataSource; +import java.io.File; + +/** Helper class to give you a ready-to-use datasource + * + * @author pesse + */ +public class DataSourceProvider { + + static { + String oracleHome = EnvironmentVariableUtil.getEnvValue("ORACLE_HOME"); + if (oracleHome != null) { + System.setProperty("oracle.net.tns_admin", + String.join(File.separator, oracleHome, "NETWORK", "ADMIN")); + } + } + + public static DataSource getDataSource(ConnectionInfo info, int maxConnections ) { + + requireOjdbc(); + + HikariDataSource pds = new HikariDataSource(); + pds.setJdbcUrl("jdbc:oracle:thin:" + info.getConnectionString()); + pds.setAutoCommit(false); + pds.setMaximumPoolSize(maxConnections); + return pds; + } + + private static void requireOjdbc() { + if ( !OracleLibraryChecker.checkOjdbcExists() ) + { + System.out.println("Could not find Oracle JDBC driver in classpath. Please download the jar from Oracle website" + + " and copy it to the 'lib' folder of your utPLSQL-cli installation."); + System.out.println("Download from http://www.oracle.com/technetwork/database/features/jdbc/jdbc-ucp-122-3110062.html"); + + throw new RuntimeException("Can't run utPLSQL-cli without Oracle JDBC driver"); + } + } +} diff --git a/src/main/java/org/utplsql/cli/HelpCommand.java b/src/main/java/org/utplsql/cli/HelpCommand.java new file mode 100644 index 0000000..3476a07 --- /dev/null +++ b/src/main/java/org/utplsql/cli/HelpCommand.java @@ -0,0 +1,23 @@ +package org.utplsql.cli; + +public class HelpCommand implements ICommand { + + private String errorMessage; + + public HelpCommand( String errorMessage ) { + this.errorMessage = errorMessage; + } + + @Override + public int run() { + if ( errorMessage != null ) + System.out.println(errorMessage); + + return 1; + } + + @Override + public String getCommand() { + return "-h"; + } +} diff --git a/src/main/java/org/utplsql/cli/ICommand.java b/src/main/java/org/utplsql/cli/ICommand.java new file mode 100644 index 0000000..4f039dc --- /dev/null +++ b/src/main/java/org/utplsql/cli/ICommand.java @@ -0,0 +1,11 @@ +package org.utplsql.cli; + +/** Interface to decouple JCommander commands + * + * @author pesse + */ +public interface ICommand { + int run(); + + String getCommand(); +} diff --git a/src/main/java/org/utplsql/cli/ReporterFactoryProvider.java b/src/main/java/org/utplsql/cli/ReporterFactoryProvider.java index 31fd314..3226054 100644 --- a/src/main/java/org/utplsql/cli/ReporterFactoryProvider.java +++ b/src/main/java/org/utplsql/cli/ReporterFactoryProvider.java @@ -5,6 +5,9 @@ import org.utplsql.api.reporter.ReporterFactory; import org.utplsql.cli.reporters.LocalAssetsCoverageHTMLReporter; +import java.sql.Connection; +import java.sql.SQLException; + /** A simple class to provide a ReporterFactory for the RunCommand * * @author pesse @@ -17,4 +20,8 @@ public static ReporterFactory createReporterFactory(CompatibilityProxy proxy ) { return reporterFactory; } + + public static ReporterFactory createReporterFactory(Connection con ) throws SQLException { + return createReporterFactory(new CompatibilityProxy(con)); + } } diff --git a/src/main/java/org/utplsql/cli/ReporterManager.java b/src/main/java/org/utplsql/cli/ReporterManager.java index aac7ddf..eed17a8 100644 --- a/src/main/java/org/utplsql/cli/ReporterManager.java +++ b/src/main/java/org/utplsql/cli/ReporterManager.java @@ -6,6 +6,7 @@ import org.utplsql.api.reporter.ReporterFactory; import org.utplsql.cli.reporters.ReporterOptionsAware; +import javax.sql.DataSource; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintStream; @@ -77,10 +78,10 @@ public List initReporters(Connection conn, ReporterFactory reporterFac /** Starts a separate thread for each Reporter to gather its results * * @param executorService - * @param ci + * @param dataSource * @param returnCode */ - public void startReporterGatherers(ExecutorService executorService, final ConnectionInfo ci, final int[] returnCode) + public void startReporterGatherers(ExecutorService executorService, final DataSource dataSource, final int[] returnCode) { // TODO: Implement Init-check // Gather each reporter results on a separate thread. @@ -89,7 +90,7 @@ public void startReporterGatherers(ExecutorService executorService, final Connec List printStreams = new ArrayList<>(); PrintStream fileOutStream = null; - try (Connection conn = ci.getConnection()) { + try (Connection conn = dataSource.getConnection()) { if (ro.outputToScreen()) { printStreams.add(System.out); ro.getReporterObj().getOutputBuffer().setFetchSize(1); diff --git a/src/main/java/org/utplsql/cli/ReportersCommand.java b/src/main/java/org/utplsql/cli/ReportersCommand.java new file mode 100644 index 0000000..80f01ff --- /dev/null +++ b/src/main/java/org/utplsql/cli/ReportersCommand.java @@ -0,0 +1,86 @@ +package org.utplsql.cli; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import org.utplsql.api.reporter.ReporterFactory; +import org.utplsql.api.reporter.inspect.ReporterInfo; +import org.utplsql.api.reporter.inspect.ReporterInspector; + +import javax.sql.DataSource; +import java.io.PrintStream; +import java.sql.Connection; +import java.util.*; + +@Parameters(separators = "=", commandDescription = "prints a list of reporters available in the specified database") +public class ReportersCommand implements ICommand { + + @Parameter( + converter = ConnectionInfo.ConnectionStringConverter.class, + arity = 1, + description = ConnectionInfo.COMMANDLINE_PARAM_DESCRIPTION) + private List connectionInfoList = new ArrayList<>(); + + private ConnectionInfo getConnectionInfo() { + assert connectionInfoList != null; + assert connectionInfoList.size() > 0; + assert connectionInfoList.get(0) != null; + + return connectionInfoList.get(0); + } + + @Override + public int run() { + + DataSource ds = DataSourceProvider.getDataSource(getConnectionInfo(), 1); + try (Connection con = ds.getConnection() ) { + + ReporterFactory reporterFactory = ReporterFactoryProvider.createReporterFactory(con); + + writeReporters(ReporterInspector.create(reporterFactory, con).getReporterInfos(), System.out); + } + catch ( Exception e ) { + e.printStackTrace(); + return 1; + } + + return 0; + } + + @Override + public String getCommand() { + return "reporters"; + } + + private int getMaxNameLength(List reporterInfos) { + return reporterInfos.stream() + .mapToInt(info -> info.getName().length()) + .max() + .orElse(0); + } + + private void writeReporters(List reporterInfos, PrintStream out) { + //int padding = getMaxNameLength(reporterInfos)+1; + reporterInfos.stream() + .sorted(Comparator.comparing(ReporterInfo::getName)) + .forEach(info -> writeReporter(info, 4, out)); + } + + private void writeReporter(ReporterInfo info, int padding, PrintStream out) { + + writeReporterName(info, padding, out); + writeReporterDescription(info, padding, out); + + out.println(); + } + + private void writeReporterName( ReporterInfo info, int paddingRight, PrintStream out ) { + out.println(info.getName()+":"); + + } + + private void writeReporterDescription( ReporterInfo info, int paddingLeft, PrintStream out ) { + String[] lines = info.getDescription().split("\n"); + String paddingLeftStr = String.format("%1$"+paddingLeft+"s", ""); + Arrays.stream(lines).forEach(line -> out.println(paddingLeftStr+line.trim())); + } +} diff --git a/src/main/java/org/utplsql/cli/RunCommand.java b/src/main/java/org/utplsql/cli/RunCommand.java index 12ea83f..01ecdbd 100644 --- a/src/main/java/org/utplsql/cli/RunCommand.java +++ b/src/main/java/org/utplsql/cli/RunCommand.java @@ -7,11 +7,14 @@ import org.utplsql.api.TestRunner; import org.utplsql.api.Version; import org.utplsql.api.compatibility.CompatibilityProxy; +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 javax.sql.DataSource; import java.io.File; import java.sql.Connection; import java.sql.SQLException; @@ -29,13 +32,13 @@ * @author pesse */ @Parameters(separators = "=", commandDescription = "run tests") -public class RunCommand { +public class RunCommand implements ICommand { @Parameter( required = true, converter = ConnectionInfo.ConnectionStringConverter.class, arity = 1, - description = "/@//[:]/ OR /@ OR /@::") + description = ConnectionInfo.COMMANDLINE_PARAM_DESCRIPTION) private List connectionInfoList = new ArrayList<>(); @Parameter( @@ -109,106 +112,113 @@ public List getTestPaths() { return testPaths; } - public int run() throws Exception { + public int run() { - RunCommandChecker.checkOracleJDBCExists(); + try { + final List reporterList; + final List testPaths = getTestPaths(); - final List reporterList; - final List testPaths = getTestPaths(); + final File baseDir = new File("").getAbsoluteFile(); + final FileMapperOptions[] sourceMappingOptions = {null}; + final FileMapperOptions[] testMappingOptions = {null}; - final File baseDir = new File("").getAbsoluteFile(); - final FileMapperOptions[] sourceMappingOptions = {null}; - final FileMapperOptions[] testMappingOptions = {null}; + final int[] returnCode = {0}; - final int[] returnCode = {0}; + sourceMappingOptions[0] = getFileMapperOptionsByParamListItem(this.sourcePathParams, baseDir); + testMappingOptions[0] = getFileMapperOptionsByParamListItem(this.testPathParams, baseDir); - sourceMappingOptions[0] = getFileMapperOptionsByParamListItem(this.sourcePathParams, baseDir); - testMappingOptions[0] = getFileMapperOptionsByParamListItem(this.testPathParams, baseDir); + ArrayList includeObjectsList; + ArrayList excludeObjectsList; - ArrayList includeObjectsList; - ArrayList excludeObjectsList; - - if (includeObjects != null && !includeObjects.isEmpty()) { - includeObjectsList = new ArrayList<>(Arrays.asList(includeObjects.split(","))); - } else { - includeObjectsList = new ArrayList<>(); - } + if (includeObjects != null && !includeObjects.isEmpty()) { + includeObjectsList = new ArrayList<>(Arrays.asList(includeObjects.split(","))); + } else { + includeObjectsList = new ArrayList<>(); + } - if (excludeObjects != null && !excludeObjects.isEmpty()) { - excludeObjectsList = new ArrayList<>(Arrays.asList(excludeObjects.split(","))); - } else { - excludeObjectsList = new ArrayList<>(); - } + if (excludeObjects != null && !excludeObjects.isEmpty()) { + excludeObjectsList = new ArrayList<>(Arrays.asList(excludeObjects.split(","))); + } else { + excludeObjectsList = new ArrayList<>(); + } - final ArrayList finalIncludeObjectsList = includeObjectsList; - final ArrayList finalExcludeObjectsList = excludeObjectsList; + final ArrayList finalIncludeObjectsList = includeObjectsList; + final ArrayList finalExcludeObjectsList = excludeObjectsList; - final ConnectionInfo ci = getConnectionInfo(); - ci.setMaxConnections(getReporterManager().getNumberOfReporters()+1); + 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 = ci.getConnection()) { + // 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(ci.getOracleDatabaseVersion(conn)); + // 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); + // First of all do a compatibility check and fail-fast + compatibilityProxy = checkFrameworkCompatibility(conn); + reporterFactory = ReporterFactoryProvider.createReporterFactory(compatibilityProxy); - reporterList = getReporterManager().initReporters(conn, reporterFactory, compatibilityProxy); + reporterList = getReporterManager().initReporters(conn, reporterFactory, compatibilityProxy); - } catch (SQLException e) { - if ( e.getErrorCode() == 1017 || e.getErrorCode() == 12514 ) { - throw new DatabaseConnectionFailed(e); - } - else { - throw e; + } catch (SQLException e) { + if (e.getErrorCode() == 1017 || e.getErrorCode() == 12514) { + throw new DatabaseConnectionFailed(e); + } else { + throw e; + } } - } - // Output a message if --failureExitCode is set but database framework is not capable of - String msg = RunCommandChecker.getCheckFailOnErrorMessage(failureExitCode, compatibilityProxy.getDatabaseVersion()); - if ( msg != null ) { - System.out.println(msg); - } - - ExecutorService executorService = Executors.newFixedThreadPool(1 + reporterList.size()); - - // Run tests. - executorService.submit(() -> { - try (Connection conn = ci.getConnection()) { - TestRunner testRunner = new TestRunner() - .addPathList(testPaths) - .addReporterList(reporterList) - .sourceMappingOptions(sourceMappingOptions[0]) - .testMappingOptions(testMappingOptions[0]) - .colorConsole(this.colorConsole) - .failOnErrors(true) - .skipCompatibilityCheck(skipCompatibilityCheck) - .includeObjects(finalIncludeObjectsList) - .excludeObjects(finalExcludeObjectsList); - - testRunner.run(conn); - } catch (SomeTestsFailedException e) { - returnCode[0] = this.failureExitCode; - } catch (SQLException e) { - System.out.println(e.getMessage()); - returnCode[0] = Cli.DEFAULT_ERROR_CODE; - executorService.shutdownNow(); + // Output a message if --failureExitCode is set but database framework is not capable of + String msg = RunCommandChecker.getCheckFailOnErrorMessage(failureExitCode, compatibilityProxy.getDatabaseVersion()); + if (msg != null) { + System.out.println(msg); } - }); - // Gather each reporter results on a separate thread. - getReporterManager().startReporterGatherers(executorService, ci, returnCode); + ExecutorService executorService = Executors.newFixedThreadPool(1 + reporterList.size()); + + // Run tests. + executorService.submit(() -> { + try (Connection conn = dataSource.getConnection()) { + TestRunner testRunner = new TestRunner() + .addPathList(testPaths) + .addReporterList(reporterList) + .sourceMappingOptions(sourceMappingOptions[0]) + .testMappingOptions(testMappingOptions[0]) + .colorConsole(this.colorConsole) + .failOnErrors(true) + .skipCompatibilityCheck(skipCompatibilityCheck) + .includeObjects(finalIncludeObjectsList) + .excludeObjects(finalExcludeObjectsList); + + testRunner.run(conn); + } catch (SomeTestsFailedException e) { + returnCode[0] = this.failureExitCode; + } catch (SQLException e) { + System.out.println(e.getMessage()); + returnCode[0] = Cli.DEFAULT_ERROR_CODE; + executorService.shutdownNow(); + } + }); - executorService.shutdown(); - executorService.awaitTermination(60, TimeUnit.MINUTES); - return returnCode[0]; - } + // Gather each reporter results on a separate thread. + getReporterManager().startReporterGatherers(executorService, dataSource, returnCode); + executorService.shutdown(); + executorService.awaitTermination(60, TimeUnit.MINUTES); + return returnCode[0]; + } + catch ( DatabaseNotCompatibleException | UtPLSQLNotInstalledException | DatabaseConnectionFailed e ) { + System.out.println(e.getMessage()); + } catch (Exception e) { + e.printStackTrace(); + } + return 1; + } + @Override + public String getCommand() { + return "run"; + } /** Returns FileMapperOptions for the first item of a given param list in a baseDir diff --git a/src/main/java/org/utplsql/cli/RunCommandChecker.java b/src/main/java/org/utplsql/cli/RunCommandChecker.java index e0c1bb2..6adce56 100644 --- a/src/main/java/org/utplsql/cli/RunCommandChecker.java +++ b/src/main/java/org/utplsql/cli/RunCommandChecker.java @@ -1,34 +1,24 @@ package org.utplsql.cli; +import org.utplsql.api.DBHelper; import org.utplsql.api.Version; import org.utplsql.api.compatibility.OptionalFeatures; +import java.sql.Connection; +import java.sql.SQLException; + /** Helper class to check several circumstances with RunCommand. Might need refactoring. * * @author pesse */ class RunCommandChecker { - /** Checks that ojdbc library exists - * - */ - static void checkOracleJDBCExists() - { - if ( !OracleLibraryChecker.checkOjdbcExists() ) - { - System.out.println("Could not find Oracle JDBC driver in classpath. Please download the jar from Oracle website" + - " and copy it to the 'lib' folder of your utPLSQL-cli installation."); - System.out.println("Download from http://www.oracle.com/technetwork/database/features/jdbc/jdbc-ucp-122-3110062.html"); - - throw new RuntimeException("Can't run utPLSQL-cli without Oracle JDBC driver"); - } - } - /** Checks that orai18n library exists if database is an oracle 11 * */ - static void checkOracleI18nExists(String oracleDatabaseVersion ) - { + static void checkOracleI18nExists(Connection con) throws SQLException { + + String oracleDatabaseVersion = DBHelper.getOracleDatabaseVersion(con); if ( oracleDatabaseVersion.startsWith("11.") && !OracleLibraryChecker.checkOrai18nExists() ) { System.out.println("Warning: Could not find Oracle i18n driver in classpath. Depending on the database charset " + diff --git a/src/main/java/org/utplsql/cli/VersionInfoCommand.java b/src/main/java/org/utplsql/cli/VersionInfoCommand.java new file mode 100644 index 0000000..0fec979 --- /dev/null +++ b/src/main/java/org/utplsql/cli/VersionInfoCommand.java @@ -0,0 +1,68 @@ +package org.utplsql.cli; + + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import org.utplsql.api.DBHelper; +import org.utplsql.api.JavaApiVersionInfo; +import org.utplsql.api.Version; +import org.utplsql.api.exception.UtPLSQLNotInstalledException; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +@Parameters(separators = "=", commandDescription = "prints version information of cli, java-api and - if connection is given - database utPLSQL framework") +public class VersionInfoCommand implements ICommand { + + @Parameter( + converter = ConnectionInfo.ConnectionStringConverter.class, + variableArity = true, + description = ConnectionInfo.COMMANDLINE_PARAM_DESCRIPTION) + private List connectionInfoList = new ArrayList<>(); + + public ConnectionInfo getConnectionInfo() { + if ( connectionInfoList != null && connectionInfoList.size() > 0 ) + return connectionInfoList.get(0); + else + return null; + } + + public int run() { + + System.out.println(CliVersionInfo.getInfo()); + System.out.println(JavaApiVersionInfo.getInfo()); + + try { + writeUtPlsqlVersion(getConnectionInfo()); + } + catch (SQLException e) { + e.printStackTrace(); + return 1; + } + + return 0; + } + + private void writeUtPlsqlVersion( ConnectionInfo ci ) throws SQLException { + if ( ci != null ) { + + DataSource dataSource = DataSourceProvider.getDataSource(ci, 1); + + try (Connection con = dataSource.getConnection()) { + Version v = DBHelper.getDatabaseFrameworkVersion( con ); + System.out.println("utPLSQL " + v.getNormalizedString()); + } + catch ( UtPLSQLNotInstalledException e ) { + System.out.println("utPLSQL framework is not installed in database."); + } + } + } + + @Override + public String getCommand() { + return "info"; + } +} diff --git a/src/test/java/org/utplsql/cli/CliHelpTest.java b/src/test/java/org/utplsql/cli/CliHelpTest.java new file mode 100644 index 0000000..6e87370 --- /dev/null +++ b/src/test/java/org/utplsql/cli/CliHelpTest.java @@ -0,0 +1,11 @@ +package org.utplsql.cli; + +import org.junit.jupiter.api.Test; + +public class CliHelpTest { + + @Test + public void showBasicHelp() { + TestHelper.runApp("help"); + } +} diff --git a/src/test/java/org/utplsql/cli/ReportersCommandIT.java b/src/test/java/org/utplsql/cli/ReportersCommandIT.java new file mode 100644 index 0000000..91a90d7 --- /dev/null +++ b/src/test/java/org/utplsql/cli/ReportersCommandIT.java @@ -0,0 +1,16 @@ +package org.utplsql.cli; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ReportersCommandIT { + + @Test + public void callReportersWorks() { + + int result = TestHelper.runApp("reporters", TestHelper.getConnectionString()); + + assertEquals(0, result); + } +} diff --git a/src/test/java/org/utplsql/cli/RunCommandCoverageReporterIT.java b/src/test/java/org/utplsql/cli/RunCommandCoverageReporterIT.java index cc661ef..4a1c6af 100644 --- a/src/test/java/org/utplsql/cli/RunCommandCoverageReporterIT.java +++ b/src/test/java/org/utplsql/cli/RunCommandCoverageReporterIT.java @@ -1,16 +1,11 @@ package org.utplsql.cli; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.HashSet; -import java.util.Scanner; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -77,10 +72,9 @@ public void run_CodeCoverageWithIncludeAndExclude() throws Exception { Path coveragePath = getTempCoverageFilePath(); - RunCommand runCmd = RunCommandTestHelper.createRunCommand(RunCommandTestHelper.getConnectionString(), + int result = TestHelper.runApp("run", TestHelper.getConnectionString(), "-f=ut_coverage_html_reporter", "-o=" + coveragePath, "-s", "-exclude=app.award_bonus,app.betwnstr"); - int result = runCmd.run(); String content = new String(Files.readAllBytes(coveragePath)); @@ -95,16 +89,13 @@ public void coverageReporterWriteAssetsToOutput() throws Exception { Path coveragePath = getTempCoverageFilePath(); Path coverageAssetsPath = Paths.get(coveragePath.toString() + "_assets"); - RunCommand runCmd = RunCommandTestHelper.createRunCommand(RunCommandTestHelper.getConnectionString(), + TestHelper.runApp("run", TestHelper.getConnectionString(), "-f=ut_coverage_html_reporter", "-o=" + coveragePath, "-s"); - runCmd.run(); - // Run twice to test overriding of assets - runCmd = RunCommandTestHelper.createRunCommand(RunCommandTestHelper.getConnectionString(), + TestHelper.runApp("run", TestHelper.getConnectionString(), "-f=ut_coverage_html_reporter", "-o=" + coveragePath, "-s"); - runCmd.run(); // Check application file exists File applicationJs = coverageAssetsPath.resolve(Paths.get("application.js")).toFile(); diff --git a/src/test/java/org/utplsql/cli/RunCommandIT.java b/src/test/java/org/utplsql/cli/RunCommandIT.java index f2f93fd..f03b4d2 100644 --- a/src/test/java/org/utplsql/cli/RunCommandIT.java +++ b/src/test/java/org/utplsql/cli/RunCommandIT.java @@ -15,16 +15,16 @@ public class RunCommandIT extends AbstractFileOutputTest { @Test public void run_Default() throws Exception { - RunCommand runCmd = RunCommandTestHelper.createRunCommand(RunCommandTestHelper.getConnectionString(), + + int result = TestHelper.runApp("run", + TestHelper.getConnectionString(), "-f=ut_documentation_reporter", "-s", "-c", "--failure-exit-code=2"); - int result = runCmd.run(); - // Only expect failure-exit-code to work on several framework versions - if (OptionalFeatures.FAIL_ON_ERROR.isAvailableFor(runCmd.getDatabaseVersion())) + if (OptionalFeatures.FAIL_ON_ERROR.isAvailableFor(TestHelper.getFrameworkVersion())) assertEquals(2, result); else assertEquals(0, result); @@ -35,7 +35,8 @@ public void run_MultipleReporters() throws Exception { String outputFileName = "output_" + System.currentTimeMillis() + ".xml"; addTempPath(Paths.get(outputFileName)); - RunCommand runCmd = RunCommandTestHelper.createRunCommand(RunCommandTestHelper.getConnectionString(), + int result = TestHelper.runApp("run", + TestHelper.getConnectionString(), "-f=ut_documentation_reporter", "-s", "-f=" + CoreReporters.UT_SONAR_TEST_REPORTER.name(), @@ -43,10 +44,8 @@ public void run_MultipleReporters() throws Exception { "-c", "--failure-exit-code=2"); - int result = runCmd.run(); - // Only expect failure-exit-code to work on several framework versions - if (OptionalFeatures.FAIL_ON_ERROR.isAvailableFor(runCmd.getDatabaseVersion())) + if (OptionalFeatures.FAIL_ON_ERROR.isAvailableFor(TestHelper.getFrameworkVersion())) assertEquals(2, result); else assertEquals(0, result); diff --git a/src/test/java/org/utplsql/cli/RunCommandTest.java b/src/test/java/org/utplsql/cli/RunCommandTest.java index ba6df7c..5658e59 100644 --- a/src/test/java/org/utplsql/cli/RunCommandTest.java +++ b/src/test/java/org/utplsql/cli/RunCommandTest.java @@ -1,7 +1,6 @@ package org.utplsql.cli; import org.junit.jupiter.api.Test; -import org.utplsql.api.CustomTypes; import org.utplsql.api.reporter.CoreReporters; import java.util.List; @@ -15,7 +14,7 @@ public class RunCommandTest { @Test public void reporterOptions_Default() { - RunCommand runCmd = RunCommandTestHelper.createRunCommand(RunCommandTestHelper.getConnectionString()); + RunCommand runCmd = TestHelper.createRunCommand(TestHelper.getConnectionString()); List reporterOptionsList = runCmd.getReporterOptionsList(); @@ -28,7 +27,7 @@ public void reporterOptions_Default() { @Test public void reporterOptions_OneReporter() { - RunCommand runCmd = RunCommandTestHelper.createRunCommand(RunCommandTestHelper.getConnectionString(), "-f=ut_documentation_reporter", "-o=output.txt"); + RunCommand runCmd = TestHelper.createRunCommand(TestHelper.getConnectionString(), "-f=ut_documentation_reporter", "-o=output.txt"); List reporterOptionsList = runCmd.getReporterOptionsList(); @@ -41,7 +40,7 @@ public void reporterOptions_OneReporter() { @Test public void reporterOptions_OneReporterForceScreen() { - RunCommand runCmd = RunCommandTestHelper.createRunCommand(RunCommandTestHelper.getConnectionString(), "-f=ut_documentation_reporter", "-o=output.txt", "-s"); + RunCommand runCmd = TestHelper.createRunCommand(TestHelper.getConnectionString(), "-f=ut_documentation_reporter", "-o=output.txt", "-s"); List reporterOptionsList = runCmd.getReporterOptionsList(); @@ -54,7 +53,7 @@ public void reporterOptions_OneReporterForceScreen() { @Test public void reporterOptions_OneReporterForceScreenInverse() { - RunCommand runCmd = RunCommandTestHelper.createRunCommand(RunCommandTestHelper.getConnectionString(), "-f=ut_documentation_reporter", "-s", "-o=output.txt"); + RunCommand runCmd = TestHelper.createRunCommand(TestHelper.getConnectionString(), "-f=ut_documentation_reporter", "-s", "-o=output.txt"); List reporterOptionsList = runCmd.getReporterOptionsList(); @@ -67,7 +66,7 @@ public void reporterOptions_OneReporterForceScreenInverse() { @Test public void reporterOptions_TwoReporters() { - RunCommand runCmd = RunCommandTestHelper.createRunCommand(RunCommandTestHelper.getConnectionString(), + RunCommand runCmd = TestHelper.createRunCommand(TestHelper.getConnectionString(), "-f=ut_documentation_reporter", "-f=ut_coverage_html_reporter", "-o=coverage.html", "-s"); diff --git a/src/test/java/org/utplsql/cli/RunCommandTestHelper.java b/src/test/java/org/utplsql/cli/TestHelper.java similarity index 58% rename from src/test/java/org/utplsql/cli/RunCommandTestHelper.java rename to src/test/java/org/utplsql/cli/TestHelper.java index 425659a..ca1abc9 100644 --- a/src/test/java/org/utplsql/cli/RunCommandTestHelper.java +++ b/src/test/java/org/utplsql/cli/TestHelper.java @@ -1,9 +1,15 @@ package org.utplsql.cli; import com.beust.jcommander.JCommander; +import org.utplsql.api.DBHelper; import org.utplsql.api.EnvironmentVariableUtil; +import org.utplsql.api.Version; -class RunCommandTestHelper { +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +class TestHelper { private static String sUrl; private static String sUser; private static String sPass; @@ -25,6 +31,17 @@ static RunCommand createRunCommand(String... args) { return runCmd; } + static int runApp(String... args) { + return Cli.runWithExitCode(args); + } + + static Version getFrameworkVersion() throws SQLException { + DataSource ds = DataSourceProvider.getDataSource(new ConnectionInfo(TestHelper.getConnectionString()), 1); + try (Connection con = ds.getConnection() ) { + return DBHelper.getDatabaseFrameworkVersion(con); + } + } + static String getConnectionString() { return sUser + "/" + sPass + "@" + sUrl; } diff --git a/src/test/java/org/utplsql/cli/VersionInfoCommandIT.java b/src/test/java/org/utplsql/cli/VersionInfoCommandIT.java new file mode 100644 index 0000000..6f4ffdc --- /dev/null +++ b/src/test/java/org/utplsql/cli/VersionInfoCommandIT.java @@ -0,0 +1,61 @@ +package org.utplsql.cli; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.utplsql.cli.util.SystemOutCapturer; + +import java.io.IOException; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class VersionInfoCommandIT { + + private SystemOutCapturer capturer; + + @BeforeEach + public void setupCaptureSystemOut() { + capturer = new SystemOutCapturer(); + } + + private int getNonEmptyLines(String content) { + return (int) Arrays.stream(content.split("[\n|\r]")) + .filter(line -> !line.isEmpty()) + .count(); + } + + private void assertNumberOfLines( int expected, String content ) { + int numOfLines = getNonEmptyLines(content); + assertEquals(expected, numOfLines, String.format("Expected output to have %n lines, but got %n", expected, numOfLines)); + } + @Test + public void infoCommandRunsWithoutConnection() throws Exception { + + capturer.start(); + + int result = TestHelper.runApp("info"); + + String output = capturer.stop(); + + assertEquals(0, result); + assertNumberOfLines(2, output); + } + @Test + public void infoCommandRunsWithConnection() throws Exception { + + capturer.start(); + + int result = TestHelper.runApp("info", TestHelper.getConnectionString()); + + String output = capturer.stop(); + + assertEquals(0, result); + assertNumberOfLines(3, output); + } + + @AfterEach + public void cleanupCaptureSystemOut() throws IOException { + capturer.stop(); + } +} diff --git a/src/test/java/org/utplsql/cli/util/SystemOutCapturer.java b/src/test/java/org/utplsql/cli/util/SystemOutCapturer.java new file mode 100644 index 0000000..33f60d6 --- /dev/null +++ b/src/test/java/org/utplsql/cli/util/SystemOutCapturer.java @@ -0,0 +1,74 @@ +package org.utplsql.cli.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Arrays; +import java.util.List; + +/** All credit to Manasjyoti Sharma: https://stackoverflow.com/a/30665299 + */ +public class SystemOutCapturer { + private ByteArrayOutputStream baos; + private PrintStream previous; + private boolean capturing; + + public void start() { + if (capturing) { + return; + } + + capturing = true; + previous = System.out; + baos = new ByteArrayOutputStream(); + + OutputStream outputStreamCombiner = + new OutputStreamCombiner(Arrays.asList(previous, baos)); + PrintStream custom = new PrintStream(outputStreamCombiner); + + System.setOut(custom); + } + + public String stop() { + if (!capturing) { + return ""; + } + + System.setOut(previous); + + String capturedValue = baos.toString(); + + baos = null; + previous = null; + capturing = false; + + return capturedValue; + } + + private static class OutputStreamCombiner extends OutputStream { + private List outputStreams; + + public OutputStreamCombiner(List outputStreams) { + this.outputStreams = outputStreams; + } + + public void write(int b) throws IOException { + for (OutputStream os : outputStreams) { + os.write(b); + } + } + + public void flush() throws IOException { + for (OutputStream os : outputStreams) { + os.flush(); + } + } + + public void close() throws IOException { + for (OutputStream os : outputStreams) { + os.close(); + } + } + } +}