Revert "Revert "plugin:task Adds support for names-factory of type change""

This reverts commit f99c7570a66adb9155320b33053e8538c1515f0f.

Re-add this functionality with tests passing.

Change-Id: I3b02981a9707b466d5b79e421c270a2fe5d322b5
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/TaskConfig.java b/src/main/java/com/googlesource/gerrit/plugins/task/TaskConfig.java
index ef23533..d9e8c4b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/task/TaskConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/task/TaskConfig.java
@@ -31,7 +31,19 @@
 
 /** Task Configuration file living in git */
 public class TaskConfig extends AbstractVersionedMetaData {
-  protected class Section extends Container {
+  public enum NamesFactoryType {
+    CHANGE,
+    STATIC;
+
+    public static NamesFactoryType getNamesFactoryType(String str) {
+      for (NamesFactoryType type : NamesFactoryType.values()) {
+        if (type.name().equalsIgnoreCase(str)) return type;
+      }
+      return null;
+    }
+  }
+
+  private class Section extends Container {
     public TaskConfig config;
 
     public Section() {
@@ -112,10 +124,12 @@
   }
 
   public class NamesFactory extends Section {
+    public String changes;
     public List<String> names;
     public String type;
 
     public NamesFactory(SubSection s) {
+      changes = getString(s, KEY_CHANGES, null);
       names = getStringList(s, KEY_NAME);
       type = getString(s, KEY_TYPE, null);
     }
@@ -142,6 +156,7 @@
   protected static final String SECTION_TASK = "task";
   protected static final String SECTION_TASKS_FACTORY = "tasks-factory";
   protected static final String KEY_APPLICABLE = "applicable";
+  protected static final String KEY_CHANGES = "changes";
   protected static final String KEY_EXPORT_PREFIX = "export-";
   protected static final String KEY_FAIL = "fail";
   protected static final String KEY_FAIL_HINT = "fail-hint";
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java b/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java
index 99a1ba9..13600e6 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java
@@ -14,6 +14,8 @@
 
 package com.googlesource.gerrit.plugins.task;
 
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.RefNames;
@@ -21,10 +23,15 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.AccountResolver;
 import com.google.gerrit.server.config.AllUsersNameProvider;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeQueryBuilder;
+import com.google.gerrit.server.query.change.ChangeQueryProcessor;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.googlesource.gerrit.plugins.task.TaskConfig.External;
 import com.googlesource.gerrit.plugins.task.TaskConfig.NamesFactory;
+import com.googlesource.gerrit.plugins.task.TaskConfig.NamesFactoryType;
 import com.googlesource.gerrit.plugins.task.TaskConfig.Task;
 import com.googlesource.gerrit.plugins.task.TaskConfig.TasksFactory;
 import com.googlesource.gerrit.plugins.task.cli.PatchSetArgument;
@@ -47,6 +54,7 @@
  * lazily loaded tree, and much of the tree validity is enforced at this layer.
  */
 public class TaskTree {
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
   protected static final String TASK_DIR = "task";
 
   protected final AccountResolver accountResolver;
@@ -54,6 +62,8 @@
   protected final CurrentUser user;
   protected final TaskConfigFactory taskFactory;
   protected final Root root = new Root();
+  protected final Provider<ChangeQueryBuilder> changeQueryBuilderProvider;
+  protected final Provider<ChangeQueryProcessor> changeQueryProcessorProvider;
 
   @Inject
   public TaskTree(
@@ -61,11 +71,15 @@
       AllUsersNameProvider allUsers,
       AnonymousUser anonymousUser,
       CurrentUser user,
-      TaskConfigFactory taskFactory) {
+      TaskConfigFactory taskFactory,
+      Provider<ChangeQueryBuilder> changeQueryBuilderProvider,
+      Provider<ChangeQueryProcessor> changeQueryProcessorProvider) {
     this.accountResolver = accountResolver;
     this.allUsers = allUsers;
     this.user = user != null ? user : anonymousUser;
     this.taskFactory = taskFactory;
+    this.changeQueryProcessorProvider = changeQueryProcessorProvider;
+    this.changeQueryBuilderProvider = changeQueryBuilderProvider;
   }
 
   public void masquerade(PatchSetArgument psa) {
@@ -188,11 +202,15 @@
         TasksFactory tasksFactory = definition.config.getTasksFactory(taskFactoryName);
         if (tasksFactory != null) {
           NamesFactory namesFactory = definition.config.getNamesFactory(tasksFactory.namesFactory);
-          if (namesFactory != null && "static".equals(namesFactory.type)) {
-            for (String name : namesFactory.names) {
-              taskList.add(definition.config.createTask(tasksFactory, name));
+          if (namesFactory != null && namesFactory.type != null) {
+            switch (NamesFactoryType.getNamesFactoryType(namesFactory.type)) {
+              case STATIC:
+                getStaticTypeTasksDefinitions(tasksFactory, namesFactory, taskList);
+                continue;
+              case CHANGE:
+                getChangesTypeTaskDefinitions(tasksFactory, namesFactory, taskList);
+                continue;
             }
-            continue;
           }
         }
         taskList.add(null);
@@ -200,6 +218,33 @@
       return taskList;
     }
 
+    protected void getStaticTypeTasksDefinitions(
+        TasksFactory tasksFactory, NamesFactory namesFactory, List<Task> taskList) {
+      for (String name : namesFactory.names) {
+        taskList.add(definition.config.createTask(tasksFactory, name));
+      }
+    }
+
+    protected void getChangesTypeTaskDefinitions(
+        TasksFactory tasksFactory, NamesFactory namesFactory, List<Task> taskList) {
+      try {
+        if (namesFactory.changes != null) {
+          List<ChangeData> changeDataList =
+              changeQueryProcessorProvider
+                  .get()
+                  .query(changeQueryBuilderProvider.get().parse(namesFactory.changes)).entities();
+          for (ChangeData changeData : changeDataList) {
+            taskList.add(definition.config.createTask(tasksFactory, changeData.getId().toString()));
+          }
+          return;
+        }
+      } catch (OrmException e) {
+        log.atSevere().withCause(e).log("ERROR: running changes query: " + namesFactory.changes);
+      } catch (QueryParseException e) {
+      }
+      taskList.add(null);
+    }
+
     protected List<Task> getTaskDefinitions(External external)
         throws ConfigInvalidException, IOException, OrmException {
       return getTaskDefinitions(resolveUserBranch(external.user), external.file);
diff --git a/src/main/resources/Documentation/task.md b/src/main/resources/Documentation/task.md
index 9fe4e3d..4a38c61 100644
--- a/src/main/resources/Documentation/task.md
+++ b/src/main/resources/Documentation/task.md
@@ -331,6 +331,16 @@
 
 The following keys may be defined in any names-factory section:
 
+`changes`
+
+: This key defines a query that is used to fetch change numbers which will be used
+as the names of the task(s).
+
+Example:
+```
+    changes = change:1 OR change:2
+```
+
 `name`
 
 : This key defines the name of the tasks.  This key may be used several times
@@ -345,12 +355,15 @@
 
 `type`
 
-: This key defines the type of the names-factory section.  The only
-accepted value is `static`.
+: This key defines the type of the names-factory section.  The type
+can be either `static` or `change`. For names-factory of type `static`,
+`name` key(s) should be defined where as names-factory of type `change`
+needs a `change` key to be defined.
 
 Example:
 ```
     type = static
+    type = change
 ```
 
 External Entries
diff --git a/src/main/resources/Documentation/task_states.md b/src/main/resources/Documentation/task_states.md
index 58d6ca7..0c29233 100644
--- a/src/main/resources/Documentation/task_states.md
+++ b/src/main/resources/Documentation/task_states.md
@@ -105,9 +105,11 @@
 
 [root "Root tasks-factory"]
   subtasks-factory = tasks-factory static
+  subtasks-factory = tasks-factory change
 
 [root "Root tasks-factory static (empty name)"]
   subtasks-factory = tasks-factory static (empty name)
+# Grouping task since it has no pass criteria, not output since it has no subtasks
 
 [root "Root tasks-factory static (empty name PASS)"]
   pass = True
@@ -147,6 +149,10 @@
   names-factory = names-factory static (empty name list)
   fail = True
 
+[tasks-factory "tasks-factory change"]
+  names-factory = names-factory change list
+  fail = True
+
 [task "Subtask APPLICABLE"]
   applicable = is:open
   pass = True
@@ -265,6 +271,10 @@
 [names-factory "names-factory static (empty name list)"]
   type = static
 
+[names-factory "names-factory change list"]
+  changes = change:_change1_number OR change:_change2_number
+  type = change
+
 ```
 
 `task/common.config` file in project `All-Projects` on ref `refs/meta/config`.
@@ -341,20 +351,75 @@
 [task "task (names-factory type INVALID)"]
   subtasks-factory = tasks-factory (names-factory type INVALID)
 
+[task "task (names-factory duplicate)"]
+  subtasks-factory = tasks-factory (names-factory duplicate)
+
+[task "task (names-factory changes type missing)"]
+  subtasks-factory = tasks-factory change (names-factory type missing)
+
+[task "task (names-factory changes missing)"]
+  subtasks-factory = tasks-factory change (names-factory changes missing)
+
+[task "task (names-factory changes invalid)"]
+  subtasks-factory = tasks-factory change (names-factory changes invalid)
+
+[task "task (tasks-factory changes loop)"]
+  subtasks-factory = tasks-factory change loop
+
 [tasks-factory "tasks-factory (names-factory type missing)"]
   names-factory = names-factory (type missing)
   fail = True
 
+[tasks-factory "tasks-factory (names-factory type INVALID)"]
+  names-factory = name-factory (type INVALID)
+
+[tasks-factory "tasks-factory (names-factory duplicate)"]
+  names-factory = names-factory duplicate
+  fail = True
+
+[tasks-factory "tasks-factory change (names-factory type missing)"]
+  names-factory = names-factory change list (type missing)
+  fail = True
+
+[tasks-factory "tasks-factory change (names-factory changes missing)"]
+  names-factory = names-factory change list (changes missing)
+  fail = True
+
+[tasks-factory "tasks-factory change (names-factory changes invalid)"]
+  names-factory = names-factory change list (changes invalid)
+  fail = True
+
+[tasks-factory "tasks-factory change loop"]
+  names-factory = names-factory change constant
+  subtask = task (tasks-factory changes loop)
+  fail = true
+
 [names-factory "names-factory (type missing)"]
   name = no type test
 
-[tasks-factory "tasks-factory (names-factory type INVALID)"]
-  names-factory = name-factory (type INVALID)
+[names-factory "names-factory change list (type missing)"]
+  changes = change:_change1_number OR change:_change2_number
 
 [names-factory "names-factory (type INVALID)"]
   name = invalid type test
   type = invalid
 
+[names-factory "names-factory duplicate"]
+  name = duplicate
+  name = duplicate
+  type = static
+
+[names-factory "names-factory change list (changes missing)"]
+  type = change
+
+[names-factory "names-factory change list (changes invalid)"]
+  change = change:invalidChange
+  type = change
+
+[names-factory "names-factory change constant"]
+  changes = change:_change1_number OR change:_change2_number
+  type = change
+
 ```
 
 `task/special.config` file in project `All-Users` on ref `refs/users/self`.
@@ -686,6 +751,16 @@
                      "hasPass" : true,
                      "name" : "my c task",
                      "status" : "FAIL"
+                  },
+                  {
+                     "hasPass" : true,
+                     "name" : "_change1_number",
+                     "status" : "FAIL"
+                  },
+                  {
+                     "hasPass" : true,
+                     "name" : "_change2_number",
+                     "status" : "FAIL"
                   }
                ]
             },
@@ -940,6 +1015,84 @@
                            "status" : "INVALID"
                         }
                      ]
+                  },
+                  {
+                     "hasPass" : false,
+                     "name" : "task (names-factory duplicate)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "hasPass" : true,
+                           "name" : "duplicate",
+                           "status" : "FAIL"
+                        },
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes type missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes invalid)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "hasPass" : false,
+                     "name" : "task (tasks-factory changes loop)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "hasPass" : true,
+                           "name" : "_change1_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        },
+                        {
+                           "hasPass" : true,
+                           "name" : "_change2_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        }
+                     ]
                   }
                ]
             }
