commit b17f3d2d9c26cad3cbc26680a39e964e23a534a8 Author: iwakeh iwakeh@torproject.org Date: Tue Aug 2 21:36:07 2016 +0200
All non-scheduling properties can now be reconfigured at runtime.
Implements #19720: Adapted testcoverage and test, restructured Configuration, adapted logging. --- build.xml | 12 +-- src/main/java/org/torproject/collector/Main.java | 28 ++--- .../torproject/collector/conf/Configuration.java | 118 +++++++++++++++++++-- .../torproject/collector/cron/CollecTorMain.java | 34 +++++- .../org/torproject/collector/cron/Scheduler.java | 2 +- src/main/resources/collector.properties | 5 + src/main/resources/logback.xml | 8 ++ .../java/org/torproject/collector/MainTest.java | 46 ++++++-- .../collector/conf/ConfigurationTest.java | 95 +++++++++++++---- 9 files changed, 280 insertions(+), 68 deletions(-)
diff --git a/build.xml b/build.xml index e06bb99..75e00f8 100644 --- a/build.xml +++ b/build.xml @@ -239,12 +239,12 @@ </fileset> </cobertura-report> <cobertura-check totallinerate="5" totalbranchrate="1" > - <regex pattern="org.torproject.collector.conf.*" branchrate="100" linerate="100"/> - <regex pattern="org.torproject.collector.cron.CollecTorMain" - branchrate="50" linerate="63" /> - <regex pattern="org.torproject.collector.cron.Scheduler" - branchrate="75" linerate="82" /> - <regex pattern="org.torproject.collector.Main" branchrate="66" linerate="94" /> + <regex pattern="org.torproject.collector.conf" + branchrate="87" linerate="100"/> + <regex pattern="org.torproject.collector.cron" + branchrate="50" linerate="71" /> + <regex pattern="org.torproject.collector.Main" + branchrate="100" linerate="100" /> </cobertura-check> </target> <target name="test" depends="compile,compile-tests"> diff --git a/src/main/java/org/torproject/collector/Main.java b/src/main/java/org/torproject/collector/Main.java index 97c7a0c..b48c3e6 100644 --- a/src/main/java/org/torproject/collector/Main.java +++ b/src/main/java/org/torproject/collector/Main.java @@ -21,6 +21,8 @@ import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.HashMap; import java.util.Map; @@ -57,20 +59,20 @@ public class Main { * See class description {@link Main}. */ public static void main(String[] args) throws Exception { - File confFile = null; + Path confPath = null; if (args == null || args.length == 0) { - confFile = new File(CONF_FILE); + confPath = Paths.get(CONF_FILE); } else if (args.length == 1) { - confFile = new File(args[0]); + confPath = Paths.get(args[0]); } else { printUsage("CollecTor takes at most one argument."); return; } - if (!confFile.exists() || confFile.length() < 1L) { - writeDefaultConfig(confFile); + if (!confPath.toFile().exists() || confPath.toFile().length() < 1L) { + writeDefaultConfig(confPath); return; } else { - readConfigurationFrom(confFile); + conf.setWatchableSourceAndLoad(confPath); } Scheduler.getInstance().scheduleModuleRuns(collecTorMains, conf); } @@ -81,10 +83,10 @@ public class Main { System.out.println(msg + "\n" + usage); }
- private static void writeDefaultConfig(File confFile) { + private static void writeDefaultConfig(Path confPath) { try { Files.copy(Main.class.getClassLoader().getResource(CONF_FILE).openStream(), - confFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + confPath, StandardCopyOption.REPLACE_EXISTING); printUsage("Could not find config file. In the default " + "configuration, we are not configured to read data from any " + "data source or write data to any data sink. You need to " @@ -93,15 +95,7 @@ public class Main { + "Refer to the manual for more information."); } catch (IOException e) { log.error("Cannot write default configuration. Reason: " + e, e); - } - } - - private static void readConfigurationFrom(File confFile) throws Exception { - try (FileInputStream fis = new FileInputStream(confFile)) { - conf.load(fis); - } catch (Exception e) { // catch all possible problems - log.error("Cannot read configuration. Reason: " + e, e); - throw e; + throw new RuntimeException(e); } }
diff --git a/src/main/java/org/torproject/collector/conf/Configuration.java b/src/main/java/org/torproject/collector/conf/Configuration.java index 9295811..205a977 100644 --- a/src/main/java/org/torproject/collector/conf/Configuration.java +++ b/src/main/java/org/torproject/collector/conf/Configuration.java @@ -3,21 +3,125 @@
package org.torproject.collector.conf;
+import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; +import java.nio.file.FileSystems; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.attribute.FileTime; +import java.util.List; +import java.util.Observable; import java.util.Properties; +import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit;
/** * Initialize configuration with defaults from collector.properties, * unless a configuration properties file is available. */ -public class Configuration extends Properties { +public class Configuration extends Observable implements Cloneable { + + private static final Logger log = LoggerFactory.getLogger(Configuration.class); + + private final ScheduledExecutorService scheduler = + Executors.newScheduledThreadPool(1);
public static final String FIELDSEP = ","; public static final String ARRAYSEP = ";";
+ private final Properties props = new Properties(); + private Path configurationFile; + private FileTime ft; + + /** + * Load the configuration from the given path and start monitoring changes. + * If the file was changed, re-read and inform all observers. + */ + public void setWatchableSourceAndLoad(final Path confPath) throws + ConfigurationException { + this.configurationFile = confPath; + try { + ft = Files.getLastModifiedTime(confPath); + reload(); + } catch (IOException e) { + throw new ConfigurationException("Cannot watch configuration file. " + + "Reason: " + e.getMessage(), e); + } + this.scheduler.scheduleAtFixedRate(new Runnable() { + public void run() { + log.trace("Check configuration file."); + try { + FileTime ftNow = Files.getLastModifiedTime(confPath); + if (ft.compareTo(ftNow) < 0) { + log.info("Configuration file was changed."); + reload(); + setChanged(); + notifyObservers(null); + } + ft = ftNow; + } catch (IOException | RuntimeException re) { + log.error("Cannot reload configuration file.", re); + } + } + }, 1, 1, TimeUnit.MINUTES); + } + + private final void reload() throws IOException { + props.clear(); + try (FileInputStream fis + = new FileInputStream(configurationFile.toFile())) { + props.load(fis); + } + } + + /** Return a copy of all properties. */ + public Properties getPropertiesCopy() { + return (Properties) props.clone(); + } + + /** + * Loads properties from the given stream. + */ + public void load(InputStream fis) throws IOException { + props.load(fis); + } + + /** Retrieves the value for key. */ + public String getProperty(String key) { + return props.getProperty(key); + } + + /** Sets the value for key. */ + public void setProperty(String key, String value) { + props.setProperty(key, value); + } + + /** clears all properties. */ + public void clear() { + props.clear(); + } + + /** Add all given properties. */ + public void putAll(Properties allProps) { + props.putAll(allProps); + } + + /** Count of properties. */ + public int size() { + return props.size(); + } + /** * Returns {@code String[][]} from a property. Commas seperate array elements * and semicolons separate arrays, e.g., @@ -26,7 +130,7 @@ public class Configuration extends Properties { public String[][] getStringArrayArray(Key key) throws ConfigurationException { try { checkClass(key, String[][].class); - String[] interim = getProperty(key.name()).split(ARRAYSEP); + String[] interim = props.getProperty(key.name()).split(ARRAYSEP); String[][] res = new String[interim.length][]; for (int i = 0; i < interim.length; i++) { res[i] = interim[i].trim().split(FIELDSEP); @@ -49,7 +153,7 @@ public class Configuration extends Properties { public String[] getStringArray(Key key) throws ConfigurationException { try { checkClass(key, String[].class); - String[] res = getProperty(key.name()).split(FIELDSEP); + String[] res = props.getProperty(key.name()).split(FIELDSEP); for (int i = 0; i < res.length; i++) { res[i] = res[i].trim(); } @@ -74,7 +178,7 @@ public class Configuration extends Properties { public boolean getBool(Key key) throws ConfigurationException { try { checkClass(key, Boolean.class); - return Boolean.parseBoolean(getProperty(key.name())); + return Boolean.parseBoolean(props.getProperty(key.name())); } catch (RuntimeException re) { throw new ConfigurationException("Corrupt property: " + key + " reason: " + re.getMessage(), re); @@ -89,7 +193,7 @@ public class Configuration extends Properties { public int getInt(Key key) throws ConfigurationException { try { checkClass(key, Integer.class); - String prop = getProperty(key.name()); + String prop = props.getProperty(key.name()); if ("inf".equals(prop)) { return Integer.MAX_VALUE; } else { @@ -108,7 +212,7 @@ public class Configuration extends Properties { public Path getPath(Key key) throws ConfigurationException { try { checkClass(key, Path.class); - return Paths.get(getProperty(key.name())); + return Paths.get(props.getProperty(key.name())); } catch (RuntimeException re) { throw new ConfigurationException("Corrupt property: " + key + " reason: " + re.getMessage(), re); @@ -122,7 +226,7 @@ public class Configuration extends Properties { public URL getUrl(Key key) throws ConfigurationException { try { checkClass(key, URL.class); - return new URL(getProperty(key.name())); + return new URL(props.getProperty(key.name())); } catch (MalformedURLException mue) { throw new ConfigurationException("Corrupt property: " + key + " reason: " + mue.getMessage(), mue); diff --git a/src/main/java/org/torproject/collector/cron/CollecTorMain.java b/src/main/java/org/torproject/collector/cron/CollecTorMain.java index 83d1ec3..34d711a 100644 --- a/src/main/java/org/torproject/collector/cron/CollecTorMain.java +++ b/src/main/java/org/torproject/collector/cron/CollecTorMain.java @@ -16,27 +16,45 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Calendar; import java.util.Map; +import java.util.Observable; +import java.util.Observer; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean;
-public abstract class CollecTorMain implements Runnable { +public abstract class CollecTorMain implements Observer, Runnable {
private static Logger log = LoggerFactory.getLogger(CollecTorMain.class);
private static final long LIMIT_MB = 200;
- protected Configuration config; + private final AtomicBoolean newConfigAvailable = new AtomicBoolean(false); + + protected Configuration config = new Configuration(); + + private Configuration newConfig;
public CollecTorMain(Configuration conf) { - this.config = conf; + this.config.putAll(conf.getPropertiesCopy()); + conf.addObserver(this); }
/** - * Log errors preventing successful completion of the module. + * Log all errors preventing successful completion of the module. */ @Override public final void run() { + synchronized (this) { + if (newConfigAvailable.get()) { + log.info("Module {} received new configuration.", module()); + synchronized (newConfig) { + config.clear(); + config.putAll(newConfig.getPropertiesCopy()); + newConfigAvailable.set(false); + } + } + } log.info("Starting {} module of CollecTor.", module()); try { startProcessing(); @@ -46,6 +64,14 @@ public abstract class CollecTorMain implements Runnable { log.info("Terminating {} module of CollecTor.", module()); }
+ @Override + public synchronized void update(Observable obs, Object obj) { + newConfigAvailable.set(true); + if (obs instanceof Configuration) { + newConfig = (Configuration) obs; + } + } + /** * Module specific code goes here. */ diff --git a/src/main/java/org/torproject/collector/cron/Scheduler.java b/src/main/java/org/torproject/collector/cron/Scheduler.java index 66fd7be..85cde5d 100644 --- a/src/main/java/org/torproject/collector/cron/Scheduler.java +++ b/src/main/java/org/torproject/collector/cron/Scheduler.java @@ -24,7 +24,7 @@ import java.util.concurrent.TimeUnit; /** * Scheduler that starts the modules configured in collector.properties. */ -public class Scheduler implements ThreadFactory { +public final class Scheduler implements ThreadFactory {
public static final String ACTIVATED = "Activated"; public static final String PERIODMIN = "PeriodMinutes"; diff --git a/src/main/resources/collector.properties b/src/main/resources/collector.properties index 9ba4c3e..0999fcb 100644 --- a/src/main/resources/collector.properties +++ b/src/main/resources/collector.properties @@ -1,6 +1,9 @@ ######## Collector Properties # ######## Run Configuration ######## +## This part of the configuration cannot be updated at runtime! +## Changes require a restart in order to become effective. +## # If RunOnce=true, the activated modules below will only be # run one time and without any delay. # Make sure only to run non-interfering modules together. @@ -35,6 +38,8 @@ UpdateindexActivated = false UpdateindexPeriodMinutes = 2 # offset in minutes since the epoch and UpdateindexOffsetMinutes = 0 +########################################## +## All below can be changed at runtime. ######## General Properties ######## InstanceBaseUrl = "https://collector.torproject.org" LockFilePath = lock diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index f31cf74..2d8a1ce 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -126,6 +126,14 @@ <appender-ref ref="FILEUPDATEINDEX" /> </logger>
+ <logger name="org.torproject.collector.conf" > + <appender-ref ref="FILEBRIDGEDESCS" /> + <appender-ref ref="FILEEXITLISTS" /> + <appender-ref ref="FILERELAYDESCS" /> + <appender-ref ref="FILETORPERF" /> + <appender-ref ref="FILEUPDATEINDEX" /> + </logger> + <logger name="org.torproject.collector.cron" > <appender-ref ref="FILEBRIDGEDESCS" /> <appender-ref ref="FILEEXITLISTS" /> diff --git a/src/test/java/org/torproject/collector/MainTest.java b/src/test/java/org/torproject/collector/MainTest.java index 6b90978..10a6958 100644 --- a/src/test/java/org/torproject/collector/MainTest.java +++ b/src/test/java/org/torproject/collector/MainTest.java @@ -9,6 +9,8 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail;
import org.torproject.collector.conf.Key; +import org.torproject.collector.conf.Configuration; +import org.torproject.collector.conf.ConfigurationException; import org.torproject.collector.cron.Scheduler;
import org.junit.Rule; @@ -19,39 +21,50 @@ import java.io.BufferedWriter; import java.io.IOException; import java.io.File; import java.nio.file.Files; +import java.nio.file.Paths; import java.util.List; import java.util.Properties; -import java.util.Random;
public class MainTest {
- private Random randomSource = new Random(); - @Rule public TemporaryFolder tmpf = new TemporaryFolder();
- @Test(expected = IOException.class) + @Test(expected = ConfigurationException.class) public void testInitializationConfigException() throws Exception { File conf = new File(Main.CONF_FILE); - assertFalse("Please remove " + Main.CONF_FILE + " before running tests!", conf.exists()); + checkCleanEnv(conf); Main.main(new String[] {"/tmp/"}); assertTrue(conf.exists()); assertTrue(conf.delete()); }
+ private void checkCleanEnv(File conf) { + assertFalse("Please remove " + Main.CONF_FILE + " before running tests!", + conf.exists()); + } + @Test() public void testInitializationNullArgs() throws Exception { File conf = new File(Main.CONF_FILE); - assertFalse("Please remove " + Main.CONF_FILE + " before running tests!", conf.exists()); + checkCleanEnv(conf); Main.main(null); assertTrue(conf.exists()); assertTrue(conf.delete()); }
+ @Test(expected = RuntimeException.class) + public void testInitializationUnwritable() throws Exception { + File conf = tmpf.newFolder("folder"); + + Main.main(new String[] { + Paths.get(conf.toString(), "x", "y", "z").toString()}); + } + @Test() public void testInitializationEmptyArgs() throws Exception { File conf = new File(Main.CONF_FILE); - assertFalse("Please remove " + Main.CONF_FILE + " before running tests!", conf.exists()); + checkCleanEnv(conf); Main.main(new String[] { }); assertTrue(conf.exists()); assertTrue(conf.delete()); @@ -60,7 +73,7 @@ public class MainTest { @Test() public void testInitializationTooManyArgs() throws Exception { File conf = new File(Main.CONF_FILE); - assertFalse("Please remove " + Main.CONF_FILE + " before running tests!", conf.exists()); + checkCleanEnv(conf); Main.main(new String[] { "x", "y" }); assertFalse(conf.exists()); } @@ -72,12 +85,23 @@ public class MainTest { assertEquals(0L, conf.length()); Main.main(new String[]{conf.toString()}); assertTrue(4_000L <= conf.length()); - changeFilePathsAndSetActivation(conf, lockPath, "TorperfActivated"); + changeFilePathsAndSetActivation(conf, lockPath, + Key.TorperfActivated.name()); Main.main(new String[]{conf.toString()}); - for(int t = 0; t < 1_000_000; t++) { } + waitSec(2); + } + + public static void waitSec(int sec) { + long now = System.currentTimeMillis(); + while (System.currentTimeMillis() - now < 1_000L * sec) { + try { + Thread.sleep(sec * 1_000L); + } catch (Exception e) {/* ignored */} + } }
- private void changeFilePathsAndSetActivation(File f, File l, String a) throws Exception { + private void changeFilePathsAndSetActivation(File f, File l, String a) + throws Exception { List<String> lines = Files.readAllLines(f.toPath()); BufferedWriter bw = Files.newBufferedWriter(f.toPath()); File in = tmpf.newFolder(); diff --git a/src/test/java/org/torproject/collector/conf/ConfigurationTest.java b/src/test/java/org/torproject/collector/conf/ConfigurationTest.java index 2055ae1..14033dc 100644 --- a/src/test/java/org/torproject/collector/conf/ConfigurationTest.java +++ b/src/test/java/org/torproject/collector/conf/ConfigurationTest.java @@ -7,17 +7,29 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue;
+import org.torproject.collector.MainTest; + +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder;
import java.io.ByteArrayInputStream; import java.io.File; +import java.nio.file.Paths; import java.util.Arrays; +import java.util.Observable; +import java.util.Observer; +import java.util.Properties; import java.util.Random; +import java.util.concurrent.atomic.AtomicBoolean;
public class ConfigurationTest {
private Random randomSource = new Random();
+ @Rule + public TemporaryFolder tmpf = new TemporaryFolder(); + @Test() public void testKeyCount() throws Exception { assertEquals("The number of properties keys in enum Key changed." @@ -28,9 +40,14 @@ public class ConfigurationTest { @Test() public void testConfiguration() throws Exception { Configuration conf = new Configuration(); - conf.load(new ByteArrayInputStream("TorperfOutputDirectory = xyz".getBytes())); + String val = "xyz"; + conf.setProperty(Key.TorperfOutputDirectory.name(), val); assertEquals(1, conf.size()); - assertEquals("xyz", conf.getProperty("TorperfOutputDirectory")); + assertEquals(val, conf.getProperty(Key.TorperfOutputDirectory.name())); + } + + private String propLine(Key key, String val) { + return key.name() + " = " + val + "\n"; }
@Test() @@ -41,14 +58,16 @@ public class ConfigurationTest { } String[] arrays = new String[] { Arrays.toString(array).replace("[", "").replace("]", ""), - Arrays.toString(array).replace("[", "").replace("]", "").replaceAll(" ", "") + Arrays.toString(array).replace("[", "").replace("]", "") + .replaceAll(" ", "") }; Configuration conf = new Configuration(); for(String input : arrays) { conf.clear(); - conf.load(new ByteArrayInputStream(("CachedRelayDescriptorsDirectories = " + input).getBytes())); + conf.setProperty(Key.CachedRelayDescriptorsDirectories.name(), input); assertArrayEquals("expected " + Arrays.toString(array) + "\nreceived: " - + Arrays.toString(conf.getStringArray(Key.CachedRelayDescriptorsDirectories)), + + Arrays.toString(conf + .getStringArray(Key.CachedRelayDescriptorsDirectories)), array, conf.getStringArray(Key.CachedRelayDescriptorsDirectories)); } } @@ -56,9 +75,9 @@ public class ConfigurationTest { @Test() public void testBoolValues() throws Exception { Configuration conf = new Configuration(); - conf.load(new ByteArrayInputStream(("CompressRelayDescriptorDownloads=false" - + "\nImportDirectoryArchives = trUe" - + "\nReplaceIpAddressesWithHashes= false").getBytes())); + conf.setProperty(Key.CompressRelayDescriptorDownloads.name(), "false"); + conf.setProperty(Key.ImportDirectoryArchives.name(), "trUe"); + conf.setProperty(Key.ReplaceIpAddressesWithHashes.name(), "false"); assertFalse(conf.getBool(Key.CompressRelayDescriptorDownloads)); assertTrue(conf.getBool(Key.ImportDirectoryArchives)); assertFalse(conf.getBool(Key.ReplaceIpAddressesWithHashes)); @@ -67,12 +86,13 @@ public class ConfigurationTest { @Test() public void testIntValues() throws Exception { Configuration conf = new Configuration(); - conf.load(new ByteArrayInputStream("BridgeDescriptorMappingsLimit = inf".getBytes())); + conf.setProperty(Key.BridgeDescriptorMappingsLimit.name(), "inf"); assertEquals(Integer.MAX_VALUE, conf.getInt(Key.BridgeDescriptorMappingsLimit)); int r = randomSource.nextInt(Integer.MAX_VALUE); conf.clear(); - conf.load(new ByteArrayInputStream(("BridgeDescriptorMappingsLimit =" + r).getBytes())); + conf.load(new ByteArrayInputStream( + propLine(Key.BridgeDescriptorMappingsLimit, "" + r).getBytes())); assertEquals(r, conf.getInt(Key.BridgeDescriptorMappingsLimit)); } @@ -83,8 +103,9 @@ public class ConfigurationTest { Configuration conf = new Configuration(); for(String file : files) { conf.clear(); - conf.load(new ByteArrayInputStream(("DirectoryArchivesOutputDirectory = " + file).getBytes())); - assertEquals(new File(file), conf.getPath(Key.DirectoryArchivesOutputDirectory).toFile()); + conf.setProperty(Key.DirectoryArchivesOutputDirectory.name(), file); + assertEquals(new File(file), + conf.getPath(Key.DirectoryArchivesOutputDirectory).toFile()); } }
@@ -94,52 +115,82 @@ public class ConfigurationTest { new String[]{"localsource", "http://127.0.0.1:12345%22%7D, new String[]{"somesource", "https://some.host.org:12345%22%7D%7D; Configuration conf = new Configuration(); - conf.load(new ByteArrayInputStream(("TorperfSources = " - + Arrays.deepToString(sourceStrings)).replace("[[", "").replace("]]", "") - .replace("], [", Configuration.ARRAYSEP).getBytes())); - assertArrayEquals(sourceStrings, conf.getStringArrayArray(Key.TorperfSources)); + conf.setProperty(Key.TorperfSources.name(), + Arrays.deepToString(sourceStrings).replace("[[", "").replace("]]", "") + .replace("], [", Configuration.ARRAYSEP)); + assertArrayEquals(sourceStrings, + conf.getStringArrayArray(Key.TorperfSources)); }
@Test(expected = ConfigurationException.class) public void testArrayArrayValueException() throws Exception { Configuration conf = new Configuration(); - conf.load(new ByteArrayInputStream("CachedRelayDescriptorsDirectories".getBytes())); + conf.setProperty(Key.CachedRelayDescriptorsDirectories.name(), ""); conf.getStringArrayArray(Key.TorperfOutputDirectory); }
@Test(expected = ConfigurationException.class) public void testArrayValueException() throws Exception { Configuration conf = new Configuration(); - conf.load(new ByteArrayInputStream("CachedRelayDescriptorsDirectories".getBytes())); + conf.setProperty(Key.CachedRelayDescriptorsDirectories.name(), ""); conf.getStringArray(Key.TorperfSources); }
@Test(expected = ConfigurationException.class) public void testBoolValueException() throws Exception { Configuration conf = new Configuration(); - conf.load(new ByteArrayInputStream("TorperfSource = http://x.y.z%22.getBytes())); + conf.setProperty(Key.TorperfSources.name(), "http://x.y.z"); conf.getBool(Key.CachedRelayDescriptorsDirectories); }
@Test(expected = ConfigurationException.class) public void testPathValueException() throws Exception { Configuration conf = new Configuration(); - conf.load(new ByteArrayInputStream("DirectoryArchivesDirectory = \u0000:".getBytes())); + conf.setProperty(Key.DirectoryArchivesDirectory.name(), "\\u0000:"); conf.getPath(Key.DirectoryArchivesDirectory); }
@Test(expected = ConfigurationException.class) public void testUrlValueException() throws Exception { Configuration conf = new Configuration(); - conf.load(new ByteArrayInputStream("ExitlistUrl = xxx://y.y.y".getBytes())); + conf.setProperty(Key.ExitlistUrl.name(), "xxx://y.y.y"); conf.getUrl(Key.ExitlistUrl); }
@Test(expected = ConfigurationException.class) public void testIntValueException() throws Exception { Configuration conf = new Configuration(); - conf.load(new ByteArrayInputStream("BridgeDescriptorMappingsLimit = y7".getBytes())); + conf.setProperty(Key.BridgeDescriptorMappingsLimit.name(), "y7"); conf.getInt(Key.BridgeDescriptorMappingsLimit); }
+ @Test(expected = ConfigurationException.class) + public void testSetWatchableSourceAndLoad() throws Exception { + Configuration conf = new Configuration(); + conf.setWatchableSourceAndLoad(Paths.get("/tmp/phantom.path")); + } + + @Test() + public void testConfigChange() throws Exception { + Configuration conf = new Configuration(); + final AtomicBoolean called = new AtomicBoolean(false); + conf.addObserver(new Observer() { + public void update(Observable obs, Object obj) { + called.set(true); + } + }); + File confFile = tmpf.newFile("empty"); + conf.setWatchableSourceAndLoad(confFile.toPath()); + confFile.setLastModified(System.currentTimeMillis()); + MainTest.waitSec(90); + assertTrue("Update was not called.", called.get()); + called.set(false); + MainTest.waitSec(60); + assertFalse("Update was called.", called.get()); + confFile.delete(); + tmpf.newFolder("empty"); + MainTest.waitSec(60); + assertEquals(0, conf.size()); + } + }