Skip to content

Commit 7f8a0bf

Browse files
committed
These changes bring 2 features
* clean up of frozen rules in `stored.rules` where the corresponding file does not exist in the store directory * clean up of files in store directory which are not referenced in `stored.rules` file Both of the above operations are enabled using `default.allowStoreUpdate` property. If `default.allowStoreUpdate=false` and obsolete entries or files are found by either of the above operations, the operation fails with an `StoreUpdateFailedException`. Signed-off-by: Masoud Kiaeeha <[email protected]> Resolves #1264
1 parent 73dfe66 commit 7f8a0bf

File tree

2 files changed

+136
-0
lines changed

2 files changed

+136
-0
lines changed

archunit/src/main/java/com/tngtech/archunit/library/freeze/TextFileBasedViolationStore.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,16 @@
2020
import java.io.FileOutputStream;
2121
import java.io.IOException;
2222
import java.nio.file.Files;
23+
import java.nio.file.Path;
24+
import java.util.Arrays;
2325
import java.util.List;
2426
import java.util.Properties;
27+
import java.util.Set;
2528
import java.util.UUID;
2629
import java.util.regex.Pattern;
30+
import java.util.stream.Collectors;
2731

32+
import com.google.common.base.Predicates;
2833
import com.google.common.base.Splitter;
2934
import com.tngtech.archunit.PublicAPI;
3035
import com.tngtech.archunit.lang.ArchRule;
@@ -110,6 +115,8 @@ public void initialize(Properties properties) {
110115
log.trace("Initializing {} at {}", TextFileBasedViolationStore.class.getSimpleName(), storedRulesFile.getAbsolutePath());
111116
storedRules = new FileSyncedProperties(storedRulesFile);
112117
checkInitialization(storedRules.initializationSuccessful(), "Cannot create rule store at %s", storedRulesFile.getAbsolutePath());
118+
removeObsoleteRules();
119+
removeObsoleteRuleFiles();
113120
}
114121

115122
private File getStoredRulesFile() {
@@ -132,6 +139,45 @@ private void checkInitialization(boolean initializationSuccessful, String messag
132139
}
133140
}
134141

