diff --git a/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/FeatureEvaluationContext.java b/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/FeatureEvaluationContext.java index 8316e65..a52c06d 100644 --- a/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/FeatureEvaluationContext.java +++ b/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/FeatureEvaluationContext.java @@ -19,17 +19,27 @@ public class FeatureEvaluationContext { private Map cache = Maps.newHashMap(); private Map> include = Maps.newHashMap(); - private Set exclude = Sets.newHashSet(); + private Map> exclude = Maps.newHashMap(); public FeatureEvaluationContext(List includedFeatures, List excludedFeatures) { - for (var featureToInclude : includedFeatures) { - Pair parsedFeature = parseFeature(featureToInclude); + processConfiguration(includedFeatures, include); + processConfiguration(excludedFeatures, exclude); + } + + private void processConfiguration(List features, Map> featureMap) { + for (var featureEntry : features) { + Pair parsedFeature = parseFeature(featureEntry); if (parsedFeature != null) { - include.put(parsedFeature.getFirst(), Sets.newHashSet(parsedFeature.getSecond()).stream() - .map(it -> it.trim()).collect(Collectors.toSet())); + Set existingInclude = featureMap.get(parsedFeature.getFirst()); + Set 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 parseFeature(String featureToInclude) { @@ -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; } @@ -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 : means include none of features. - var moduleEntry = include.get(modulePrefix); - // if : 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) { diff --git a/yang-lsp/io.typefox.yang/src/test/java/io/typefox/yang/tests/processor/FeatureContextTest.java b/yang-lsp/io.typefox.yang/src/test/java/io/typefox/yang/tests/processor/FeatureContextTest.java index 5948fa1..3a283e6 100644 --- a/yang-lsp/io.typefox.yang/src/test/java/io/typefox/yang/tests/processor/FeatureContextTest.java +++ b/yang-lsp/io.typefox.yang/src/test/java/io/typefox/yang/tests/processor/FeatureContextTest.java @@ -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")); } } diff --git a/yang-lsp/io.typefox.yang/src/test/java/io/typefox/yang/tests/processor/YangProcessorTest.java b/yang-lsp/io.typefox.yang/src/test/java/io/typefox/yang/tests/processor/YangProcessorTest.java index ddabb42..93b047e 100644 --- a/yang-lsp/io.typefox.yang/src/test/java/io/typefox/yang/tests/processor/YangProcessorTest.java +++ b/yang-lsp/io.typefox.yang/src/test/java/io/typefox/yang/tests/processor/YangProcessorTest.java @@ -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); @@ -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. diff --git a/yang-lsp/io.typefox.yang/src/test/resources/processor/expectation/expectation-feature-only-example.txt b/yang-lsp/io.typefox.yang/src/test/resources/processor/expectation/expectation-disable-all-features-for-module.txt similarity index 100% rename from yang-lsp/io.typefox.yang/src/test/resources/processor/expectation/expectation-feature-only-example.txt rename to yang-lsp/io.typefox.yang/src/test/resources/processor/expectation/expectation-disable-all-features-for-module.txt