diff --git a/src/main/java/com/pdsl/gherkin/filter/GherkinTagFiltererAdapter.java b/src/main/java/com/pdsl/gherkin/filter/GherkinTagFiltererAdapter.java new file mode 100644 index 0000000..044f1d9 --- /dev/null +++ b/src/main/java/com/pdsl/gherkin/filter/GherkinTagFiltererAdapter.java @@ -0,0 +1,42 @@ +package com.pdsl.gherkin.filter; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * This class adapts the `GherkinTagFilterer` interface to the `TagFilterer` interface. It provides + * a way to use the functionality of `GherkinTagFilterer` with code that expects a `TagFilterer`. + */ +public class GherkinTagFiltererAdapter implements TagFilterer { + + /** + * The instance of the `GherkinTagFilterer` to be adapted. + */ + private final GherkinTagFilterer gherkinTagFilterer; + + /** + * Constructor for the `GherkinTagFiltererAdapter`. + * + * @param gherkinTagFilterer The instance of the `GherkinTagFilterer` to be adapted. + */ + public GherkinTagFiltererAdapter(GherkinTagFilterer gherkinTagFilterer) { + this.gherkinTagFilterer = gherkinTagFilterer; + } + + /** + * This method checks whether a collection of tags matches a given tag expression. It converts the + * collection of tags to a `Set` using a `HashSet` and then delegates the logic to the + * `gherkinTagFilterer.tagExpressionMatchesPickle` method. + * + * @param tags The collection of tags to be checked. + * @param tagExpression The tag expression to match against. + * @return True if the tags match the tag expression, False otherwise. + */ + + @Override + public boolean tagExpressionMatches(Collection tags, String tagExpression) { + return gherkinTagFilterer.tagExpressionMatchesPickle(new HashSet<>(tags), tagExpression); + } +} \ No newline at end of file diff --git a/src/main/java/com/pdsl/gherkin/filter/TagFilterer.java b/src/main/java/com/pdsl/gherkin/filter/TagFilterer.java new file mode 100644 index 0000000..4f50e47 --- /dev/null +++ b/src/main/java/com/pdsl/gherkin/filter/TagFilterer.java @@ -0,0 +1,13 @@ +package com.pdsl.gherkin.filter; + +import java.util.Collection; +import java.util.Set; + +/** + * Determines if a test case derived from a gherkin file (i.e., a pickle) matches a particular tag + * expression. + */ +public interface TagFilterer { + + boolean tagExpressionMatches(Collection tags, String tagExpression); +} \ No newline at end of file diff --git a/src/main/java/com/pdsl/runners/JUnit4RecognizerParamsConverter.java b/src/main/java/com/pdsl/runners/JUnit4RecognizerParamsConverter.java index 22e671a..64a547d 100644 --- a/src/main/java/com/pdsl/runners/JUnit4RecognizerParamsConverter.java +++ b/src/main/java/com/pdsl/runners/JUnit4RecognizerParamsConverter.java @@ -179,7 +179,7 @@ public InterpreterObj get() { private static PdslTestParams getPdslTestParams( PdslTestDto pdslTestDto, PdslConfiguration pdslConfiguration) { - return new PdslTestParams( + return PdslTestParams.from( getRecognizerLexer(pdslTestDto, pdslConfiguration), getRecognizerParser(pdslTestDto, pdslConfiguration), getInterpreterParams(pdslTestDto, pdslConfiguration), diff --git a/src/main/java/com/pdsl/runners/PdslTestParams.java b/src/main/java/com/pdsl/runners/PdslTestParams.java index cb06384..c0616d2 100644 --- a/src/main/java/com/pdsl/runners/PdslTestParams.java +++ b/src/main/java/com/pdsl/runners/PdslTestParams.java @@ -1,9 +1,11 @@ package com.pdsl.runners; +import com.pdsl.gherkin.filter.TagFilterer; import org.antlr.v4.runtime.Lexer; import org.antlr.v4.runtime.Parser; import java.util.List; +import org.junit.jupiter.engine.descriptor.PdslTestParameter; /** * A container for parameters useful for any framework implementing PDSL. @@ -17,15 +19,32 @@ * @param excludesResources the tests to ignore */ public record PdslTestParams ( + Class lexerRecognizerClass, + Class parserRecognizerClass, + InterpreterParam[] interpreters, + List tags, + String[] includesResources, + String[] excludesResources, + TagFilterer tagFilterer, + String tagExpression) { + public static final String DEFAULT_ALL_RULE = "polymorphicDslAllRules"; + public static final String DEFAULT_SYNTAX_RULE = "polymorphicDslSyntaxCheck"; + + public static PdslTestParams from( Class lexerRecognizerClass, Class parserRecognizerClass, InterpreterParam[] interpreters, List tags, String[] includesResources, - String[] excludesResources) { - public static final String DEFAULT_ALL_RULE = "polymorphicDslAllRules"; - public static final String DEFAULT_SYNTAX_RULE = "polymorphicDslSyntaxCheck"; + String[] excludesResources){ + /* Creates a default tag filter that always returns true, effectively disabling tag filtering., + Empty tag expression as no filtering is applied */ + return new PdslTestParams( + lexerRecognizerClass, parserRecognizerClass, interpreters, + tags, includesResources, excludesResources, + (tags1, tagExpression) -> true, ""); + } /** * A visitor used to extend the behavior of the PdslTestParams obje3ct without modifying * the record itself. diff --git a/src/main/java/com/pdsl/runners/SharedTestSuiteVisitor.java b/src/main/java/com/pdsl/runners/SharedTestSuiteVisitor.java index b8fe896..396770b 100644 --- a/src/main/java/com/pdsl/runners/SharedTestSuiteVisitor.java +++ b/src/main/java/com/pdsl/runners/SharedTestSuiteVisitor.java @@ -2,15 +2,19 @@ import com.google.common.base.Preconditions; import com.pdsl.executors.InterpreterObj; +import com.pdsl.gherkin.filter.TagFilterer; import com.pdsl.specifications.TestResourceFinder; import com.pdsl.specifications.TestResourceFinderGenerator; import com.pdsl.specifications.TestSpecification; import com.pdsl.specifications.TestSpecificationFactory; +import com.pdsl.testcases.DefaultTaggedTestCase; import com.pdsl.testcases.SharedTestSuite; +import com.pdsl.testcases.TaggedTestCase; import com.pdsl.testcases.TestCase; import com.pdsl.testcases.TestCaseFactory; import com.pdsl.transformers.DefaultPolymorphicDslPhraseFilter; import com.pdsl.transformers.PolymorphicDslPhraseFilter; +import java.util.stream.Collectors; import org.antlr.v4.runtime.Lexer; import org.antlr.v4.runtime.Parser; @@ -27,6 +31,7 @@ */ public class SharedTestSuiteVisitor implements RecognizerParams.RecognizerParamsOperation { + @Override public SharedTestSuite recognizerParamsOperation(RecognizerParams recognizerParams) { Preconditions.checkNotNull(recognizerParams.applicationName(), "Application name cannot be null"); @@ -39,6 +44,7 @@ public SharedTestSuite recognizerParamsOperation(RecognizerParams recognizerPara recognizerParams.pdslTestParams().forEach(interpreter -> Preconditions.checkNotNull(interpreter, "No null objects can be in the interpreter array!")); // Create a Shared Test Suite List> testCasesPerInterpreters = new ArrayList<>(); + List matchedTestCases = new ArrayList<>(); List interpreterObjs = new ArrayList<>(); for (PdslTestParams params : recognizerParams.pdslTestParams()) { @@ -57,9 +63,34 @@ public SharedTestSuite recognizerParamsOperation(RecognizerParams recognizerPara // test cases to create from each file, which parts to ignore, etc. Collection specifications = getSpecifications(parser, recognizerParams, params, testResources); // Convert the specificaitons into test cases + + /* + * Filters test cases based on tags and organizes them per interpreter. + * + * This code iterates through a collection of test cases for a single interpreter, + * filters them based on tags using a provided `TagFilterer`, and then adds the + * matched test cases to a list of test cases per interpreter. It also collects + * the interpreter objects. + */ List testCasesForSingleInterpreter = new ArrayList<>(getTestCases(recognizerParams.providers().testCaseFactoryProvider().get(), specifications)); - testCasesPerInterpreters.add(testCasesForSingleInterpreter); + for (TestCase testcase : testCasesForSingleInterpreter) { + if (testcase instanceof TaggedTestCase) { + // Cast the TestCase to TaggedTestCase to access tag information. + TaggedTestCase test = (TaggedTestCase) testcase; + //Filter test cases by tags using the injected TagFilterer + // Check if any of the tags on the test case match the given tag expression. + boolean anyMatchedTag = params.tagFilterer().tagExpressionMatches(test.getTags(), params.tagExpression()); + // If the test case's tags match the tag expression, add it to the matched test cases. + if (anyMatchedTag) { + matchedTestCases.add(testcase); + } + } + } + // Add the list of matched test cases for the current interpreter to the overall list. + testCasesPerInterpreters.add(matchedTestCases); + // Get the interpreter object from the parser provider and add it to the list of interpreter objects. interpreterObjs.add(parser.interpreterProvider().get()); + } } return SharedTestSuite.of(testCasesPerInterpreters, interpreterObjs); diff --git a/src/main/java/com/pdsl/runners/junit/PdslGherkinJUnit4Runner.java b/src/main/java/com/pdsl/runners/junit/PdslGherkinJUnit4Runner.java index f116eef..15a1d42 100644 --- a/src/main/java/com/pdsl/runners/junit/PdslGherkinJUnit4Runner.java +++ b/src/main/java/com/pdsl/runners/junit/PdslGherkinJUnit4Runner.java @@ -23,7 +23,6 @@ public class PdslGherkinJUnit4Runner extends BlockJUnit4ClassRunner { public PdslGherkinJUnit4Runner(Class testClass) throws InitializationError { super(testClass); - System.out.print("JS23001 : 50"); pdslGherkinApplication = testClass.getAnnotation(PdslGherkinApplication.class); Preconditions.checkNotNull(pdslGherkinApplication, String.format("Class run with %s must be annotated with %s!", getClass().getSimpleName(), PdslConfiguration.class.getSimpleName())); diff --git a/src/main/java/com/pdsl/testcases/PreorderTestCaseFactory.java b/src/main/java/com/pdsl/testcases/PreorderTestCaseFactory.java index a4a460d..2a85265 100644 --- a/src/main/java/com/pdsl/testcases/PreorderTestCaseFactory.java +++ b/src/main/java/com/pdsl/testcases/PreorderTestCaseFactory.java @@ -1,5 +1,6 @@ package com.pdsl.testcases; +import com.pdsl.gherkin.filter.TagFilterer; import com.pdsl.specifications.PolymorphicDslTransformationException; import com.pdsl.specifications.TaggedTestSpecification; import com.pdsl.specifications.TestSpecification; @@ -73,13 +74,13 @@ private List recursiveWalkAndCreateOnLeaf(TestSpecification testSpecif } return testCases; } else { - TestCase testCase = new DefaultPdslTestCase(testSpecification.getName(), childTestBodyFragments, testSpecification.getOriginalTestResource()); - if (!tags.isEmpty()) { - testCase = new DefaultTaggedTestCase(testCase, tags); - } - List singleTestCase = new ArrayList<>(1); - singleTestCase.add(testCase); - return singleTestCase; + TestCase testCase = new DefaultPdslTestCase(testSpecification.getName(), childTestBodyFragments, testSpecification.getOriginalTestResource()); + if (!tags.isEmpty()) { + testCase = new DefaultTaggedTestCase(testCase, tags); + } + List singleTestCase = new ArrayList<>(1); + singleTestCase.add(testCase); + return singleTestCase; } } diff --git a/src/main/java/org/junit/jupiter/engine/descriptor/PdslSharedInvocationContextProvider.java b/src/main/java/org/junit/jupiter/engine/descriptor/PdslSharedInvocationContextProvider.java index ae93fe7..2412949 100644 --- a/src/main/java/org/junit/jupiter/engine/descriptor/PdslSharedInvocationContextProvider.java +++ b/src/main/java/org/junit/jupiter/engine/descriptor/PdslSharedInvocationContextProvider.java @@ -150,7 +150,10 @@ private static List convert(Collection parame getInterpreterParams(config, p), List.of(p.getTagExpression()), p.getIncludesResources(), - p.getExcludesResources()); + p.getExcludesResources(), + p.getTagFilterer(), + p.getTagExpression() + ); params.add(pdslTestParams); } return params; diff --git a/src/main/java/org/junit/jupiter/engine/descriptor/PdslTestParameter.java b/src/main/java/org/junit/jupiter/engine/descriptor/PdslTestParameter.java index 12ecd42..d0f9dc4 100644 --- a/src/main/java/org/junit/jupiter/engine/descriptor/PdslTestParameter.java +++ b/src/main/java/org/junit/jupiter/engine/descriptor/PdslTestParameter.java @@ -1,6 +1,10 @@ package org.junit.jupiter.engine.descriptor; import com.google.common.base.Preconditions; +import com.pdsl.gherkin.filter.GherkinTagFiltererAdapter; +import com.pdsl.gherkin.filter.GherkinTagsVisitorImpl; +import com.pdsl.gherkin.filter.TagFilterer; +import java.util.logging.Logger; import org.antlr.v4.runtime.Lexer; import org.antlr.v4.runtime.Parser; import org.antlr.v4.runtime.tree.ParseTreeListener; @@ -23,6 +27,7 @@ */ public class PdslTestParameter { + private static final Logger logger = Logger.getLogger(PdslTestParameter.class.getName()); private final Optional> recognizedByParser; private final Optional> recognizedByLexer; private final Class parser; @@ -35,9 +40,10 @@ public class PdslTestParameter { private final String[] excludesResources; private final String startRule; private final Optional recognizerRule; + private final TagFilterer tagFilterer; private PdslTestParameter(Builder builder) { - Preconditions.checkArgument((builder.listener.isPresent() + Preconditions.checkArgument((builder.listener.isPresent() ^ builder.visitor.isPresent() ^ builder.interpreterParams.isPresent()), "You can only have one of a visitor, listener or list of interpreterDtos!"); this.parser = builder.parser; @@ -52,6 +58,7 @@ private PdslTestParameter(Builder builder) { this.recognizedByLexer = builder.recognizedByLexer; this.recognizedByParser = builder.recognizedByParser; this.interpreterParams = builder.interpreterParams; + this.tagFilterer = builder.tagFilterer; } public Optional> getInterpreters() { return interpreterParams; } @@ -100,7 +107,11 @@ public Optional getRecognizerRule() { return recognizerRule; } - public static class Builder { + public TagFilterer getTagFilterer() { + return tagFilterer; + } + + public static class Builder { private Class parser; private Class lexer; private Optional> recognizedByParser = Optional.empty(); @@ -114,6 +125,15 @@ public static class Builder { private Optional recognizerRule = Optional.empty(); private Optional> interpreterParams = Optional.empty(); + private TagFilterer tagFilterer = new GherkinTagFiltererAdapter(new GherkinTagsVisitorImpl()); + + public Builder withTagFilterer(TagFilterer tagFilterer) { + this.tagFilterer = tagFilterer; + return this; + } + + + public Builder( Class lexer, Class parser, @@ -186,9 +206,22 @@ public Builder withVisitor(Supplier> visitor) { } public Builder withTagExpression(String str) { - Preconditions.checkNotNull(str, "Tag expression cannot be null!"); + Preconditions.checkNotNull(str, "Tag expression cannot be null!"); + // Check if there are tags specified using the system property "tags". + String commandLineTags = System.getProperty("tags"); + if (commandLineTags != null && !commandLineTags.isEmpty()) { + // If command-line tags are present, use them and log a message informing the user + // that the provided tag expression is being ignored. + this.tagExpression = commandLineTags; + logger.info("Overriding runner tags with tags provided in the command line {%s}".formatted( + commandLineTags)); + logger.info("Ignoring provided tag expression {%s}".formatted(str)); + } else { + // If no command-line tags are present, use the provided tag expression. this.tagExpression = str; - return this; + } + + return this; } public Builder withIncludedResources(String[] resources) { diff --git a/src/test/java/com/pdsl/uat/java/jupiter/JupiterTagFilteringTest.java b/src/test/java/com/pdsl/uat/java/jupiter/JupiterTagFilteringTest.java index 0efd48a..6138305 100644 --- a/src/test/java/com/pdsl/uat/java/jupiter/JupiterTagFilteringTest.java +++ b/src/test/java/com/pdsl/uat/java/jupiter/JupiterTagFilteringTest.java @@ -32,11 +32,16 @@ private static class PdslExtension extends PdslGherkinInvocationContextProvider @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + String defaultTagExpression = "@comment_tag#2"; + String commandLineTags = System.getProperty("tags"); + String effectiveTagExpression = (commandLineTags != null && !commandLineTags.isEmpty()) + ? commandLineTags + : defaultTagExpression; return getInvocationContext(PdslConfigParameter.createGherkinPdslConfig( List.of( new PdslTestParameter.Builder(parseTreeListenerSupplier, AllGrammarsLexer.class, AllGrammarsParser.class) - .withTagExpression("@comment_tag#2") + .withTagExpression(effectiveTagExpression) .withIncludedResources(new String[] {"tags.feature"}) .build() ) diff --git a/src/test/java/com/pdsl/uat/java/jupiter/OverrideRunnerTagViaCLITest.java b/src/test/java/com/pdsl/uat/java/jupiter/OverrideRunnerTagViaCLITest.java new file mode 100644 index 0000000..c586292 --- /dev/null +++ b/src/test/java/com/pdsl/uat/java/jupiter/OverrideRunnerTagViaCLITest.java @@ -0,0 +1,89 @@ +package com.pdsl.uat.java.jupiter; + +import com.pdsl.executors.InterpreterObj; +import com.pdsl.grammars.AllGrammarsLexer; +import com.pdsl.grammars.AllGrammarsParser; +import com.pdsl.grammars.AllGrammarsParserBaseVisitor; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; +import org.junit.jupiter.engine.descriptor.Interpreter; +import org.junit.jupiter.engine.descriptor.PdslConfigParameter; +import org.junit.jupiter.engine.descriptor.PdslExecutable; +import org.junit.jupiter.engine.descriptor.PdslSharedInvocationContextProvider; +import org.junit.jupiter.engine.descriptor.PdslTestParameter; + +/** + * This test class demonstrates how to override the tag expression set in the test + * with tags provided via the command line using the system property "tags". + */ +public class OverrideRunnerTagViaCLITest { + + private static int totalRunTests = 0; + + /** + * This test template uses the `PdslExtension` to configure and run the test. + * The purpose of this test is to verify that the total number of tests run is 1. + */ + @TestTemplate + @ExtendWith(PdslExtension.class) + public void pdslGherkinTestFrameworkResources(PdslExecutable executable) { + totalRunTests++; + } + + /** + * This method asserts that only one test was run. This verifies that the + * overridden tag expression from the command line took effect. + */ + @AfterAll + public static void executeAfterAll(){ + Assertions.assertEquals(1,totalRunTests); + } + + private static class PdslExtension extends PdslSharedInvocationContextProvider { + + /** + * This method provides the test template invocation contexts. + * It sets the system property "tags" to "@ex_tag1" before creating the + * PdslConfigParameter and then clears the property afterwards. + * + * @param context The ExtensionContext for the test. + * @return A stream of TestTemplateInvocationContext objects. + */ + public Stream provideTestTemplateInvocationContexts( + ExtensionContext context) { + System.setProperty("tags", "@ex_tag1"); + Stream contexts = getInvocationContext( + PdslConfigParameter.createGherkinPdslConfig( + List.of( + new PdslTestParameter.Builder( + List.of( + new Interpreter(AllGrammarsLexer.class, AllGrammarsParser.class, + new InterpreterObj(new AllGrammarsParserBaseVisitor<>(), + "polymorphicDslAllRules")) + )) + .withIncludedResources(new String[]{"tags.feature"}) + .withRecognizer(AllGrammarsLexer.class, AllGrammarsParser.class) + .withTagExpression("@feature_tag1") + .build() + + )) + .withRecognizerRule("polymorphicDslAllRules") + .withResourceRoot( + Paths.get("src/test/resources/testdata/good/") + .toUri()) + .build()) + .stream(); + // Clear command-line tags + System.clearProperty("tags"); + return contexts; + } + } + +} diff --git a/src/test/java/com/pdsl/uat/java/sut/TaggedScenarioRunner.java b/src/test/java/com/pdsl/uat/java/sut/TaggedScenarioRunner.java index 7e198d4..dff2819 100644 --- a/src/test/java/com/pdsl/uat/java/sut/TaggedScenarioRunner.java +++ b/src/test/java/com/pdsl/uat/java/sut/TaggedScenarioRunner.java @@ -2,6 +2,7 @@ import com.pdsl.runners.PdslGherkinApplication; import com.pdsl.runners.PdslTest; +import com.pdsl.runners.RecognizedBy; import com.pdsl.runners.junit.PdslGherkinJUnit4Runner; import org.antlr.v4.runtime.tree.ParseTreeListener; import org.junit.runner.RunWith; @@ -12,26 +13,29 @@ @RunWith(PdslGherkinJUnit4Runner.class) @PdslGherkinApplication( - applicationName = "Polymorphic DSL Test Framework", - context = "User Acceptance Testing", - resourceRoot = "src/test/resources/framework_specifications/features/java" + applicationName = "Polymorphic DSL Test Framework", + context = "User Acceptance Testing", + resourceRoot = "src/test/resources/framework_specifications/features/java" ) public class TaggedScenarioRunner { - @PdslTest( - parser = AllGrammarsParser.class, - lexer = AllGrammarsLexer.class, - tags = "@TaggedScenario", - listener = TaggedScenarioRunner.Listener.class - ) - public void tagFiltering_runsOnlySubsetOfScenarios(){} + @PdslTest( + parser = AllGrammarsParser.class, + lexer = AllGrammarsLexer.class, + tags = "@TaggedScenario", + listener = TaggedScenarioRunner.Listener.class + ) + @RecognizedBy(recognizerRule = "polymorphicDslAllRules", dslRecognizerParser = AllGrammarsParser.class, dslRecognizerLexer = AllGrammarsLexer.class) + public void tagFiltering_runsOnlySubsetOfScenarios() { + } - public static final class Listener implements Provider { - @Override - public ParseTreeListener get() { - return new AllGrammarsParserBaseListener(); - } + public static final class Listener implements Provider { + + @Override + public ParseTreeListener get() { + return new AllGrammarsParserBaseListener(); } + } }