142+
private void removeObsoleteRules() {
143+
Set<String> obsoleteStoredRules = storedRules.keySet().stream()
144+
.filter(ruleDescription -> !new File(storeFolder, storedRules.getProperty(ruleDescription)).exists())
145+
.collect(Collectors.toSet());
146+
if (!obsoleteStoredRules.isEmpty() && !storeUpdateAllowed) {
147+
throw new StoreUpdateFailedException(String.format(
148+
"Failed to remove %d obsolete stored rule(s). Updating frozen violations is disabled (enable by configuration %s.%s=true)",
149+
obsoleteStoredRules.size(), ViolationStoreFactory.FREEZE_STORE_PROPERTY_NAME, ALLOW_STORE_UPDATE_PROPERTY_NAME));
150+
}
151+
obsoleteStoredRules.forEach(storedRules::removeProperty);
152+
}
153+
154+
private void removeObsoleteRuleFiles() {
155+
Set<String> ruleFiles = storedRules.keySet().stream()
156+
.map(storedRules::getProperty)
157+
.collect(Collectors.toSet());
158+
159+
List<String> danglingFiles = Arrays.stream(storeFolder.list())
160+
.filter(name -> !name.equals(STORED_RULES_FILE_NAME))
161+
.filter(Predicates.not(ruleFiles::contains))
162+
.collect(toList());
163+
164+
if (!danglingFiles.isEmpty() && !storeUpdateAllowed) {
165+
throw new StoreUpdateFailedException(String.format(
166+
"Failed to remove %d unreferenced rule files. Updating frozen store is disabled (enable by configuration %s.%s=true)",
167+
danglingFiles.size(), ViolationStoreFactory.FREEZE_STORE_PROPERTY_NAME, ALLOW_STORE_UPDATE_PROPERTY_NAME));
168+
169+
}
170+
171+
for (String fileName : danglingFiles) {
172+
Path path = new File(storeFolder, fileName).toPath();
173+
try {
174+
Files.delete(path);
175+
} catch (IOException e) {
176+
throw new StoreInitializationFailedException("Cannot delete unreferenced rule file: " + fileName, e);
177+
}
178+
}
179+
}
180+
135181
@Override
136182
public boolean contains(ArchRule rule) {
137183
return storedRules.containsKey(rule.getDescription());
@@ -255,6 +301,15 @@ void setProperty(String propertyName, String value) {
255301
syncFileSystem();
256302
}
257303

304+
void removeProperty(String propertyName) {
305+
loadedProperties.remove(ensureUnixLineBreaks(propertyName));
306+
syncFileSystem();
307+
}
308+
309+
Set<String> keySet() {
310+
return loadedProperties.stringPropertyNames();
311+
}
312+
258313
private void syncFileSystem() {
259314
try (FileOutputStream outputStream = new FileOutputStream(propertiesFile)) {
260315
loadedProperties.store(outputStream, "");

archunit/src/test/java/com/tngtech/archunit/library/freeze/TextFileBasedViolationStoreTest.java

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.io.File;
44
import java.io.FileInputStream;
5+
import java.io.FileOutputStream;
56
import java.io.IOException;
67
import java.util.LinkedList;
78
import java.util.List;
@@ -10,6 +11,7 @@
1011
import com.google.common.collect.ImmutableList;
1112
import com.google.common.io.Files;
1213
import com.tngtech.archunit.lang.ArchRule;
14+
import org.assertj.core.api.ThrowableAssert;
1315
import org.junit.Before;
1416
import org.junit.Rule;
1517
import org.junit.Test;
@@ -38,6 +40,79 @@ public void setUp() throws Exception {
3840
"default.allowStoreCreation", String.valueOf(true)));
3941
}
4042

43+
@Test
44+
public void throws_exception_when_there_are_obsolete_entries_in_storedRules_files() throws Exception {
45+
// given
46+
store.save(defaultRule(), ImmutableList.of("first violation", "second violation"));
47+
Properties properties = readProperties(new File(configuredFolder, "stored.rules"));
48+
File ruleViolationsFile = new File(configuredFolder, properties.getProperty(defaultRule().getDescription()));
49+
assertThat(ruleViolationsFile.delete()).isTrue();
50+
51+
// when && then
52+
ThrowableAssert.ThrowingCallable storeInitialization = () -> store.initialize(propertiesOf(
53+
"default.path", configuredFolder.getAbsolutePath(),
54+
"default.allowStoreUpdate", String.valueOf(false)));
55+
assertThatThrownBy(storeInitialization)
56+
.isInstanceOf(StoreUpdateFailedException.class)
57+
.hasMessage("Failed to remove 1 obsolete stored rule(s). Updating frozen violations is disabled (enable by configuration freeze.store.default.allowStoreUpdate=true)");
58+
assertThat(store.contains(defaultRule())).isTrue();
59+
}
60+
61+
@Test
62+
public void deletes_obsolete_entries_from_storedRules_files() throws Exception {
63+
// given
64+
store.save(defaultRule(), ImmutableList.of("first violation", "second violation"));
65+
Properties properties = readProperties(new File(configuredFolder, "stored.rules"));
66+
File ruleViolationsFile = new File(configuredFolder, properties.getProperty(defaultRule().getDescription()));
67+
assertThat(ruleViolationsFile.delete()).isTrue();
68+
69+
// when
70+
store.initialize(propertiesOf("default.path", configuredFolder.getAbsolutePath()));
71+
72+
// then
73+
assertThat(store.contains(defaultRule())).isFalse();
74+
}
75+
76+
@Test
77+
public void throws_exception_when_there_are_unreferenced_in_store_directory() throws Exception {
78+
// given
79+
store.save(defaultRule(), ImmutableList.of("first violation", "second violation"));
80+
File propertiesFile = new File(configuredFolder, "stored.rules");
81+
Properties properties = readProperties(propertiesFile);
82+
File ruleViolationsFile = new File(configuredFolder, properties.getProperty(defaultRule().getDescription()));
83+
assertThat(ruleViolationsFile).exists();
84+
properties.remove(defaultRule().getDescription());
85+
storeProperties(propertiesFile, properties);
86+
87+
// when && then
88+
ThrowableAssert.ThrowingCallable storeInitialization = () -> store.initialize(propertiesOf(
89+
"default.path", configuredFolder.getAbsolutePath(),
90+
"default.allowStoreUpdate", String.valueOf(false)));
91+
assertThatThrownBy(storeInitialization)
92+
.isInstanceOf(StoreUpdateFailedException.class)
93+
.hasMessage("Failed to remove 1 unreferenced rule files. Updating frozen store is disabled (enable by configuration freeze.store.default.allowStoreUpdate=true)");
94+
assertThat(ruleViolationsFile).exists();
95+
}
96+
97+
@Test
98+
public void deletes_files_not_referenced_in_storedRules() throws Exception {
99+
// given
100+
store.save(defaultRule(), ImmutableList.of("first violation", "second violation"));
101+
File propertiesFile = new File(configuredFolder, "stored.rules");
102+
Properties properties = readProperties(propertiesFile);
103+
File ruleViolationsFile = new File(configuredFolder, properties.getProperty(defaultRule().getDescription()));
104+
assertThat(ruleViolationsFile).exists();
105+
properties.remove(defaultRule().getDescription());
106+
storeProperties(propertiesFile, properties);
107+
108+
// when
109+
store.initialize(propertiesOf("default.path", configuredFolder.getAbsolutePath()));
110+
111+
// then
112+
assertThat(store.contains(defaultRule())).isFalse();
113+
assertThat(ruleViolationsFile).doesNotExist();
114+
}
115+
41116
@Test
42117
public void reports_unknown_rule_as_unstored() {
43118
assertThat(store.contains(defaultRule())).as("store contains random rule").isFalse();
@@ -126,6 +201,12 @@ private Properties readProperties(File file) throws IOException {
126201
return properties;
127202
}
128203

204+
private static void storeProperties(File propertiesFile, Properties properties) throws IOException {
205+
try (FileOutputStream outputStream = new FileOutputStream(propertiesFile)) {
206+
properties.store(outputStream, "");
207+
}
208+
}
209+
129210
private Properties propertiesOf(String... keyValuePairs) {
130211
Properties result = new Properties();
131212
LinkedList<String> keyValues = new LinkedList<>(asList(keyValuePairs));

0 commit comments

Comments
 (0)