diff --git a/test/all b/test/all
index 6e29127..6c4f10e 100644
--- a/test/all
+++ b/test/all
@@ -380,6 +380,18 @@
                      "hasPass" : true,
                      "name" : "my c task",
                      "status" : "FAIL"
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : true,
+                     "name" : "_change1_number",
+                     "status" : "FAIL"
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : true,
+                     "name" : "_change2_number",
+                     "status" : "FAIL"
                   }
                ]
             },
@@ -692,6 +704,92 @@
                            "status" : "INVALID"
                         }
                      ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory duplicate)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "duplicate",
+                           "status" : "FAIL"
+                        },
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes type missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes invalid)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (tasks-factory changes loop)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "_change1_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        },
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "_change2_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        }
+                     ]
                   }
                ]
             },
@@ -876,6 +974,92 @@
                            "status" : "INVALID"
                         }
                      ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory duplicate)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "duplicate",
+                           "status" : "FAIL"
+                        },
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes type missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes invalid)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (tasks-factory changes loop)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "_change1_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        },
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "_change2_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        }
+                     ]
                   }
                ]
             }
diff --git a/test/check_task_statuses.sh b/test/check_task_statuses.sh
index 3b2bf0b..abe209f 100755
--- a/test/check_task_statuses.sh
+++ b/test/check_task_statuses.sh
@@ -16,6 +16,7 @@
 }
 
 # --------
