You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2015/02/12 17:14:29 UTC

svn commit: r1659301 [3/3] - in /subversion/branches/pin-externals: ./ subversion/include/private/ subversion/libsvn_delta/ subversion/libsvn_fs_base/ subversion/libsvn_fs_fs/ subversion/libsvn_repos/ subversion/libsvn_subr/ subversion/libsvn_wc/ subve...

Modified: subversion/branches/pin-externals/subversion/tests/libsvn_ra/ra-test.c
URL: http://svn.apache.org/viewvc/subversion/branches/pin-externals/subversion/tests/libsvn_ra/ra-test.c?rev=1659301&r1=1659300&r2=1659301&view=diff
==============================================================================
--- subversion/branches/pin-externals/subversion/tests/libsvn_ra/ra-test.c (original)
+++ subversion/branches/pin-externals/subversion/tests/libsvn_ra/ra-test.c Thu Feb 12 16:14:28 2015
@@ -41,8 +41,6 @@
 #include "../svn_test_fs.h"
 #include "../../libsvn_ra_local/ra_local.h"
 
-static const char tunnel_repos_name[] = "test-repo-tunnel";
-
 /*-------------------------------------------------------------------*/
 
 /** Helper routines. **/
@@ -136,18 +134,36 @@ commit_tree(svn_ra_session_t *session,
   return SVN_NO_ERROR;
 }
 
-static svn_boolean_t last_tunnel_check;
-static int tunnel_open_count;
-static void *check_tunnel_baton;
-static void *open_tunnel_context;
+/* Baton for opening tunnels */
+typedef struct tunnel_baton_t
+{
+  int magic; /* TUNNEL_MAGIC */
+  int open_count;
+  svn_boolean_t last_check;
+} tunnel_baton_t;
+
+#define TUNNEL_MAGIC 0xF00DF00F
+
+/* Baton for closing a specific tunnel */
+typedef struct close_baton_t
+{
+  int magic;
+  tunnel_baton_t *tb;
+  apr_proc_t *proc;
+} close_baton_t;
+
+#define CLOSE_MAGIC 0x1BADBAD1
 
 static svn_boolean_t
 check_tunnel(void *tunnel_baton, const char *tunnel_name)
 {
-  if (tunnel_baton != check_tunnel_baton)
+  tunnel_baton_t *b = tunnel_baton;
+
+  if (b->magic != TUNNEL_MAGIC)
     abort();
-  last_tunnel_check = (0 == strcmp(tunnel_name, "test"));
-  return last_tunnel_check;
+
+  b->last_check = (0 == strcmp(tunnel_name, "test"));
+  return b->last_check;
 }
 
 static void
@@ -168,8 +184,10 @@ open_tunnel(svn_stream_t **request, svn_
   apr_status_t status;
   const char *args[] = { "svnserve", "-t", "-r", ".", NULL };
   const char *svnserve;
+  tunnel_baton_t *b = tunnel_baton;
+  close_baton_t *cb;
 
-  SVN_TEST_ASSERT(tunnel_baton == check_tunnel_baton);
+  SVN_TEST_ASSERT(b->magic == TUNNEL_MAGIC);
 
   SVN_ERR(svn_dirent_get_absolute(&svnserve, "../../svnserve/svnserve", pool));
 #ifdef WIN32
@@ -193,11 +211,7 @@ open_tunnel(svn_stream_t **request, svn_
                              args, NULL, attr, pool);
   if (status != APR_SUCCESS)
     return svn_error_wrap_apr(status, "Could not run svnserve");
-#ifdef WIN32
   apr_pool_note_subprocess(pool, proc, APR_KILL_NEVER);
-#else
-  apr_pool_note_subprocess(pool, proc, APR_KILL_ONLY_ONCE);
-#endif
 
   /* APR pipe objects inherit by default.  But we don't want the
    * tunnel agent's pipes held open by future child processes
@@ -205,20 +219,42 @@ open_tunnel(svn_stream_t **request, svn_
   apr_file_inherit_unset(proc->in);
   apr_file_inherit_unset(proc->out);
 
+  cb = apr_pcalloc(pool, sizeof(*cb));
+  cb->magic = CLOSE_MAGIC;
+  cb->tb = b;
+  cb->proc = proc;
+
   *request = svn_stream_from_aprfile2(proc->in, FALSE, pool);
   *response = svn_stream_from_aprfile2(proc->out, FALSE, pool);
   *close_func = close_tunnel;
-  open_tunnel_context = *close_baton = &last_tunnel_check;
-  ++tunnel_open_count;
+  *close_baton = cb;
+  ++b->open_count;
   return SVN_NO_ERROR;
 }
 
 static void
 close_tunnel(void *tunnel_context, void *tunnel_baton)
 {
-  assert(tunnel_context == open_tunnel_context);
-  assert(tunnel_baton == check_tunnel_baton);
-  --tunnel_open_count;
+  close_baton_t *b = tunnel_context;
+
+  if (b->magic != CLOSE_MAGIC)
+    abort();
+  if (--b->tb->open_count == 0)
+    {
+      apr_status_t child_exit_status;
+      int child_exit_code;
+      apr_exit_why_e child_exit_why;
+
+      SVN_TEST_ASSERT_NO_RETURN(0 == apr_file_close(b->proc->in));
+      SVN_TEST_ASSERT_NO_RETURN(0 == apr_file_close(b->proc->out));
+
+      child_exit_status =
+        apr_proc_wait(b->proc, &child_exit_code, &child_exit_why, APR_WAIT);
+
+      SVN_TEST_ASSERT_NO_RETURN(child_exit_status == APR_CHILD_DONE);
+      SVN_TEST_ASSERT_NO_RETURN(child_exit_code == 0);
+      SVN_TEST_ASSERT_NO_RETURN(child_exit_why == APR_PROC_EXIT);
+    }
 }
 
 
@@ -293,6 +329,7 @@ static svn_error_t *
 check_tunnel_callback_test(const svn_test_opts_t *opts,
                            apr_pool_t *pool)
 {
+  tunnel_baton_t b = { TUNNEL_MAGIC };
   svn_ra_callbacks2_t *cbtable;
   svn_ra_session_t *session;
   svn_error_t *err;
@@ -300,7 +337,7 @@ check_tunnel_callback_test(const svn_tes
   SVN_ERR(svn_ra_create_callbacks(&cbtable, pool));
   cbtable->check_tunnel_func = check_tunnel;
   cbtable->open_tunnel_func = open_tunnel;
-  cbtable->tunnel_baton = check_tunnel_baton = &cbtable;
+  cbtable->tunnel_baton = &b;
   SVN_ERR(svn_cmdline_create_auth_baton(&cbtable->auth_baton,
                                         TRUE  /* non_interactive */,
                                         "jrandom", "rayjandom",
@@ -309,13 +346,12 @@ check_tunnel_callback_test(const svn_tes
                                         FALSE /* trust_server_cert */,
                                         NULL, NULL, NULL, pool));
 
