diff --git a/build.gradle b/build.gradle index 795ee9ae63..5c04144ff3 100644 --- a/build.gradle +++ b/build.gradle @@ -157,8 +157,7 @@ configure(subprojects.findAll { !it.name.contains("android") }) { dependencies { implementation "org.slf4j:slf4j-api:$slf4jVersion" - testImplementation "org.slf4j:jcl-over-slf4j:$slf4jVersion" - testImplementation "org.slf4j:slf4j-simple:$slf4jVersion" + testImplementation "org.slf4j:slf4j-jdk14:$slf4jVersion" testImplementation "junit:junit:$junitVersion" testImplementation "org.assertj:assertj-core:$assertjVersion" testImplementation "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion" @@ -177,7 +176,6 @@ configure(subprojects.findAll { !it.name.contains("android") }) { test { systemProperty 'jgiven.report.dir', 'build/reports/jgiven/json' systemProperty 'jgiven.report.text', 'false' - systemProperty 'org.slf4j.simpleLogger.defaultLogLevel', 'warn' if (jacocoEnabled) { jacoco { diff --git a/docs/report_generation.adoc b/docs/report_generation.adoc index f428661e62..49778ccc82 100644 --- a/docs/report_generation.adoc +++ b/docs/report_generation.adoc @@ -142,7 +142,7 @@ Now run: $ mvn verify ---- -HTML reports are then generated into the `target/jgiven-reports/html` directory. +HTML reports are then generated into the `target/jgiven-reports/html` directory. Note that the plugin relies on the existence of the JSON output, so if the property `jgiven.reports.enabled` was set to `false`, no output will be generated. ==== Gradle @@ -171,6 +171,7 @@ buildscript { } apply plugin: "com.tngtech.jgiven.gradle-plugin" + ---- Now run: @@ -180,7 +181,7 @@ Now run: $ gradle test jgivenTestReport ---- -HTML reports are then generated into the `build/reports/jgiven/test/html/` directory. +HTML reports are then generated into the `build/reports/jgiven/test/html/` directory. Note that the plugin relies on the existence of the JSON output, so if the property `jgiven.reports.enabled` was set to `false`, no output will be generated. If you want that the HTML report is always generated after the tests have been executed, you can configure the `test` task in your Gradle @@ -193,3 +194,24 @@ test.finalizedBy jgivenTestReport For additional information about the Gradle plugin refer to https://plugins.gradle.org/plugin/com.tngtech.jgiven.gradle-plugin + +=== Configuration File + +JGiven will optionally load a configuration properties file, defaulting to: +`jgiven.properties`. The path to the configuration can be customized with the system property: +---- +jgiven.config.path +---- +The encoding for the file is assumed to be `UTF-8`, but can be customized with the system property: +---- +jgiven.config.charset +---- +The following can be defined in the properties file: +---- +jgiven.report.enabled=false +jgiven.report.dir= +jgiven.report.text=false +jgiven.report.text.color +jgiven.report.filterStackTrace=true +---- +Configuration defined via Java system properties will take precedence over values in the configuration file. diff --git a/example-projects/spock/src/test/resources/log4j.properties b/example-projects/spock/src/test/resources/log4j.properties deleted file mode 100644 index c876481944..0000000000 --- a/example-projects/spock/src/test/resources/log4j.properties +++ /dev/null @@ -1,4 +0,0 @@ -log4j.rootLogger=ERROR, stdout -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n \ No newline at end of file diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/Config.java b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/Config.java index 2f604ba91f..f0c386e4bb 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/Config.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/Config.java @@ -1,17 +1,22 @@ package com.tngtech.jgiven.impl; import com.tngtech.jgiven.config.ConfigValue; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Optional; +import java.util.Properties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Helper class to access all system properties to configure JGiven. */ public class Config { - private static final Logger log = LoggerFactory.getLogger( Config.class ); + private static final Logger log = LoggerFactory.getLogger(Config.class); private static final Config INSTANCE = new Config(); private static final String TRUE = "true"; @@ -23,69 +28,113 @@ public class Config { private static final String JGIVEN_REPORT_TEXT_COLOR = "jgiven.report.text.color"; private static final String JGIVEN_FILTER_STACK_TRACE = "jgiven.report.filterStackTrace"; private static final String JGIVEN_REPORT_DRY_RUN = "jgiven.report.dry-run"; + private static final String JGIVEN_CONFIG_PATH = "jgiven.config.path"; + private static final String JGIVEN_CONFIG_CHARSET = "jgiven.config.charset"; - public static Config config(){ + private final Properties configFileProperties = loadConfigFileProperties(); + + public static Config config() { return INSTANCE; } - static{ - if( INSTANCE.dryRun() ) { - log.info( "Dry Run enabled." ); + static { + logDryRunEnabled(); + logReportEnabled(); + } + + static void logDryRunEnabled() { + if (INSTANCE.dryRun()) { + log.info("Dry Run enabled."); + } + } + + static void logReportEnabled() { + if (!INSTANCE.isReportEnabled()) { + log.info("Please note that the report generation is turned off."); + } + } + + private Config() { + } + + private static Properties loadConfigFileProperties() { + String path = System.getProperty(JGIVEN_CONFIG_PATH, "jgiven.properties"); + String charset = System.getProperty(JGIVEN_CONFIG_CHARSET, "UTF-8"); + Properties properties = new Properties(); + try (Reader reader = Files.newBufferedReader(Paths.get(path), Charset.forName(charset))) { + properties.load(reader); + } catch (IOException e) { + log.debug("config file " + path + " not loaded: " + e.getMessage()); } + return properties; + } + + private String resolveProperty(String name) { + return resolveProperty(name, null); + } + + private String resolveProperty(String name, String defaultValue) { + return System.getProperty(name, configFileProperties.getProperty(name, defaultValue)); } - public Optional getReportDir(){ - String reportDirName = System.getProperty( JGIVEN_REPORT_DIR ); - if( reportDirName == null ) { - if( System.getProperty( "surefire.test.class.path" ) != null ) { + /** + * Returns the directory set either via a configuration file or a system property. + * If no value is specified and the surefire test classpath is set, the default maven directory will be used, + * otherwise a default is returned. + */ + public Optional getReportDir() { + String reportDirName = resolveProperty(JGIVEN_REPORT_DIR); + if (reportDirName == null) { + if (resolveProperty("surefire.test.class.path") != null) { reportDirName = "target/jgiven-reports/json"; - log.info( JGIVEN_REPORT_DIR + " not set, but detected surefire plugin, generating reports to " + reportDirName ); + log.info(JGIVEN_REPORT_DIR + " not set, but detected surefire plugin, generating reports to " + + reportDirName); } else { reportDirName = "jgiven-reports"; - log.debug( JGIVEN_REPORT_DIR + " not set, using default value jgiven-reports" ); + log.debug(JGIVEN_REPORT_DIR + " not set, using default value jgiven-reports"); } } - File reportDir = new File( reportDirName ); - if( reportDir.exists() && !reportDir.isDirectory() ) { - log.warn( reportDirName + " exists but is not a directory. Will not generate JGiven reports." ); + File reportDir = new File(reportDirName); + if (reportDir.exists() && !reportDir.isDirectory()) { + log.warn(reportDirName + " exists but is not a directory. Will not generate JGiven reports."); return Optional.empty(); } - log.debug( "Using folder " + reportDirName + " to store JGiven reports" ); + log.debug("Using folder " + reportDirName + " to store JGiven reports"); - return Optional.of( reportDir ); + return Optional.of(reportDir); } - public boolean isReportEnabled(){ - return TRUE.equalsIgnoreCase( System.getProperty( JGIVEN_REPORT_ENABLED, TRUE ) ); + public boolean isReportEnabled() { + return TRUE.equalsIgnoreCase(resolveProperty(JGIVEN_REPORT_ENABLED, TRUE)); } - public void setReportEnabled( boolean enabled ){ - System.setProperty( JGIVEN_REPORT_ENABLED, "" + enabled ); + public void setReportEnabled(boolean enabled) { + System.setProperty(JGIVEN_REPORT_ENABLED, "" + enabled); } - public ConfigValue textColorEnabled(){ - return ConfigValue.fromString( System.getProperty( JGIVEN_REPORT_TEXT_COLOR, AUTO ) ); + public ConfigValue textColorEnabled() { + return ConfigValue.fromString(resolveProperty(JGIVEN_REPORT_TEXT_COLOR, AUTO)); } - public boolean textReport(){ - return TRUE.equalsIgnoreCase( System.getProperty( JGIVEN_REPORT_TEXT, TRUE ) ); + public boolean textReport() { + return TRUE.equalsIgnoreCase(resolveProperty(JGIVEN_REPORT_TEXT, TRUE)); } - public void setTextReport( boolean b ){ - System.setProperty( JGIVEN_REPORT_TEXT, "" + b ); + public void setTextReport(boolean b) { + System.setProperty(JGIVEN_REPORT_TEXT, "" + b); } - public boolean filterStackTrace(){ - return TRUE.equalsIgnoreCase( System.getProperty( JGIVEN_FILTER_STACK_TRACE, TRUE ) ); + public boolean filterStackTrace() { + return TRUE.equalsIgnoreCase(resolveProperty(JGIVEN_FILTER_STACK_TRACE, TRUE)); } - public void setReportDir( File reportDir ){ - System.setProperty( JGIVEN_REPORT_DIR, reportDir.getAbsolutePath() ); + public void setReportDir(File reportDir) { + System.setProperty(JGIVEN_REPORT_DIR, reportDir.getAbsolutePath()); } - public boolean dryRun(){ - return TRUE.equals( System.getProperty( JGIVEN_REPORT_DRY_RUN, FALSE ) ); + public boolean dryRun() { + return TRUE.equals(System.getProperty(JGIVEN_REPORT_DRY_RUN, FALSE)); } } diff --git a/jgiven-core/src/test/java/com/tngtech/jgiven/impl/ConfigTest.java b/jgiven-core/src/test/java/com/tngtech/jgiven/impl/ConfigTest.java new file mode 100644 index 0000000000..919f5cf8f2 --- /dev/null +++ b/jgiven-core/src/test/java/com/tngtech/jgiven/impl/ConfigTest.java @@ -0,0 +1,159 @@ +package com.tngtech.jgiven.impl; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.common.base.Charsets; +import com.google.common.io.CharSink; +import com.google.common.io.FileWriteMode; +import com.google.common.io.Files; +import com.tngtech.jgiven.config.ConfigValue; +import com.tngtech.jgiven.impl.TestUtil.JGivenLogHandler; +import java.io.File; +import java.lang.reflect.Constructor; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class ConfigTest { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private final Map systemPropertiesBackup = new HashMap<>(); + private final JGivenLogHandler handler = new JGivenLogHandler(); + private CharSink jgivenConfig; + + @Before + public void setupPropertiesFile() throws Exception { + Logger.getLogger(Config.class.getName()).addHandler(handler); + File configFile = temporaryFolder.newFile(); + jgivenConfig = Files.asCharSink(configFile, Charsets.UTF_8, FileWriteMode.APPEND); + setSystemProperty("jgiven.config.path", configFile.getAbsolutePath()); + setSystemProperty("jgiven.report.dir", null); + } + + @After + public void teardown() { + Logger.getLogger(Config.class.getName()).removeHandler(handler); + } + + @Test + public void disabledReportsLogAMessage() { + setSystemProperty("jgiven.report.enabled", "false"); + + JGivenLogHandler.resetEvents(); + Config.logReportEnabled(); + + assertThat(JGivenLogHandler.containsLoggingEvent("Please note that the report generation is turned off.", + Level.INFO)).isTrue(); + } + + @Test + public void enabledReportsDontLogAMessage() { + setSystemProperty("jgiven.report.enabled", "true"); + + JGivenLogHandler.resetEvents(); + Config.logReportEnabled(); + + assertThat(JGivenLogHandler.containsLoggingEvent("Please note that the report generation is turned off.", + Level.INFO)).isFalse(); + } + + @Test + public void dryRunEnabledLogsAMessage() { + setSystemProperty("jgiven.report.dry-run", "true"); + + JGivenLogHandler.resetEvents(); + Config.logDryRunEnabled(); + + assertThat(JGivenLogHandler.containsLoggingEvent("Dry Run enabled.", + Level.INFO)).isTrue(); + } + + @Test + public void dryRunDisabledDoesntLogAMessage() { + setSystemProperty("jgiven.report.dry-run", "false"); + + JGivenLogHandler.resetEvents(); + Config.logDryRunEnabled(); + + assertThat(JGivenLogHandler.containsLoggingEvent("Dry Run enabled.", + Level.INFO)).isFalse(); + } + + @Test + public void configValuesHaveDefaults() throws Exception { + Config underTest = createNewTestInstance(); + + assertThat(underTest.isReportEnabled()).isTrue(); + assertThat(underTest.getReportDir()).get().extracting(File::getPath).isEqualTo("jgiven-reports"); + assertThat(underTest.textColorEnabled()).extracting(Enum::name).isEqualTo("AUTO"); + assertThat(underTest.filterStackTrace()).isTrue(); + } + + @Test + public void configFileValuesAreRecognized() throws Exception { + File reportPath = temporaryFolder.newFolder(); + jgivenConfig.write("jgiven.report.enabled=false\n"); + jgivenConfig.write("jgiven.report.dir=" + + reportPath.getAbsolutePath().replace("\\", "/") + "\n"); + jgivenConfig.write("jgiven.report.text=false\n"); + jgivenConfig.write("jgiven.report.text.color=true\n"); + jgivenConfig.write("jgiven.report.filterStackTrace=false\n"); + + Config underTest = createNewTestInstance(); + + assertThat(underTest.isReportEnabled()).isFalse(); + assertThat(underTest.getReportDir()).contains(reportPath); + assertThat(underTest.textReport()).isFalse(); + assertThat(underTest.textColorEnabled()).isEqualTo(ConfigValue.TRUE); + assertThat(underTest.filterStackTrace()).isFalse(); + } + + @Test + public void testCommandLinePropertiesTakePrecedenceOverConfigFile() throws Exception { + jgivenConfig.write("jgiven.report.enabled=false\n"); + setSystemProperty("jgiven.report.enabled", "true"); + + Config underTest = createNewTestInstance(); + + assertThat(underTest.isReportEnabled()).isTrue(); + } + + @After + public void cleanupSystemProperties() { + systemPropertiesBackup.entrySet() + .stream() + .peek(entry -> System.clearProperty(entry.getKey())) + .filter(entry -> entry.getValue() != null) + .forEach(entry -> System.setProperty(entry.getKey(), entry.getValue())); + } + + private static Config createNewTestInstance() throws Exception { + Constructor constructor = Config.class.getDeclaredConstructor(); + try { + constructor.setAccessible(true); + return constructor.newInstance(); + } finally { + constructor.setAccessible(false); + } + } + + private void setSystemProperty(String key, String value) { + String originalValue = System.getProperty(key); + if (!systemPropertiesBackup.containsKey(key)) { + systemPropertiesBackup.put(key, originalValue); + } + if (value == null) { + System.clearProperty(key); + } else { + System.setProperty(key, value); + } + } +} diff --git a/jgiven-core/src/test/java/com/tngtech/jgiven/impl/TestUtil/JGivenLogHandler.java b/jgiven-core/src/test/java/com/tngtech/jgiven/impl/TestUtil/JGivenLogHandler.java new file mode 100644 index 0000000000..d7c619f5c5 --- /dev/null +++ b/jgiven-core/src/test/java/com/tngtech/jgiven/impl/TestUtil/JGivenLogHandler.java @@ -0,0 +1,32 @@ +package com.tngtech.jgiven.impl.TestUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +public class JGivenLogHandler extends Handler { + static private List logList = new ArrayList<>(); + + @Override + public void publish(LogRecord record) { + logList.add(record); + } + + @Override + public void flush() { + resetEvents(); + } + + @Override + public void close() throws SecurityException {} + + public static boolean containsLoggingEvent(String message, Level level) { + return logList.stream().anyMatch(logRecord -> logRecord.getMessage().equals(message) + && logRecord.getLevel().equals(level)); + } + public static void resetEvents() { + logList.clear(); + } +} diff --git a/jgiven-core/src/test/resources/log4j.properties b/jgiven-core/src/test/resources/log4j.properties deleted file mode 100644 index 8db56469cf..0000000000 --- a/jgiven-core/src/test/resources/log4j.properties +++ /dev/null @@ -1,8 +0,0 @@ -log4j.rootLogger=ERROR, stdout -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n - - -log4j.logger.com.tngtech.jgiven=INFO -log4j.logger.com.tngtech.jgiven.impl.ScenarioExecutor=OFF diff --git a/jgiven-junit/src/test/resources/log4j.properties b/jgiven-junit/src/test/resources/log4j.properties deleted file mode 100644 index c876481944..0000000000 --- a/jgiven-junit/src/test/resources/log4j.properties +++ /dev/null @@ -1,4 +0,0 @@ -log4j.rootLogger=ERROR, stdout -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n \ No newline at end of file diff --git a/jgiven-spock/src/test/resources/log4j.properties b/jgiven-spock/src/test/resources/log4j.properties deleted file mode 100644 index c876481944..0000000000 --- a/jgiven-spock/src/test/resources/log4j.properties +++ /dev/null @@ -1,4 +0,0 @@ -log4j.rootLogger=ERROR, stdout -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n \ No newline at end of file diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/json/ReportConfigurationTest.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/json/ReportConfigurationTest.java new file mode 100644 index 0000000000..70894d4617 --- /dev/null +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/json/ReportConfigurationTest.java @@ -0,0 +1,110 @@ +package com.tngtech.jgiven.report.json; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.tngtech.jgiven.Stage; +import com.tngtech.jgiven.annotation.AfterScenario; +import com.tngtech.jgiven.annotation.BeforeScenario; +import com.tngtech.jgiven.junit.SimpleScenarioTest; +import com.tngtech.jgiven.tests.TestScenarioRepository; +import com.tngtech.jgiven.tests.TestScenarios; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.JUnitCore; +import org.junit.runner.Request; + +public class ReportConfigurationTest extends SimpleScenarioTest { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void jgiven_report_is_disabled_by_a_system_property() throws IOException { + File reportFolder = temporaryFolder.newFolder(); + given().a_set_system_property("jgiven.report.dir", getWindowsCompatiblePath(reportFolder)) + .and().a_set_system_property("jgiven.report.enabled", "false") + .and().a_Test_scenario(); + + when().the_test_is_executed_with_junit(); + + then().the_report_is_not_written_to(reportFolder); + } + + @Test + public void jgiven_report_directory_is_set_via_a_system_property() throws IOException { + File reportFolder = temporaryFolder.newFolder(); + given().a_set_system_property("jgiven.report.dir", getWindowsCompatiblePath(reportFolder)) + .and().a_set_system_property("jgiven.report.enabled", "true") + .and().a_Test_scenario(); + + when().the_test_is_executed_with_junit(); + + then().the_report_is_written_to(reportFolder); + } + + private String getWindowsCompatiblePath(File file) { + return file.getAbsolutePath().replace("\\", "/"); + } + + static class ReportConfigurationTestStage extends Stage { + + private TestScenarioRepository.TestScenario testScenario; + + private final Map systemPropertiesBackup = new HashMap<>(); + + @BeforeScenario + private void createConfigurationFile() { + a_set_system_property("jgiven.report.dir", null); + } + + ReportConfigurationTestStage a_set_system_property(String key, String value) { + String originalValue = System.getProperty(key); + if (!systemPropertiesBackup.containsKey(key)) { + systemPropertiesBackup.put(key, originalValue); + } + if (value == null) { + System.clearProperty(key); + } else { + System.setProperty(key, value); + } + return self(); + } + + ReportConfigurationTestStage a_Test_scenario() { + testScenario = new TestScenarioRepository.TestScenario(TestScenarios.class, "test_with_tag_annotation"); + return self(); + } + + ReportConfigurationTestStage the_test_is_executed_with_junit() { + assertThat(testScenario).as("No matching test scenario found").isNotNull(); + + JUnitCore junitCore = new JUnitCore(); + junitCore.run(Request.method(testScenario.testClass, testScenario.testMethod)); + return self(); + } + + ReportConfigurationTestStage the_report_is_not_written_to(File file) { + assertThat(file).isEmptyDirectory(); + return self(); + } + + ReportConfigurationTestStage the_report_is_written_to(File file) { + assertThat(file).isNotEmptyDirectory(); + return self(); + } + + @AfterScenario + private void clearSystemProperties() { + systemPropertiesBackup.entrySet() + .stream() + .peek(entry -> System.clearProperty(entry.getKey())) + .filter(entry -> entry.getValue() != null) + .forEach(entry -> System.setProperty(entry.getKey(), entry.getValue())); + } + } +} diff --git a/jgiven-tests/src/test/resources/log4j.properties b/jgiven-tests/src/test/resources/log4j.properties deleted file mode 100644 index c876481944..0000000000 --- a/jgiven-tests/src/test/resources/log4j.properties +++ /dev/null @@ -1,4 +0,0 @@ -log4j.rootLogger=ERROR, stdout -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n \ No newline at end of file