+gssh() { ssh -x -p "$PORT" "$SERVER" gerrit "$@" ; } # cmd [args]...
 
 q() { "$@" > /dev/null 2>&1 ; } # cmd [args...]  # quiet a command
 
@@ -79,10 +80,10 @@
 }
 
 query() { # query
-    ssh -x -p "$PORT" "$SERVER" gerrit query "$@" \
-            --format json | head -1 | python -c "import sys, json; \
-            print json.dumps(json.loads(sys.stdin.read()), indent=3, \
-            separators=(',', ' : '), sort_keys=True)"
+    gssh query "$@" \
+        --format json | head -1 | python -c "import sys, json; \
+        print json.dumps(json.loads(sys.stdin.read()), indent=3, \
+        separators=(',', ' : '), sort_keys=True)"
 }
 
 query_plugins() { query "$@" | awk '$0=="   \"plugins\" : [",$0=="   ],"' ; }
@@ -101,6 +102,10 @@
     test_tasks "$name" "$MYDIR/$name" "$@"
 }
 
+update_change_number() { # file change_number change_token
+      sed -i -e "s|_change$3_number|$2|" "$1"
+}
+
 MYDIR=$(dirname "$0")
 DOCS=$MYDIR/.././src/main/resources/Documentation
 OUT=$MYDIR/../target/tests