-  last_tunnel_check = TRUE;
-  open_tunnel_context = NULL;
+  b.last_check = TRUE;
   err = svn_ra_open4(&session, NULL, "svn+foo://localhost/no-repo",
                      NULL, cbtable, NULL, NULL, pool);
   svn_error_clear(err);
   SVN_TEST_ASSERT(err);
-  SVN_TEST_ASSERT(!last_tunnel_check);
+  SVN_TEST_ASSERT(!b.last_check);
   return SVN_NO_ERROR;
 }
 
@@ -323,21 +359,26 @@ static svn_error_t *
 tunnel_callback_test(const svn_test_opts_t *opts,
                      apr_pool_t *pool)
 {
-  apr_pool_t *connection_pool;
-  svn_repos_t *repos;
+  tunnel_baton_t b = { TUNNEL_MAGIC };
+  apr_pool_t *scratch_pool = svn_pool_create(pool);
   const char *url;
   svn_ra_callbacks2_t *cbtable;
   svn_ra_session_t *session;
   svn_error_t *err;
+  const char tunnel_repos_name[] = "test-repo-tunnel";
+
+  SVN_ERR(svn_test__create_repos(NULL, tunnel_repos_name, opts, scratch_pool));
 
-  SVN_ERR(svn_test__create_repos(&repos, tunnel_repos_name, opts, pool));
+  /* Immediately close the repository to avoid race condition with svnserve
+     (and then the cleanup code) with BDB when our pool is cleared. */
+  svn_pool_clear(scratch_pool);
 
   url = apr_pstrcat(pool, "svn+test://localhost/", tunnel_repos_name,
                     SVN_VA_NULL);
   SVN_ERR(svn_ra_create_callbacks(&cbtable, pool));
   cbtable->check_tunnel_func = check_tunnel;
   cbtable->open_tunnel_func = open_tunnel;
-  cbtable->tunnel_baton = check_tunnel_baton = &cbtable;
+  cbtable->tunnel_baton = &b;
   SVN_ERR(svn_cmdline_create_auth_baton(&cbtable->auth_baton,
                                         TRUE  /* non_interactive */,
                                         "jrandom", "rayjandom",
@@ -346,12 +387,9 @@ tunnel_callback_test(const svn_test_opts
                                         FALSE /* trust_server_cert */,
                                         NULL, NULL, NULL, pool));
 
-  last_tunnel_check = FALSE;
-  open_tunnel_context = NULL;
-  tunnel_open_count = 0;
-  connection_pool = svn_pool_create(pool);
+  b.last_check = FALSE;
   err = svn_ra_open4(&session, NULL, url, NULL, cbtable, NULL, NULL,
-                     connection_pool);
+                     scratch_pool);
   if (err && err->apr_err == SVN_ERR_TEST_FAILED)
     {
       svn_handle_error2(err, stderr, FALSE, "svn_tests: ");
@@ -359,10 +397,10 @@ tunnel_callback_test(const svn_test_opts
       return SVN_NO_ERROR;
     }
   SVN_ERR(err);
-  SVN_TEST_ASSERT(last_tunnel_check);
-  SVN_TEST_ASSERT(tunnel_open_count > 0);
-  svn_pool_destroy(connection_pool);
-  SVN_TEST_ASSERT(tunnel_open_count == 0);
+  SVN_TEST_ASSERT(b.last_check);
+  SVN_TEST_ASSERT(b.open_count > 0);
+  svn_pool_destroy(scratch_pool);
+  SVN_TEST_ASSERT(b.open_count == 0);
   return SVN_NO_ERROR;
 }
 

Modified: subversion/branches/pin-externals/subversion/tests/libsvn_wc/op-depth-test.c
URL: http://svn.apache.org/viewvc/subversion/branches/pin-externals/subversion/tests/libsvn_wc/op-depth-test.c?rev=1659301&r1=1659300&r2=1659301&view=diff
==============================================================================
--- subversion/branches/pin-externals/subversion/tests/libsvn_wc/op-depth-test.c (original)
+++ subversion/branches/pin-externals/subversion/tests/libsvn_wc/op-depth-test.c Thu Feb 12 16:14:28 2015
@@ -105,12 +105,22 @@ typedef struct nodes_row_t {
     const char *props;  /* comma-separated list of prop names */
 } nodes_row_t;
 
+/* Tree conflict details */
+typedef struct tree_conflict_info
+{
+  svn_wc_conflict_action_t action;
+  svn_wc_conflict_reason_t reason;
+  const char *delete_path;
+  svn_boolean_t conflicted_fb; /* fallback for reason, action and path 0 */
+} tree_conflict_info;
+
 /* What conflicts are on a path. */
 typedef struct conflict_info_t {
     const char *local_relpath;
     svn_boolean_t text_conflicted;
     svn_boolean_t prop_conflicted;
-    svn_boolean_t tree_conflicted;
+
+    tree_conflict_info tc;
 } conflict_info_t;
 
 /* Macro for filling in the REPO_* fields of a non-base NODES_ROW_T
@@ -263,7 +273,6 @@ check_db_rows(svn_test__sandbox_t *b,
               const char *root_path,
               const nodes_row_t *expected_rows)
 {
-  const char *base_relpath = root_path;
   svn_sqlite__db_t *sdb;
   int i;
   svn_sqlite__stmt_t *stmt;
@@ -281,10 +290,8 @@ check_db_rows(svn_test__sandbox_t *b,
   /* Fill ACTUAL_HASH with data from the WC DB. */
   SVN_ERR(open_wc_db(&sdb, b->wc_abspath, b->pool, b->pool));
   SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_NODES_INFO));
