From 2ba70b782ace2b0272d6023898e1be1637c3dfa5 Mon Sep 17 00:00:00 2001 From: Toshiya Kobayashi Date: Thu, 20 Jun 2024 21:13:05 +0900 Subject: [PATCH] [incubator-kie-drools-6007] Executable model doesn't report an error when duplicated * removing kie-ci from dependency, because it causes a test failure in KieBaseIncludeTest * Use canonicalKieModule.getKiePackages() rather than getKieBase() * null check for kiePackage * move populateIncludedRuleNameMap out of packages loop * removed unused FileManager * performance improvement. Use getModelForKBase instead of getKiePackages * Fit into build phases * clean up --- .../kie/builder/impl/AbstractKieProject.java | 8 +- .../kie/builder/impl/BuildContext.java | 13 ++ .../execmodel/CanonicalModelBuildContext.java | 14 ++ .../codegen/execmodel/ModelBuilderImpl.java | 3 +- .../processors/ModelMainCompilationPhase.java | 18 +- .../processors/ModelRuleValidator.java | 64 ++++++ .../PopulateIncludedRuleNameMapPhase.java | 63 ++++++ .../modelcompiler/CanonicalKieModule.java | 7 +- .../integrationtests/KieBaseIncludesTest.java | 196 ++++++++++++++++++ 9 files changed, 380 insertions(+), 6 deletions(-) create mode 100644 drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/processors/ModelRuleValidator.java create mode 100644 drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/processors/PopulateIncludedRuleNameMapPhase.java diff --git a/drools-compiler/src/main/java/org/drools/compiler/kie/builder/impl/AbstractKieProject.java b/drools-compiler/src/main/java/org/drools/compiler/kie/builder/impl/AbstractKieProject.java index 7d8c399bea8..7775abacc43 100644 --- a/drools-compiler/src/main/java/org/drools/compiler/kie/builder/impl/AbstractKieProject.java +++ b/drools-compiler/src/main/java/org/drools/compiler/kie/builder/impl/AbstractKieProject.java @@ -225,6 +225,8 @@ public KnowledgeBuilder buildKnowledgePackages( KieBaseModelImpl kBaseModel, Bui Set assets = new LinkedHashSet<>(); + InternalKieModule kModule = getKieModuleForKBase(kBaseModel.getName()); + boolean allIncludesAreValid = true; for (String include : getTransitiveIncludes(kBaseModel)) { if ( StringUtils.isEmpty( include )) { @@ -240,6 +242,11 @@ public KnowledgeBuilder buildKnowledgePackages( KieBaseModelImpl kBaseModel, Bui } if (compileIncludedKieBases()) { addFiles( buildFilter, assets, getKieBaseModel( include ), includeModule, useFolders ); + } else { + if (kModule != includeModule) { + // includeModule is not part of the current kModule + buildContext.addIncludeModule(getKieBaseModel(include), includeModule); + } } } @@ -247,7 +254,6 @@ public KnowledgeBuilder buildKnowledgePackages( KieBaseModelImpl kBaseModel, Bui return null; } - InternalKieModule kModule = getKieModuleForKBase(kBaseModel.getName()); addFiles( buildFilter, assets, kBaseModel, kModule, useFolders ); KnowledgeBuilder kbuilder; diff --git a/drools-compiler/src/main/java/org/drools/compiler/kie/builder/impl/BuildContext.java b/drools-compiler/src/main/java/org/drools/compiler/kie/builder/impl/BuildContext.java index f9b564d7745..2d731956aba 100644 --- a/drools-compiler/src/main/java/org/drools/compiler/kie/builder/impl/BuildContext.java +++ b/drools-compiler/src/main/java/org/drools/compiler/kie/builder/impl/BuildContext.java @@ -18,6 +18,11 @@ */ package org.drools.compiler.kie.builder.impl; +import java.util.Collections; +import java.util.Map; + +import org.kie.api.builder.model.KieBaseModel; + public class BuildContext { private final ResultsImpl messages; @@ -36,4 +41,12 @@ public ResultsImpl getMessages() { public boolean registerResourceToBuild(String kBaseName, String resource) { return true; } + + public void addIncludeModule(KieBaseModel kieBaseModel, InternalKieModule includeModule) { + // no op + } + + public Map getIncludeModules() { + return Collections.emptyMap(); + } } diff --git a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/CanonicalModelBuildContext.java b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/CanonicalModelBuildContext.java index ddf51f164ad..3b019df9670 100644 --- a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/CanonicalModelBuildContext.java +++ b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/CanonicalModelBuildContext.java @@ -26,7 +26,9 @@ import java.util.Set; import org.drools.compiler.kie.builder.impl.BuildContext; +import org.drools.compiler.kie.builder.impl.InternalKieModule; import org.drools.compiler.kie.builder.impl.ResultsImpl; +import org.kie.api.builder.model.KieBaseModel; public class CanonicalModelBuildContext extends BuildContext { @@ -36,6 +38,8 @@ public class CanonicalModelBuildContext extends BuildContext { private final Collection allGeneratedPojos = new HashSet<>(); private final Map> allCompiledClasses = new HashMap<>(); + private final Map includeModules = new HashMap<>(); + public CanonicalModelBuildContext() { } public CanonicalModelBuildContext(ResultsImpl messages) { @@ -84,4 +88,14 @@ private String resource2Package(String resource) { int pathEndPos = resource.lastIndexOf('/'); return pathEndPos <= 0 ? "" :resource.substring(0, pathEndPos).replace('/', '.'); } + + @Override + public void addIncludeModule(KieBaseModel kieBaseModel, InternalKieModule includeModule) { + includeModules.put(kieBaseModel, includeModule); + } + + @Override + public Map getIncludeModules() { + return includeModules; + } } diff --git a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/ModelBuilderImpl.java b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/ModelBuilderImpl.java index 6c9df9af3fd..a9622724173 100644 --- a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/ModelBuilderImpl.java +++ b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/ModelBuilderImpl.java @@ -108,7 +108,8 @@ protected void doSecondBuildStep(Collection compositePack this.getGlobalVariableContext(), this.sourcesGenerator, this.packageSources, - oneClassPerRule)); + oneClassPerRule, + this.getBuildContext())); for (CompilationPhase phase : phases) { phase.process(); diff --git a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/processors/ModelMainCompilationPhase.java b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/processors/ModelMainCompilationPhase.java index c66d6e2fc0c..8485df83da1 100644 --- a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/processors/ModelMainCompilationPhase.java +++ b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/processors/ModelMainCompilationPhase.java @@ -20,7 +20,10 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.function.Function; import org.drools.compiler.builder.PackageRegistryManager; @@ -33,9 +36,9 @@ import org.drools.compiler.builder.impl.processors.FunctionCompilationPhase; import org.drools.compiler.builder.impl.processors.GlobalCompilationPhase; import org.drools.compiler.builder.impl.processors.IteratingPhase; -import org.drools.compiler.builder.impl.processors.RuleValidator; import org.drools.compiler.builder.impl.processors.SinglePackagePhaseFactory; import org.drools.compiler.builder.impl.processors.WindowDeclarationCompilationPhase; +import org.drools.compiler.kie.builder.impl.BuildContext; import org.drools.compiler.lang.descr.CompositePackageDescr; import org.drools.kiesession.rulebase.InternalKnowledgeBase; import org.drools.model.codegen.execmodel.PackageModel; @@ -61,6 +64,8 @@ public class ModelMainCompilationPhase implements CompilationPhase { private final PackageSourceManager packageSourceManager; private final boolean oneClassPerRule; + private final BuildContext buildContext; + public ModelMainCompilationPhase( PackageModelManager packageModels, PackageRegistryManager pkgRegistryManager, @@ -69,7 +74,11 @@ public ModelMainCompilationPhase( boolean hasMvel, InternalKnowledgeBase kBase, TypeDeclarationContext typeDeclarationContext, - GlobalVariableContext globalVariableContext, Function sourceGenerator, PackageSourceManager packageSourceManager, boolean oneClassPerRule) { + GlobalVariableContext globalVariableContext, + Function sourceGenerator, + PackageSourceManager packageSourceManager, + boolean oneClassPerRule, + BuildContext buildContext) { this.packageModels = packageModels; this.pkgRegistryManager = pkgRegistryManager; this.packages = packages; @@ -81,6 +90,7 @@ public ModelMainCompilationPhase( this.sourceGenerator = sourceGenerator; this.packageSourceManager = packageSourceManager; this.oneClassPerRule = oneClassPerRule; + this.buildContext = buildContext; } @Override @@ -94,7 +104,9 @@ public void process() { phases.add(iteratingPhase((reg, acc) -> GlobalCompilationPhase.of(reg, acc, kBase, globalVariableContext, acc.getFilter()))); phases.add(new DeclaredTypeDeregistrationPhase(packages, pkgRegistryManager)); - phases.add(iteratingPhase((reg, acc) -> new RuleValidator(reg, acc, configuration))); // validateUniqueRuleNames + Map> includedRuleNameMap = new HashMap<>(); + phases.add(new PopulateIncludedRuleNameMapPhase(buildContext.getIncludeModules(), includedRuleNameMap)); + phases.add(iteratingPhase((reg, acc) -> new ModelRuleValidator(reg, acc, configuration, includedRuleNameMap))); // validateUniqueRuleNames phases.add(iteratingPhase((reg, acc) -> new ModelGeneratorPhase(reg, acc, packageModels.getPackageModel(acc, reg, acc.getName()), typeDeclarationContext))); // validateUniqueRuleNames phases.add(iteratingPhase((reg, acc) -> new SourceCodeGenerationPhase<>( packageModels.getPackageModel(acc, reg, acc.getName()), packageSourceManager, sourceGenerator, oneClassPerRule))); // validateUniqueRuleNames diff --git a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/processors/ModelRuleValidator.java b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/processors/ModelRuleValidator.java new file mode 100644 index 00000000000..b4bc7a9ce75 --- /dev/null +++ b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/processors/ModelRuleValidator.java @@ -0,0 +1,64 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.drools.model.codegen.execmodel.processors; + +import java.util.Map; +import java.util.Set; + +import org.drools.compiler.builder.impl.processors.RuleValidator; +import org.drools.compiler.compiler.PackageRegistry; +import org.drools.drl.ast.descr.PackageDescr; +import org.drools.drl.ast.descr.RuleDescr; +import org.drools.drl.parser.ParserError; +import org.kie.internal.builder.KnowledgeBuilderConfiguration; + +public class ModelRuleValidator extends RuleValidator { + + // extra rule names which need to be checked for duplicates. + // Non-executable model doesn't need this because included kbase assets are added to the packageDescr + // Executable model requires this because included kbase assets are modeled as separated kjar, so not added to the packageDescr + private final Map> includedRuleNameMap; + + public ModelRuleValidator(PackageRegistry pkgRegistry, PackageDescr packageDescr, KnowledgeBuilderConfiguration configuration, Map> includedRuleNameMap) { + super(pkgRegistry, packageDescr, configuration); + this.includedRuleNameMap = includedRuleNameMap; + } + + @Override + public void process() { + super.process(); + + // Check with included rule names, because exec-model doesn't add assets of included kbase to the packageDescr + String packageName = packageDescr.getNamespace(); + if (includedRuleNameMap.containsKey(packageName)) { + Set ruleNames = includedRuleNameMap.get(packageName); + for (final RuleDescr rule : packageDescr.getRules()) { + final String name = rule.getUnitQualifiedName(); + if (ruleNames.contains(name)) { + this.results.add(new ParserError(rule.getResource(), + "Duplicate rule name: " + name, + rule.getLine(), + rule.getColumn(), + packageName)); + } + } + } + } +} diff --git a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/processors/PopulateIncludedRuleNameMapPhase.java b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/processors/PopulateIncludedRuleNameMapPhase.java new file mode 100644 index 00000000000..ea66ed29ec1 --- /dev/null +++ b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/processors/PopulateIncludedRuleNameMapPhase.java @@ -0,0 +1,63 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.drools.model.codegen.execmodel.processors; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.drools.compiler.builder.impl.processors.CompilationPhase; +import org.drools.compiler.kie.builder.impl.InternalKieModule; +import org.drools.compiler.kproject.models.KieBaseModelImpl; +import org.drools.model.Model; +import org.drools.modelcompiler.CanonicalKieModule; +import org.kie.api.builder.model.KieBaseModel; +import org.kie.internal.builder.KnowledgeBuilderResult; + +public class PopulateIncludedRuleNameMapPhase implements CompilationPhase { + + private final Map includeModules; + private final Map> includedRuleNameMap; + + public PopulateIncludedRuleNameMapPhase(Map includeModules, Map> includedRuleNameMap) { + this.includeModules = includeModules; + this.includedRuleNameMap = includedRuleNameMap; + } + + @Override + public void process() { + for (Map.Entry entry : includeModules.entrySet()) { + KieBaseModel kieBaseModel = entry.getKey(); + InternalKieModule includeModule = entry.getValue(); + if ((includeModule instanceof CanonicalKieModule canonicalKieModule) && canonicalKieModule.hasModelFile()) { + Collection includeModels = canonicalKieModule.getModelForKBase((KieBaseModelImpl)kieBaseModel); + for (Model includeModel : includeModels) { + includeModel.getRules().forEach(rule -> includedRuleNameMap.computeIfAbsent(includeModel.getPackageName(), k -> new HashSet<>()).add(rule.getName())); + } + } + } + } + + @Override + public Collection getResults() { + return Collections.emptyList(); + } +} diff --git a/drools-model/drools-model-compiler/src/main/java/org/drools/modelcompiler/CanonicalKieModule.java b/drools-model/drools-model-compiler/src/main/java/org/drools/modelcompiler/CanonicalKieModule.java index f5c4ddb66f6..f64be9e4257 100644 --- a/drools-model/drools-model-compiler/src/main/java/org/drools/modelcompiler/CanonicalKieModule.java +++ b/drools-model/drools-model-compiler/src/main/java/org/drools/modelcompiler/CanonicalKieModule.java @@ -466,7 +466,7 @@ private Collection getRuleClassNames() { return ruleClassesNames; } - private Collection getModelForKBase(KieBaseModelImpl kBaseModel) { + public Collection getModelForKBase(KieBaseModelImpl kBaseModel) { Map modelsMap = getModels(); if (kBaseModel.getPackages().isEmpty()) { return modelsMap.values(); @@ -484,6 +484,11 @@ private Collection getModelForKBase(KieBaseModelImpl kBaseModel) { return models; } + // This method indicates if the kjar was already compiled with the executable model + public boolean hasModelFile() { + return resourceFileExists(getModelFileWithGAV(internalKieModule.getReleaseId())); + } + private Collection findRuleClassesNames() { ReleaseId releaseId = internalKieModule.getReleaseId(); String modelFiles = readExistingResourceWithName(getModelFileWithGAV(releaseId)); diff --git a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/KieBaseIncludesTest.java b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/KieBaseIncludesTest.java index e9b358f02da..f68ac7bcfa3 100644 --- a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/KieBaseIncludesTest.java +++ b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/KieBaseIncludesTest.java @@ -18,7 +18,9 @@ */ package org.drools.mvel.integrationtests; +import java.io.IOException; import java.util.Collection; +import java.util.List; import org.drools.testcoverage.common.util.KieBaseTestConfiguration; import org.drools.testcoverage.common.util.KieUtil; @@ -28,11 +30,14 @@ import org.junit.runners.Parameterized; import org.kie.api.KieBase; import org.kie.api.KieServices; +import org.kie.api.builder.KieBuilder; import org.kie.api.builder.KieFileSystem; +import org.kie.api.builder.Message; import org.kie.api.builder.ReleaseId; import org.kie.api.definition.KiePackage; import org.kie.api.definition.rule.Rule; import org.kie.api.runtime.KieContainer; +import org.kie.api.runtime.KieSession; import static org.assertj.core.api.Assertions.assertThat; @@ -243,4 +248,195 @@ private static long getNumberOfRules(KieBase kieBase) { } return nrOfRules; } + + /** + * Test the inclusion of a KieBase defined in one KJAR into the KieBase of another KJAR. + *