@@ -139,6 +144,20 @@
 
 mkdir -p "$ALL_TASKS" "$USER_TASKS"
 
+CHANGES=($(gssh query "status:open limit:2" | grep 'number:' | awk '{print $2}'))
+update_change_number "$DOC_STATES" "${CHANGES[0]}" "1"
+update_change_number "$DOC_STATES" "${CHANGES[1]}" "2"
+update_change_number "$MYDIR/all" "${CHANGES[0]}" "1"
+update_change_number "$MYDIR/all" "${CHANGES[1]}" "2"
+update_change_number "$MYDIR/preview" "${CHANGES[0]}" "1"
+update_change_number "$MYDIR/preview" "${CHANGES[1]}" "2"
+update_change_number "$MYDIR/preview.invalid" "${CHANGES[0]}" "1"
+update_change_number "$MYDIR/preview.invalid" "${CHANGES[1]}" "2"
+update_change_number "$MYDIR/invalid" "${CHANGES[0]}" "1"
+update_change_number "$MYDIR/invalid" "${CHANGES[1]}" "2"
+update_change_number "$MYDIR/invalid-applicable" "${CHANGES[0]}" "1"
+update_change_number "$MYDIR/invalid-applicable" "${CHANGES[1]}" "2"
+
 example 1 |sed -e"s/current-user/$USER/" > "$ROOT_CFG"
 example 2 > "$COMMON_CFG"
 example 3 > "$INVALIDS_CFG"