-  SVN_ERR(svn_sqlite__bindf(stmt, "ss", base_relpath,
-                            (base_relpath[0]
-                             ? apr_psprintf(b->pool, "%s/%%", base_relpath)
-                             : "_%")));
+  SVN_ERR(svn_sqlite__bindf(stmt, "is", (apr_int64_t)1 /* wc_id */,
+                                        root_path));
   SVN_ERR(svn_sqlite__step(&have_row, stmt));
   while (have_row)
     {
@@ -332,16 +339,84 @@ check_db_rows(svn_test__sandbox_t *b,
   return comparison_baton.errors;
 }
 
-
+#define EDIT_EDIT_TC {svn_wc_conflict_reason_edited, \
+                      svn_wc_conflict_action_edit, \
+                      NULL, TRUE}
+#define NO_TC { 0 }
 static const char *
 print_conflict(const conflict_info_t *row,
                apr_pool_t *result_pool)
 {
+  const char *tc_text;
+
+  if (!row->tc.reason && !row->tc.action && !row->tc.delete_path)
+    {
+      if (row->tc.conflicted_fb)
+        tc_text = "EDIT_EDIT_TC";
+      else
+        tc_text = "NO_TC";
+    }
+  else
+    {
+      const char *action;
+      const char *reason;
+      const char *path;
+
+#define CASE_ENUM_STRVAL(x, y) case y: x = #y; break
+      switch(row->tc.action)
+        {
+          CASE_ENUM_STRVAL(action, svn_wc_conflict_action_edit);
+          CASE_ENUM_STRVAL(action, svn_wc_conflict_action_add);
+          CASE_ENUM_STRVAL(action, svn_wc_conflict_action_delete);
+          CASE_ENUM_STRVAL(action, svn_wc_conflict_action_replace);
+          default:
+            SVN_ERR_MALFUNCTION_NO_RETURN();
+        }
+      switch(row->tc.reason)
+        {
+          CASE_ENUM_STRVAL(reason, svn_wc_conflict_reason_edited);
+          CASE_ENUM_STRVAL(reason, svn_wc_conflict_reason_obstructed);
+          CASE_ENUM_STRVAL(reason, svn_wc_conflict_reason_deleted);
+          CASE_ENUM_STRVAL(reason, svn_wc_conflict_reason_missing);
+          CASE_ENUM_STRVAL(reason, svn_wc_conflict_reason_unversioned);
+          CASE_ENUM_STRVAL(reason, svn_wc_conflict_reason_added);
+          CASE_ENUM_STRVAL(reason, svn_wc_conflict_reason_replaced);
+          CASE_ENUM_STRVAL(reason, svn_wc_conflict_reason_moved_away);
+          CASE_ENUM_STRVAL(reason, svn_wc_conflict_reason_moved_here);
+          default:
+            SVN_ERR_MALFUNCTION_NO_RETURN();
+        }
+
+      if (row->tc.delete_path)
+        path = apr_psprintf(result_pool, ", \"%s\"", row->tc.delete_path);
+      else
+        path = "";
+
+      tc_text = apr_psprintf(result_pool, "{%s, %s%s}", action,
+                             reason, path);
+    }
+
   return apr_psprintf(result_pool, "\"%s\", %s, %s, %s",
                       row->local_relpath,
                       row->text_conflicted ? "TRUE" : "FALSE",
                       row->prop_conflicted ? "TRUE" : "FALSE",
-                      row->tree_conflicted ? "TRUE" : "FALSE");
+                      tc_text);
+}
+
+static svn_boolean_t
+tree_conflicts_match(const tree_conflict_info *expected,
+                     const tree_conflict_info *actual)
+{
+    if (expected->action != actual->action)
+      return FALSE;
+    else if (expected->reason != actual->reason)
+      return FALSE;
+    else if (strcmp_null(expected->delete_path, actual->delete_path) != 0)
+      return FALSE;
+    else if (expected->conflicted_fb != actual->conflicted_fb)
+      return FALSE;
+
+    return TRUE;
 }
 
 static svn_error_t *
@@ -369,7 +444,7 @@ compare_conflict_info(const void *key, a
     }
   else if (expected->text_conflicted != found->text_conflicted
            || expected->prop_conflicted != found->prop_conflicted
-           || expected->tree_conflicted != found->tree_conflicted)
+           || !tree_conflicts_match(&expected->tc, &found->tc))
     {
       b->errors = svn_error_createf(
                     SVN_ERR_TEST_FAILED, b->errors,
@@ -387,7 +462,6 @@ check_db_conflicts(svn_test__sandbox_t *
                    const char *root_path,
                    const conflict_info_t *expected_conflicts)
 {
-  const char *base_relpath = root_path;
   svn_sqlite__db_t *sdb;
   int i;
   svn_sqlite__stmt_t *stmt;
@@ -407,14 +481,12 @@ check_db_conflicts(svn_test__sandbox_t *
   /* Fill ACTUAL_HASH with data from the WC DB. */
   SVN_ERR(open_wc_db(&sdb, b->wc_abspath, b->pool, b->pool));
   SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_ACTUAL_INFO));
-  SVN_ERR(svn_sqlite__bindf(stmt, "ss", base_relpath,
-                            (base_relpath[0]
-                             ? apr_psprintf(b->pool, "%s/%%", base_relpath)
-                             : "_%")));
+  SVN_ERR(svn_sqlite__bindf(stmt, "is", (apr_int64_t)1 /* wc_id */,
+                                        root_path));
   SVN_ERR(svn_sqlite__step(&have_row, stmt));
   while (have_row)
     {
-      conflict_info_t *row = apr_palloc(b->pool, sizeof(*row));
+      conflict_info_t *row = apr_pcalloc(b->pool, sizeof(*row));
 
       row->local_relpath = svn_sqlite__column_text(stmt, 0, b->pool);
 
@@ -430,6 +502,7 @@ check_db_conflicts(svn_test__sandbox_t *
       svn_skel_t *conflict;
       conflict_info_t *info = apr_hash_this_val(hi);
       const char *local_abspath;
+      svn_boolean_t tree_conflicted;
 
       svn_pool_clear(iterpool);
 
@@ -444,10 +517,33 @@ check_db_conflicts(svn_test__sandbox_t *
       SVN_ERR(svn_wc__conflict_read_info(NULL, NULL,
                                          &info->text_conflicted,
                                          &info->prop_conflicted,
-                                         &info->tree_conflicted,
+                                         &tree_conflicted,
                                          b->wc_ctx->db, local_abspath,
                                          conflict,
                                          iterpool, iterpool));
+
+      if (tree_conflicted)
+        {
+          const char *move_src_abspath;
+          SVN_ERR(svn_wc__conflict_read_tree_conflict(&info->tc.reason,
+                                                      &info->tc.action,
+                                                      &move_src_abspath,
+                                                      b->wc_ctx->db,
+                                                      local_abspath,
+                                                      conflict,
+                                                      b->pool, iterpool));
+
+          if (move_src_abspath)
+            info->tc.delete_path =
+                svn_dirent_skip_ancestor(b->wc_abspath, move_src_abspath);
+
+          if (!info->tc.reason
+              && !info->tc.action
+              && !info->tc.delete_path)
+            {
+              info->tc.conflicted_fb = TRUE;
+            }
+        }
     }
 
   /* Fill EXPECTED_HASH with data from EXPECTED_ROWS. */
@@ -1159,29 +1255,17 @@ insert_dirs(svn_test__sandbox_t *b,
 
   while(nodes->local_relpath)
     {
-      if (nodes->local_relpath[0])
-        {
-          SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_NODE));
-          SVN_ERR(svn_sqlite__bindf(stmt, "sdssrs",
-                                    nodes->local_relpath,
-                                    nodes->op_depth,
-                                    nodes->presence,
-                                    nodes->repo_relpath,
-                                    nodes->repo_revnum,
-                                    svn_relpath_dirname(nodes->local_relpath,
-                                                        b->pool)));
-        }
-      else
-        {
-          SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_NODE_ROOT));
-          SVN_ERR(svn_sqlite__bindf(stmt, "sdssr",
-                                    nodes->local_relpath,
-                                    nodes->op_depth,
-                                    nodes->presence,
-                                    nodes->repo_relpath,
-                                    nodes->repo_revnum));
-        }
-
+      SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_NODE));
+      SVN_ERR(svn_sqlite__bindf(stmt, "sdssrs",
+                                nodes->local_relpath,
+                                nodes->op_depth,
+                                nodes->presence,
+                                nodes->repo_relpath,
+                                nodes->repo_revnum,
+                                nodes->local_relpath[0]
+                                  ? svn_relpath_dirname(nodes->local_relpath,
+                                                        b->pool)
+                                  : NULL));
       SVN_ERR(svn_sqlite__step_done(stmt));
       ++nodes;
     }
