diff --git a/archunit/src/main/java/com/tngtech/archunit/library/freeze/TextFileBasedViolationStore.java b/archunit/src/main/java/com/tngtech/archunit/library/freeze/TextFileBasedViolationStore.java index 05eba8224..f8c224d9b 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/freeze/TextFileBasedViolationStore.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/freeze/TextFileBasedViolationStore.java @@ -22,8 +22,10 @@ import java.nio.file.Files; import java.util.List; import java.util.Properties; +import java.util.Set; import java.util.UUID; import java.util.regex.Pattern; +import java.util.stream.Collectors; import com.google.common.base.Splitter; import com.tngtech.archunit.PublicAPI; @@ -110,6 +112,7 @@ public void initialize(Properties properties) { log.trace("Initializing {} at {}", TextFileBasedViolationStore.class.getSimpleName(), storedRulesFile.getAbsolutePath()); storedRules = new FileSyncedProperties(storedRulesFile); checkInitialization(storedRules.initializationSuccessful(), "Cannot create rule store at %s", storedRulesFile.getAbsolutePath()); + cleanupObsoleteRules(); } private File getStoredRulesFile() { @@ -132,6 +135,18 @@ private void checkInitialization(boolean initializationSuccessful, String messag } } + private void cleanupObsoleteRules() { + Set obsoleteStoredRules = storedRules.keySet().stream() + .filter(ruleDescription -> !new File(storeFolder, storedRules.getProperty(ruleDescription)).exists()) + .collect(Collectors.toSet()); + if (!obsoleteStoredRules.isEmpty() && !storeUpdateAllowed) { + throw new StoreUpdateFailedException(String.format( + "Failed to remove %d obsolete stored rule(s). Updating frozen violations is disabled (enable by configuration %s.%s=true)", + obsoleteStoredRules.size(), ViolationStoreFactory.FREEZE_STORE_PROPERTY_NAME, ALLOW_STORE_UPDATE_PROPERTY_NAME)); + } + obsoleteStoredRules.forEach(storedRules::removeProperty); + } + @Override public boolean contains(ArchRule rule) { return storedRules.containsKey(rule.getDescription()); @@ -255,6 +270,15 @@ void setProperty(String propertyName, String value) { syncFileSystem(); } + void removeProperty(String propertyName) { + loadedProperties.remove(ensureUnixLineBreaks(propertyName)); + syncFileSystem(); + } + + Set keySet() { + return loadedProperties.stringPropertyNames(); + } + private void syncFileSystem() { try (FileOutputStream outputStream = new FileOutputStream(propertiesFile)) { loadedProperties.store(outputStream, ""); diff --git a/archunit/src/test/java/com/tngtech/archunit/library/freeze/TextFileBasedViolationStoreTest.java b/archunit/src/test/java/com/tngtech/archunit/library/freeze/TextFileBasedViolationStoreTest.java index 9916799d5..7160b8894 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/freeze/TextFileBasedViolationStoreTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/freeze/TextFileBasedViolationStoreTest.java @@ -10,6 +10,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.io.Files; import com.tngtech.archunit.lang.ArchRule; +import org.assertj.core.api.ThrowableAssert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -38,6 +39,38 @@ public void setUp() throws Exception { "default.allowStoreCreation", String.valueOf(true))); } + @Test + public void throws_exception_when_there_are_obsolete_entries_in_storedRules_files() throws Exception { + // given + store.save(defaultRule(), ImmutableList.of("first violation", "second violation")); + Properties properties = readProperties(new File(configuredFolder, "stored.rules")); + File ruleViolationsFile = new File(configuredFolder, properties.getProperty(defaultRule().getDescription())); + assertThat(ruleViolationsFile.delete()).isTrue(); + + // when && then + ThrowableAssert.ThrowingCallable storeInitialization = () -> store.initialize(propertiesOf( + "default.path", configuredFolder.getAbsolutePath(), + "default.allowStoreUpdate", String.valueOf(false))); + assertThatThrownBy(storeInitialization) + .isInstanceOf(StoreUpdateFailedException.class) + .hasMessage("Failed to remove 1 obsolete stored rule(s). Updating frozen violations is disabled (enable by configuration freeze.store.default.allowStoreUpdate=true)"); + } + + @Test + public void deletes_obsolete_entries_from_storedRules_files() throws Exception { + // given + store.save(defaultRule(), ImmutableList.of("first violation", "second violation")); + Properties properties = readProperties(new File(configuredFolder, "stored.rules")); + File ruleViolationsFile = new File(configuredFolder, properties.getProperty(defaultRule().getDescription())); + assertThat(ruleViolationsFile.delete()).isTrue(); + + // when + store.initialize(propertiesOf("default.path", configuredFolder.getAbsolutePath())); + + // then + assertThat(store.contains(defaultRule())).isFalse(); + } + @Test public void reports_unknown_rule_as_unstored() { assertThat(store.contains(defaultRule())).as("store contains random rule").isFalse();