diff --git a/test/docker/run_tests/create-test-project-and-changes.sh b/test/docker/run_tests/create-test-project-and-changes.sh
index 159882b..6192461 100755
--- a/test/docker/run_tests/create-test-project-and-changes.sh
+++ b/test/docker/run_tests/create-test-project-and-changes.sh
@@ -16,17 +16,8 @@
     touch readme.txt && echo "$(date)" >> readme.txt
     git add . && git commit -m "$1"
     git push ssh://"$GERRIT_HOST":"$PORT"/"$2" HEAD:refs/for/master
-    commitRevision=$(git rev-parse HEAD)
-}
-
-submit_change() { # commit_revision
-    gssh review --code-review +2 --submit "$1"
 }
 
 create_project 'test'
 create_change 'Change 1' 'test'
-commit1Revision=$commitRevision
 create_change 'Change 2' 'test'
-#sleep to avoid race conditions
-sleep 60
-submit_change "$commit1Revision"
diff --git a/test/invalid b/test/invalid
index adab1d1..4e2984e 100644
--- a/test/invalid
+++ b/test/invalid
@@ -215,6 +215,86 @@
                            "status" : "INVALID"
                         }
                      ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory duplicate)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes type missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes invalid)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (tasks-factory changes loop)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "_change1_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        },
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "_change2_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        }
+                     ]
                   }
                ]
             },
@@ -387,6 +467,86 @@
                            "status" : "INVALID"
                         }
                      ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory duplicate)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes type missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes invalid)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (tasks-factory changes loop)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "_change1_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        },
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "_change2_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        }
+                     ]
                   }
                ]
             }
diff --git a/test/invalid-applicable b/test/invalid-applicable
index 449e595..867c177 100644
--- a/test/invalid-applicable
+++ b/test/invalid-applicable
@@ -159,6 +159,79 @@
                            "status" : "INVALID"
                         }
                      ]
+                  },
+                  {
+                     "hasPass" : false,
+                     "name" : "task (names-factory duplicate)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes type missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes invalid)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "hasPass" : false,
+                     "name" : "task (tasks-factory changes loop)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "hasPass" : true,
+                           "name" : "_change1_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        },
+                        {
+                           "hasPass" : true,
+                           "name" : "_change2_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        }
+                     ]
                   }
                ]
             }
diff --git a/test/preview b/test/preview
index e74d35d..235eaca 100644
--- a/test/preview
+++ b/test/preview
@@ -171,6 +171,92 @@
                            "status" : "INVALID"
                         }
                      ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory duplicate)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "duplicate",
+                           "status" : "FAIL"
+                        },
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes type missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes invalid)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (tasks-factory changes loop)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "_change1_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        },
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "_change2_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        }
+                     ]
                   }
                ]
             },
@@ -390,6 +476,92 @@
                            "status" : "INVALID"
                         }
                      ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory duplicate)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "duplicate",
+                           "status" : "FAIL"
+                        },
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes type missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes invalid)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (tasks-factory changes loop)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "_change1_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        },
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "_change2_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        }
+                     ]
                   }
                ]
             }
diff --git a/test/preview.invalid b/test/preview.invalid
index 056bb80..3f0c844 100644
--- a/test/preview.invalid
+++ b/test/preview.invalid
@@ -171,6 +171,86 @@
                            "status" : "INVALID"
                         }
                      ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory duplicate)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes type missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes invalid)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (tasks-factory changes loop)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "_change1_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        },
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "_change2_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        }
+                     ]
                   }
                ]
             },
@@ -343,6 +423,86 @@
                            "status" : "INVALID"
                         }
                      ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory duplicate)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes type missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes missing)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (names-factory changes invalid)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "name" : "UNKNOWN",
+                           "status" : "INVALID"
+                        }
+                     ]
+                  },
+                  {
+                     "applicable" : true,
+                     "hasPass" : false,
+                     "name" : "task (tasks-factory changes loop)",
+                     "status" : "WAITING",
+                     "subTasks" : [
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "_change1_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        },
+                        {
+                           "applicable" : true,
+                           "hasPass" : true,
+                           "name" : "_change2_number",
+                           "status" : "FAIL",
+                           "subTasks" : [
+                              {
+                                 "name" : "UNKNOWN",
+                                 "status" : "INVALID"
+                              }
+                           ]
+                        }
+                     ]
                   }
                ]
             }