@@ -1949,22 +2033,14 @@ insert_actual(svn_test__sandbox_t *b,
 
   while(actual->local_relpath)
     {
-      if (actual->local_relpath[0])
-        {
-          SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_ACTUAL));
-          SVN_ERR(svn_sqlite__bindf(stmt, "sss",
-                                    actual->local_relpath,
-                                    svn_relpath_dirname(actual->local_relpath,
-                                                        b->pool),
-                                    actual->changelist));
-        }
-      else
-        {
-          SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_ACTUAL_ROOT));
-          SVN_ERR(svn_sqlite__bindf(stmt, "ss",
-                                    actual->local_relpath,
-                                    actual->changelist));
-        }
+      SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_ACTUAL));
+      SVN_ERR(svn_sqlite__bindf(stmt, "sss",
+                                actual->local_relpath,
+                                actual->local_relpath[0]
+                                  ? svn_relpath_dirname(actual->local_relpath,
+                                                        b->pool)
+                                  : NULL,
+                                actual->changelist));
       SVN_ERR(svn_sqlite__step_done(stmt));
       if (actual->changelist)
         {
@@ -5315,7 +5391,7 @@ mixed_rev_move(const svn_test_opts_t *op
 
   {
     conflict_info_t conflicts[] = {
-      { "X/D", FALSE, FALSE, TRUE },
+      { "X/D", FALSE, FALSE, {0 /* ### Needs fixing */} },
       {0}
     };
 
@@ -5421,7 +5497,8 @@ update_prop_mod_into_moved(const svn_tes
 
   {
     conflict_info_t conflicts[] = {
-      { "A", FALSE, FALSE, TRUE },
+      { "A", FALSE, FALSE, {svn_wc_conflict_action_edit,
+                            svn_wc_conflict_reason_moved_away, "A"}},
       {0}
     };
 
@@ -6191,6 +6268,34 @@ move_in_delete(const svn_test_opts_t *op
   }
 
   SVN_ERR(sbox_wc_update(&b, "", 3));
+  {
+    nodes_row_t nodes[] = {
+      {0, "",          "normal",       3, ""},
+      {0, "A",         "normal",       3, "A"},
+      {0, "A/B",       "normal",       3, "A/B"},
+      {0, "A/B/C",     "normal",       3, "A/B/C"},
+      {0, "A/B/C/D",   "normal",       3, "A/B/C/D"},
+      {0, "A/B/C/D/E", "normal",       3, "A/B/C/D/E"},
+
+      {1, "C2",        "normal",       2, "A/B/C", MOVED_HERE},
+      {1, "C2/D",      "normal",       2, "A/B/C/D", MOVED_HERE},
+
+      {2, "A/B",              "base-deleted", NO_COPY_FROM},
+      {2, "A/B/C",            "base-deleted", NO_COPY_FROM, "C2"},
+      {2, "A/B/C/D",          "base-deleted", NO_COPY_FROM},
+      {2, "A/B/C/D/E",        "base-deleted", NO_COPY_FROM},
+
+      {0}
+    };
+    conflict_info_t conflicts[] = {
+      {"A/B", FALSE, FALSE, {svn_wc_conflict_action_edit,
+                             svn_wc_conflict_reason_deleted}},
+      {0}
+    };
+
+    SVN_ERR(check_db_rows(&b, "", nodes));
+    SVN_ERR(check_db_conflicts(&b, "", conflicts));
+  }
   SVN_ERR(sbox_wc_revert(&b, "A/B", svn_depth_empty));
   {
     nodes_row_t nodes[] = {
@@ -6207,7 +6312,14 @@ move_in_delete(const svn_test_opts_t *op
       {1, "C2/D",      "normal",       2, "A/B/C/D", MOVED_HERE},
       {0}
     };
+    conflict_info_t conflicts[] = {
+      {"A/B/C", FALSE, FALSE, {svn_wc_conflict_action_edit,
+                               svn_wc_conflict_reason_moved_away, "A/B/C"}},
+      {0}
+    };
     SVN_ERR(check_db_rows(&b, "", nodes));
+    /* Where did this conflict come from? */
+    SVN_ERR(check_db_conflicts(&b, "", conflicts));
   }
 
   /* Revert should have left a tree-conflict (or broken the move). */
@@ -6230,6 +6342,7 @@ move_in_delete(const svn_test_opts_t *op
       {0}
     };
     SVN_ERR(check_db_rows(&b, "", nodes));
+    SVN_ERR(check_db_conflicts(&b, "", NULL));
   }
 
   return SVN_NO_ERROR;
@@ -6889,8 +7002,10 @@ finite_move_update_bump(const svn_test_o
   SVN_ERR(sbox_wc_update_depth(&b, "P/Q", 2, svn_depth_files, FALSE));
   {
     conflict_info_t conflicts[] = {
-      {"A/B", FALSE, FALSE, TRUE},
-      {"P/Q", FALSE, FALSE, TRUE},
+      {"A/B", FALSE, FALSE, {svn_wc_conflict_action_edit,
+                             svn_wc_conflict_reason_moved_away, "A/B"}},
+      {"P/Q", FALSE, FALSE, {svn_wc_conflict_action_edit,
+                             svn_wc_conflict_reason_moved_away, "P/Q"}},
       {0}
     };
     SVN_ERR(check_db_conflicts(&b, "", conflicts));
@@ -6936,8 +7051,10 @@ finite_move_update_bump(const svn_test_o
   SVN_ERR(sbox_wc_update_depth(&b, "P", 2, svn_depth_immediates, FALSE));
   {
     conflict_info_t conflicts[] = {
-      {"A/B", FALSE, FALSE, TRUE},
-      {"P", FALSE, FALSE, TRUE},
+      {"A/B", FALSE, FALSE, {svn_wc_conflict_action_edit,
+                             svn_wc_conflict_reason_moved_away, "A/B"}},
+      {"P",   FALSE, FALSE, {svn_wc_conflict_action_edit,
+                             svn_wc_conflict_reason_moved_away, "P"}},
       {0}
     };
     SVN_ERR(check_db_conflicts(&b, "", conflicts));
@@ -6981,8 +7098,11 @@ finite_move_update_bump(const svn_test_o
   SVN_ERR(sbox_wc_update_depth(&b, "P/Q", 2, svn_depth_empty, FALSE));
   {
     conflict_info_t conflicts[] = {
-      {"A/B/C", FALSE, FALSE, TRUE},
-      {"P/Q", FALSE, FALSE, TRUE},
+      {"A/B/C", FALSE, FALSE, {svn_wc_conflict_action_edit,
+                               svn_wc_conflict_reason_moved_away, "A/B/C"}},
+      {"P/Q",   FALSE, FALSE, {svn_wc_conflict_action_edit,
+                               svn_wc_conflict_reason_moved_away, "P/Q"}},
+
       {0}
     };
     SVN_ERR(check_db_conflicts(&b, "", conflicts));
@@ -9342,11 +9462,14 @@ del4_update_edit_AAA(const svn_test_opts
   SVN_ERR(sbox_wc_update(&b, "", 2));
   {
     conflict_info_t conflicts[] = {
-      {"A", FALSE, FALSE, TRUE},
-      {"B", FALSE, FALSE, TRUE},
-      {"C/A", FALSE, FALSE, TRUE},
-      {"D/A/A", FALSE, FALSE, TRUE},
-
+      {"A",     FALSE, FALSE, {svn_wc_conflict_action_edit,
+                               svn_wc_conflict_reason_replaced}},
+      {"B",     FALSE, FALSE, {svn_wc_conflict_action_edit,
+                               svn_wc_conflict_reason_moved_away, "B"}},
+      {"C/A",   FALSE, FALSE, {svn_wc_conflict_action_edit,
+                               svn_wc_conflict_reason_moved_away, "C/A"}},
+      {"D/A/A", FALSE, FALSE, {svn_wc_conflict_action_edit,
+                               svn_wc_conflict_reason_moved_away, "D/A/A"}},
       {0},
     };
 
@@ -9481,10 +9604,14 @@ del4_update_delself_AAA(const svn_test_o
 
   {
     conflict_info_t conflicts[] = {
-      {"A", FALSE, FALSE, TRUE},
-      {"B", FALSE, FALSE, TRUE},
-      {"C/A", FALSE, FALSE, TRUE},
-      {"D/A/A", FALSE, FALSE, TRUE},
+      {"A",     FALSE, FALSE, {svn_wc_conflict_action_edit,
+                               svn_wc_conflict_reason_replaced}},
+      {"B",     FALSE, FALSE, {svn_wc_conflict_action_edit,
+                               svn_wc_conflict_reason_moved_away, "B"}},
+      {"C/A",   FALSE, FALSE, {svn_wc_conflict_action_edit,
+                               svn_wc_conflict_reason_moved_away, "C/A"}},
+      {"D/A/A", FALSE, FALSE, {svn_wc_conflict_action_delete,
+                               svn_wc_conflict_reason_moved_away, "D/A/A"}},
       {0}
     };
 
@@ -9534,11 +9661,14 @@ del4_update_delself_AAA(const svn_test_o
   {
     conflict_info_t conflicts[] = {
       /* Not resolved yet */
-      {"D/A/A", FALSE, FALSE, TRUE},
+      {"D/A/A", FALSE, FALSE, {svn_wc_conflict_action_delete,
+                               svn_wc_conflict_reason_moved_away, "D/A/A"}},
 
       /* New */
-      {"A/A", FALSE, FALSE, TRUE},
-      {"A/A/A", FALSE, FALSE, TRUE},
+      {"A/A",   FALSE, FALSE, {svn_wc_conflict_action_edit,
+                               svn_wc_conflict_reason_deleted}},
+      {"A/A/A", FALSE, FALSE, {svn_wc_conflict_action_delete,
+                               svn_wc_conflict_reason_moved_away, "A/A/A"}},
 
       {0}
     };
@@ -9556,7 +9686,40 @@ del4_update_delself_AAA(const svn_test_o
   SVN_ERR(sbox_wc_resolve(&b, "", svn_depth_infinity, svn_wc_conflict_choose_mine_conflict));
   /* Go back to start position */
   SVN_ERR(sbox_wc_update(&b, "", 1));
-  SVN_ERR(sbox_wc_resolve(&b, "", svn_depth_infinity, svn_wc_conflict_choose_mine_conflict));
+  {
+    conflict_info_t conflicts[] = {
+      {"A", FALSE, FALSE,   { svn_wc_conflict_action_edit,
+                              svn_wc_conflict_reason_replaced}},
+      {"B", FALSE, FALSE,   { svn_wc_conflict_action_edit,
+                              svn_wc_conflict_reason_moved_away, "B"}},
+      {"C/A", FALSE, FALSE, { svn_wc_conflict_action_edit,
+                              svn_wc_conflict_reason_moved_away, "C/A"}},
+      {0}
+    };
+
+    SVN_ERR(check_db_conflicts(&b, "", conflicts));
+  }
+  SVN_ERR(sbox_wc_resolve(&b, "A", svn_depth_empty,
+                          svn_wc_conflict_choose_mine_conflict));
+  SVN_ERR(sbox_wc_resolve(&b, "B", svn_depth_empty,
+                          svn_wc_conflict_choose_mine_conflict));
+  SVN_ERR(sbox_wc_resolve(&b, "C/A", svn_depth_empty,
+                          svn_wc_conflict_choose_mine_conflict));
+  {
+    conflict_info_t conflicts[] = {
+      {"A/A",  FALSE, FALSE,  { svn_wc_conflict_action_edit,
+                                svn_wc_conflict_reason_deleted}},
+      {"A/A/A", FALSE, FALSE, { svn_wc_conflict_action_add,
+                                svn_wc_conflict_reason_added}},
+      {0}
+    };
+
+    SVN_ERR(check_db_conflicts(&b, "", conflicts));
+  }
+  SVN_ERR(sbox_wc_resolve(&b, "A/A/A", svn_depth_empty,
+                          svn_wc_conflict_choose_merged));
+  SVN_ERR(sbox_wc_resolve(&b, "", svn_depth_infinity,
+                          svn_wc_conflict_choose_mine_conflict));
   /* Update and resolve via their strategy */
   SVN_ERR(sbox_wc_update(&b, "", 2));
   SVN_ERR(sbox_wc_resolve(&b, "", svn_depth_infinity, svn_wc_conflict_choose_merged));
@@ -9859,14 +10022,46 @@ move4_update_delself_AAA(const svn_test_
 
         { 0 },
       };
-        SVN_ERR(check_db_rows(&b, "", nodes));
+
+      conflict_info_t conflicts[] = {
+        {"A", FALSE, FALSE,   { svn_wc_conflict_action_edit,
+                                svn_wc_conflict_reason_moved_away, "A"}},
+        {"B", FALSE, FALSE,   { svn_wc_conflict_action_edit,
+                                svn_wc_conflict_reason_moved_away, "B"}},
+        {"C/A", FALSE, FALSE, { svn_wc_conflict_action_edit,
+                                svn_wc_conflict_reason_moved_away, "C/A"}},
+        {0}
+      };
+
+      SVN_ERR(check_db_rows(&b, "", nodes));
+      SVN_ERR(check_db_conflicts(&b, "", conflicts));
     }
-    SVN_ERR(sbox_wc_resolve(&b, "", svn_depth_infinity, svn_wc_conflict_choose_mine_conflict));
-    /* Update and resolve via their strategy */
-    SVN_ERR(sbox_wc_update(&b, "", 2));
-    SVN_ERR(sbox_wc_resolve(&b, "", svn_depth_infinity, svn_wc_conflict_choose_merged));
 
-    return SVN_NO_ERROR;
+  SVN_ERR(sbox_wc_resolve(&b, "A", svn_depth_empty,
+                          svn_wc_conflict_choose_mine_conflict));
+  SVN_ERR(sbox_wc_resolve(&b, "B", svn_depth_empty,
+                          svn_wc_conflict_choose_mine_conflict));
+  SVN_ERR(sbox_wc_resolve(&b, "C/A", svn_depth_empty,
+                          svn_wc_conflict_choose_mine_conflict));
+  {
+    conflict_info_t conflicts[] = {
+      {"A/A",  FALSE, FALSE,  { svn_wc_conflict_action_edit,
+                                svn_wc_conflict_reason_moved_away, "A/A"}},
+      {"A/A/A", FALSE, FALSE, { svn_wc_conflict_action_add,
+                                svn_wc_conflict_reason_added}},
+      {0}
+    };
+
+    SVN_ERR(check_db_conflicts(&b, "", conflicts));
+  }
+  SVN_ERR(sbox_wc_resolve(&b, "A/A/A", svn_depth_empty,
+                          svn_wc_conflict_choose_merged));
+  SVN_ERR(sbox_wc_resolve(&b, "", svn_depth_infinity, svn_wc_conflict_choose_mine_conflict));
+  /* Update and resolve via their strategy */
+  SVN_ERR(sbox_wc_update(&b, "", 2));
+  SVN_ERR(sbox_wc_resolve(&b, "", svn_depth_infinity, svn_wc_conflict_choose_merged));
+
+  return SVN_NO_ERROR;
 }
 
 static svn_error_t *
@@ -10061,8 +10256,35 @@ movedhere_extract_retract(const svn_test
   SVN_ERR(sbox_wc_mkdir(&b, "Z/E2"));
 
   SVN_ERR(sbox_wc_update(&b, "", 2));
+  {
+    conflict_info_t conflicts[] = {
+      {"A",    FALSE, FALSE, {svn_wc_conflict_action_edit,
+                              svn_wc_conflict_reason_moved_away, "A"}},
+      {0}
+    };
+
+    SVN_ERR(check_db_conflicts(&b, "", conflicts));
+  }
   SVN_ERR(sbox_wc_resolve(&b, "A", svn_depth_empty,
                           svn_wc_conflict_choose_mine_conflict));
+  {
+    conflict_info_t conflicts[] = {
+      {"Z/B1", FALSE, FALSE, {svn_wc_conflict_action_edit,
+                              svn_wc_conflict_reason_deleted}},
+      {"Z/B2", FALSE, FALSE, {svn_wc_conflict_action_edit,
+                              svn_wc_conflict_reason_moved_away, "Z/B2"}},
+      {"Z/C1", FALSE, FALSE, {svn_wc_conflict_action_delete,
+                              svn_wc_conflict_reason_deleted}},
+      {"Z/C2", FALSE, FALSE, {svn_wc_conflict_action_delete,
+                              svn_wc_conflict_reason_moved_away, "Z/C2"}},
+      {"Z/E2", FALSE, FALSE, {svn_wc_conflict_action_add,
+                              svn_wc_conflict_reason_added}},
+
+      {0}
+    };
+
+    SVN_ERR(check_db_conflicts(&b, "", conflicts));
+  }
   SVN_ERR(sbox_wc_resolve(&b, "Z/B1", svn_depth_empty,
                           svn_wc_conflict_choose_mine_conflict));
   SVN_ERR(sbox_wc_resolve(&b, "Z/B2", svn_depth_empty,
@@ -10073,8 +10295,10 @@ movedhere_extract_retract(const svn_test
   SVN_ERR(sbox_wc_resolve(&b, "Z/C2", svn_depth_empty,
                           svn_wc_conflict_choose_merged));
 
-  SVN_ERR(sbox_wc_resolve(&b, "", svn_depth_infinity,
-                          svn_wc_conflict_choose_mine_conflict));
+  SVN_ERR(sbox_wc_resolve(&b, "Z/E2", svn_depth_empty,
+                          svn_wc_conflict_choose_merged));
+
+  SVN_ERR(check_db_conflicts(&b, "", NULL));
   {
     nodes_row_t nodes[] = {
 
@@ -10129,6 +10353,8 @@ movedhere_extract_retract(const svn_test
       { 0 },
     };
     SVN_ERR(check_db_rows(&b, "", nodes));
+
+    SVN_ERR(check_db_conflicts(&b, "", NULL));
   }
 
   return SVN_NO_ERROR;
@@ -10255,11 +10481,13 @@ break_move_in_delete(const svn_test_opts
       {0}
     };
     conflict_info_t conflicts1[] = {
-      {"X", FALSE, FALSE, TRUE},
+      {"X",     FALSE, FALSE, {svn_wc_conflict_action_edit,
+                               svn_wc_conflict_reason_deleted}},
       {0}
     };
     conflict_info_t conflicts2[] = {
-      {"X/Y/Z", FALSE, FALSE, TRUE},
+      {"X/Y/Z", FALSE, FALSE, {svn_wc_conflict_action_edit,
+                               svn_wc_conflict_reason_moved_away, "X"}},
       {0}
     };
     SVN_ERR(check_db_rows(&b, "", nodes));
@@ -10286,6 +10514,7 @@ break_move_in_delete(const svn_test_opts
       {0}
     };
     SVN_ERR(check_db_rows(&b, "", nodes));
+    SVN_ERR(check_db_conflicts(&b, "", NULL));
   }
 
   return SVN_NO_ERROR;
@@ -10668,9 +10897,10 @@ move_edit_obstruction(const svn_test_opt
       {0}
     };
     conflict_info_t conflicts[] = {
-      { "A_mv/B/E/alpha", FALSE, FALSE, TRUE },
-      { "A_mv/B/F", FALSE, FALSE, TRUE },
-
+      {"A_mv/B/E/alpha", FALSE, FALSE, {svn_wc_conflict_action_edit,
+                                        svn_wc_conflict_reason_obstructed}},
+      {"A_mv/B/F",       FALSE, FALSE, {svn_wc_conflict_action_edit,
+                                        svn_wc_conflict_reason_obstructed}},
       {0}
     };
 

Modified: subversion/branches/pin-externals/subversion/tests/libsvn_wc/wc-test-queries.sql
URL: http://svn.apache.org/viewvc/subversion/branches/pin-externals/subversion/tests/libsvn_wc/wc-test-queries.sql?rev=1659301&r1=1659300&r2=1659301&view=diff
==============================================================================
--- subversion/branches/pin-externals/subversion/tests/libsvn_wc/wc-test-queries.sql (original)
+++ subversion/branches/pin-externals/subversion/tests/libsvn_wc/wc-test-queries.sql Thu Feb 12 16:14:28 2015
@@ -22,28 +22,26 @@
  */
 
 -- STMT_SELECT_NODES_INFO
-SELECT op_depth, nodes.presence, nodes.local_relpath, revision,
+SELECT op_depth, n.presence, n.local_relpath, revision,
        repos_path, file_external, def_local_relpath, moved_to, moved_here,
        properties
-FROM nodes
-LEFT OUTER JOIN externals
-            ON nodes.wc_id = externals.wc_id
-                AND nodes.local_relpath = externals.local_relpath
-WHERE nodes.local_relpath = ?1 OR nodes.local_relpath LIKE ?2
+FROM nodes n
+LEFT OUTER JOIN externals e
+            ON n.wc_id = e.wc_id
+                AND n.local_relpath = e.local_relpath
+WHERE n.wc_id = ?1
+  AND (n.local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(n.local_relpath, ?2))
 
 -- STMT_SELECT_ACTUAL_INFO
 SELECT local_relpath
 FROM actual_node
-WHERE conflict_data is NOT NULL AND local_relpath = ?1 OR local_relpath LIKE ?2
+WHERE wc_id = ?1
+  AND conflict_data is NOT NULL
+  AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
 
 -- STMT_DELETE_NODES
 DELETE FROM nodes;
 
--- STMT_INSERT_NODE_ROOT
-INSERT INTO nodes (local_relpath, op_depth, presence, repos_path,
-                   revision, wc_id, repos_id, kind, depth)
-           VALUES (?1, ?2, ?3, ?4, ?5, 1, 1, 'dir', 'infinity');
-
 -- STMT_INSERT_NODE
 INSERT INTO nodes (local_relpath, op_depth, presence, repos_path,
                    revision, parent_relpath, wc_id, repos_id, kind, depth)
@@ -52,10 +50,6 @@ INSERT INTO nodes (local_relpath, op_dep
 -- STMT_DELETE_ACTUAL
 DELETE FROM actual_node;
 
--- STMT_INSERT_ACTUAL_ROOT
-INSERT INTO actual_node (local_relpath, changelist, wc_id)
-                 VALUES (?1, ?2, 1)
-
 -- STMT_INSERT_ACTUAL
 INSERT INTO actual_node (local_relpath, parent_relpath, changelist, wc_id)
                 VALUES (?1, ?2, ?3, 1)

Modified: subversion/branches/pin-externals/subversion/tests/svn_test.h
URL: http://svn.apache.org/viewvc/subversion/branches/pin-externals/subversion/tests/svn_test.h?rev=1659301&r1=1659300&r2=1659301&view=diff
==============================================================================
--- subversion/branches/pin-externals/subversion/tests/svn_test.h (original)
+++ subversion/branches/pin-externals/subversion/tests/svn_test.h Thu Feb 12 16:14:28 2015
@@ -27,6 +27,8 @@
 #define SVN_DEPRECATED
 #endif /* ! SVN_ENABLE_DEPRECATION_WARNINGS_IN_TESTS */
 
+#include <stdio.h>
+
 #include <apr_pools.h>
 
 #include "svn_delta.h"
@@ -54,6 +56,23 @@ extern "C" {
                                #expr, __FILE__, __LINE__);        \
   } while (0)
 
+/**
+ * Macro for testing assumptions when the context does not allow
+ * returning an svn_error_t*.
+ *
+ * Will write to stderr and cause a segfault if EXPR is false.
+ */
+#define SVN_TEST_ASSERT_NO_RETURN(expr)                           \
+  do {                                                            \
+    if (!(expr))                                                  \
+      {                                                           \
+        unsigned int z_e_r_o_p_a_g_e__;                           \
+        fprintf(stderr, "TEST ASSERTION FAILED: %s\n", #expr);    \
+        z_e_r_o_p_a_g_e__ = *(volatile unsigned int*)0;           \
+        *(volatile unsigned int*)0 = z_e_r_o_p_a_g_e__;           \
+      }                                                           \
+  } while (0)
+
 /** Handy macro for testing an expected svn_error_t return value.
  * EXPECTED must be a real error (neither SVN_NO_ERROR nor APR_SUCCESS).
  * The error returned by EXPR will be cleared.
@@ -146,6 +165,11 @@ typedef svn_error_t* (*svn_test_driver2_
 typedef svn_error_t* (*svn_test_driver_opts_t)(const svn_test_opts_t *opts,
                                                apr_pool_t *pool);
 
+/* Prototype for test predicate functions. */
+typedef svn_boolean_t (*svn_test_predicate_func_t)(const svn_test_opts_t *opts,
+                                                   const char *predicate_value,
+                                                   apr_pool_t *pool);
+
 /* Test modes. */
 enum svn_test_mode_t
   {
@@ -155,6 +179,23 @@ enum svn_test_mode_t
     svn_test_all
   };
 
+/* Structure for runtime test predicates. */
+struct svn_test_predicate_t
+{
+  /* The predicate function. */
+  svn_test_predicate_func_t func;
+
+  /* The value that the predicate function tests. */
+  const char *value;
+
+  /* The test mode that's used if the predicate matches. */
+  enum svn_test_mode_t alternate_mode;
+
+  /* Description for the test log */
+  const char *description;
+};
+
+
 /* Each test gets a test descriptor, holding the function and other
  * associated data.
  */
@@ -174,6 +215,9 @@ struct svn_test_descriptor_t
 
   /* An optional description of a work-in-progress test. */
   const char *wip;
+
+  /* An optional runtiume predicate. */
+  struct svn_test_predicate_t predicate;
 };
 
 /* All Subversion test programs include an array of svn_test_descriptor_t's
@@ -217,6 +261,8 @@ int svn_test_main(int argc, const char *
 #define SVN_TEST_OPTS_XFAIL(func, msg) {svn_test_xfail, NULL, func, msg}
 #define SVN_TEST_OPTS_XFAIL_COND(func, p, msg) \
   {(p) ? svn_test_xfail : svn_test_pass, NULL, func, msg}
+#define SVN_TEST_OPTS_XFAIL_OTOH(func, msg, predicate) \
+  {svn_test_xfail, NULL, func, msg, NULL, predicate}
 #define SVN_TEST_OPTS_SKIP(func, p, msg) \
   {(p) ? svn_test_skip : svn_test_pass, NULL, func, msg}
 
@@ -283,6 +329,33 @@ svn_error_t *
 svn_test__init_auth_baton(svn_auth_baton_t **baton,
                           apr_pool_t *result_pool);
 
+
+/*
+ * Test predicates
+ */
+
+#define SVN_TEST_PASS_IF_FS_TYPE_IS(fs_type) \
+  { svn_test__fs_type_is, fs_type, svn_test_pass, \
+    "PASS if fs-type = " fs_type }
+
+#define SVN_TEST_PASS_IF_FS_TYPE_IS_NOT(fs_type) \
+  { svn_test__fs_type_not, fs_type, svn_test_pass, \
+    "PASS if fs-type != " fs_type }
+
+/* Return TRUE if the fs-type in OPTS matches PREDICATE_VALUE. */
+svn_boolean_t
+svn_test__fs_type_is(const svn_test_opts_t *opts,
+                     const char *predicate_value,
+                     apr_pool_t *pool);
+
+
+/* Return TRUE if the fs-type in OPTS does not matches PREDICATE_VALUE. */
+svn_boolean_t
+svn_test__fs_type_not(const svn_test_opts_t *opts,
+                      const char *predicate_value,
+                      apr_pool_t *pool);
+
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */

Modified: subversion/branches/pin-externals/subversion/tests/svn_test_main.c
URL: http://svn.apache.org/viewvc/subversion/branches/pin-externals/subversion/tests/svn_test_main.c?rev=1659301&r1=1659300&r2=1659301&view=diff
==============================================================================
--- subversion/branches/pin-externals/subversion/tests/svn_test_main.c (original)
+++ subversion/branches/pin-externals/subversion/tests/svn_test_main.c Thu Feb 12 16:14:28 2015
@@ -328,14 +328,18 @@ log_results(const char *progname,
 
   if (msg_only)
     {
+      const svn_boolean_t otoh = !!desc->predicate.description;
+
       if (run_this_test)
-        printf(" %3d    %-5s  %s%s%s%s\n",
+        printf(" %3d    %-5s  %s%s%s%s%s%s\n",
                test_num,
                (xfail ? "XFAIL" : (skip ? "SKIP" : "")),
                msg ? msg : "(test did not provide name)",
                (wimp && verbose_mode) ? " [[" : "",
                (wimp && verbose_mode) ? desc->wip : "",
-               (wimp && verbose_mode) ? "]]" : "");
+               (wimp && verbose_mode) ? "]]" : "",
+               (otoh ? " / " : ""),
+               (otoh ? desc->predicate.description : ""));
     }
   else if (run_this_test && ((! quiet_mode) || test_failed))
     {
@@ -388,6 +392,7 @@ do_test_num(const char *progname,
   const struct svn_test_descriptor_t *desc;
   const int array_size = get_array_size(test_funcs);
   svn_boolean_t run_this_test; /* This test's mode matches DESC->MODE. */
+  enum svn_test_mode_t test_mode;
 
   /* Check our array bounds! */
   if (test_num < 0)
@@ -402,11 +407,18 @@ do_test_num(const char *progname,
     }
 
   desc = &test_funcs[test_num];
-  skip = desc->mode == svn_test_skip;
-  xfail = desc->mode == svn_test_xfail;
+  /* Check the test predicate. */
+  if (desc->predicate.func
+      && desc->predicate.func(opts, desc->predicate.value, pool))
+    test_mode = desc->predicate.alternate_mode;
+  else
+    test_mode = desc->mode;
+
+  skip = test_mode == svn_test_skip;
+  xfail = test_mode == svn_test_xfail;
   wimp = xfail && desc->wip;
   msg = desc->msg;
-  run_this_test = mode_filter == svn_test_all || mode_filter == desc->mode;
+  run_this_test = mode_filter == svn_test_all || mode_filter == test_mode;
 
   if (run_this_test && header_msg && *header_msg)
     {
@@ -488,6 +500,7 @@ test_thread(apr_thread_t *thread, void *
   svn_error_t *err;
   const struct svn_test_descriptor_t *desc;
   svn_boolean_t run_this_test; /* This test's mode matches DESC->MODE. */
+  enum svn_test_mode_t test_mode;
   test_params_t *params = data;
   svn_atomic_t test_num;
   apr_pool_t *pool;
@@ -510,11 +523,18 @@ test_thread(apr_thread_t *thread, void *
 #endif
 
       desc = &params->test_funcs[test_num];
-      skip = desc->mode == svn_test_skip;
-      xfail = desc->mode == svn_test_xfail;
+      /* Check the test predicate. */
+      if (desc->predicate.func
+          && desc->predicate.func(params->opts, desc->predicate.value, pool))
+        test_mode = desc->predicate.alternate_mode;
+      else
+        test_mode = desc->mode;
+
+      skip = test_mode == svn_test_skip;
+      xfail = test_mode == svn_test_xfail;
       wimp = xfail && desc->wip;
       run_this_test = mode_filter == svn_test_all
-                   || mode_filter == desc->mode;
+                   || mode_filter == test_mode;
 
       /* Do test */
       if (skip || !run_this_test)
@@ -1024,3 +1044,20 @@ svn_test_main(int argc, const char *argv
 
   return got_error;
 }
+
+
+svn_boolean_t
+svn_test__fs_type_is(const svn_test_opts_t *opts,
+                     const char *predicate_value,
+                     apr_pool_t *pool)
+{
+  return (0 == strcmp(predicate_value, opts->fs_type));
+}
+
+svn_boolean_t
+svn_test__fs_type_not(const svn_test_opts_t *opts,
+                      const char *predicate_value,
+                      apr_pool_t *pool)
+{
+  return (0 != strcmp(predicate_value, opts->fs_type));
+}