diff --git a/core/src/main/java/org/arquillian/smart/testing/configuration/Configuration.java b/core/src/main/java/org/arquillian/smart/testing/configuration/Configuration.java index 72847d3a1..bd3faf3e6 100644 --- a/core/src/main/java/org/arquillian/smart/testing/configuration/Configuration.java +++ b/core/src/main/java/org/arquillian/smart/testing/configuration/Configuration.java @@ -10,6 +10,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.StreamSupport; import org.arquillian.smart.testing.RunMode; import org.arquillian.smart.testing.hub.storage.local.LocalStorage; @@ -38,6 +39,8 @@ public class Configuration implements ConfigurationSection { public static final String SMART_TESTING_DEBUG = "smart.testing.debug"; public static final String SMART_TESTING_AUTOCORRECT = "smart.testing.autocorrect"; + static final String INHERIT = "inherit"; + private String[] strategies = new String[0]; private String[] customStrategies = new String[0]; private String[] customProviders = new String[0]; @@ -166,25 +169,25 @@ public void loadStrategyConfigurations(String... strategies) { } private List getStrategiesConfigurations(String... strategies) { - List convertedList = new ArrayList<>(); - - StreamSupport.stream(new JavaSPILoader().all(TestExecutionPlannerFactory.class).spliterator(), false) + return StreamSupport.stream(new JavaSPILoader().all(TestExecutionPlannerFactory.class).spliterator(), false) .filter( testExecutionPlannerFactory -> Arrays.asList(strategies).contains(testExecutionPlannerFactory.alias())) .map(TestExecutionPlannerFactory::strategyConfiguration) .filter(Objects::nonNull) - .forEach(strategyConfiguration -> { - final Class strategyConfigurationClass = - (Class) strategyConfiguration.getClass(); - final Object strategyConfig = strategiesConfig.get(strategyConfiguration.name()); - Map strategyConfigMap = new HashMap<>(); - if (strategyConfig != null) { - strategyConfigMap = (Map) strategyConfig; - } - convertedList.add(mapToObject(strategyConfigurationClass, strategyConfigMap)); - }); + .map(this::loadStrategyConfiguration) + .collect(Collectors.toList()); + } + + private StrategyConfiguration loadStrategyConfiguration(StrategyConfiguration strategyConfiguration) { + final Class strategyConfigurationClass = + (Class) strategyConfiguration.getClass(); + final Object strategyConfig = strategiesConfig.get(strategyConfiguration.name()); + Map strategyConfigMap = new HashMap<>(); + if (strategyConfig != null) { + strategyConfigMap = (Map) strategyConfig; + } - return convertedList; + return mapToObject(strategyConfigurationClass, strategyConfigMap); } public File dump(File rootDir) { diff --git a/core/src/main/java/org/arquillian/smart/testing/configuration/ConfigurationLoader.java b/core/src/main/java/org/arquillian/smart/testing/configuration/ConfigurationLoader.java index 24d07a83d..974aff509 100644 --- a/core/src/main/java/org/arquillian/smart/testing/configuration/ConfigurationLoader.java +++ b/core/src/main/java/org/arquillian/smart/testing/configuration/ConfigurationLoader.java @@ -3,14 +3,8 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; import java.util.function.Function; import org.arquillian.smart.testing.hub.storage.local.LocalStorage; @@ -18,6 +12,7 @@ import org.arquillian.smart.testing.logger.Logger; import org.yaml.snakeyaml.Yaml; +import static org.arquillian.smart.testing.configuration.ConfigurationReader.readEffectiveConfiguration; import static org.arquillian.smart.testing.configuration.ObjectMapper.mapToObject; public class ConfigurationLoader { @@ -72,8 +67,13 @@ public static Configuration load(File projectDir, Function stopCo configFile = projectDir; } - Map yamlConfiguration = readConfiguration(configFile); - return parseConfiguration(yamlConfiguration); + return loadEffectiveConfiguration(configFile); + } + + private static Configuration loadEffectiveConfiguration(File configFile) { + final Map effectiveConfig = readEffectiveConfiguration(configFile); + + return loadAsConfiguration(effectiveConfig); } /** @@ -105,42 +105,6 @@ public static Configuration loadPrecalculated(File projectDir) { } } - private static Map readConfiguration(File configPath) { - if (!configPath.isDirectory()) { - return getConfigParametersFromFile(getConfigurationFilePath(configPath)); - } - - final File[] files = - configPath.listFiles((dir, name) -> name.equals(SMART_TESTING_YML) || name.equals(SMART_TESTING_YAML)); - - if (files == null) { - throw new RuntimeException("I/O errors occurs while listing dir " + configPath); - } - - if (files.length == 0) { - logger.info("Config file `" + SMART_TESTING_YAML + "` OR `" + SMART_TESTING_YML + "` is not found. " - + "Using system properties to load configuration for smart testing."); - } else { - return getConfigParametersFromFile(getConfigurationFilePath(files)); - } - return Collections.emptyMap(); - } - - private static Map getConfigParametersFromFile(Path filePath) { - try (InputStream io = Files.newInputStream(filePath)) { - final Yaml yaml = new Yaml(); - Map yamlConfig = yaml.load(io); - if (yamlConfig == null) { - logger.warn(String.format("The configuration file %s is empty.", filePath)); - return new HashMap<>(); - } else { - return yamlConfig; - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - static Configuration loadConfigurationFromFile(File configFile) { try (FileReader fileReader = new FileReader(configFile)) { final Yaml yaml = new Yaml(); @@ -150,50 +114,9 @@ static Configuration loadConfigurationFromFile(File configFile) { } } - // testing - static Configuration load(Path path) { - try (InputStream io = Files.newInputStream(path)) { - final Yaml yaml = new Yaml(); - Map yamlConfiguration = yaml.load(io); - return parseConfiguration(yamlConfiguration); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private static Path getConfigurationFilePath(File... files) { - Path configPath; - if (files.length == 1) { - configPath = files[0].toPath(); - } else { - configPath = getDefaultConfigFile(files); - } - logger.info("Using configuration from " + configPath); - return configPath; - } - - private static Path getDefaultConfigFile(File... files) { - if (files.length == 2) { - logger.warn( - "Found multiple config files with supported names: " + SMART_TESTING_YAML + ", " + SMART_TESTING_YML); - } - - final Path configFilePath = Arrays.stream(files) - .filter(file -> { - if (files.length == 2) { - return file.getName().equals(SMART_TESTING_YML); - } - return file.getName().equals(SMART_TESTING_YAML) || file.getName().equals(SMART_TESTING_YML); - }) - .map(File::toPath) - .findFirst() - .get(); - - return configFilePath; - } - - private static Configuration parseConfiguration(Map yamlConfiguration) { + private static Configuration loadAsConfiguration(Map yamlConfiguration) { final Object strategiesConfiguration = yamlConfiguration.get("strategiesConfiguration"); + final Configuration configuration = mapToObject(Configuration.class, yamlConfiguration); if (strategiesConfiguration != null) { configuration.setStrategiesConfig((Map) strategiesConfiguration); diff --git a/core/src/main/java/org/arquillian/smart/testing/configuration/ConfigurationReader.java b/core/src/main/java/org/arquillian/smart/testing/configuration/ConfigurationReader.java new file mode 100644 index 000000000..17434cce4 --- /dev/null +++ b/core/src/main/java/org/arquillian/smart/testing/configuration/ConfigurationReader.java @@ -0,0 +1,135 @@ +package org.arquillian.smart.testing.configuration; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Collections; +import java.util.Deque; +import java.util.Map; +import org.arquillian.smart.testing.logger.Log; +import org.arquillian.smart.testing.logger.Logger; +import org.yaml.snakeyaml.Yaml; + +import static org.arquillian.smart.testing.configuration.Configuration.INHERIT; +import static org.arquillian.smart.testing.configuration.ConfigurationLoader.SMART_TESTING_YAML; +import static org.arquillian.smart.testing.configuration.ConfigurationLoader.SMART_TESTING_YML; + +class ConfigurationReader { + + private static final Logger logger = Log.getLogger(); + + static Map readEffectiveConfiguration(File configPath) { + if (!configPath.isDirectory()) { + return readEffectiveConfig(getConfigurationFilePath(configPath)); + } + + final File[] files = + configPath.listFiles((dir, name) -> name.equals(SMART_TESTING_YML) || name.equals(SMART_TESTING_YAML)); + + if (files == null) { + throw new RuntimeException("I/O errors occurs while listing dir " + configPath); + } + + if (files.length == 0) { + logger.info("Config file `" + SMART_TESTING_YAML + "` OR `" + SMART_TESTING_YML + "` is not found. " + + "Using system properties to load configuration for smart testing."); + } else { + return readEffectiveConfig(getConfigurationFilePath(files)); + } + return Collections.emptyMap(); + } + + private static Map readEffectiveConfig(Path filePath){ + Map config = getConfigParametersFromFile(filePath); + Deque> configs = new ArrayDeque<>(); + configs.add(config); + while (config.get(INHERIT) != null) { + String inherit = String.valueOf(config.get(INHERIT)); + filePath = filePath.getParent().resolve(inherit); + config = getConfigParametersFromFile(filePath); + if (!config.isEmpty()) { + configs.addFirst(config); + } + } + + Map effectiveConfig = configs.pollFirst(); + while (!configs.isEmpty()) { + overwriteInnerProperties(effectiveConfig, configs.pollFirst()); + } + + effectiveConfig.remove(INHERIT); + + return effectiveConfig; + } + + private static void overwriteInnerProperties(Map effective, Map inner) { + for (String key: inner.keySet()) { + if (isNonTrivialPropertyContainedInMap(key, inner, effective)) { + effective.put(key, inner.get(key)); + } else { + final Map effectiveValue = ((Map) effective.get(key)); + final Map innerValue = ((Map) inner.get(key)); + overwriteInnerProperties(effectiveValue, innerValue); + effective.put(key, effectiveValue); + } + } + } + + private static boolean isNonTrivialPropertyContainedInMap(String key, Map inner, + Map effective) { + return !Map.class.isAssignableFrom(inner.get(key).getClass()) || !effective.containsKey(key); + } + + private static Map getConfigParametersFromFile(Path filePath) { + if (!filePath.toFile().exists()) { + logger.warn(String.format("The configuration file %s is not exists.", filePath)); + return Collections.emptyMap(); + } + try (InputStream io = Files.newInputStream(filePath)) { + final Yaml yaml = new Yaml(); + Map yamlConfig = yaml.load(io); + if (yamlConfig == null) { + logger.warn(String.format("The configuration file %s is empty.", filePath)); + return Collections.emptyMap(); + } else { + return yamlConfig; + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static Path getConfigurationFilePath(File... files) { + Path configPath; + if (files.length == 1) { + configPath = files[0].toPath(); + } else { + configPath = getDefaultConfigFile(files); + } + logger.info("Using configuration from " + configPath); + return configPath; + } + + private static Path getDefaultConfigFile(File... files) { + if (files.length == 2) { + logger.warn( + "Found multiple config files with supported names: " + SMART_TESTING_YAML + ", " + SMART_TESTING_YML); + } + + return Arrays.stream(files) + .filter(file -> { + if (files.length == 2) { + return file.getName().equals(SMART_TESTING_YML); + } + return file.getName().equals(SMART_TESTING_YAML) || file.getName().equals(SMART_TESTING_YML); + }) + .map(File::toPath) + .findFirst() + .get(); + } +} diff --git a/core/src/main/java/org/arquillian/smart/testing/configuration/ObjectMapper.java b/core/src/main/java/org/arquillian/smart/testing/configuration/ObjectMapper.java index 66f34c1d3..08f28296e 100644 --- a/core/src/main/java/org/arquillian/smart/testing/configuration/ObjectMapper.java +++ b/core/src/main/java/org/arquillian/smart/testing/configuration/ObjectMapper.java @@ -8,7 +8,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; @@ -42,10 +42,7 @@ private static void invokeMethodWithMappedValue(List conf final String property = Character.toLowerCase(field.charAt(0)) + field.substring(1); Object configFileValue = map.get(property); - Optional foundConfigItem = - configItems.stream().filter(item -> property.equals(item.getParamName())).findFirst(); - - Object converted = getConvertedObject(method, configFileValue, foundConfigItem); + Object converted = getConvertedObject(method, configFileValue, property, configItems); try { if (converted != null) { @@ -56,21 +53,24 @@ private static void invokeMethodWithMappedValue(List conf } } - private static Object getConvertedObject(Method method, Object configFileValue, Optional foundConfigItem) { + private static Object getConvertedObject(Method method, Object configFileValue, + String property, List configItems) { + Optional foundConfigItem = + configItems.stream().filter(item -> property.equals(item.getParamName())).findFirst(); + if (!foundConfigItem.isPresent()) { Class parameterType = method.getParameterTypes()[0]; if (!ConfigurationSection.class.isAssignableFrom(parameterType)) { return null; } else if (configFileValue == null) { - return mapToObject((Class) parameterType, new HashMap<>(0)); + return mapToObject((Class) parameterType, Collections.emptyMap()); } else { return mapToObject((Class) parameterType, (Map) configFileValue); } } else { - Object mappedValue = null; ConfigurationItem configItem = foundConfigItem.get(); + Object mappedValue = getUserSetProperty(method, configItem, configFileValue); - mappedValue = getUserSetProperty(method, configItem, configFileValue); if (mappedValue == null && configItem.getDefaultValue() != null) { mappedValue = configItem.getDefaultValue(); } @@ -101,7 +101,7 @@ private static List createMultipleOccurrenceProperty(Method method, Conf System.getProperties().entrySet() .stream() .filter(prop -> prop.getKey().toString().startsWith(sysPropKey)) - .collect(Collectors.toMap(prop -> prop.getKey(), prop -> prop.getValue())); + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); List multipleValue = new ArrayList<>(); if (configFileValue != null) { diff --git a/core/src/test/java/org/arquillian/smart/testing/configuration/ConfigurationFileBuilder.java b/core/src/test/java/org/arquillian/smart/testing/configuration/ConfigurationFileBuilder.java new file mode 100644 index 000000000..5845dcf63 --- /dev/null +++ b/core/src/test/java/org/arquillian/smart/testing/configuration/ConfigurationFileBuilder.java @@ -0,0 +1,95 @@ +package org.arquillian.smart.testing.configuration; + +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; + +class ConfigurationFileBuilder { + + private HashMap properties; + + private ConfigurationFileBuilder() { + properties = new HashMap<>(); + } + + static ConfigurationFileBuilder configurationFile() { + return new ConfigurationFileBuilder(); + } + + ConfigurationFileBuilder inherit(String inherit) { + properties.put("inherit", inherit); + return this; + } + + ConfigurationFileBuilder mode(String mode) { + properties.put("mode", mode); + return this; + } + + ConfigurationFileBuilder applyTo(String applyTo) { + properties.put("applyTo", applyTo); + return this; + } + + ConfigurationFileBuilder strategies(String strategies) { + properties.put("strategies", strategies); + return this; + } + + ConfigurationFileBuilder debug(boolean debug) { + properties.put("debug", debug); + return this; + } + + ConfigurationFileBuilder disable(boolean disable) { + properties.put("disable", disable); + return this; + } + + ConfigurationFileBuilder scm() { + properties.put("scm", new HashMap<>(0)); + return this; + } + + ConfigurationFileBuilder range() { + Map scm = new HashMap<>(); + scm.put("range", new HashMap<>(0)); + + properties.put("scm", scm); + return this; + } + + ConfigurationFileBuilder head(String head) { + final Map scm = (Map) properties.get("scm"); + final Map range = (Map) scm.get("range"); + range.put("head", head); + + return this; + } + + ConfigurationFileBuilder tail(String tail) { + final Map scm = (Map) properties.get("scm"); + final Map range = (Map) scm.get("range"); + range.put("tail", tail); + + return this; + } + + void writeTo(Path filePath) { + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + options.setPrettyFlow(true); + + Yaml yaml = new Yaml(options); + try { + FileWriter writer = new FileWriter(filePath.toString()); + yaml.dump(properties, writer); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/core/src/test/java/org/arquillian/smart/testing/configuration/ConfigurationOverwriteUsingInheritTest.java b/core/src/test/java/org/arquillian/smart/testing/configuration/ConfigurationOverwriteUsingInheritTest.java new file mode 100644 index 000000000..12384c3f7 --- /dev/null +++ b/core/src/test/java/org/arquillian/smart/testing/configuration/ConfigurationOverwriteUsingInheritTest.java @@ -0,0 +1,138 @@ +package org.arquillian.smart.testing.configuration; + +import java.io.IOException; +import java.nio.file.Paths; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import static org.arquillian.smart.testing.RunMode.ORDERING; +import static org.arquillian.smart.testing.RunMode.SELECTING; +import static org.arquillian.smart.testing.configuration.ConfigurationFileBuilder.configurationFile; +import static org.arquillian.smart.testing.configuration.ConfigurationLoader.SMART_TESTING_YAML; +import static org.arquillian.smart.testing.configuration.ConfigurationLoader.SMART_TESTING_YML; +import static org.arquillian.smart.testing.scm.ScmRunnerProperties.HEAD; +import static org.assertj.core.api.Assertions.assertThat; + +public class ConfigurationOverwriteUsingInheritTest { + + private static final String IMPL_BASE = "impl-base"; + static final String CONFIG = "config"; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void should_load_configuration_properties_from_absolute_inherit_path_if_not_defined_in_child() throws IOException { + // given + final String root = temporaryFolder.getRoot().toString(); + + configurationFile() + .mode("ordering") + .applyTo("surefire") + .inherit(Paths.get(root, SMART_TESTING_YAML).toString()) + .writeTo(Paths.get(root, SMART_TESTING_YML)); + + configurationFile() + .strategies("new, changed, affected") + .writeTo(Paths.get(root, SMART_TESTING_YAML)); + + // when + final Configuration configuration = ConfigurationLoader.load(temporaryFolder.getRoot()); + + // then + assertThat(configuration.getMode()).isEqualTo(ORDERING); + assertThat(configuration.getApplyTo()).isEqualTo("surefire"); + assertThat(configuration.getStrategies()).isEqualTo(new String[]{"new", "changed", "affected"}); + } + + @Test + public void should_load_configuration_properties_from_relative_inherit_path_if_not_defined_in_child () throws IOException { + // given + temporaryFolder.newFolder(CONFIG, IMPL_BASE); + final String root = temporaryFolder.getRoot().toString(); + + configurationFile() + .inherit("../smart-testing.yml") + .mode("selecting") + .debug(true) + .writeTo(Paths.get(root, CONFIG, IMPL_BASE, SMART_TESTING_YML)); + + configurationFile() + .inherit("../smart-testing.yml") + .writeTo(Paths.get(root, CONFIG, SMART_TESTING_YML)); + + configurationFile() + .strategies("new, changed, affected") + .writeTo(Paths.get(root, SMART_TESTING_YML)); + + // when + final Configuration configuration = ConfigurationLoader.load(Paths.get(root, CONFIG, IMPL_BASE).toFile()); + + // then + assertThat(configuration.getMode()).isEqualTo(SELECTING); + assertThat(configuration.isDebug()).isTrue(); + assertThat(configuration.getStrategies()).isEqualTo(new String[]{"new", "changed", "affected"}); + } + + @Test + public void should_not_overwrite_disable_parameter_from_inherit_path() throws IOException { + // given + temporaryFolder.newFolder(CONFIG); + final String root = temporaryFolder.getRoot().toString(); + + configurationFile() + .inherit("../smart-testing.yml") + .mode("ordering") + .disable(true) + .writeTo(Paths.get(root, CONFIG, SMART_TESTING_YML)); + + configurationFile() + .strategies("new, changed, affected") + .disable(false) + .writeTo(Paths.get(root, SMART_TESTING_YML)); + + // when + final Configuration configuration = ConfigurationLoader.load(Paths.get(root, CONFIG).toFile()); + + // then + assertThat(configuration.getMode()).isEqualTo(ORDERING); + assertThat(configuration.isDisable()).isTrue(); + assertThat(configuration.getStrategies()).isEqualTo(new String[]{"new", "changed", "affected"}); + } + + @Test + public void should_aggregate_nested_parameters_from_inherit_path() throws IOException { + // given + temporaryFolder.newFolder(CONFIG); + final String root = temporaryFolder.getRoot().toString(); + + configurationFile() + .inherit("../smart-testing.yml") + .mode("ordering") + .disable(true) + .scm() + .range() + .head(HEAD) + .writeTo(Paths.get(root, CONFIG, SMART_TESTING_YML)); + + configurationFile() + .strategies("new, changed, affected") + .disable(false) + .scm() + .range() + .tail(HEAD + "~1") + .writeTo(Paths.get(root, SMART_TESTING_YML)); + + // when + final Configuration configuration = ConfigurationLoader.load(Paths.get(root, CONFIG).toFile()); + + // then + final Range range = configuration.getScm().getRange(); + assertThat(configuration.getMode()).isEqualTo(ORDERING); + assertThat(configuration.isDisable()).isTrue(); + assertThat(configuration.getStrategies()).isEqualTo(new String[]{"new", "changed", "affected"}); + assertThat(range.getHead()).isEqualTo(HEAD); + assertThat(range.getTail()).isEqualTo(HEAD + "~1"); + } +} diff --git a/core/src/test/java/org/arquillian/smart/testing/configuration/ConfigurationOverwriteUsingInheritWithSystemPropertyTest.java b/core/src/test/java/org/arquillian/smart/testing/configuration/ConfigurationOverwriteUsingInheritWithSystemPropertyTest.java new file mode 100644 index 000000000..ad81f7cf1 --- /dev/null +++ b/core/src/test/java/org/arquillian/smart/testing/configuration/ConfigurationOverwriteUsingInheritWithSystemPropertyTest.java @@ -0,0 +1,60 @@ +package org.arquillian.smart.testing.configuration; + +import java.io.IOException; +import java.nio.file.Paths; +import net.jcip.annotations.NotThreadSafe; +import org.junit.Rule; +import org.junit.Test; +import org.junit.contrib.java.lang.system.RestoreSystemProperties; +import org.junit.experimental.categories.Category; +import org.junit.rules.TemporaryFolder; + +import static org.arquillian.smart.testing.RunMode.ORDERING; +import static org.arquillian.smart.testing.configuration.Configuration.SMART_TESTING; +import static org.arquillian.smart.testing.configuration.ConfigurationFileBuilder.configurationFile; +import static org.arquillian.smart.testing.configuration.ConfigurationLoader.SMART_TESTING_YML; +import static org.arquillian.smart.testing.configuration.ConfigurationOverwriteUsingInheritTest.CONFIG; +import static org.arquillian.smart.testing.scm.ScmRunnerProperties.HEAD; +import static org.arquillian.smart.testing.scm.ScmRunnerProperties.SCM_LAST_CHANGES; +import static org.assertj.core.api.Assertions.assertThat; + +@Category(NotThreadSafe.class) +public class ConfigurationOverwriteUsingInheritWithSystemPropertyTest { + + @Rule + public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(); + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void system_properties_should_take_precedence_over_config_file() throws IOException { + // given + System.setProperty(SMART_TESTING, "changed"); + System.setProperty(SCM_LAST_CHANGES, "3"); + + temporaryFolder.newFolder(CONFIG); + final String root = temporaryFolder.getRoot().toString(); + + configurationFile() + .inherit("../smart-testing.yml") + .mode("ordering") + .writeTo(Paths.get(root, CONFIG, SMART_TESTING_YML)); + + configurationFile() + .strategies("new, changed, affected") + .writeTo(Paths.get(root, SMART_TESTING_YML)); + + final Range range = new Range(); + range.setHead(HEAD); + range.setTail(HEAD + "~3"); + + // when + final Configuration configuration = ConfigurationLoader.load(Paths.get(root, CONFIG).toFile()); + + // then + assertThat(configuration.getStrategies()).isEqualTo(new String[] {"changed"}); + assertThat(configuration.getScm().getRange()).isEqualToComparingFieldByField(range); + assertThat(configuration.getMode()).isEqualTo(ORDERING); + } +} diff --git a/core/src/test/java/org/arquillian/smart/testing/configuration/ConfigurationTest.java b/core/src/test/java/org/arquillian/smart/testing/configuration/ConfigurationTest.java index 2af5f08ad..8f512a2b2 100644 --- a/core/src/test/java/org/arquillian/smart/testing/configuration/ConfigurationTest.java +++ b/core/src/test/java/org/arquillian/smart/testing/configuration/ConfigurationTest.java @@ -73,7 +73,7 @@ public void should_load_configuration_with_default_values_if_property_is_not_spe // when final Configuration actualConfiguration = - ConfigurationLoader.load(getResourceAsPath("configuration/smart-testing.yml")); + ConfigurationLoader.load(getResourceAsFile("configuration/smart-testing.yml")); // then assertThat(actualConfiguration).isEqualToComparingFieldByFieldRecursively(expectedConfiguration); diff --git a/core/src/test/java/org/arquillian/smart/testing/configuration/ConfigurationUsingPropertyTest.java b/core/src/test/java/org/arquillian/smart/testing/configuration/ConfigurationUsingPropertyTest.java index 42a1c9686..073518e9a 100644 --- a/core/src/test/java/org/arquillian/smart/testing/configuration/ConfigurationUsingPropertyTest.java +++ b/core/src/test/java/org/arquillian/smart/testing/configuration/ConfigurationUsingPropertyTest.java @@ -50,7 +50,7 @@ public void should_load_configuration_with_overwriting_system_property_for_scmLa // when final Configuration actualConfiguration = - ConfigurationLoader.load(getResourceAsPath("configuration/smart-testing-with-lastChanges.yml")); + ConfigurationLoader.load(getResourceAsFile("configuration/smart-testing-with-lastChanges.yml")); // then final Range range = actualConfiguration.getScm().getRange(); @@ -61,7 +61,7 @@ public void should_load_configuration_with_overwriting_system_property_for_scmLa public void should_load_configuration_for_scmLastChanges_from_config_file() { // when final Configuration actualConfiguration = - ConfigurationLoader.load(getResourceAsPath("configuration/smart-testing-with-lastChanges.yml")); + ConfigurationLoader.load(getResourceAsFile("configuration/smart-testing-with-lastChanges.yml")); // then final Range range = actualConfiguration.getScm().getRange(); @@ -72,7 +72,7 @@ public void should_load_configuration_for_scmLastChanges_from_config_file() { public void should_load_configuration_for_rangeHead_and_rangeTail_from_config_file() { // when final Configuration actualConfiguration = - ConfigurationLoader.load(getResourceAsPath("configuration/smart-testing.yml")); + ConfigurationLoader.load(getResourceAsFile("configuration/smart-testing.yml")); // then final Range range = actualConfiguration.getScm().getRange(); @@ -87,7 +87,7 @@ public void should_load_configuration_with_overwriting_system_property_for_range // when final Configuration actualConfiguration = - ConfigurationLoader.load(getResourceAsPath("configuration/smart-testing.yml")); + ConfigurationLoader.load(getResourceAsFile("configuration/smart-testing.yml")); // then final Range range = actualConfiguration.getScm().getRange(); @@ -143,7 +143,7 @@ public void should_load_configuration_with_overwriting_system_properties_over_pr // when final Configuration actualConfiguration = - ConfigurationLoader.load(getResourceAsPath("configuration/smart-testing.yml")); + ConfigurationLoader.load(getResourceAsFile("configuration/smart-testing.yml")); // then assertThat(actualConfiguration).isEqualToComparingFieldByFieldRecursively(expectedConfiguration); diff --git a/core/src/test/resources/configuration/smart-testing.yml b/core/src/test/resources/configuration/smart-testing.yml index a6512e769..79e8ee181 100644 --- a/core/src/test/resources/configuration/smart-testing.yml +++ b/core/src/test/resources/configuration/smart-testing.yml @@ -1,26 +1,27 @@ -mode: ordering # -strategies: new, changed, affected # -applyTo: surefire # -debug: true # -disable: false # +inherit: ../smart-testing.yml # +mode: ordering # +strategies: new, changed, affected # +applyTo: surefire # +debug: true # +disable: false # report: - enable: true # + enable: true # scm: range: - head: HEAD # - tail: HEAD~2 # -autocorrect: true # -customStrategies: # + head: HEAD # + tail: HEAD~2 # +autocorrect: true # +customStrategies: # - smart.testing.strategy.cool=org.arquillian.smart.testing:strategy-cool:1.0.0 - smart.testing.strategy.experimental=org.arquillian.smart.testing:strategy-experimental:1.0.0 -strategiesConfiguration: # +strategiesConfiguration: # affected: - transitivity: true # - exclusions: # + transitivity: true # + exclusions: # - org.package.* - org.arquillian.package.* - inclusions: # + inclusions: # - org.package.exclude.* - org.arquillian.package.exclude.* -customProviders: # +customProviders: # - org.foo:my-custom-provider=fully.qualified.name.to.SurefireProviderImpl diff --git a/docs/configfile.adoc b/docs/configfile.adoc index 34e0f6c91..7efb09771 100644 --- a/docs/configfile.adoc +++ b/docs/configfile.adoc @@ -13,21 +13,22 @@ include::../core/src/test/resources/configuration/smart-testing.yml[] ---- copyToClipboard:config-file[] -<1> This defines mode to be used by Smart Testing. -<2> This defines strategies to be used while finding important tests. -<3> This defines plugin to be used for Smart Testing. -<4> This enables debug logs for Smart Testing. -<5> This disables Smart Testing if set to true. -<6> This enables Smart Testing report if set to true. -<7> This sets first commit sha or `HEAD` notation for inspecting changes. -<8> This sets last commit sha or `HEAD` notation for inspecting changes. -<9> This defines if smart testing should auto correct misspelled strategies. -<10> This defines the pair key/value of custom strategies as list. -<11> This defines list of strategy related configurations that you want to apply while applying strategy. -<12> This enables transitivity. -<13> This defines list of packages to be excluded while applying transitivity. -<14> This defines list of packages to be included while applying transitivity. -<15> GroupId and artifactId with fully qualified name of the implementation of SurefireProvider the execution should be delegated to. +<1> Config file's absolute or relative path from where Smart Testing overwrites parameters not defined in current config file. +<2> This defines mode to be used by Smart Testing. +<3> This defines strategies to be used while finding important tests. +<4> This defines plugin to be used for Smart Testing. +<5> This enables debug logs for Smart Testing. +<6> This disables Smart Testing if set to true. +<7> This enables Smart Testing report if set to true. +<8> This sets first commit sha or `HEAD` notation for inspecting changes. +<9> This sets last commit sha or `HEAD` notation for inspecting changes. +<10> This defines if smart testing should auto correct misspelled strategies. +<11> This defines the pair key/value of custom strategies as list. +<12> This defines list of strategy related configurations that you want to apply while applying strategy. +<13> This enables transitivity. +<14> This defines list of packages to be excluded while applying transitivity. +<15> This defines list of packages to be included while applying transitivity. +<16> GroupId and artifactId with fully qualified name of the implementation of SurefireProvider the execution should be delegated to. All parameters in configuration file are optional. If you haven't used any parameter in configuration file, Smart testing will use default value for that parameter. You can look at <<_reference_card, references>> for default value of parameter. @@ -35,6 +36,77 @@ You can look at <<_reference_card, references>> for default value of parameter. However you can overwrite all configuration options using system properties supported by Smart Testing. You can look <<_reference_card, references>> for all supported system properties. +=== How configuration is applying to each module? +Smart Testing is looking in each module's root dir for configuration file. If it didn't find config file there, it'll look recursively for the first config file in +it's parent dir. + +=== How Configuration parameters are inherited? +If config file has `inherit` defined with absolute or relative path of config file, then Smart Testing will +lookup for all undefined properties in config file defined using inherit. However it won't lookup for `disable` parameter. + +If config file doesn't contain `inherit` parameter. Then Smart Testing won't inherit any configuration properties. + +=== Configuration File per Maven Module: +Let's take a look into following example: + +e.g. +[source,xml] +---- +parent + - config + - smart-testing.yml // <1> + - api + - impl-base + - spi + - smart-testing.yml // <2> + - container + - api + - impl-base + - smart-testing.yml // <3> +---- + +Configuration files used in above example: + +.parent/config/smart-testing.yml +[source,yml] +---- +inherit: ../smart-testing.yml +strategies: affected +---- + +.parent/config/spi/smart-testing.yml +[source,yml] +---- +strategies: affected,new +---- + +.parent/smart-testing.yml +[source,yml] +---- +strategies: new +scm: + lastChanges: 1 +---- + +Configuration file selection for the above example will be as follows: + +* config/api - parent/config/smart-testing.yml +* config/impl-base - parent/config/smart-testing.yml +* config/spi - parent/config/spi/smart-testing.yml +* container/api - parent/smart-testing.yml +* container/impl-base - parent/smart-testing.yml + +<1> inside this file we have `inherit: ../smart-testing.yml`, so it will take all undefined properties in this file from the one in `parent/smart-testing.yml`. +<2> inside this file we have only `strategies: affected,new`, so it won't inherit from any other configuration file. +<3> inside this file we have `strategies: new` & `scm: lastChanges: 1` defined.(You can set it using respective system property also). + +IMPORTANT: In case of mvn extension, whenever you are using configuration file per maven module, make sure to define strategies in project's parent configuration file +or using system property. If you haven't defined strategy in parent config, Smart testing will load parent configuration having no strategy defined which will +disable ST by saying `Smart Testing is disabled. Reason: strategy not defined`. +If you are running tests against range of commits locally, you need to also define scm properties in project's parent config file or using system +properties. If you haven't defined it in parent config, Smart Testing will use default scm configuration with head as `HEAD` & tail as `HEAD~0` +due to which Smart Testing won't find required changes in range of commits. + === Configuration File Reference The `const:core/src/main/java/org/arquillian/smart/testing/configuration/ConfigurationLoader.java[name="SMART_TESTING_YML"]` file is a `YAML` file defining required configuration to configure Smart Testing. @@ -44,6 +116,9 @@ NOTE: You can use either a `.yml` or `.yaml` extension for this file. |=== |Field | Description +a| inherit +a| This is used to define absolute or relative path to parent configuration file + a| strategies a| This is used to define required strategies to find important tests. Look at <<_strategies, strategies>> for all supported options. @@ -122,5 +197,3 @@ a| Sets list of packages to be excluded while applying transitivity. a| inclusions a| Sets list of packages to be included while applying transitivity. |=== - - diff --git a/functional-tests/test-bed/src/main/java/org/arquillian/smart/testing/ftest/testbed/project/ProjectConfigurator.java b/functional-tests/test-bed/src/main/java/org/arquillian/smart/testing/ftest/testbed/project/ProjectConfigurator.java index 7e5b3f6f4..5dd9bc947 100644 --- a/functional-tests/test-bed/src/main/java/org/arquillian/smart/testing/ftest/testbed/project/ProjectConfigurator.java +++ b/functional-tests/test-bed/src/main/java/org/arquillian/smart/testing/ftest/testbed/project/ProjectConfigurator.java @@ -1,11 +1,10 @@ package org.arquillian.smart.testing.ftest.testbed.project; import java.io.BufferedReader; +import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; -import java.io.UncheckedIOException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; @@ -38,7 +37,7 @@ public class ProjectConfigurator { private final Project project; private final Path root; private boolean createConfigFile; - private String customConfigFile; + private Path configFilePath; ProjectConfigurator(Project project, Path root) { this.project = project; @@ -69,10 +68,13 @@ public ProjectConfigurator createConfigFile() { return this; } - public ProjectConfigurator createConfigFile(String customConfigFile) { + public ProjectConfigurator createConfigFileIn(String path) { + return createConfigFile(path + File.separator + SMART_TESTING_YML); + } + + public ProjectConfigurator createConfigFile(String configFile) { this.createConfigFile = true; - this.customConfigFile = customConfigFile; - createConfigurationFile(customConfigFile); + createConfigurationFile(configFile); return this; } @@ -113,12 +115,8 @@ public Project enable(Using usingInstallation) { .strategies(strategies().split("\\s*,\\s*")) .mode(RunMode.valueOf(getMode().getName().toUpperCase())) .build(); + dumpConfiguration(this.configFilePath); } - - this.project.configureSmartTesting() - .withConfiguration(configuration) - .createConfigFile(customConfigFile != null ? customConfigFile : SMART_TESTING_YML); - } return this.project; } @@ -127,16 +125,11 @@ public String strategies() { return Arrays.stream(getStrategies()).map(Strategy::getName).collect(Collectors.joining(",")); } - private void createConfigurationFile(String customConfigFile) { - Path configFilePath = Paths.get(root.toString(), customConfigFile); - if (!Files.exists(configFilePath)) { - try { - Files.createFile(configFilePath); - } catch (IOException e) { - throw new UncheckedIOException("Failed creating custom configuration file.", e); - } + private void createConfigurationFile(String configFilePath) { + this.configFilePath = Paths.get(root.toString(), configFilePath); + if (configuration != null) { + dumpConfiguration(this.configFilePath); } - dumpConfiguration(configFilePath); } private void dumpConfiguration(Path configFilePath) { diff --git a/functional-tests/test-bed/src/test/java/org/arquillian/smart/testing/ftest/configurationfile/ConfigurationFilePerModuleFunctionalTest.java b/functional-tests/test-bed/src/test/java/org/arquillian/smart/testing/ftest/configurationfile/ConfigurationFilePerModuleFunctionalTest.java new file mode 100644 index 000000000..fe6c6abdc --- /dev/null +++ b/functional-tests/test-bed/src/test/java/org/arquillian/smart/testing/ftest/configurationfile/ConfigurationFilePerModuleFunctionalTest.java @@ -0,0 +1,64 @@ +package org.arquillian.smart.testing.ftest.configurationfile; + +import java.util.Collection; +import org.arquillian.smart.testing.configuration.Configuration; +import org.arquillian.smart.testing.ftest.testbed.configuration.builder.ConfigurationBuilder; +import org.arquillian.smart.testing.ftest.testbed.project.Project; +import org.arquillian.smart.testing.ftest.testbed.project.TestResults; +import org.arquillian.smart.testing.ftest.testbed.testresults.TestResult; +import org.arquillian.smart.testing.rules.TestBed; +import org.arquillian.smart.testing.rules.git.GitClone; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; + +import static org.arquillian.smart.testing.RunMode.SELECTING; +import static org.arquillian.smart.testing.ftest.testbed.TestRepository.testRepository; +import static org.arquillian.smart.testing.ftest.testbed.configuration.Strategy.CHANGED; +import static org.arquillian.smart.testing.ftest.testbed.configuration.Strategy.NEW; +import static org.assertj.core.api.Assertions.assertThat; + +public class ConfigurationFilePerModuleFunctionalTest { + @ClassRule + public static final GitClone GIT_CLONE = new GitClone(testRepository()); + + @Rule + public final TestBed testBed = new TestBed(GIT_CLONE); + + @Test + public void should_load_config_from_module_config_file_for_local_changes_instead_of_parent_config_with_scm_and_strategy() { + // given + final Project project = testBed.getProject(); + + final Configuration parentConfiguration = new ConfigurationBuilder() + .strategies(NEW) + .scm() + .lastChanges("2") + .build() + .build(); + + + final Configuration changedConfiguration = new ConfigurationBuilder() + .strategies(CHANGED) + .mode(SELECTING) + .build(); + + project.configureSmartTesting() + .withConfiguration(parentConfiguration) + .createConfigFile() + .withConfiguration(changedConfiguration) + .createConfigFileIn("junit/core") + .enable(); + + + final Collection expectedTestResults = project.applyAsCommits("Deletes one test", "Renames unit test"); + + // when + final TestResults actualTestResults = + project.build("junit/core").run(); + + // then + assertThat(actualTestResults.accumulatedPerTestClass()).containsAll(expectedTestResults) + .hasSameSizeAs(expectedTestResults); + } +} diff --git a/mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/ConfigurationChecker.java b/mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/ConfigurationChecker.java new file mode 100644 index 000000000..ac0c03b84 --- /dev/null +++ b/mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/ConfigurationChecker.java @@ -0,0 +1,39 @@ +package org.arquillian.smart.testing.mvn.ext; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; + +import static org.arquillian.smart.testing.configuration.ConfigurationLoader.SMART_TESTING_CONFIG; +import static org.arquillian.smart.testing.configuration.ConfigurationLoader.SMART_TESTING_YAML; +import static org.arquillian.smart.testing.configuration.ConfigurationLoader.SMART_TESTING_YML; + +class ConfigurationChecker { + + private final String projectDir; + + ConfigurationChecker(String projectDir) { + this.projectDir = projectDir; + } + + boolean hasModuleSpecificConfigurations() { + return System.getProperty(SMART_TESTING_CONFIG) == null && hasMoreThanOneConfigFile(); + } + + private boolean hasMoreThanOneConfigFile(){ + final long count; + try { + count = Files.walk(Paths.get(projectDir)) + .parallel() + .filter(p -> Arrays.asList(SMART_TESTING_YML, SMART_TESTING_YAML).contains(p.toFile().getName())) + .count(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + return count > 1; + } + +} diff --git a/mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/MavenProjectConfigurator.java b/mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/MavenProjectConfigurator.java index 13a96600e..1264b0cc4 100644 --- a/mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/MavenProjectConfigurator.java +++ b/mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/MavenProjectConfigurator.java @@ -8,6 +8,7 @@ import org.arquillian.smart.testing.configuration.Configuration; import org.arquillian.smart.testing.logger.Log; import org.arquillian.smart.testing.logger.Logger; +import org.arquillian.smart.testing.mvn.ext.checker.SkipModuleChecker; import org.arquillian.smart.testing.mvn.ext.dependencies.DependencyResolver; import org.arquillian.smart.testing.mvn.ext.dependencies.ExtensionVersion; import org.arquillian.smart.testing.mvn.ext.dependencies.Version; diff --git a/mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/SmartTestingMavenConfigurer.java b/mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/SmartTestingMavenConfigurer.java index 85da5b46d..d1ffca7c0 100644 --- a/mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/SmartTestingMavenConfigurer.java +++ b/mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/SmartTestingMavenConfigurer.java @@ -5,11 +5,13 @@ import java.nio.file.Files; import java.util.Arrays; import java.util.Collection; +import java.util.function.Consumer; import java.util.stream.Collectors; import org.apache.maven.AbstractMavenLifecycleParticipant; import org.apache.maven.MavenExecutionException; import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Model; +import org.apache.maven.project.MavenProject; import org.arquillian.smart.testing.configuration.Configuration; import org.arquillian.smart.testing.configuration.ConfigurationLoader; import org.arquillian.smart.testing.hub.storage.ChangeStorage; @@ -17,6 +19,7 @@ import org.arquillian.smart.testing.hub.storage.local.LocalStorage; import org.arquillian.smart.testing.logger.Log; import org.arquillian.smart.testing.logger.Logger; +import org.arquillian.smart.testing.mvn.ext.checker.SkipInstallationChecker; import org.arquillian.smart.testing.mvn.ext.dependencies.ExtensionVersion; import org.arquillian.smart.testing.mvn.ext.logger.MavenExtensionLoggerFactory; import org.arquillian.smart.testing.scm.Change; @@ -26,7 +29,6 @@ import org.codehaus.plexus.component.annotations.Requirement; import static java.util.stream.StreamSupport.stream; -import static org.arquillian.smart.testing.configuration.Configuration.SMART_TESTING_DISABLE; @Component(role = AbstractMavenLifecycleParticipant.class, description = "Entry point to install and manage Smart-Testing extension. Takes care of adding needed dependencies and " @@ -58,14 +60,10 @@ public void afterProjectsRead(MavenSession session) throws MavenExecutionExcepti logger.debug("Applied user properties: %s", session.getUserProperties()); File projectDirectory = session.getTopLevelProject().getModel().getProjectDirectory(); - if (configuration.areStrategiesDefined()) { - logger.info("Enabling extension."); - configureExtension(session, configuration); - calculateChanges(projectDirectory, configuration); - Runtime.getRuntime().addShutdownHook(new Thread(() -> purgeLocalStorageAndExportPom(session))); - } else { - logStrategiesNotDefined(); - } + logger.info("Enabling extension."); + configureExtension(session, configuration); + calculateChanges(projectDirectory, configuration); + Runtime.getRuntime().addShutdownHook(new Thread(() -> purgeLocalStorageAndExportPom(session))); } private void loadConfigAndCheckIfInstallationShouldBeSkipped(MavenSession session) { @@ -80,11 +78,9 @@ private void loadConfigAndCheckIfInstallationShouldBeSkipped(MavenSession sessio configuration = ConfigurationLoader.load(executionRootDirectory, this::isProjectRootDirectory); Log.setLoggerFactory(new MavenExtensionLoggerFactory(mavenLogger, configuration)); logger = Log.getLogger(); - - if (configuration.isDisable()) { + if (skipInstallationChecker.shouldSkipForConfiguration(configuration)) { skipExtensionInstallation = true; - logExtensionDisableReason(logger, "System Property " + SMART_TESTING_DISABLE + " is set."); - return; + logExtensionDisableReason(logger, skipInstallationChecker.getReason()); } } @@ -133,16 +129,39 @@ private void calculateChanges(File projectDirectory, Configuration configuration } private void configureExtension(MavenSession session, Configuration configuration) { + final Consumer configureSmartTestingExtensionAction; + final ConfigurationChecker configurationChecker = + new ConfigurationChecker(session.getExecutionRootDirectory()); + if (configurationChecker.hasModuleSpecificConfigurations()) { + configureSmartTestingExtensionAction = applyModuleSpecificConfiguration(); + } else { + configureSmartTestingExtensionAction = mavenProject -> configureMavenProject(mavenProject, configuration); + } + session.getAllProjects().forEach(configureSmartTestingExtensionAction); + } + + private Consumer applyModuleSpecificConfiguration() { + return mavenProject -> { + Configuration mavenProjectConfiguration = + ConfigurationLoader.load(mavenProject.getBasedir(), this::isProjectRootDirectory); + final SkipInstallationChecker skipInstallationChecker = new SkipInstallationChecker(mavenProject); + if (skipInstallationChecker.shouldSkipForConfiguration(mavenProjectConfiguration)) { + logger.info(skipInstallationChecker.getReason()); + } else { + configureMavenProject(mavenProject, mavenProjectConfiguration); + } + }; + } + + private void configureMavenProject(MavenProject mavenProject, Configuration configuration) { final MavenProjectConfigurator mavenProjectConfigurator = new MavenProjectConfigurator(configuration); - session.getAllProjects().forEach(mavenProject -> { - boolean wasConfigured = mavenProjectConfigurator.configureTestRunner(mavenProject.getModel()); - if (wasConfigured) { - configuration.dump(mavenProject.getBasedir()); - if (isFailedStrategyUsed()) { - SurefireReportStorage.copySurefireReports(mavenProject.getModel()); - } + boolean wasConfigured = mavenProjectConfigurator.configureTestRunner(mavenProject.getModel()); + if (wasConfigured) { + configuration.dump(mavenProject.getBasedir()); + if (isFailedStrategyUsed()) { + SurefireReportStorage.copySurefireReports(mavenProject.getModel()); } - }); + } } private boolean isFailedStrategyUsed() { diff --git a/mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/SkipInstallationChecker.java b/mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/checker/SkipInstallationChecker.java similarity index 57% rename from mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/SkipInstallationChecker.java rename to mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/checker/SkipInstallationChecker.java index a9801dda8..d2036c15d 100644 --- a/mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/SkipInstallationChecker.java +++ b/mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/checker/SkipInstallationChecker.java @@ -1,19 +1,23 @@ -package org.arquillian.smart.testing.mvn.ext; +package org.arquillian.smart.testing.mvn.ext.checker; import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.maven.execution.MavenSession; +import org.apache.maven.project.MavenProject; +import org.arquillian.smart.testing.configuration.Configuration; +import org.arquillian.smart.testing.mvn.ext.dependencies.ExtensionVersion; -import static org.arquillian.smart.testing.mvn.ext.SkipModuleChecker.MAVEN_TEST_SKIP; -import static org.arquillian.smart.testing.mvn.ext.SkipModuleChecker.SKIP_TESTS; +import static org.arquillian.smart.testing.configuration.Configuration.SMART_TESTING_DISABLE; +import static org.arquillian.smart.testing.mvn.ext.checker.SkipModuleChecker.MAVEN_TEST_SKIP; +import static org.arquillian.smart.testing.mvn.ext.checker.SkipModuleChecker.SKIP_TESTS; -class SkipInstallationChecker { +public class SkipInstallationChecker { private static final List EXPECTED_GOALS = Arrays.asList( - new String[] {"test", "prepare-package", "package", "pre-integration-test", "integration-test", - "post-integration-test", "verify", "install", "deploy", "pre-site", "site", "post-site", "site-deploy"}); + "test", "prepare-package", "package", "pre-integration-test", "integration-test", + "post-integration-test", "verify", "install", "deploy", "pre-site", "site", "post-site", "site-deploy"); static final String NO_GOAL_REASON = "No goals have been specified for the build."; static final String NO_TEST_GOAL_REASON = @@ -23,14 +27,19 @@ class SkipInstallationChecker { private final Pattern TEST_CLASS_PATTERN = Pattern.compile("[^a-z0-9 ]", Pattern.CASE_INSENSITIVE); - private final MavenSession session; + private MavenSession session; + private MavenProject mavenProject; private String reason; - SkipInstallationChecker(MavenSession session) { + public SkipInstallationChecker(MavenSession session) { this.session = session; } - boolean shouldSkip() { + public SkipInstallationChecker(MavenProject mavenProject) { + this.mavenProject = mavenProject; + } + + public boolean shouldSkip() { List goals = session.getGoals(); String defaultGoal = session.getTopLevelProject().getBuild().getDefaultGoal(); if (goals.isEmpty() && (defaultGoal == null || defaultGoal.isEmpty())) { @@ -56,6 +65,23 @@ boolean shouldSkip() { return reason != null; } + public boolean shouldSkipForConfiguration(Configuration configuration) { + if (mavenProject == null) { + mavenProject = session.getTopLevelProject(); + } + if (configuration.isDisable()) { + reason = String.format("Disabling Smart Testing %s in %s module. Reason: " + SMART_TESTING_DISABLE + " is set.", + ExtensionVersion.version().toString(), mavenProject.getArtifactId()); + return true; + } + if (!configuration.areStrategiesDefined()) { + reason = String.format("Smart Testing Extension is installed but no strategies are provided for %s module. It won't influence the way how your tests are executed. " + + "For details on how to configure it head over to http://bit.ly/st-config", mavenProject.getArtifactId()); + return true; + } + return false; + } + private boolean isSkipTestExecutionSet() { return isSkipTests() || isSkip(); } @@ -79,7 +105,7 @@ private boolean containsPattern(String testClasses) { .anyMatch(Matcher::find); } - String getReason(){ + public String getReason(){ return reason; } diff --git a/mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/SkipModuleChecker.java b/mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/checker/SkipModuleChecker.java similarity index 89% rename from mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/SkipModuleChecker.java rename to mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/checker/SkipModuleChecker.java index 42314b2e8..6cb097ed3 100644 --- a/mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/SkipModuleChecker.java +++ b/mvn-extension/src/main/java/org/arquillian/smart/testing/mvn/ext/checker/SkipModuleChecker.java @@ -1,13 +1,14 @@ -package org.arquillian.smart.testing.mvn.ext; +package org.arquillian.smart.testing.mvn.ext.checker; import org.apache.maven.model.Model; import org.apache.maven.model.Plugin; +import org.arquillian.smart.testing.mvn.ext.ApplicablePlugins; import org.codehaus.plexus.util.xml.Xpp3Dom; import static org.arquillian.smart.testing.mvn.ext.ApplicablePlugins.FAILSAFE; import static org.arquillian.smart.testing.mvn.ext.ApplicablePlugins.SUREFIRE; -class SkipModuleChecker { +public class SkipModuleChecker { private final Model model; private final Plugin surefirePlugin; @@ -18,17 +19,17 @@ class SkipModuleChecker { public static final String SKIP = "skip"; public static final String MAVEN_TEST_SKIP = "maven.test.skip"; - SkipModuleChecker(Model model) { + public SkipModuleChecker(Model model) { this.model = model; this.surefirePlugin = getPlugin(SUREFIRE); this.failsafePlugin = getPlugin(FAILSAFE); } - boolean areIntegrationTestsSkipped() { + public boolean areIntegrationTestsSkipped() { return Boolean.valueOf(System.getProperty(SKIP_ITs)) || isSkipITsSetInPom(); } - boolean areUnitTestsSkipped() { + public boolean areUnitTestsSkipped() { if (surefirePlugin != null) { Xpp3Dom surefirePluginConfiguration = (Xpp3Dom) surefirePlugin.getConfiguration(); if (surefirePluginConfiguration != null) { @@ -39,7 +40,7 @@ boolean areUnitTestsSkipped() { return false; } - boolean areAllTestsSkipped() { + public boolean areAllTestsSkipped() { return isPropertyInPom(MAVEN_TEST_SKIP) || isPropertyInPom(SKIP_TESTS); } diff --git a/mvn-extension/src/test/java/org/arquillian/smart/testing/mvn/ext/SkipInstallationCheckerTest.java b/mvn-extension/src/test/java/org/arquillian/smart/testing/mvn/ext/checker/SkipInstallationCheckerTest.java similarity index 92% rename from mvn-extension/src/test/java/org/arquillian/smart/testing/mvn/ext/SkipInstallationCheckerTest.java rename to mvn-extension/src/test/java/org/arquillian/smart/testing/mvn/ext/checker/SkipInstallationCheckerTest.java index 6e1ebccaf..07d325460 100644 --- a/mvn-extension/src/test/java/org/arquillian/smart/testing/mvn/ext/SkipInstallationCheckerTest.java +++ b/mvn-extension/src/test/java/org/arquillian/smart/testing/mvn/ext/checker/SkipInstallationCheckerTest.java @@ -1,4 +1,4 @@ -package org.arquillian.smart.testing.mvn.ext; +package org.arquillian.smart.testing.mvn.ext.checker; import java.util.Arrays; import java.util.List; @@ -6,6 +6,7 @@ import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Build; import org.apache.maven.project.MavenProject; +import org.arquillian.smart.testing.mvn.ext.checker.SkipInstallationChecker; import org.assertj.core.api.JUnitSoftAssertions; import org.junit.Rule; import org.junit.Test; @@ -13,10 +14,10 @@ import org.junit.experimental.categories.Category; import org.mockito.Mockito; -import static org.arquillian.smart.testing.mvn.ext.SkipInstallationChecker.NO_GOAL_REASON; -import static org.arquillian.smart.testing.mvn.ext.SkipInstallationChecker.NO_TEST_GOAL_REASON; -import static org.arquillian.smart.testing.mvn.ext.SkipInstallationChecker.SPECIFIC_CLASSES_REASON; -import static org.arquillian.smart.testing.mvn.ext.SkipInstallationChecker.TEST_SKIPPED_REASON; +import static org.arquillian.smart.testing.mvn.ext.checker.SkipInstallationChecker.NO_GOAL_REASON; +import static org.arquillian.smart.testing.mvn.ext.checker.SkipInstallationChecker.NO_TEST_GOAL_REASON; +import static org.arquillian.smart.testing.mvn.ext.checker.SkipInstallationChecker.SPECIFIC_CLASSES_REASON; +import static org.arquillian.smart.testing.mvn.ext.checker.SkipInstallationChecker.TEST_SKIPPED_REASON; import static org.assertj.core.api.Assertions.assertThat; @Category(NotThreadSafe.class)