generated from DeepBlueRobotics/EmptyProject2024
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #58 from DeepBlueRobotics/add-config-support
Add support for overrideable configuration settings such as feature flags.
- Loading branch information
Showing
4 changed files
with
186 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package org.carlmontrobotics; | ||
|
||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Modifier; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import edu.wpi.first.util.sendable.Sendable; | ||
import edu.wpi.first.util.sendable.SendableBuilder; | ||
|
||
abstract class Config implements Sendable { | ||
public static final Config CONFIG = new Config() { | ||
{ | ||
// Override config settings here, like this: | ||
// this.exampleFlagEnabled = true; | ||
|
||
// NOTE: PRs with overrides will NOT be merged because we don't want them | ||
// polluting the master branch. | ||
// Feel free to add them when testing, but remove them before pushing. | ||
} | ||
}; | ||
|
||
// Add additional config settings by declaring a protected field, and... | ||
protected boolean exampleFlagEnabled = false; | ||
|
||
// ...a public getter starting with "is" for booleans or "get" for other types. | ||
// Do NOT remove this example. It is used by unit tests. | ||
public boolean isExampleFlagEnabled() { | ||
return exampleFlagEnabled; | ||
} | ||
|
||
// --- For clarity, place additional config settings ^above^ this line --- | ||
|
||
private static class MethodResult { | ||
String methodName = null; | ||
Object retVal = null; | ||
Object defaultRetVal = null; | ||
|
||
MethodResult(String name, Object retVal, Object defaultRetval) { | ||
this.methodName = name; | ||
this.retVal = retVal; | ||
this.defaultRetVal = defaultRetval; | ||
} | ||
} | ||
|
||
private List<MethodResult> getMethodResults() { | ||
var methodResults = new ArrayList<MethodResult>(); | ||
var defaultConfig = new Config() { | ||
}; | ||
for (Method m : Config.class.getDeclaredMethods()) { | ||
var name = m.getName(); | ||
if (!Modifier.isPublic(m.getModifiers()) || m.isSynthetic() || m.getParameterCount() != 0 | ||
|| !name.matches("^(get|is)[A-Z].*")) { | ||
continue; | ||
} | ||
Object retVal = null; | ||
try { | ||
retVal = m.invoke(this); | ||
} catch (Exception ex) { | ||
retVal = ex; | ||
} | ||
Object defaultRetVal = null; | ||
try { | ||
defaultRetVal = m.invoke(defaultConfig); | ||
} catch (Exception ex) { | ||
defaultRetVal = ex; | ||
} | ||
methodResults.add(new MethodResult(name, retVal, defaultRetVal)); | ||
} | ||
return methodResults; | ||
} | ||
|
||
@Override | ||
public void initSendable(SendableBuilder builder) { | ||
getMethodResults().forEach(mr -> { | ||
if (!mr.retVal.equals(mr.defaultRetVal)) { | ||
builder.publishConstString("%s()".formatted(mr.methodName), | ||
String.format("%s (default is %s)", mr.retVal, mr.defaultRetVal)); | ||
} | ||
}); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
StringBuilder stringBuilder = new StringBuilder(); | ||
getMethodResults().forEach(mr -> { | ||
if (!mr.retVal.equals(mr.defaultRetVal)) { | ||
stringBuilder.append( | ||
String.format("%s() returns %s (default is %s)", mr.methodName, mr.retVal, mr.defaultRetVal)); | ||
} | ||
}); | ||
if (stringBuilder.isEmpty()) { | ||
stringBuilder.append("Using default config values"); | ||
} else { | ||
stringBuilder.insert(0, "WARNING: USING OVERRIDDEN CONFIG VALUES\n"); | ||
} | ||
return stringBuilder.toString(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package org.carlmontrobotics; | ||
|
||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; | ||
|
||
import edu.wpi.first.util.sendable.SendableBuilder; | ||
import edu.wpi.first.wpilibj.smartdashboard.SendableBuilderImpl; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
||
import java.util.HashMap; | ||
|
||
public class ConfigTest { | ||
@Test | ||
void testIsExampleFlagEnabled() { | ||
assertEquals(false, new Config() { | ||
}.isExampleFlagEnabled()); | ||
assertEquals(true, new Config() { | ||
{ | ||
this.exampleFlagEnabled = true; | ||
} | ||
}.isExampleFlagEnabled()); | ||
} | ||
|
||
@Test | ||
void testInitSendable() throws Exception { | ||
var publishedStrings = new HashMap<String, String>(); | ||
try (SendableBuilder testBuilder = new SendableBuilderImpl() { | ||
@Override | ||
public void publishConstString(String key, String value) { | ||
publishedStrings.put(key, value); | ||
} | ||
}) { | ||
Config testConfig = new Config() { | ||
}; | ||
testConfig.initSendable(testBuilder); | ||
assertEquals(new HashMap<String, String>(), publishedStrings, | ||
"A default config should not publish anything."); | ||
|
||
testConfig = new Config() { | ||
{ | ||
this.exampleFlagEnabled = true; | ||
} | ||
}; | ||
testConfig.initSendable(testBuilder); | ||
assertEquals(new HashMap<String, String>() { | ||
{ | ||
this.put("isExampleFlagEnabled()", "true (default is false)"); | ||
} | ||
}, publishedStrings, "A config with overrides should publish what is overriden."); | ||
} | ||
} | ||
|
||
@Test | ||
public void testToString() { | ||
assertEquals("Using default config values", new Config() { | ||
}.toString()); | ||
assertEquals("WARNING: USING OVERRIDDEN CONFIG VALUES\nisExampleFlagEnabled() returns true (default is false)", | ||
new Config() { | ||
{ | ||
exampleFlagEnabled = true; | ||
} | ||
}.toString()); | ||
} | ||
|
||
@Test | ||
@EnabledIfEnvironmentVariable(named = "testCONFIGIsDefault", matches = "true", disabledReason = "not trying to modify GitHub master") | ||
public void testNoConfigSettingsOverridden() throws Exception { | ||
var publishedStrings = new HashMap<String, String>(); | ||
try (SendableBuilder testBuilder = new SendableBuilderImpl() { | ||
@Override | ||
public void publishConstString(String key, String value) { | ||
publishedStrings.put(key, value); | ||
} | ||
}) { | ||
Config.CONFIG.initSendable(testBuilder); | ||
assertEquals(new HashMap<String, String>(), publishedStrings, | ||
"Config.CONFIG must be empty to be on the master branch."); | ||
} | ||
} | ||
} |