+ * The 2 KieBases use the duplicate rule names, so an error should be reported + */ + @Test + public void kieBaseIncludesCrossKJarDuplicateRuleNames_shouldReportError() throws IOException { + + String pomContentMain = "\n" + + "4.0.0\n" + + "org.kie\n" + + "rules-main\n" + + "1.0.0\n" + + "jar\n" + + "\n" + + "\n" + + "org.kie\n" + + "rules-sub\n" + + "1.0.0\n" + + "\n" + + "\n" + + "\n"; + + String kmoduleContentMain = "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + + String drlMain = "package rules\n" + + "\n" + + "rule \"RuleA\"\n" + + "when\n" + + "then\n" + + "System.out.println(\"Rule in KieBaseMain\");\n" + + "end"; + + String kmoduleContentSub = "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + + String drlSub = "package rules\n" + + "\n" + + "rule \"RuleA\"\n" + + "when\n" + + "then\n" + + "System.out.println(\"Rule in KieBaseSub\");\n" + + "end"; + + KieServices ks = KieServices.Factory.get(); + ReleaseId releaseIdSub = ks.newReleaseId("org.kie", "rules-sub", "1.0.0"); + + //First deploy the second KJAR on which the first one depends. + KieFileSystem kfsSub = ks.newKieFileSystem() + .generateAndWritePomXML(releaseIdSub) + .write("src/main/resources/rules/rules.drl", drlSub) + .writeKModuleXML(kmoduleContentSub); + + KieUtil.getKieBuilderFromKieFileSystem(kieBaseTestConfiguration, kfsSub, true); + + KieFileSystem kfsMain = ks.newKieFileSystem() + .writePomXML(pomContentMain) + .write("src/main/resources/rules/rules.drl", drlMain) + .writeKModuleXML(kmoduleContentMain); + + KieBuilder kieBuilderMain = KieUtil.getKieBuilderFromKieFileSystem(kieBaseTestConfiguration, kfsMain, false); + List messages = kieBuilderMain.getResults().getMessages(Message.Level.ERROR); + + assertThat(messages).as("Duplication error should be reported") + .extracting(Message::getText).anyMatch(text -> text.contains("Duplicate rule name")); + } + + /** + * One KieBase that includes another KieBase from the same KJAR. Not duplicate names. + */ + @Test + public void kieBaseIncludesSameKJar() { + + String pomContent = "\n" + + "4.0.0\n" + + "org.kie\n" + + "rules-main-sub\n" + + "1.0.0\n" + + "jar\n" + + "\n"; + + String kmoduleContent = "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + + String drlMain = "package rules.main\n" + + "\n" + + "rule \"RuleA\"\n" + + "when\n" + + " $s : String()\n" + + "then\n" + + " System.out.println(\"Rule in KieBaseMain\");\n" + + "end"; + + String drlSub = "package rules.sub\n" + + "\n" + + "rule \"RuleB\"\n" + + "when\n" + + " $s : String()\n" + + "then\n" + + " System.out.println(\"Rule in KieBaseSub\");\n" + + "end"; + + KieServices ks = KieServices.Factory.get(); + + KieFileSystem kfsMain = ks.newKieFileSystem() + .writePomXML(pomContent) + .write("src/main/resources/rules/main/ruleMain.drl", drlMain) + .write("src/main/resources/rules/sub/ruleSub.drl", drlSub) + .writeKModuleXML(kmoduleContent); + + KieUtil.getKieBuilderFromKieFileSystem(kieBaseTestConfiguration, kfsMain, true); + ReleaseId releaseId = ks.newReleaseId("org.kie", "rules-main-sub", "1.0.0"); + KieContainer kieContainer = ks.newKieContainer(releaseId); + KieSession kieSession = kieContainer.newKieSession("ksessionMain"); + kieSession.insert("test"); + int fired = kieSession.fireAllRules(); + assertThat(fired).as("fire rules in main and sub").isEqualTo(2); + kieSession.dispose(); + } + + /** + * One KieBase that includes another KieBase from the same KJAR. Duplicate rule names. + */ + @Test + public void kieBaseIncludesSameKJarDuplicateRuleNames_shouldReportError() { + + String pomContent = "\n" + + "4.0.0\n" + + "org.kie\n" + + "rules-main-sub\n" + + "1.0.0\n" + + "jar\n" + + "\n"; + + String kmoduleContent = "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + + String drlMain = "package rules\n" + + "\n" + + "rule \"RuleA\"\n" + + "when\n" + + " $s : String()\n" + + "then\n" + + " System.out.println(\"Rule in KieBaseMain\");\n" + + "end"; + + String drlSub = "package rules\n" + // same package, same rule name + "\n" + + "rule \"RuleA\"\n" + + "when\n" + + " $s : String()\n" + + "then\n" + + " System.out.println(\"Rule in KieBaseSub\");\n" + + "end"; + + KieServices ks = KieServices.Factory.get(); + + KieFileSystem kfsMain = ks.newKieFileSystem() + .writePomXML(pomContent) + .write("src/main/resources/rules/main/ruleMain.drl", drlMain) + .write("src/main/resources/rules/sub/ruleSub.drl", drlSub) + .writeKModuleXML(kmoduleContent); + + KieBuilder kieBuilderMain = KieUtil.getKieBuilderFromKieFileSystem(kieBaseTestConfiguration, kfsMain, false); + List messages = kieBuilderMain.getResults().getMessages(Message.Level.ERROR); + + assertThat(messages).as("Duplication error should be reported") + .extracting(Message::getText).anyMatch(text -> text.contains("Duplicate rule name")); + } }