diff --git a/src/main/java/com/askimed/nf/test/commands/RunTestsCommand.java b/src/main/java/com/askimed/nf/test/commands/RunTestsCommand.java index f13a9dad..cc3fb636 100644 --- a/src/main/java/com/askimed/nf/test/commands/RunTestsCommand.java +++ b/src/main/java/com/askimed/nf/test/commands/RunTestsCommand.java @@ -132,6 +132,11 @@ public class RunTestsCommand extends AbstractCommand { "--tag" }, split = ",", description = "Execute only tests with this tag", required = false, showDefaultValue = Visibility.ALWAYS) private List tags = new Vector(); + @Option(names = { + "--query" }, description = "Execute only tests that match this expression", required = false) + private String query = null; + + private static Logger log = LoggerFactory.getLogger(RunTestsCommand.class); @Override @@ -254,7 +259,13 @@ public Integer execute() throws Exception { environment.setPluginManager(manager); TestSuiteResolver testSuiteResolver = new TestSuiteResolver(environment); - List testSuits = testSuiteResolver.parse(scripts, new TagQuery(tags)); + + TagQuery tagQuery = new TagQuery(tags); + if (query != null) { + tagQuery = new TagQueryExpression(query); + } + + List testSuits = testSuiteResolver.parse(scripts, tagQuery); testSuits.sort(TestSuiteSorter.getDefault()); if (shard != null && !testSuits.isEmpty()) { diff --git a/src/main/java/com/askimed/nf/test/core/TagQuery.java b/src/main/java/com/askimed/nf/test/core/TagQuery.java index b960b12f..802a0aae 100644 --- a/src/main/java/com/askimed/nf/test/core/TagQuery.java +++ b/src/main/java/com/askimed/nf/test/core/TagQuery.java @@ -34,12 +34,36 @@ public boolean matches(ITaggable taggable) { return true; } - if (tags.contains(taggable.getName().toLowerCase())) { + // Separate positive and negative tags + List positiveTags = new Vector(); + List negativeTags = new Vector(); + + for (String tag : tags) { + if (tag.startsWith("!")) { + negativeTags.add(tag.substring(1).toLowerCase()); // Remove '!' prefix for negative tags + } else { + positiveTags.add(tag.toLowerCase()); + } + } + + // Check for negative matches + if (negativeTags.contains(taggable.getName().toLowerCase())) { + return false; + } + + for (String tag : taggable.getTags()) { + if (negativeTags.contains(tag.toLowerCase())) { + return false; + } + } + + // Check for positive matches + if (positiveTags.contains(taggable.getName().toLowerCase())) { return true; } for (String tag : taggable.getTags()) { - if (tags.contains(tag.toLowerCase())) { + if (positiveTags.contains(tag.toLowerCase())) { return true; } } @@ -48,12 +72,11 @@ public boolean matches(ITaggable taggable) { return matches(taggable.getParent()); } - return false; + return !negativeTags.isEmpty(); } @Override public String toString() { return tags.toString(); } - } diff --git a/src/main/java/com/askimed/nf/test/core/TagQueryExpression.java b/src/main/java/com/askimed/nf/test/core/TagQueryExpression.java new file mode 100644 index 00000000..428cc9b4 --- /dev/null +++ b/src/main/java/com/askimed/nf/test/core/TagQueryExpression.java @@ -0,0 +1,85 @@ +package com.askimed.nf.test.core; + +import groovy.lang.Binding; +import groovy.lang.GroovyShell; +import groovy.util.Eval; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TagQueryExpression extends TagQuery { + + private String query; + + public TagQueryExpression(String query) { + this.query = query; + } + + public boolean matches(ITaggable taggable) { + if (query == null || query.trim().isEmpty()) { + return true; + } + + Binding binding = createBindingContext(taggable); + + try { + GroovyShell shell = new GroovyShell(binding); + return (Boolean) shell.evaluate(query); } catch (Exception e) { + throw new IllegalArgumentException("Invalid query: " + query, e); + } + } + + private Binding createBindingContext(ITaggable taggable) { + Map tagMap = new HashMap<>(); + Binding binding = new groovy.lang.Binding(); + + // Add tags from the current taggable + taggable.getTags().forEach(tag -> { + tagMap.put(tag.toLowerCase(), true); + }); + tagMap.put(taggable.getName(), true); + + // Add parent tags recursively + ITaggable parent = taggable.getParent(); + while (parent != null) { + parent.getTags().forEach(tag -> { + tagMap.put(tag.toLowerCase(), true); + }); + tagMap.put(parent.getName(), true); + parent = parent.getParent(); + } + + binding.setVariable("tags", new DefaultTagMap(tagMap)); // Map for key-based access + + if (taggable instanceof AbstractTest) { + binding.setVariable("test", taggable.getName()); + binding.setVariable("name", taggable.getParent().getName()); + } else { + binding.setVariable("test", ""); + binding.setVariable("name", ""); + } + + + return binding; + } + + @Override + public String toString() { + return query; + } + + + // Custom map to handle missing keys + private static class DefaultTagMap extends HashMap { + public DefaultTagMap(Map map) { + super(map); + } + + @Override + public Boolean get(Object key) { + return super.getOrDefault(key, false); + } + } +} diff --git a/src/test/java/com/askimed/nf/test/core/TestSuiteResolverTest.java b/src/test/java/com/askimed/nf/test/core/TestSuiteResolverTest.java index b6c36346..9c6525de 100644 --- a/src/test/java/com/askimed/nf/test/core/TestSuiteResolverTest.java +++ b/src/test/java/com/askimed/nf/test/core/TestSuiteResolverTest.java @@ -35,6 +35,30 @@ public void executeTestByName() throws Throwable { } + @Test + public void executeTestByTestNameQuery() throws Throwable { + + TagQuery query = new TagQueryExpression("test == 'test 1'"); + List tests = collectTests(query); + Assertions.assertEquals(1, tests.size()); + Assertions.assertTrue(tests.contains("test 1")); + Assertions.assertFalse(tests.contains("test 2")); + Assertions.assertFalse(tests.contains("test 3")); + + } + + @Test + public void executeTestByNameQuery() throws Throwable { + + TagQuery query = new TagQueryExpression("name == 'suite 1'"); + List tests = collectTests(query); + Assertions.assertEquals(2, tests.size()); + Assertions.assertTrue(tests.contains("test 1")); + Assertions.assertTrue(tests.contains("test 2")); + Assertions.assertFalse(tests.contains("test 3")); + + } + @Test public void executeTestByWrongHash() throws Throwable { List scripts = new Vector(); @@ -79,6 +103,13 @@ public void executeTestSuiteByName() throws Throwable { Assertions.assertTrue(tests.contains("test 1")); Assertions.assertTrue(tests.contains("test 2")); } + { + TagQuery query = new TagQueryExpression("tags['suite 1']"); + List tests = collectTests(query); + Assertions.assertEquals(2, tests.size()); + Assertions.assertTrue(tests.contains("test 1")); + Assertions.assertTrue(tests.contains("test 2")); + } } @Test @@ -95,36 +126,80 @@ public void executeTestsByTag() throws Throwable { Assertions.assertEquals(1, tests.size()); Assertions.assertTrue(tests.contains("test 1")); } + { + TagQuery query = new TagQueryExpression("tags['tag2']"); + List tests = collectTests(query); + Assertions.assertEquals(1, tests.size()); + Assertions.assertTrue(tests.contains("test 1")); + } } @Test public void executeTestsByTagAcrossSuites() throws Throwable { - - TagQuery query = new TagQuery("tag5"); - List tests = collectTests(query); - Assertions.assertEquals(2, tests.size()); - Assertions.assertTrue(tests.contains("test 2")); - Assertions.assertTrue(tests.contains("test 3")); + { + TagQuery query = new TagQuery("tag5"); + List tests = collectTests(query); + Assertions.assertEquals(2, tests.size()); + Assertions.assertTrue(tests.contains("test 2")); + Assertions.assertTrue(tests.contains("test 3")); + } + { + TagQuery query = new TagQueryExpression("tags['tag5']"); + List tests = collectTests(query); + Assertions.assertEquals(2, tests.size()); + Assertions.assertTrue(tests.contains("test 2")); + Assertions.assertTrue(tests.contains("test 3")); + } + { + TagQuery query = new TagQuery("!tag5"); + List tests = collectTests(query); + Assertions.assertEquals(1, tests.size()); + Assertions.assertTrue(!tests.contains("test 2")); + Assertions.assertTrue(!tests.contains("test 3")); + } + { + TagQuery query = new TagQueryExpression("!tags['tag5']"); + List tests = collectTests(query); + Assertions.assertEquals(1, tests.size()); + Assertions.assertTrue(!tests.contains("test 2")); + Assertions.assertTrue(!tests.contains("test 3")); + } } @Test public void executeTestsBySuiteTag() throws Throwable { - - TagQuery query = new TagQuery("tag1"); - List tests = collectTests(query); - Assertions.assertEquals(2, tests.size()); - Assertions.assertTrue(tests.contains("test 1")); - Assertions.assertTrue(tests.contains("test 2")); + { + TagQuery query = new TagQuery("tag1"); + List tests = collectTests(query); + Assertions.assertEquals(2, tests.size()); + Assertions.assertTrue(tests.contains("test 1")); + Assertions.assertTrue(tests.contains("test 2")); + } + { + TagQuery query = new TagQueryExpression("tags['tag1']"); + List tests = collectTests(query); + Assertions.assertEquals(2, tests.size()); + Assertions.assertTrue(tests.contains("test 1")); + Assertions.assertTrue(tests.contains("test 2")); + } } @Test public void executeTestsByMultipleTags() throws Throwable { - - TagQuery query = new TagQuery("tag3", "tag4"); - List tests = collectTests(query); - Assertions.assertEquals(2, tests.size()); - Assertions.assertTrue(tests.contains("test 1")); - Assertions.assertTrue(tests.contains("test 2")); + { + TagQuery query = new TagQuery("tag3", "tag4"); + List tests = collectTests(query); + Assertions.assertEquals(2, tests.size()); + Assertions.assertTrue(tests.contains("test 1")); + Assertions.assertTrue(tests.contains("test 2")); + } + { + TagQueryExpression query = new TagQueryExpression("tags['tag3'] || tags['tag4']"); + List tests = collectTests(query); + Assertions.assertEquals(2, tests.size()); + Assertions.assertTrue(tests.contains("test 1")); + Assertions.assertTrue(tests.contains("test 2")); + } } protected List collectTests(TagQuery query) throws Throwable {