Support Pass/Fail tasks
Pass/Fail tasks are defined by only defining a fail criteria. These
tasks are never ready, if they would otherwise be ready, they will PASS
instead. This new feature will allow a single pass/fail criteria to be
defined in lieu of defining both a pass and a complemented fail
criteria. This single fail query definition feature makes defining such
tasks less error-prone all while making their evaluation by the plugin
non-racy.
Change-Id: If14fe2df89d396a4bd057c10591b3ccc392398df
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java b/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java
index 6857203..21a5baa 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java
@@ -249,7 +249,7 @@
protected Status getStatusWithExceptions(ChangeData c, Task task, TaskAttribute a)
throws OrmException, QueryParseException {
- if (task.pass == null && a.subTasks == null) {
+ if (isAllNull(task.pass, task.fail, a.subTasks)) {
// A leaf task has no defined subtasks.
boolean hasDefinedSubtasks =
!(task.subTasks.isEmpty()
@@ -257,36 +257,43 @@
&& task.subTasksExternals.isEmpty());
if (hasDefinedSubtasks) {
// Remove 'Grouping" tasks (tasks with subtasks but no PASS
- // criteria) from the output if none of their subtasks are
- // applicable. i.e. grouping tasks only really apply if at
+ // or FAIL criteria) from the output if none of their subtasks
+ // are applicable. i.e. grouping tasks only really apply if at
// least one of their subtasks apply.
return null;
}
- // A leaf configuration without a PASS criteria is a missconfiguration.
- // Either someone forgot to add subtasks, or they forgot to add
- // the PASS criteria.
+ // A leaf configuration without a PASS or FAIL criteria is a
+ // missconfiguration. Either someone forgot to add subtasks, or
+ // they forgot to add a PASS or FAIL criteria.
return Status.INVALID;
}
- if (task.fail != null && match(c, task.fail)) {
- // A FAIL definition is meant to be a hard blocking criteria
- // (like a CodeReview -2). Thus, if hard blocked, it is
- // irrelevant what the subtask states, or the pass criteria are.
- //
- // It is also important that FAIL be useable to indicate that
- // the task has actually executed. Thus subtask status,
- // including a subtask FAIL should not appear as a FAIL on the
- // parent task. This means that this is should be the only path
- // to make a task have a FAIL status.
- return Status.FAIL;
+ if (task.fail != null) {
+ if (match(c, task.fail)) {
+ // A FAIL definition is meant to be a hard blocking criteria
+ // (like a CodeReview -2). Thus, if hard blocked, it is
+ // irrelevant what the subtask states, or the PASS criteria are.
+ //
+ // It is also important that FAIL be useable to indicate that
+ // the task has actually executed. Thus subtask status,
+ // including a subtask FAIL should not appear as a FAIL on the
+ // parent task. This means that this is should be the only path
+ // to make a task have a FAIL status.
+ return Status.FAIL;
+ }
+ if (task.pass == null) {
+ // A task with a FAIL but no PASS criteria is a PASS-FAIL task
+ // (they are never "READY"). It didn't fail, so pass.
+ return Status.PASS;
+ }
}
if (a.subTasks != null && !isAll(a.subTasks, Status.PASS)) {
// It is possible for a subtask's PASS criteria to change while
// a parent task is executing, or even after the parent task
// completes. This can result in the parent PASS criteria being
- // met while one or more of its subtasks no longer meets its pass
- // criteria (the subtask may now even meet a fail criteria). We
+ // met while one or more of its subtasks no longer meets its PASS
+ // criteria (the subtask may now even meet a FAIL criteria). We
// never want the parent task to reflect a PASS criteria in these
// cases, thus we can safely return here without ever evaluating
// the task's PASS criteria.
@@ -321,4 +328,13 @@
}
return ((Matchable) cqb.parse(query)).match(c);
}
+
+ protected static boolean isAllNull(Object... vals) {
+ for (Object val : vals) {
+ if (val != null) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/src/main/resources/Documentation/task.md b/src/main/resources/Documentation/task.md
index 3f5c606..46ada9b 100644
--- a/src/main/resources/Documentation/task.md
+++ b/src/main/resources/Documentation/task.md
@@ -97,7 +97,9 @@
: This key defines a query that is used to determine whether a task is
currently in-progress or not. A CI system may use this to ensure that it
-only runs one verification instance for a specific change.
+only runs one verification instance for a specific change. Either a pass
+or fail key is mandatory for leaf tasks. A task with a fail criteria,
+but no pass criteria, will pass if it otherwise would be ready.
Example:
```
@@ -107,10 +109,10 @@
`pass`
: This key defines a query that is used to determine whether a task has
-already executed and passed for each change. This key is mandatory for
-leaf tasks. Tasks with no defined pass criteria and with defined subtasks
-are valid, but they are only applicable when at least one subtask is
-applicable.
+already executed and passed for each change. Either a pass or fail
+key is mandatory for leaf tasks. Tasks with no defined pass criteria and
+with defined subtasks are valid, but they are only applicable when at least
+one subtask is applicable.
Example:
```
diff --git a/src/main/resources/Documentation/task_states.md b/src/main/resources/Documentation/task_states.md
index b405c19..4563390 100644
--- a/src/main/resources/Documentation/task_states.md
+++ b/src/main/resources/Documentation/task_states.md
@@ -19,6 +19,14 @@
fail = is:open
pass = is:open
+[root "Root PASS-fail"]
+ applicable = is:open
+ fail = NOT is:open
+
+[root "Root pass-FAIL"]
+ applicable = is:open
+ fail = is:open
+
[root "Root grouping PASS (subtask PASS)"]
applicable = is:open
subtask = Subtask PASS
@@ -201,6 +209,14 @@
"status" : "FAIL"
},
{
+ "name" : "Root PASS-fail",
+ "status" : "PASS"
+ },
+ {
+ "name" : "Root pass-FAIL",
+ "status" : "FAIL"
+ },
+ {
"name" : "Root grouping PASS (subtask PASS)",
"status" : "PASS",
"subTasks" : [