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