Skip to content

Commit

Permalink
Including all features should behave same as not defining an include
Browse files Browse the repository at this point in the history
  • Loading branch information
dhuebner committed Mar 15, 2024
1 parent 57ba957 commit 7f38476
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,27 @@ public class FeatureEvaluationContext {
private Map<String, Boolean> cache = Maps.newHashMap();

private Map<String, Set<String>> include = Maps.newHashMap();
private Set<String> exclude = Sets.newHashSet();
private Map<String, Set<String>> exclude = Maps.newHashMap();

public FeatureEvaluationContext(List<String> includedFeatures, List<String> excludedFeatures) {
for (var featureToInclude : includedFeatures) {
Pair<String, String[]> parsedFeature = parseFeature(featureToInclude);
processConfiguration(includedFeatures, include);
processConfiguration(excludedFeatures, exclude);
}

private void processConfiguration(List<String> features, Map<String, Set<String>> featureMap) {
for (var featureEntry : features) {
Pair<String, String[]> parsedFeature = parseFeature(featureEntry);
if (parsedFeature != null) {
include.put(parsedFeature.getFirst(), Sets.newHashSet(parsedFeature.getSecond()).stream()
.map(it -> it.trim()).collect(Collectors.toSet()));
Set<String> existingInclude = featureMap.get(parsedFeature.getFirst());
Set<String> toAdd = Sets.newHashSet(parsedFeature.getSecond()).stream().map(it -> it.trim())
.filter(it -> !it.isEmpty()).collect(Collectors.toSet());
if (existingInclude != null) {
existingInclude.addAll(toAdd);
} else {
featureMap.put(parsedFeature.getFirst(), toAdd);
}
}
}
exclude.addAll(excludedFeatures);
}

private Pair<String, String[]> parseFeature(String featureToInclude) {
Expand All @@ -51,8 +61,7 @@ public boolean isActive(Feature feature) {
if (cache.containsKey(featureQName)) {
return cache.get(featureQName);
}
var active = isActive(featureModule.name + ":", featureQName, feature.getName())
&& featureIfConditionsActive(feature);
var active = isActive(featureModule.name + ":", feature.getName()) && featureIfConditionsActive(feature);
cache.put(featureQName, active);
return active;
}
Expand All @@ -61,17 +70,42 @@ private boolean featureIfConditionsActive(Feature feature) {
return ProcessorUtility.checkIfFeatures(ProcessorUtility.findIfFeatures(feature), this);
}

public boolean isActive(String modulePrefix, String featureQName, String featureName) {
// include <module>: means include none of <module> features.
var moduleEntry = include.get(modulePrefix);
// if <module>: not listed in include, all features are enabled by default.
/**
* For testing only. Use cached
* {@link FeatureEvaluationContext#isActive(Feature)} instead.
*
* @param modulePrefix
* @param featureName
* @return
*/
public boolean isActive(String modulePrefix, String featureName) {
// All modules and features are enabled by default.
boolean included = true;
if (moduleEntry != null) {
var includeEntry = include.get(modulePrefix);
if (includeEntry != null) {
// include e.g. 'example-system-ext:' means any of example-system-ext module
// features should be included
included = moduleEntry.contains(featureName);
included = includeEntry.contains(featureName);
}

if (!included) {
return false;
}
return (included) && (exclude.isEmpty() || !(exclude.contains(featureQName) || exclude.contains(modulePrefix)));

boolean excluded = false;
var excludeEntry = exclude.get(modulePrefix);
if (excludeEntry != null) {
if (excludeEntry.isEmpty()) {
// exclude all e.g. 'example-system-ext:' means exclude all of example-system-ex
// features
excluded = true;
} else {
// otherwise check feature entry
excluded = excludeEntry.contains(featureName);
}
}

return included && !excluded;
}

private String featureGlobalQName(ElementIdentifier module, String featureName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,98 @@
import io.typefox.yang.processor.FeatureEvaluationContext;

public class FeatureContextTest {

// Include
@Test
public void testIncludeOneFeature() {
FeatureEvaluationContext ctx = new FeatureEvaluationContext(Lists.newArrayList("module:feature1"),
Collections.emptyList());
assertTrue(ctx.isActive("module:", "module:feature1", "feature1"));
assertFalse(ctx.isActive("module:", "module:feature2", "feature2"));
assertTrue(ctx.isActive("module:", "feature1"));
assertFalse(ctx.isActive("module:", "feature2"));
// all other modules that are not listed are completely disabled
assertTrue(ctx.isActive("module2:", "feature1"));
assertTrue(ctx.isActive("module2:", "feature2"));
}

@Test
public void testIncludeNoFeature() {
FeatureEvaluationContext ctx = new FeatureEvaluationContext(Lists.newArrayList("module:"),
Collections.emptyList());
assertFalse(ctx.isActive("module:", "module:feature1", "feature1"));
assertFalse(ctx.isActive("module:", "module:feature2", "feature2"));
assertFalse(ctx.isActive("module:", "feature1"));
assertFalse(ctx.isActive("module:", "feature2"));
assertTrue(ctx.isActive("module2:", "feature1"));
assertTrue(ctx.isActive("module2:", "feature2"));
}

@Test
public void testIncludeTwoFeatures() {
FeatureEvaluationContext ctx = new FeatureEvaluationContext(
Lists.newArrayList("module:feature1", "module:feature2"), Collections.emptyList());
assertTrue(ctx.isActive("module:", "feature1"));
assertTrue(ctx.isActive("module:", "feature2"));
assertTrue(ctx.isActive("module2:", "feature1"));
assertTrue(ctx.isActive("module2:", "feature2"));
}

@Test
public void testIncludeTwoFeaturesCommaNotation() {
FeatureEvaluationContext ctx = new FeatureEvaluationContext(Lists.newArrayList("module:feature1,feature2"),
Collections.emptyList());
assertTrue(ctx.isActive("module:", "module:feature1", "feature1"));
assertTrue(ctx.isActive("module:", "module:feature2", "feature2"));
assertTrue(ctx.isActive("module:", "feature1"));
assertTrue(ctx.isActive("module:", "feature2"));
assertTrue(ctx.isActive("module2:", "feature1"));
assertTrue(ctx.isActive("module2:", "feature2"));
}

@Test
public void testIncludeAllFeatures() {
FeatureEvaluationContext ctx = new FeatureEvaluationContext(
Lists.newArrayList("module:feature1", "module:feature2", "module2:feature1", "module2:feature2"),
Collections.emptyList());
assertTrue(ctx.isActive("module:", "feature1"));
assertTrue(ctx.isActive("module:", "feature2"));
assertTrue(ctx.isActive("module2:", "feature1"));
assertTrue(ctx.isActive("module2:", "feature2"));
}

@Test
public void testMissingFeatureInclude() {
FeatureEvaluationContext ctx = new FeatureEvaluationContext(Collections.emptyList(), Collections.emptyList());
assertTrue(ctx.isActive("module:", "feature1"));
assertTrue(ctx.isActive("module:", "feature2"));
assertTrue(ctx.isActive("module2:", "feature1"));
assertTrue(ctx.isActive("module2:", "feature2"));
}

// Excludes
@Test
public void testExcludeOneFeature() {
FeatureEvaluationContext ctx = new FeatureEvaluationContext(Collections.emptyList(),
Lists.newArrayList("module:feature1"));
assertFalse(ctx.isActive("module:", "feature1"));
assertTrue(ctx.isActive("module:", "feature2"));
}

@Test
public void testExcludeNoFeature() {
FeatureEvaluationContext ctx = new FeatureEvaluationContext(Collections.emptyList(),
Lists.newArrayList("module:"));
assertFalse(ctx.isActive("module:", "feature1"));
assertFalse(ctx.isActive("module:", "feature2"));
}

@Test
public void testExcludeTwoFeatures() {
FeatureEvaluationContext ctx = new FeatureEvaluationContext(Collections.emptyList(),
Lists.newArrayList("module:feature1", "module:feature2"));
assertFalse(ctx.isActive("module:", "feature1"));
assertFalse(ctx.isActive("module:", "feature2"));
}

@Test
public void testExcludeTwoFeaturesCommaNotation() {
FeatureEvaluationContext ctx = new FeatureEvaluationContext(Collections.emptyList(),
Lists.newArrayList("module:feature1,feature2"));
assertFalse(ctx.isActive("module:", "feature1"));
assertFalse(ctx.isActive("module:", "feature2"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,66 @@ public void processModules_TreeTest() throws IOException {

}

/*
Should return the same result as processModules_TreeTest()
*/
@Test
public void processModules_TreeTest_FeatureIncludeAllListed() throws IOException {

var sysModule = processData(true, newArrayList(
"example-system-ext:ldap",
"example-system-ext:ldap-authentication",
"example-system-ext:ldap-clear",
"example-system-ext:ldap-posix-filter",
"example-system-ext:ldap-custom-filter",
"example-system-ext:ldap-sasl-external",
"example-system-ext:local-target-classes",
"example-system-ext:authentication-failure-alarm",
"example-system-ext:ntp-security",
"example-system-ext:oauth2-client-authentication"
), null);

String expectation = null;

// CLI tree test expect output like:
/*
pyang -f tree ietf-system.yang --deviation-module example-system-ext.yang \
-F example-system-ext:example-system-ext:ldap\
-F example-system-ext:ldap-authentication \
-F example-system-ext:ldap-clear \
-F example-system-ext:ldap-posix-filter \
-F example-system-ext:ldap-custom-filter \
-F example-system-ext:ldap-sasl-external \
-F example-system-ext:local-target-classes \
-F example-system-ext:authentication-failure-alarm \
-F example-system-ext:ntp-security \
-F example-system-ext:oauth2-client-authentication \
-F example-system-ext:oauth2-client-authentication > pyang-enable-all-as-features.txt
pyang -f tree ietf-system.yang --deviation-module example-system-ext.yang -F example-system-ext:example-system-ext:ldap,ldap-authentication,ldap-clear,ldap-posix-filter,ldap-custom-filter,ldap-sasl-external,local-target-classes,authentication-failure-alarm,ntp-security,oauth2-client-authentication > pyang-include-all-comma.txt
*/
try (InputStream in = this.getClass().getClassLoader()
.getResourceAsStream("processor/expectation/expectation.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
var writer = new StringWriter();
br.transferTo(writer);
expectation = writer.getBuffer().toString();
}
assertEqualsReduceSpace(expectation, new DataTreeSerializer().serialize(sysModule.get()).toString());
}

@Test
public void processModules_TreeTest_FeatureInclude() throws IOException {
public void processModules_TreeTest_FeatureIncludeEmptyModule() throws IOException {

var sysModule = processData(true, newArrayList("example-system-ext:"), null);

String expectation = null;

// CLI tree test expect output like:
// pyang -f tree ietf-system.yang --deviation-module example-system-ext.yang -F
// example-system-ext:
// pyang -f tree ietf-system.yang --deviation-module example-system-ext.yang -F example-system-ext:
try (InputStream in = this.getClass().getClassLoader()
.getResourceAsStream("processor/expectation/expectation-feature-only-example.txt");
.getResourceAsStream("processor/expectation/expectation-disable-all-features-for-module.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
var writer = new StringWriter();
br.transferTo(writer);
Expand All @@ -86,6 +134,8 @@ public void processModules_TreeTest_FeatureInclude() throws IOException {
assertEqualsReduceSpace(expectation, new DataTreeSerializer().serialize(sysModule.get()).toString());

}



/*
* enables ietf-keystore:local-definitions-supported feature.
Expand Down

0 comments on commit 7f38476

Please sign in to comment.