Add RuleBase implementation Change-Id: I8a93cbaa34df7295c1c90d044f71f974e4a9951a
diff --git a/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/ItsHookModule.java b/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/ItsHookModule.java index de7709d..0fcf032 100644 --- a/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/ItsHookModule.java +++ b/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/ItsHookModule.java
@@ -21,12 +21,14 @@ import com.googlesource.gerrit.plugins.hooks.its.ItsName; import com.googlesource.gerrit.plugins.hooks.validation.ItsValidateComment; import com.googlesource.gerrit.plugins.hooks.workflow.ActionRequest; +import com.googlesource.gerrit.plugins.hooks.workflow.Condition; import com.googlesource.gerrit.plugins.hooks.workflow.GerritHookFilterAddComment; import com.googlesource.gerrit.plugins.hooks.workflow.GerritHookFilterAddRelatedLinkToChangeId; import com.googlesource.gerrit.plugins.hooks.workflow.GerritHookFilterAddRelatedLinkToGitWeb; import com.googlesource.gerrit.plugins.hooks.workflow.GerritHookFilterChangeState; import com.googlesource.gerrit.plugins.hooks.workflow.ActionController; import com.googlesource.gerrit.plugins.hooks.workflow.Property; +import com.googlesource.gerrit.plugins.hooks.workflow.Rule; public class ItsHookModule extends FactoryModule { @@ -53,5 +55,7 @@ ActionController.class); factory(ActionRequest.Factory.class); factory(Property.Factory.class); + factory(Condition.Factory.class); + factory(Rule.Factory.class); } }
diff --git a/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/Condition.java b/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/Condition.java new file mode 100644 index 0000000..1bb1564 --- /dev/null +++ b/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/Condition.java
@@ -0,0 +1,94 @@ +// Copyright (C) 2013 The Android Open Source Project +// +// Licensed 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 com.googlesource.gerrit.plugins.hooks.workflow; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; + +import javax.annotation.Nullable; + +import com.google.common.collect.Sets; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; + +/** + * A condition as used in {@link Rule}, as precondition to {@link Action}s. + * + * A condition consists of a key and an associated set of values. A condition + * is said to match a set of properties, is this set contains at least one + * property that matches the rules key and whose value matches at least one + * of the rule's value. + */ +public class Condition { + private final String key; + private final Set<String> values; + + public interface Factory { + Condition create(@Assisted("key") String key, + @Assisted("values") String values); + } + + /** + * Constructs a condition. + * @param key The key to use for values. + * @param values A comma separated list of values to associate to the key. + */ + @Inject + public Condition(@Assisted("key") String key, + @Nullable @Assisted("values") String values) { + this.key = key; + Set<String> modifyableValues; + if (values == null) { + modifyableValues = Collections.emptySet(); + } else { + modifyableValues = Sets.newHashSet(Arrays.asList(values.split(","))); + } + this.values = Collections.unmodifiableSet(modifyableValues); + } + + public String getKey() { + return key; + } + + public Set<String> getValues() { + return values; + } + + /** + * Checks whether or not the Condition matches the given set of properties + * + * @param properties The set of properties to match against. + * @return True iff properties contains at least one property that matches + * the rules key and whose value matches at least one of the rule's value. + */ + public boolean isMetBy(Iterable<Property> properties) { + for (Property property : properties) { + String propertyKey = property.getKey(); + if ((key == null && propertyKey == null) + || (key != null && key.equals(propertyKey))) { + if (values.contains(property.getValue())) { + return true; + } + } + } + return false; + } + + @Override + public String toString() { + return "[" + key + " = " + values + "]"; + } +}
diff --git a/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/Rule.java b/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/Rule.java new file mode 100644 index 0000000..512af73 --- /dev/null +++ b/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/Rule.java
@@ -0,0 +1,91 @@ +// Copyright (C) 2013 The Android Open Source Project +// +// Licensed 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 com.googlesource.gerrit.plugins.hooks.workflow; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; + +/** + * A single rule that associates {@code Action}s to {@code Condition}s. + */ +public class Rule { + private final String name; + private List<ActionRequest> actionRequests; + private Set<Condition> conditions; + + public interface Factory { + Rule create(String name); + } + + @Inject + public Rule(@Assisted String name) { + this.name = name; + this.actionRequests = Lists.newLinkedList(); + this.conditions = Sets.newHashSet(); + } + + public String getName() { + return name; + } + + /** + * Adds a condition to the rule. + * + * @param condition The condition to add. + */ + public void addCondition(Condition condition) { + conditions.add(condition); + } + + /** + * Adds an action to the rule. + * + * @param action The action to add. + */ + public void addActionRequest(ActionRequest actionRequest) { + actionRequests.add(actionRequest); + } + + /** + * Gets this rule's the action requests for a given set of properties. + * + * If the given set of properties meets all of the rule's conditions, the + * rule's actions are returned. Otherwise the empty collection is returned. + * + * @param properties The properties to check against the rule's conditions. + * @return The actions that should get fired. + */ + public Collection<ActionRequest> actionRequestsFor( + Iterable<Property> properties) { + for (Condition condition : conditions) { + if (!condition.isMetBy(properties)) { + return Collections.emptyList(); + } + } + return Collections.unmodifiableList(actionRequests); + } + + @Override + public String toString() { + return "[" + name + ", " + conditions + " -> " + actionRequests + "]"; + } +}
diff --git a/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBase.java b/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBase.java index e7ef399..101dfa6 100644 --- a/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBase.java +++ b/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBase.java
@@ -14,14 +14,116 @@ package com.googlesource.gerrit.plugins.hooks.workflow; +import java.io.File; import java.util.Collection; +import java.util.Collections; + +import com.google.common.collect.Lists; +import com.google.gerrit.server.config.SitePath; +import com.google.inject.Inject; + +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.util.FS; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Collection and matcher agains {@link Rule}s. */ public class RuleBase { - public RuleBase() { - // TODO construct rule base + private static final Logger log = LoggerFactory.getLogger(RuleBase.class); + + /** + * File (relative to site) to load rules from + */ + private static final String ITS_CONFIG_FILE = "etc" + File.separatorChar + + "its" + File.separator + "action.config"; + + /** + * The section for rules within {@link #ITS_CONFIG_FILE} + */ + private static final String RULE_SECTION = "rule"; + + /** + * The key for actions within {@link #ITS_CONFIG_FILE} + */ + private static final String ACTION_KEY = "action"; + + private final File sitePath; + private final Rule.Factory ruleFactory; + private final Condition.Factory conditionFactory; + private final ActionRequest.Factory actionRequestFactory; + + private Collection<Rule> rules; + + public interface Factory { + RuleBase create(); + } + + @Inject + public RuleBase(@SitePath File sitePath, Rule.Factory ruleFactory, + Condition.Factory conditionFactory, + ActionRequest.Factory actionRequestFactory) { + this.sitePath = sitePath; + this.ruleFactory = ruleFactory; + this.conditionFactory = conditionFactory; + this.actionRequestFactory = actionRequestFactory; + loadRules(); + } + + /** + * Loads the rules for the RuleBase. + * + * Consider using {@link #loadRules()@}, as that method only loads the rules, + * if they have not yet been loaded. + */ + private void forceLoadRules() throws Exception { + File configFile = new File(sitePath, ITS_CONFIG_FILE); + if (configFile.exists()) { + FileBasedConfig cfg = new FileBasedConfig(configFile, FS.DETECTED); + cfg.load(); + + rules = Lists.newArrayList(); + Collection<String> subsections = cfg.getSubsections(RULE_SECTION); + for (String subsection : subsections) { + Rule rule = ruleFactory.create(subsection); + Collection<String> keys = cfg.getNames(RULE_SECTION, subsection); + for (String key : keys) { + String values[] = cfg.getStringList(RULE_SECTION, subsection, key); + if (ACTION_KEY.equals(key)) { + for (String value : values) { + ActionRequest actionRequest = actionRequestFactory.create(value); + rule.addActionRequest(actionRequest); + } + } else { + for (String value : values) { + Condition condition = conditionFactory.create(key, value); + rule.addCondition(condition); + } + } + } + rules.add(rule); + } + } else { + // configFile does not exist. + log.warn("ITS actions configuration file (" + configFile + ") does not exist."); + rules = Collections.emptySet(); + } + } + + /** + * Loads the rules for the RuleBase, if they have not yet been loaded. + */ + private void loadRules() { + if (rules == null) { + try { + forceLoadRules(); + } catch (Exception e) { + log.error("Invalid ITS action configuration", e); + rules = Collections.emptySet(); + } + } } /** @@ -32,7 +134,10 @@ */ public Collection<ActionRequest> actionRequestsFor( Iterable<Property> properties) { - // TODO implement - throw new RuntimeException("unimplemented"); + Collection<ActionRequest> ret = Lists.newLinkedList(); + for (Rule rule : rules) { + ret.addAll(rule.actionRequestsFor(properties)); + } + return ret; } }
diff --git a/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ConditionTest.java b/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ConditionTest.java new file mode 100644 index 0000000..d672ab7 --- /dev/null +++ b/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ConditionTest.java
@@ -0,0 +1,229 @@ +// Copyright (C) 2013 The Android Open Source Project +// +// Licensed 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 com.googlesource.gerrit.plugins.hooks.workflow; + +import static org.easymock.EasyMock.expect; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.gerrit.server.config.FactoryModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase; + +public class ConditionTest extends LoggingMockingTestCase { + private Injector injector; + + public void testSimpleValue() { + Condition condition = createCondition("testKey", "testValue"); + assertEquals("key not matching 'testKey'", "testKey", condition.getKey()); + Set<String> expectedValues = Sets.newHashSet(); + expectedValues.add("testValue"); + assertEquals("values do not match", expectedValues, condition.getValues()); + } + + public void testGetKeyNull() { + Condition condition = new Condition(null, "testValues"); + assertNull("key is not null", condition.getKey()); + } + + public void testGetValuesNull() { + Condition condition = createCondition("testKey", null); + Set<String> values = condition.getValues(); + assertNotNull("values is null", values); + assertTrue("values is not empty", values.isEmpty()); + } + + public void testOredValue() { + Condition condition = createCondition("testKey", "value1,value2,value3"); + assertEquals("key not matching 'testKey'", "testKey", condition.getKey()); + Set<String> expectedValues = Sets.newLinkedHashSet(); + expectedValues.add("value1"); + expectedValues.add("value2"); + expectedValues.add("value3"); + assertEquals("values do not match", expectedValues, condition.getValues()); + } + + public void testIsMetBySimple() { + Condition condition = createCondition("testKey", "testValue"); + + Property property1 = createMock(Property.class); + expect(property1.getKey()).andReturn("testKey").anyTimes(); + expect(property1.getValue()).andReturn("testValue").anyTimes(); + + Collection<Property> properties = Lists.newArrayListWithCapacity(1); + properties.add(property1); + + replayMocks(); + + assertTrue("isMetBy gave false", condition.isMetBy(properties)); + } + + public void testIsMetBySimpleEmpty() { + Condition condition = createCondition("testKey", "testValue"); + + Collection<Property> properties = Collections.emptySet(); + + replayMocks(); + + assertFalse("isMetBy gave true", condition.isMetBy(properties)); + } + + public void testIsMetByMismatchedKey() { + Condition condition = createCondition("testKey", "testValue"); + + Property property1 = createMock(Property.class); + expect(property1.getKey()).andReturn("otherKey").anyTimes(); + expect(property1.getValue()).andReturn("testValue").anyTimes(); + + Collection<Property> properties = Lists.newArrayListWithCapacity(1); + properties.add(property1); + + replayMocks(); + + assertFalse("isMetBy gave true", condition.isMetBy(properties)); + } + + public void testIsMetByMismatchedValue() { + Condition condition = createCondition("testKey", "testValue"); + + Property property1 = createMock(Property.class); + expect(property1.getKey()).andReturn("testKey").anyTimes(); + expect(property1.getValue()).andReturn("otherValue").anyTimes(); + + Collection<Property> properties = Lists.newArrayListWithCapacity(1); + properties.add(property1); + + replayMocks(); + + assertFalse("isMetBy gave true", condition.isMetBy(properties)); + } + + public void testIsMetByOredSingle() { + Condition condition = createCondition("testKey", "value1,value2,value3"); + + Property property1 = createMock(Property.class); + expect(property1.getKey()).andReturn("testKey").anyTimes(); + expect(property1.getValue()).andReturn("value2").anyTimes(); + + Collection<Property> properties = Lists.newArrayListWithCapacity(1); + properties.add(property1); + + replayMocks(); + + assertTrue("isMetBy gave false", condition.isMetBy(properties)); + } + + public void testIsMetByOredMultiple() { + Condition condition = createCondition("testKey", "value1,value2,value3"); + + Property property1 = createMock(Property.class); + expect(property1.getKey()).andReturn("testKey").anyTimes(); + expect(property1.getValue()).andReturn("value1").anyTimes(); + + Property property2 = createMock(Property.class); + expect(property2.getKey()).andReturn("testKey").anyTimes(); + expect(property2.getValue()).andReturn("value3").anyTimes(); + + Collection<Property> properties = Lists.newArrayListWithCapacity(2); + properties.add(property1); + properties.add(property2); + + replayMocks(); + + assertTrue("isMetBy gave false", condition.isMetBy(properties)); + } + + public void testIsMetByOredAll() { + Condition condition = createCondition("testKey", "value1,value2,value3"); + + Property property1 = createMock(Property.class); + expect(property1.getKey()).andReturn("testKey").anyTimes(); + expect(property1.getValue()).andReturn("value1").anyTimes(); + + Property property2 = createMock(Property.class); + expect(property2.getKey()).andReturn("testKey").anyTimes(); + expect(property2.getValue()).andReturn("value2").anyTimes(); + + Property property3 = createMock(Property.class); + expect(property3.getKey()).andReturn("testKey").anyTimes(); + expect(property3.getValue()).andReturn("value3").anyTimes(); + + Collection<Property> properties = Lists.newArrayListWithCapacity(1); + properties.add(property1); + properties.add(property2); + properties.add(property3); + + replayMocks(); + + assertTrue("isMetBy gave false", condition.isMetBy(properties)); + } + + public void testIsMetByOredOvershoot() { + Condition condition = createCondition("testKey", "value1,value2,value3"); + + Property property1 = createMock(Property.class); + expect(property1.getKey()).andReturn("testKey").anyTimes(); + expect(property1.getValue()).andReturn("otherValue1").anyTimes(); + + Property property2 = createMock(Property.class); + expect(property2.getKey()).andReturn("testKey").anyTimes(); + expect(property2.getValue()).andReturn("value2").anyTimes(); + + Property property3 = createMock(Property.class); + expect(property3.getKey()).andReturn("testKey").anyTimes(); + expect(property3.getValue()).andReturn("otherValue3").anyTimes(); + + Collection<Property> properties = Lists.newArrayListWithCapacity(3); + properties.add(property1); + properties.add(property2); + properties.add(property3); + + replayMocks(); + + assertTrue("isMetBy gave false", condition.isMetBy(properties)); + } + + public void testUnmodifiableValue() { + Condition condition = createCondition("testKey", "testValue"); + Set<String> values = condition.getValues(); + try { + values.add("value2"); + fail("value is not unmodifyable"); + } catch (UnsupportedOperationException e) { + } + } + + private Condition createCondition(String key, String value) { + Condition.Factory factory = injector.getInstance(Condition.Factory.class); + return factory.create(key, value); + } + + public void setUp() throws Exception { + super.setUp(); + + injector = Guice.createInjector(new TestModule()); + } + + private class TestModule extends FactoryModule { + @Override + protected void configure() { + factory(Condition.Factory.class); + } + } +} \ No newline at end of file
diff --git a/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBaseTest.java b/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBaseTest.java new file mode 100644 index 0000000..2c6b466 --- /dev/null +++ b/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBaseTest.java
@@ -0,0 +1,279 @@ +// Copyright (C) 2013 The Android Open Source Project +// +// Licensed 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 com.googlesource.gerrit.plugins.hooks.workflow; + +import static org.easymock.EasyMock.expect; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import org.eclipse.jgit.util.FileUtils; + +import com.google.common.collect.Lists; +import com.google.gerrit.server.config.FactoryModule; +import com.google.gerrit.server.config.SitePath; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase; + +public class RuleBaseTest extends LoggingMockingTestCase { + private Injector injector; + + private File sitePath; + private Rule.Factory ruleFactory; + private Condition.Factory conditionFactory; + private ActionRequest.Factory actionRequestFactory; + + private boolean cleanupSitePath; + + public void testWarnNonExistingRuleBase() { + replayMocks(); + + createRuleBase(); + + assertLogMessageContains("does not exist"); + } + + public void testEmptyRuleBase() throws IOException { + injectRuleBase(""); + + replayMocks(); + + createRuleBase(); + } + + public void testSimpleRuleBase() throws IOException { + injectRuleBase("[rule \"rule1\"]\n" + + "\tconditionA = value1\n" + + "\taction = action1"); + + Rule rule1 = createMock(Rule.class); + expect(ruleFactory.create("rule1")).andReturn(rule1); + + Condition condition1 = createMock(Condition.class); + expect(conditionFactory.create("conditionA", "value1")).andReturn(condition1); + rule1.addCondition(condition1); + + ActionRequest actionRequest1 = createMock(ActionRequest.class); + expect(actionRequestFactory.create("action1")).andReturn(actionRequest1); + rule1.addActionRequest(actionRequest1); + + replayMocks(); + + createRuleBase(); + } + + public void testBasicRuleBase() throws IOException { + injectRuleBase("[rule \"rule1\"]\n" + + "\tconditionA = value1,value2\n" + + "\tconditionA = value3,value of 4\n" + + "\tconditionB = value5\n" + + "\taction = action1\n" + + "\taction = action2 param\n" + + "\n" + + "[ruleXZ \"nonrule\"]\n" + + "\tconditionA = value1\n" + + "\taction = action2\n" + + "[rule \"rule2\"]\n" + + "\tconditionC = value6\n" + + "\taction = action3"); + + Rule rule1 = createMock(Rule.class); + expect(ruleFactory.create("rule1")).andReturn(rule1); + + Condition condition1 = createMock(Condition.class); + expect(conditionFactory.create("conditionA", "value1,value2")). + andReturn(condition1); + rule1.addCondition(condition1); + + Condition condition2 = createMock(Condition.class); + expect(conditionFactory.create("conditionA", "value3,value of 4")). + andReturn(condition2); + rule1.addCondition(condition2); + + Condition condition3 = createMock(Condition.class); + expect(conditionFactory.create("conditionB", "value5")). + andReturn(condition3); + rule1.addCondition(condition3); + + ActionRequest actionRequest1 = createMock(ActionRequest.class); + expect(actionRequestFactory.create("action1")).andReturn(actionRequest1); + rule1.addActionRequest(actionRequest1); + + ActionRequest actionRequest2 = createMock(ActionRequest.class); + expect(actionRequestFactory.create("action2 param")).andReturn(actionRequest2); + rule1.addActionRequest(actionRequest2); + + Rule rule2 = createMock(Rule.class); + expect(ruleFactory.create("rule2")).andReturn(rule2); + + Condition condition4 = createMock(Condition.class); + expect(conditionFactory.create("conditionC", "value6")). + andReturn(condition4); + rule2.addCondition(condition4); + + ActionRequest actionRequest3 = createMock(ActionRequest.class); + expect(actionRequestFactory.create("action3")).andReturn(actionRequest3); + rule2.addActionRequest(actionRequest3); + + replayMocks(); + + createRuleBase(); + } + + public void testActionRequestsForSimple() throws IOException { + injectRuleBase("[rule \"rule1\"]\n" + + "\taction = action1"); + + Rule rule1 = createMock(Rule.class); + expect(ruleFactory.create("rule1")).andReturn(rule1); + + ActionRequest actionRequest1 = createMock(ActionRequest.class); + expect(actionRequestFactory.create("action1")).andReturn(actionRequest1); + rule1.addActionRequest(actionRequest1); + + Collection<Property> properties = Collections.emptySet(); + + List<ActionRequest> rule1Match = Lists.newArrayListWithCapacity(1); + rule1Match.add(actionRequest1); + expect(rule1.actionRequestsFor(properties)).andReturn(rule1Match); + + replayMocks(); + + RuleBase ruleBase = createRuleBase(); + Collection<ActionRequest> actual = ruleBase.actionRequestsFor(properties); + + List<ActionRequest> expected = Lists.newArrayListWithCapacity(3); + expected.add(actionRequest1); + + assertEquals("Matched actionRequests do not match", expected, actual); + } + + public void testActionRequestsForExtended() throws IOException { + injectRuleBase("[rule \"rule1\"]\n" + + "\taction = action1\n" + + "\taction = action2\n" + + "\n" + + "[rule \"rule2\"]\n" + + "\taction = action3"); + + Rule rule1 = createMock(Rule.class); + expect(ruleFactory.create("rule1")).andReturn(rule1); + + ActionRequest actionRequest1 = createMock(ActionRequest.class); + expect(actionRequestFactory.create("action1")).andReturn(actionRequest1); + rule1.addActionRequest(actionRequest1); + + ActionRequest actionRequest2 = createMock(ActionRequest.class); + expect(actionRequestFactory.create("action2")).andReturn(actionRequest2); + rule1.addActionRequest(actionRequest2); + + Rule rule2 = createMock(Rule.class); + expect(ruleFactory.create("rule2")).andReturn(rule2); + + ActionRequest actionRequest3 = createMock(ActionRequest.class); + expect(actionRequestFactory.create("action3")).andReturn(actionRequest3); + rule2.addActionRequest(actionRequest3); + + Collection<Property> properties = Lists.newArrayListWithCapacity(1); + Property property1 = createMock(Property.class); + properties.add(property1); + + List<ActionRequest> rule1Match = Lists.newArrayListWithCapacity(2); + rule1Match.add(actionRequest1); + rule1Match.add(actionRequest2); + expect(rule1.actionRequestsFor(properties)).andReturn(rule1Match); + + List<ActionRequest> rule2Match = Lists.newArrayListWithCapacity(1); + rule2Match.add(actionRequest3); + expect(rule2.actionRequestsFor(properties)).andReturn(rule2Match); + + replayMocks(); + + RuleBase ruleBase = createRuleBase(); + Collection<ActionRequest> actual = ruleBase.actionRequestsFor(properties); + + List<ActionRequest> expected = Lists.newArrayListWithCapacity(3); + expected.add(actionRequest1); + expected.add(actionRequest2); + expected.add(actionRequest3); + + assertEquals("Matched actionRequests do not match", expected, actual); + } + + private RuleBase createRuleBase() { + return injector.getInstance(RuleBase.class); + } + + private void injectRuleBase(String rules) throws IOException { + File ruleBaseFile = new File(sitePath, "etc" + File.separatorChar + "its" + + File.separator + "action.config"); + File ruleBaseParentFile = ruleBaseFile.getParentFile(); + assertTrue("Failed to create parent (" + ruleBaseParentFile + ") for " + + "rule base", ruleBaseParentFile.mkdirs()); + FileWriter unbufferedWriter = new FileWriter(ruleBaseFile); + BufferedWriter writer = new BufferedWriter(unbufferedWriter); + writer.write(rules); + writer.close(); + unbufferedWriter.close(); + } + + public void setUp() throws Exception { + super.setUp(); + cleanupSitePath = false; + injector = Guice.createInjector(new TestModule()); + } + + public void tearDown() throws Exception { + if (cleanupSitePath) { + if (sitePath.exists()) { + FileUtils.delete(sitePath, FileUtils.RECURSIVE); + } + } + super.tearDown(); + } + + private File randomTargetFile() { + final File t = new File("target"); + return new File(t, "random-name-" + UUID.randomUUID().toString()); + } + + private class TestModule extends FactoryModule { + @Override + protected void configure() { + + sitePath = randomTargetFile(); + assertFalse("sitePath already (" + sitePath + ") already exists", + sitePath.exists()); + cleanupSitePath = true; + + bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath); + + ruleFactory = createMock(Rule.Factory.class); + bind(Rule.Factory.class).toInstance(ruleFactory); + + conditionFactory = createMock(Condition.Factory.class); + bind(Condition.Factory.class).toInstance(conditionFactory); + + actionRequestFactory = createMock(ActionRequest.Factory.class); + bind(ActionRequest.Factory.class).toInstance(actionRequestFactory); + } + } +} \ No newline at end of file
diff --git a/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleTest.java b/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleTest.java new file mode 100644 index 0000000..d09a790 --- /dev/null +++ b/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleTest.java
@@ -0,0 +1,134 @@ +// Copyright (C) 2013 The Android Open Source Project +// +// Licensed 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 com.googlesource.gerrit.plugins.hooks.workflow; + +import static org.easymock.EasyMock.expect; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import com.google.common.collect.Lists; +import com.google.gerrit.server.config.FactoryModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase; + +public class RuleTest extends LoggingMockingTestCase { + private Injector injector; + + public void testGetName() { + Rule rule = createRule("testRule"); + assertEquals("Rule name does not match", "testRule", rule.getName()); + } + + public void testActionsForUnconditionalRule() { + Collection<Property> properties = Collections.emptySet(); + + Rule rule = createRule("testRule"); + + ActionRequest actionRequest1 = createMock(ActionRequest.class); + rule.addActionRequest(actionRequest1); + + replayMocks(); + + Collection<ActionRequest> actual = rule.actionRequestsFor(properties); + + Collection<ActionRequest> expected = Lists.newArrayListWithCapacity(1); + expected.add(actionRequest1); + assertEquals("Matched actionRequests do not match", expected, actual); + } + + public void testActionRequestsForConditionalRuleEmptyProperties() { + Collection<Property> properties = Collections.emptySet(); + + Rule rule = createRule("testRule"); + + Condition condition1 = createMock(Condition.class); + expect(condition1.isMetBy(properties)).andReturn(false).anyTimes(); + rule.addCondition(condition1); + + ActionRequest actionRequest1 = createMock(ActionRequest.class); + rule.addActionRequest(actionRequest1); + + replayMocks(); + + Collection<ActionRequest> actual = rule.actionRequestsFor(properties); + + List<ActionRequest> expected = Collections.emptyList(); + assertEquals("Matched actionRequests do not match", expected, actual); + } + + public void testActionRequestsForConditionalRules() { + Collection<Property> properties = Collections.emptySet(); + + Rule rule = createRule("testRule"); + + Condition condition1 = createMock(Condition.class); + expect(condition1.isMetBy(properties)).andReturn(true).anyTimes(); + rule.addCondition(condition1); + + Condition condition2 = createMock(Condition.class); + expect(condition2.isMetBy(properties)).andReturn(false).anyTimes(); + rule.addCondition(condition2); + + ActionRequest actionRequest1 = createMock(ActionRequest.class); + rule.addActionRequest(actionRequest1); + + replayMocks(); + + Collection<ActionRequest> actual = rule.actionRequestsFor(properties); + + List<ActionRequest> expected = Collections.emptyList(); + assertEquals("Matched actionRequests do not match", expected, actual); + } + + public void testActionRequestsForMultipleActionRequests() { + Collection<Property> properties = Collections.emptySet(); + + Rule rule = createRule("testRule"); + + ActionRequest actionRequest1 = createMock(ActionRequest.class); + rule.addActionRequest(actionRequest1); + + ActionRequest actionRequest2 = createMock(ActionRequest.class); + rule.addActionRequest(actionRequest2); + + replayMocks(); + + Collection<ActionRequest> actual = rule.actionRequestsFor(properties); + + List<ActionRequest> expected = Lists.newArrayListWithCapacity(1); + expected.add(actionRequest1); + expected.add(actionRequest2); + assertEquals("Matched actionRequests do not match", expected, actual); + } + + private Rule createRule(String name) { + Rule.Factory factory = injector.getInstance(Rule.Factory.class); + return factory.create(name); + } + + public void setUp() throws Exception { + super.setUp(); + injector = Guice.createInjector(new TestModule()); + } + + private class TestModule extends FactoryModule { + @Override + protected void configure() { + factory(Rule.Factory.class); + } + } +} \ No newline at end of file