Modified: 
subversion/branches/addremove/subversion/tests/cmdline/svntest/verify.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/tests/cmdline/svntest/verify.py?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/tests/cmdline/svntest/verify.py 
(original)
+++ subversion/branches/addremove/subversion/tests/cmdline/svntest/verify.py 
Sat May 23 14:16:56 2020
@@ -150,8 +150,9 @@ class ExpectedOutput(object):
        MESSAGE unless it is None, the expected lines, the ACTUAL lines,
        and a diff, all labeled with LABEL.
     """
-    display_lines(message, self.expected, actual, label, label)
-    display_lines_diff(self.expected, actual, label, label)
+    e_label = label + ' (match_all=%s)' % (self.match_all,)
+    display_lines(message, self.expected, actual, e_label, label)
+    display_lines_diff(self.expected, actual, e_label, label)
 
 
 class AnyOutput(ExpectedOutput):
@@ -181,12 +182,36 @@ class AnyOutput(ExpectedOutput):
       logger.warn(message)
 
 
+def re_fullmatch(pattern, string, flags=0):
+  """If the whole STRING matches the regular expression PATTERN,
+     return a corresponding match object.
+     Based on re.fullmatch() in Python 3.4.
+  """
+  if pattern.endswith('$'):
+    return re.match(pattern, string, flags)
+
+  return re.match(pattern + '$', string, flags)
+
+def regex_fullmatch(rx, string):
+  """If the whole STRING matches the compiled regular expression RX,
+     return a corresponding match object.
+     Based on regex.fullmatch() in Python 3.4.
+  """
+  if rx.pattern.endswith('$'):
+    return rx.match(string)
+
+  return re_fullmatch(rx.pattern, string, rx.flags)
+
 class RegexOutput(ExpectedOutput):
   """Matches a single regular expression.
 
      If MATCH_ALL is true, every actual line must match the RE.  If
      MATCH_ALL is false, at least one actual line must match the RE.  In
      any case, there must be at least one line of actual output.
+
+     The RE must match a prefix of the actual line, in contrast to the
+     RegexListOutput and UnorderedRegexListOutput classes which match
+     whole lines.
   """
 
   def __init__(self, expected, match_all=True):
@@ -212,7 +237,8 @@ class RegexOutput(ExpectedOutput):
       return any(self.expected_re.match(line) for line in actual)
 
   def display_differences(self, message, label, actual):
-    display_lines(message, self.expected, actual, label + ' (regexp)', label)
+    e_label = label + ' (regexp, match_all=%s)' % (self.match_all,)
+    display_lines(message, self.expected, actual, e_label, label)
 
   def insert(self, index, line):
     self.expected.insert(index, line)
@@ -228,6 +254,9 @@ class RegexListOutput(ExpectedOutput):
      ones.
 
      In any case, there must be at least one line of actual output.
+
+     The REs must match whole actual lines, in contrast to the RegexOutput
+     class which matches a prefix of the actual line.
   """
 
   def __init__(self, expected, match_all=True):
@@ -243,18 +272,37 @@ class RegexListOutput(ExpectedOutput):
 
     if self.match_all:
       return (len(self.expected_res) == len(actual) and
-              all(e.match(a) for e, a in zip(self.expected_res, actual)))
+              all(regex_fullmatch(e, a) for e, a in zip(self.expected_res, 
actual)))
 
     i_expected = 0
     for actual_line in actual:
-      if self.expected_res[i_expected].match(actual_line):
+      if regex_fullmatch(self.expected_res[i_expected], actual_line):
         i_expected += 1
         if i_expected == len(self.expected_res):
           return True
     return False
 
   def display_differences(self, message, label, actual):
-    display_lines(message, self.expected, actual, label + ' (regexp)', label)
+    e_label = label + ' (regexp, match_all=%s)' % (self.match_all,)
+    display_lines(message, self.expected, actual, e_label, label)
+
+    assert actual is not None
+    if not isinstance(actual, list):
+      actual = [actual]
+
+    if self.match_all:
+      logger.warn('DIFF ' + label + ':')
+      if len(self.expected) != len(actual):
+        logger.warn('# Expected %d lines; actual %d lines' %
+                    (len(self.expected), len(actual)))
+      for e, a in map(None, self.expected_res, actual):
+        if e is not None and a is not None and regex_fullmatch(e, a):
+          logger.warn("|  " + a.rstrip())
+        else:
+          if e is not None:
+            logger.warn("| -" + repr(e.pattern))
+          if a is not None:
+            logger.warn("| +" + repr(a))
 
   def insert(self, index, line):
     self.expected.insert(index, line)
@@ -279,8 +327,9 @@ class UnorderedOutput(ExpectedOutput):
     return sorted(self.expected) == sorted(actual)
 
   def display_differences(self, message, label, actual):
-    display_lines(message, self.expected, actual, label + ' (unordered)', 
label)
-    display_lines_diff(self.expected, actual, label + ' (unordered)', label)
+    e_label = label + ' (unordered)'
+    display_lines(message, self.expected, actual, e_label, label)
+    display_lines_diff(sorted(self.expected), sorted(actual), e_label, label)
 
 
 class UnorderedRegexListOutput(ExpectedOutput):
@@ -295,6 +344,9 @@ class UnorderedRegexListOutput(ExpectedO
      expressions.  The implementation matches each expression in turn to
      the first unmatched actual line that it can match, and does not try
      all the permutations when there are multiple possible matches.
+
+     The REs must match whole actual lines, in contrast to the RegexOutput
+     class which matches a prefix of the actual line.
   """
 
   def __init__(self, expected):
@@ -305,13 +357,16 @@ class UnorderedRegexListOutput(ExpectedO
     assert actual is not None
     if not isinstance(actual, list):
       actual = [actual]
+    else:
+      # copy the list so we can remove elements without affecting caller
+      actual = actual[:]
 
     if len(self.expected) != len(actual):
       return False
     for e in self.expected:
       expect_re = re.compile(e)
       for actual_line in actual:
-        if expect_re.match(actual_line):
+        if regex_fullmatch(expect_re, actual_line):
           actual.remove(actual_line)
           break
       else:
@@ -320,9 +375,30 @@ class UnorderedRegexListOutput(ExpectedO
     return True
 
   def display_differences(self, message, label, actual):
-    display_lines(message, self.expected, actual,
-                  label + ' (regexp) (unordered)', label)
+    e_label = label + ' (regexp) (unordered)'
+    display_lines(message, self.expected, actual, e_label, label)
 
+    assert actual is not None
+    if not isinstance(actual, list):
+      actual = [actual]
+    else:
+      # copy the list so we can remove elements without affecting caller
+      actual = actual[:]
+
+    logger.warn('DIFF ' + label + ':')
+    if len(self.expected) != len(actual):
+      logger.warn('# Expected %d lines; actual %d lines' %
+                  (len(self.expected), len(actual)))
+    for e in self.expected:
+      expect_re = re.compile(e)
+      for actual_line in actual:
+        if regex_fullmatch(expect_re, actual_line):
+          actual.remove(actual_line)
+          break
+      else:
+        logger.warn("| -" + expect_re.pattern.rstrip())
+    for a in actual:
+      logger.warn("| +" + a.rstrip())
 
 class AlternateOutput(ExpectedOutput):
   """Matches any one of a list of ExpectedOutput instances.
@@ -467,10 +543,11 @@ def verify_exit_code(message, actual, ex
 # A simple dump file parser.  While sufficient for the current
 # testsuite it doesn't cope with all valid dump files.
 class DumpParser:
-  def __init__(self, lines):
+  def __init__(self, lines, ignore_sha1=False):
     self.current = 0
     self.lines = lines
     self.parsed = {}
+    self.ignore_sha1 = ignore_sha1
 
   def parse_line(self, regex, required=True):
     m = re.match(regex, self.lines[self.current])
@@ -660,6 +737,9 @@ class DumpParser:
       if not header in headers:
         node[key] = None
         continue
+      if self.ignore_sha1 and (key in ['copy_sha1', 'text_sha1']):
+        node[key] = None
+        continue
       m = re.match(regex, headers[header])
       if not m:
         raise SVNDumpParseError("expected '%s' at line %d\n%s"
@@ -726,7 +806,8 @@ class DumpParser:
     self.parse_all_revisions()
     return self.parsed
 
-def compare_dump_files(message, label, expected, actual,
+def compare_dump_files(label_expected, label_actual,
+                       expected, actual,
                        ignore_uuid=False,
                        expect_content_length_always=False,
                        ignore_empty_prop_sections=False,
@@ -735,8 +816,7 @@ def compare_dump_files(message, label, e
   of lines as returned by run_and_verify_dump, and check that the same
   revisions, nodes, properties, etc. are present in both dumps.
   """
-
-  parsed_expected = DumpParser(expected).parse()
+  parsed_expected = DumpParser(expected, not 
svntest.main.fs_has_sha1()).parse()
   parsed_actual = DumpParser(actual).parse()
 
   if ignore_uuid:
@@ -769,6 +849,8 @@ def compare_dump_files(message, label, e
 
   if parsed_expected != parsed_actual:
     print('DIFF of raw dumpfiles (including expected differences)')
+    print('--- ' + (label_expected or 'expected'))
+    print('+++ ' + (label_actual or 'actual'))
     print(''.join(ndiff(expected, actual)))
     raise svntest.Failure('DIFF of parsed dumpfiles (ignoring expected 
differences)\n'
                           + '\n'.join(ndiff(

Modified: subversion/branches/addremove/subversion/tests/cmdline/svntest/wc.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/tests/cmdline/svntest/wc.py?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/tests/cmdline/svntest/wc.py 
(original)
+++ subversion/branches/addremove/subversion/tests/cmdline/svntest/wc.py Sat 
May 23 14:16:56 2020
@@ -829,7 +829,7 @@ class State:
       match = _re_parse_eid_ele.search(line)
       if match and match.group(2) != 'none':
         eid = match.group(1)
-        parent_eid = match.group(3) 
+        parent_eid = match.group(3)
         path = match.group(4)
         if path == '.':
           path = ''
@@ -851,7 +851,7 @@ class State:
     add_to_desc(eids, desc, branch_id)
 
     return cls('', desc)
-  
+
 
 class StateItem:
   """Describes an individual item within a working copy.
@@ -1092,17 +1092,7 @@ def svn_uri_quote(url):
 
 def python_sqlite_can_read_wc():
   """Check if the Python builtin is capable enough to peek into wc.db"""
-
-  try:
-    db = svntest.sqlite3.connect('')
-
-    c = db.cursor()
-    c.execute('select sqlite_version()')
-    ver = tuple(map(int, c.fetchall()[0][0].split('.')))
-
-    return ver >= (3, 6, 18) # Currently enough (1.7-1.9)
-  except:
-    return False
+  return svntest.main.python_sqlite_can_read_our_wc_db()
 
 def open_wc_db(local_path):
   """Open the SQLite DB for the WC path LOCAL_PATH.

Modified: 
subversion/branches/addremove/subversion/tests/cmdline/svnversion_tests.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/tests/cmdline/svnversion_tests.py?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/tests/cmdline/svnversion_tests.py 
(original)
+++ subversion/branches/addremove/subversion/tests/cmdline/svnversion_tests.py 
Sat May 23 14:16:56 2020
@@ -71,9 +71,8 @@ def svnversion_test(sbox):
   expected_output = wc.State(wc_dir, {'A/mu' : Item(verb='Sending')})
   expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
   expected_status.tweak('A/mu', wc_rev=2)
-  if svntest.actions.run_and_verify_commit(wc_dir,
-                                           expected_output, expected_status):
-    raise svntest.Failure
+  svntest.actions.run_and_verify_commit(wc_dir,
+                                        expected_output, expected_status)
 
   # Unmodified, mixed
   svntest.actions.run_and_verify_svnversion(wc_dir, repo_url,
@@ -98,13 +97,12 @@ def svnversion_test(sbox):
                       + 'appended mu text')
   expected_disk.tweak('iota',
                       contents=expected_disk.desc['A/D/gamma'].contents)
-  if svntest.actions.run_and_verify_switch(wc_dir, iota_path, gamma_url,
-                                           expected_output,
-                                           expected_disk,
-                                           expected_status,
-                                           [],
-                                           False, '--ignore-ancestry'):
-    raise svntest.Failure
+  svntest.actions.run_and_verify_switch(wc_dir, iota_path, gamma_url,
+                                        expected_output,
+                                        expected_disk,
+                                        expected_status,
+                                        [],
+                                        False, '--ignore-ancestry')
 
   # Prop modified, mixed, part wc switched
   svntest.actions.run_and_verify_svnversion(wc_dir, repo_url,

Modified: subversion/branches/addremove/subversion/tests/cmdline/switch_tests.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/tests/cmdline/switch_tests.py?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/tests/cmdline/switch_tests.py 
(original)
+++ subversion/branches/addremove/subversion/tests/cmdline/switch_tests.py Sat 
May 23 14:16:56 2020
@@ -1336,25 +1336,23 @@ def mergeinfo_switch_elision(sbox):
 
   # Make branches A/B_COPY_1 and A/B_COPY_2
   expected_stdout = verify.UnorderedOutput([
-     "A    " + sbox.ospath('A/B_COPY_1/lambda') + "\n",
-     "A    " + sbox.ospath('A/B_COPY_1/E') + "\n",
-     "A    " + sbox.ospath('A/B_COPY_1/E/alpha') + "\n",
-     "A    " + sbox.ospath('A/B_COPY_1/E/beta') + "\n",
-     "A    " + sbox.ospath('A/B_COPY_1/F') + "\n",
-     "Checked out revision 1.\n",
      "A         " + B_COPY_1_path + "\n",
+     "A         " + sbox.ospath('A/B_COPY_1/lambda') + "\n",
+     "A         " + sbox.ospath('A/B_COPY_1/E') + "\n",
+     "A         " + sbox.ospath('A/B_COPY_1/E/alpha') + "\n",
+     "A         " + sbox.ospath('A/B_COPY_1/E/beta') + "\n",
+     "A         " + sbox.ospath('A/B_COPY_1/F') + "\n",
     ])
   svntest.actions.run_and_verify_svn(expected_stdout, [], 'copy',
                                      sbox.repo_url + "/A/B", B_COPY_1_path)
 
   expected_stdout = verify.UnorderedOutput([
-     "A    " + sbox.ospath('A/B_COPY_2/lambda') + "\n",
-     "A    " + sbox.ospath('A/B_COPY_2/E') + "\n",
-     "A    " + sbox.ospath('A/B_COPY_2/E/alpha') + "\n",
-     "A    " + sbox.ospath('A/B_COPY_2/E/beta') + "\n",
-     "A    " + sbox.ospath('A/B_COPY_2/F') + "\n",
-     "Checked out revision 1.\n",
      "A         " + B_COPY_2_path + "\n",
+     "A         " + sbox.ospath('A/B_COPY_2/lambda') + "\n",
+     "A         " + sbox.ospath('A/B_COPY_2/E') + "\n",
+     "A         " + sbox.ospath('A/B_COPY_2/E/alpha') + "\n",
+     "A         " + sbox.ospath('A/B_COPY_2/E/beta') + "\n",
+     "A         " + sbox.ospath('A/B_COPY_2/F') + "\n",
     ])
   svntest.actions.run_and_verify_svn(expected_stdout, [], 'copy',
                                      sbox.repo_url + "/A/B", B_COPY_2_path)
@@ -2029,8 +2027,9 @@ def tolerate_local_mods(sbox):
   svntest.main.run_svn(None, 'add', L_path)
   sbox.simple_commit(message='Commit added folder')
 
-  # locally modified unversioned file
+  # locally modified versioned file
   svntest.main.file_write(LM_path, 'Locally modified file.\n', 'w+')
+  sbox.simple_add('A/L/local_mod')
 
   expected_output = svntest.wc.State(wc_dir, {
     'A/L' : Item(status='  ', treeconflict='C'),
@@ -2046,7 +2045,8 @@ def tolerate_local_mods(sbox):
   expected_status.tweak('', 'iota', wc_rev=1)
   expected_status.tweak('A', switched='S')
   expected_status.add({
-    'A/L' : Item(status='A ', copied='+', treeconflict='C', wc_rev='-')
+    'A/L' : Item(status='A ', copied='+', treeconflict='C', wc_rev='-'),
+    'A/L/local_mod' : Item(status='A ', wc_rev='-'),
   })
 
   # Used to fail with locally modified or unversioned files
@@ -2870,7 +2870,7 @@ def switch_moves(sbox):
 
   # In Subversion 1.8 this scenario causes an Sqlite row not found error.
   # It would be nice if we could handle the tree conflict more intelligent, as
-  # the working copy matches the incomming change.
+  # the working copy matches the incoming change.
   svntest.actions.run_and_verify_switch(sbox.wc_dir, sbox.ospath(''), 
branch_url,
                                         None, expected_disk, expected_status)
 

Modified: subversion/branches/addremove/subversion/tests/cmdline/trans_tests.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/tests/cmdline/trans_tests.py?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/tests/cmdline/trans_tests.py 
(original)
+++ subversion/branches/addremove/subversion/tests/cmdline/trans_tests.py Sat 
May 23 14:16:56 2020
@@ -814,7 +814,8 @@ def props_only_file_update(sbox):
                       ]
 
   # Create r2 with iota's contents and svn:keywords modified
-  open(iota_path, 'w').writelines(content)
+  with open(iota_path, 'w') as f:
+    f.writelines(content)
   svntest.main.run_svn(None, 'propset', 'svn:keywords', 'Author', iota_path)
 
   expected_output = wc.State(wc_dir, {
@@ -831,7 +832,8 @@ def props_only_file_update(sbox):
   # Create r3 that drops svn:keywords
 
   # put the content back to its untranslated form
-  open(iota_path, 'w').writelines(content)
+  with open(iota_path, 'w') as f:
+    f.writelines(content)
 
   svntest.main.run_svn(None, 'propdel', 'svn:keywords', iota_path)
 

Modified: 
subversion/branches/addremove/subversion/tests/cmdline/tree_conflict_tests.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/tests/cmdline/tree_conflict_tests.py?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- 
subversion/branches/addremove/subversion/tests/cmdline/tree_conflict_tests.py 
(original)
+++ 
subversion/branches/addremove/subversion/tests/cmdline/tree_conflict_tests.py 
Sat May 23 14:16:56 2020
@@ -473,6 +473,7 @@ def ensure_tree_conflict(sbox, operation
         run_and_verify_svn(expected_stdout, [],
                            'merge',
                            '--allow-mixed-revisions',
+                           '--accept=postpone',
                            '-r', str(source_left_rev) + ':' + 
str(source_right_rev),
                            source_url, target_path)
       else:
@@ -1096,13 +1097,15 @@ def at_directory_external(sbox):
   svntest.main.run_svn(None, 'update', wc_dir)
 
   # r3: modify ^/A/B/E/alpha
-  open(sbox.ospath('A/B/E/alpha'), 'a').write('This is still A/B/E/alpha.\n')
+  with open(sbox.ospath('A/B/E/alpha'), 'a') as f:
+    f.write('This is still A/B/E/alpha.\n')
   svntest.main.run_svn(None, 'commit', '-m', 'file mod', wc_dir)
   svntest.main.run_svn(None, 'update', wc_dir)
   merge_rev = svntest.main.youngest(sbox.repo_dir)
 
   # r4: create ^/A/B/E/alpha2
-  open(sbox.ospath('A/B/E/alpha2'), 'a').write("This is the file 'alpha2'.\n")
+  with open(sbox.ospath('A/B/E/alpha2'), 'a') as f:
+    f.write("This is the file 'alpha2'.\n")
   svntest.main.run_svn(None, 'add', sbox.ospath('A/B/E/alpha2'))
   svntest.main.run_svn(None, 'commit', '-m', 'file add', wc_dir)
   svntest.main.run_svn(None, 'update', wc_dir)
@@ -1503,6 +1506,47 @@ def update_delete_mixed_rev(sbox):
   }
   run_and_verify_info([expected_info], sbox.repo_url + '/A/B/E/alpha2')
 
+# NB: This test will run forever if the bug it is testing for is present!
+def local_missing_dir_endless_loop(sbox):
+  "endless loop when resolving local-missing dir"
+
+  sbox.build()
+  wc_dir = sbox.wc_dir
+  sbox.simple_copy('A', 'A1')
+  sbox.simple_commit()
+  sbox.simple_update()
+  sbox.simple_move('A/B', 'A/B2')
+  sbox.simple_commit()
+  sbox.simple_update()
+  main.file_append(sbox.ospath("A/B2/lambda"), "This is more content.\n")
+  sbox.simple_commit()
+  sbox.simple_update()
+
+  # Create a config which enables the interactive conflict resolver
+  config_contents = '''\
+[auth]
+password-stores =
+
+[miscellany]
+interactive-conflicts = true
+'''
+  config_dir = sbox.create_config_dir(config_contents)
+
+  # Bug: 'svn' keeps retrying interactive conflict resolution while the library
+  # keeps signalling 'SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE' -> endless loop
+  main.run_svn("Tree conflict on '%s'" % sbox.ospath("A1/B2"),
+      'merge', '-c4', '^/A', sbox.ospath('A1'),
+      '--config-dir', config_dir, '--force-interactive')
+
+  # If everything works as expected the resolver will recommended a
+  # resolution option and 'svn' will resolve the conflict automatically.
+  # Verify that 'A1/B/lambda' contains the merged content:
+  contents = open(sbox.ospath('A1/B/lambda'), 'r').readlines()
+  svntest.verify.compare_and_display_lines(
+    "A1/B/lambda has unexpectected contents", sbox.ospath("A1/B/lambda"),
+    [ "This is the file 'lambda'.\n", "This is more content.\n"], contents)
+
+
 #######################################################################
 # Run the tests
 
@@ -1534,6 +1578,7 @@ test_list = [ None,
               actual_only_node_behaviour,
               update_dir_with_not_present,
               update_delete_mixed_rev,
+              local_missing_dir_endless_loop,
              ]
 
 if __name__ == '__main__':

Modified: subversion/branches/addremove/subversion/tests/cmdline/update_tests.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/tests/cmdline/update_tests.py?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/tests/cmdline/update_tests.py 
(original)
+++ subversion/branches/addremove/subversion/tests/cmdline/update_tests.py Sat 
May 23 14:16:56 2020
@@ -480,7 +480,7 @@ def update_to_rev_zero(sbox):
 def receive_overlapping_same_change(sbox):
   "overlapping identical changes should not conflict"
 
-  ### (See http://subversion.tigris.org/issues/show_bug.cgi?id=682.)
+  ### (See https://issues.apache.org/jira/browse/SVN-682.)
   ###
   ### How this test works:
   ###
@@ -3046,13 +3046,12 @@ def mergeinfo_update_elision(sbox):
 
   # Make a branch A/B_COPY
   expected_stdout =  verify.UnorderedOutput([
-     "A    " + sbox.ospath('A/B_COPY/lambda') + "\n",
-     "A    " + sbox.ospath('A/B_COPY/E') + "\n",
-     "A    " + sbox.ospath('A/B_COPY/E/alpha') + "\n",
-     "A    " + sbox.ospath('A/B_COPY/E/beta') + "\n",
-     "A    " + sbox.ospath('A/B_COPY/F') + "\n",
-     "Checked out revision 1.\n",
      "A         " + B_COPY_path + "\n",
+     "A         " + sbox.ospath('A/B_COPY/lambda') + "\n",
+     "A         " + sbox.ospath('A/B_COPY/E') + "\n",
+     "A         " + sbox.ospath('A/B_COPY/E/alpha') + "\n",
+     "A         " + sbox.ospath('A/B_COPY/E/beta') + "\n",
+     "A         " + sbox.ospath('A/B_COPY/F') + "\n",
     ])
   svntest.actions.run_and_verify_svn(expected_stdout, [], 'copy',
                                      sbox.repo_url + "/A/B", B_COPY_path)
@@ -3643,10 +3642,6 @@ def update_accept_conflicts(sbox):
   sbox.build()
   wc_dir = sbox.wc_dir
 
-  # Make a backup copy of the working copy
-  wc_backup = sbox.add_wc_path('backup')
-  svntest.actions.duplicate_dir(wc_dir, wc_backup)
-
   # Make a few local mods to files which will be committed
   iota_path = sbox.ospath('iota')
   lambda_path = sbox.ospath('A/B/lambda')
@@ -3654,13 +3649,25 @@ def update_accept_conflicts(sbox):
   alpha_path = sbox.ospath('A/B/E/alpha')
   beta_path = sbox.ospath('A/B/E/beta')
   pi_path = sbox.ospath('A/D/G/pi')
+  p_i_path = sbox.ospath('A/D/G/p; i')
   rho_path = sbox.ospath('A/D/G/rho')
+
+  # Rename pi to "p; i" so we can exercise SVN_EDITOR's handling of paths with
+  # special characters
+  sbox.simple_move('A/D/G/pi', 'A/D/G/p; i')
+  sbox.simple_commit()
+  sbox.simple_update()
+
+  # Make a backup copy of the working copy
+  wc_backup = sbox.add_wc_path('backup')
+  svntest.actions.duplicate_dir(wc_dir, wc_backup)
+
   svntest.main.file_append(lambda_path, 'Their appended text for lambda\n')
   svntest.main.file_append(iota_path, 'Their appended text for iota\n')
   svntest.main.file_append(mu_path, 'Their appended text for mu\n')
   svntest.main.file_append(alpha_path, 'Their appended text for alpha\n')
   svntest.main.file_append(beta_path, 'Their appended text for beta\n')
-  svntest.main.file_append(pi_path, 'Their appended text for pi\n')
+  svntest.main.file_append(p_i_path, 'Their appended text for pi\n')
   svntest.main.file_append(rho_path, 'Their appended text for rho\n')
 
   # Make a few local mods to files which will be conflicted
@@ -3669,7 +3676,7 @@ def update_accept_conflicts(sbox):
   mu_path_backup = os.path.join(wc_backup, 'A', 'mu')
   alpha_path_backup = os.path.join(wc_backup, 'A', 'B', 'E', 'alpha')
   beta_path_backup = os.path.join(wc_backup, 'A', 'B', 'E', 'beta')
-  pi_path_backup = os.path.join(wc_backup, 'A', 'D', 'G', 'pi')
+  p_i_path_backup = os.path.join(wc_backup, 'A', 'D', 'G', 'p; i')
   rho_path_backup = os.path.join(wc_backup, 'A', 'D', 'G', 'rho')
   svntest.main.file_append(iota_path_backup,
                            'My appended text for iota\n')
@@ -3681,7 +3688,7 @@ def update_accept_conflicts(sbox):
                            'My appended text for alpha\n')
   svntest.main.file_append(beta_path_backup,
                            'My appended text for beta\n')
-  svntest.main.file_append(pi_path_backup,
+  svntest.main.file_append(p_i_path_backup,
                            'My appended text for pi\n')
   svntest.main.file_append(rho_path_backup,
                            'My appended text for rho\n')
@@ -3693,18 +3700,19 @@ def update_accept_conflicts(sbox):
     'A/mu' : Item(verb='Sending'),
     'A/B/E/alpha': Item(verb='Sending'),
     'A/B/E/beta': Item(verb='Sending'),
-    'A/D/G/pi' : Item(verb='Sending'),
+    'A/D/G/p; i' : Item(verb='Sending'),
     'A/D/G/rho' : Item(verb='Sending'),
     })
 
-  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
-  expected_status.tweak('iota', wc_rev=2)
-  expected_status.tweak('A/B/lambda', wc_rev=2)
-  expected_status.tweak('A/mu', wc_rev=2)
-  expected_status.tweak('A/B/E/alpha', wc_rev=2)
-  expected_status.tweak('A/B/E/beta', wc_rev=2)
-  expected_status.tweak('A/D/G/pi', wc_rev=2)
-  expected_status.tweak('A/D/G/rho', wc_rev=2)
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
+  expected_status.tweak('iota', wc_rev=3)
+  expected_status.tweak('A/B/lambda', wc_rev=3)
+  expected_status.tweak('A/mu', wc_rev=3)
+  expected_status.tweak('A/B/E/alpha', wc_rev=3)
+  expected_status.tweak('A/B/E/beta', wc_rev=3)
+  expected_status.rename({'A/D/G/pi': 'A/D/G/p; i'})
+  expected_status.tweak('A/D/G/p; i', wc_rev=3)
+  expected_status.tweak('A/D/G/rho', wc_rev=3)
 
   # Commit.
   svntest.actions.run_and_verify_commit(wc_dir, expected_output,
@@ -3720,14 +3728,14 @@ def update_accept_conflicts(sbox):
   # Just leave the conflicts alone, since run_and_verify_svn already uses
   # the --non-interactive option.
   svntest.actions.run_and_verify_svn(update_output_with_conflicts(
-                                       2, iota_path_backup),
+                                       3, iota_path_backup),
                                      [],
                                      'update', iota_path_backup)
 
   # lambda: --accept=postpone
   # Just leave the conflicts alone.
   svntest.actions.run_and_verify_svn(update_output_with_conflicts(
-                                       2, lambda_path_backup),
+                                       3, lambda_path_backup),
                                      [],
                                      'update', '--accept=postpone',
                                      lambda_path_backup)
@@ -3735,7 +3743,7 @@ def update_accept_conflicts(sbox):
   # mu: --accept=base
   # Accept the pre-update base file.
   svntest.actions.run_and_verify_svn(update_output_with_conflicts_resolved(
-                                       2, mu_path_backup),
+                                       3, mu_path_backup),
                                      [],
                                      'update', '--accept=base',
                                      mu_path_backup)
@@ -3743,7 +3751,7 @@ def update_accept_conflicts(sbox):
   # alpha: --accept=mine
   # Accept the user's working file.
   svntest.actions.run_and_verify_svn(update_output_with_conflicts_resolved(
-                                       2, alpha_path_backup),
+                                       3, alpha_path_backup),
                                      [],
                                      'update', '--accept=mine-full',
                                      alpha_path_backup)
@@ -3751,7 +3759,7 @@ def update_accept_conflicts(sbox):
   # beta: --accept=theirs
   # Accept their file.
   svntest.actions.run_and_verify_svn(update_output_with_conflicts_resolved(
-                                       2, beta_path_backup),
+                                       3, beta_path_backup),
                                      [],
                                      'update', '--accept=theirs-full',
                                      beta_path_backup)
@@ -3761,16 +3769,16 @@ def update_accept_conflicts(sbox):
   # conflicts in place, so expect a message on stderr, but expect
   # svn to exit with an exit code of 0.
   svntest.actions.run_and_verify_svn2(update_output_with_conflicts_resolved(
-                                        2, pi_path_backup),
+                                        3, p_i_path_backup),
                                       "system(.*) returned.*", 0,
                                       'update', '--accept=edit',
                                       '--force-interactive',
-                                      pi_path_backup)
+                                      p_i_path_backup)
 
   # rho: --accept=launch
   # Run the external merge tool, it should leave conflict markers in place.
   svntest.actions.run_and_verify_svn(update_output_with_conflicts(
-                                       2, rho_path_backup),
+                                       3, rho_path_backup),
                                      [],
                                      'update', '--accept=launch',
                                      '--force-interactive',
@@ -3782,55 +3790,57 @@ def update_accept_conflicts(sbox):
   expected_disk.tweak('iota', contents=("This is the file 'iota'.\n"
                                         '<<<<<<< .mine\n'
                                         'My appended text for iota\n'
-                                        '||||||| .r1\n'
+                                        '||||||| .r2\n'
                                         '=======\n'
                                         'Their appended text for iota\n'
-                                        '>>>>>>> .r2\n'))
+                                        '>>>>>>> .r3\n'))
   expected_disk.tweak('A/B/lambda', contents=("This is the file 'lambda'.\n"
                                               '<<<<<<< .mine\n'
                                               'My appended text for lambda\n'
-                                              '||||||| .r1\n'
+                                              '||||||| .r2\n'
                                               '=======\n'
                                               'Their appended text for 
lambda\n'
-                                              '>>>>>>> .r2\n'))
+                                              '>>>>>>> .r3\n'))
   expected_disk.tweak('A/mu', contents="This is the file 'mu'.\n")
   expected_disk.tweak('A/B/E/alpha', contents=("This is the file 'alpha'.\n"
                                                'My appended text for alpha\n'))
   expected_disk.tweak('A/B/E/beta', contents=("This is the file 'beta'.\n"
                                               'Their appended text for 
beta\n'))
-  expected_disk.tweak('A/D/G/pi', contents=("This is the file 'pi'.\n"
-                                             '<<<<<<< .mine\n'
-                                             'My appended text for pi\n'
-                                             '||||||| .r1\n'
-                                             '=======\n'
-                                             'Their appended text for pi\n'
-                                             '>>>>>>> .r2\n'
-                                             'foo\n'))
+  expected_disk.rename({'A/D/G/pi': 'A/D/G/p; i'})
+  expected_disk.tweak('A/D/G/p; i', contents=("This is the file 'pi'.\n"
+                                              '<<<<<<< .mine\n'
+                                              'My appended text for pi\n'
+                                              '||||||| .r2\n'
+                                              '=======\n'
+                                              'Their appended text for pi\n'
+                                              '>>>>>>> .r3\n'
+                                              'foo\n'))
   expected_disk.tweak('A/D/G/rho', contents=("This is the file 'rho'.\n"
                                              '<<<<<<< .mine\n'
                                              'My appended text for rho\n'
-                                             '||||||| .r1\n'
+                                             '||||||| .r2\n'
                                              '=======\n'
                                              'Their appended text for rho\n'
-                                             '>>>>>>> .r2\n'
+                                             '>>>>>>> .r3\n'
                                              'foo\n'))
 
   # Set the expected extra files for the test
-  extra_files = ['iota.*\.r1', 'iota.*\.r2', 'iota.*\.mine',
-                 'lambda.*\.r1', 'lambda.*\.r2', 'lambda.*\.mine',
-                 'rho.*\.r1', 'rho.*\.r2', 'rho.*\.mine']
+  extra_files = ['iota.*\.r2', 'iota.*\.r3', 'iota.*\.mine',
+                 'lambda.*\.r2', 'lambda.*\.r3', 'lambda.*\.mine',
+                 'rho.*\.r2', 'rho.*\.r3', 'rho.*\.mine']
 
   # Set the expected status for the test
-  expected_status = svntest.actions.get_virginal_state(wc_backup, 2)
+  expected_status = svntest.actions.get_virginal_state(wc_backup, 3)
+  expected_status.rename({'A/D/G/pi': 'A/D/G/p; i'})
   expected_status.tweak('iota', 'A/B/lambda', 'A/mu',
                         'A/B/E/alpha', 'A/B/E/beta',
-                        'A/D/G/pi', 'A/D/G/rho', wc_rev=2)
+                        'A/D/G/p; i', 'A/D/G/rho', wc_rev=3)
   expected_status.tweak('iota', status='C ')
   expected_status.tweak('A/B/lambda', status='C ')
   expected_status.tweak('A/mu', status='M ')
   expected_status.tweak('A/B/E/alpha', status='M ')
   expected_status.tweak('A/B/E/beta', status='  ')
-  expected_status.tweak('A/D/G/pi', status='M ')
+  expected_status.tweak('A/D/G/p; i', status='M ')
   expected_status.tweak('A/D/G/rho', status='C ')
 
   # Set the expected output for the test
@@ -6719,6 +6729,7 @@ def update_conflict_details(sbox):
 # Keywords should be updated in local file even if text change is shortcut
 # (due to the local change being the same as the incoming change, for example).
 @XFail()
+@Issue(4585)
 def update_keywords_on_shortcut(sbox):
   "update_keywords_on_shortcut"
 
@@ -6840,6 +6851,63 @@ def update_delete_switched(sbox):
   svntest.actions.run_and_verify_update(wc_dir, None, None, expected_status,
                                         [], False, sbox.ospath('A'), '-r', 0)
 
+@XFail()
+def update_add_missing_local_add(sbox):
+  "update adds missing local addition"
+
+  sbox.build(read_only=True)
+
+  # Note that updating 'A' to r0 doesn't reproduce this issue...
+  sbox.simple_update('', revision='0')
+  sbox.simple_mkdir('A')
+  sbox.simple_add_text('mumumu', 'A/mu')
+  os.unlink(sbox.ospath('A/mu'))
+  os.rmdir(sbox.ospath('A'))
+
+  sbox.simple_update()
+
+# Verify that deleting an unmodified directory leaves behind any unversioned
+# items on disk
+def update_keeps_unversioned_items_in_deleted_dir(sbox):
+  "update keeps unversioned items in deleted dir"
+  sbox.build()
+  wc_dir = sbox.wc_dir
+
+  sbox.simple_rm('A/D/G')
+  sbox.simple_commit()
+
+  sbox.simple_update('', revision='1')
+
+  os.mkdir(sbox.ospath('A/D/G/unversioned-dir'))
+  svntest.main.file_write(sbox.ospath('A/D/G/unversioned.txt'),
+                          'unversioned file', 'wb')
+
+  expected_output = svntest.wc.State(wc_dir, {
+    'A/D/G' : Item(status='D '),
+    })
+
+  expected_disk = svntest.main.greek_state.copy()
+  # The unversioned items should be left behind on disk
+  expected_disk.add({
+    'A/D/G/unversioned-dir' : Item(),
+    'A/D/G/unversioned.txt' : Item('unversioned file'),
+    })
+  expected_disk.remove('A/D/G/pi')
+  expected_disk.remove('A/D/G/rho')
+  expected_disk.remove('A/D/G/tau')
+
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
+  expected_status.remove('A/D/G')
+  expected_status.remove('A/D/G/pi')
+  expected_status.remove('A/D/G/rho')
+  expected_status.remove('A/D/G/tau')
+
+  svntest.actions.run_and_verify_update(wc_dir,
+                                        expected_output,
+                                        expected_disk,
+                                        expected_status,
+                                        [], True)
+
 #######################################################################
 # Run the tests
 
@@ -6930,6 +6998,8 @@ test_list = [ None,
               update_add_conflicted_deep,
               missing_tmp_update,
               update_delete_switched,
+              update_add_missing_local_add,
+              update_keeps_unversioned_items_in_deleted_dir,
              ]
 
 if __name__ == '__main__':

Modified: 
subversion/branches/addremove/subversion/tests/cmdline/upgrade_tests.py
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/tests/cmdline/upgrade_tests.py?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/tests/cmdline/upgrade_tests.py 
(original)
+++ subversion/branches/addremove/subversion/tests/cmdline/upgrade_tests.py Sat 
May 23 14:16:56 2020
@@ -392,7 +392,8 @@ def xml_entries_relocate(path, from_url,
   entries = os.path.join(path, adm_name, 'entries')
   txt = open(entries).read().replace('url="' + from_url, 'url="' + to_url)
   os.chmod(entries, svntest.main.S_ALL_RWX)
-  open(entries, 'w').write(txt)
+  with open(entries, 'w') as f:
+    f.write(txt)
 
   for dirent in os.listdir(path):
     item_path = os.path.join(path, dirent)
@@ -410,7 +411,8 @@ def simple_entries_replace(path, from_ur
   entries = os.path.join(path, adm_name, 'entries')
   txt = open(entries).read().replace(from_url, to_url)
   os.chmod(entries, svntest.main.S_ALL_RWX)
-  open(entries, 'wb').write(txt.encode())
+  with open(entries, 'wb') as f:
+    f.write(txt.encode())
 
   for dirent in os.listdir(path):
     item_path = os.path.join(path, dirent)

Propchange: subversion/branches/addremove/subversion/tests/libsvn_client/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Sat May 23 14:16:56 2020
@@ -1,4 +1,15 @@
-.libs
 *-test
 *.lo
+.libs
+mtcc-anchoring
+mtcc-file-revs
+mtcc-iprops-paths
+mtcc-mkdir
+mtcc-mkgreek
+mtcc-move-and-delete
+mtcc-overwrite
+mtcc-propset
+mtcc-replace_tree
+mtcc-swap
+mtcc-update-files
 svn-test-work

Modified: 
subversion/branches/addremove/subversion/tests/libsvn_client/client-test.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/tests/libsvn_client/client-test.c?rev=1878061&r1=1878060&r2=1878061&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/tests/libsvn_client/client-test.c 
(original)
+++ subversion/branches/addremove/subversion/tests/libsvn_client/client-test.c 
Sat May 23 14:16:56 2020
@@ -31,6 +31,7 @@
 #include "../../libsvn_client/client.h"
 #include "svn_pools.h"
 #include "svn_client.h"
+#include "private/svn_client_private.h"
 #include "private/svn_client_mtcc.h"
 #include "svn_repos.h"
 #include "svn_subst.h"
@@ -60,7 +61,7 @@ create_greek_repos(const char **repos_ur
   svn_fs_txn_t *txn;
   svn_fs_root_t *txn_root;
 
-  /* Create a filesytem and repository. */
+  /* Create a filesystem and repository. */
   SVN_ERR(svn_test__create_repos(
               &repos, svn_test_data_path(name, pool), opts, pool));
 
@@ -371,7 +372,7 @@ test_patch(const svn_test_opts_t *opts,
     "+It is really the file 'gamma'."
   };
 
-  /* Create a filesytem and repository containing the Greek tree. */
+  /* Create a filesystem and repository containing the Greek tree. */
   SVN_ERR(create_greek_repos(&repos_url, "test-patch-repos", opts, pool));
 
   /* Check out the HEAD revision */
@@ -446,7 +447,7 @@ test_wc_add_scenarios(const svn_test_opt
   const char *ex_dir_path;
   const char *ex2_dir_path;
 
-  /* Create a filesytem and repository containing the Greek tree. */
+  /* Create a filesystem and repository containing the Greek tree. */
   SVN_ERR(create_greek_repos(&repos_url, "test-wc-add-repos", opts, pool));
   committed_rev = 1;
 
@@ -575,7 +576,7 @@ test_copy_crash(const svn_test_opts_t *o
   const char *dest;
   const char *repos_url;
 
-  /* Create a filesytem and repository containing the Greek tree. */
+  /* Create a filesystem and repository containing the Greek tree. */
   SVN_ERR(create_greek_repos(&repos_url, "test-copy-crash", opts, pool));
 
   SVN_ERR(svn_client_create_context(&ctx, pool));
@@ -609,7 +610,7 @@ test_16k_add(const svn_test_opts_t *opts
   apr_pool_t *iterpool = svn_pool_create(pool);
   int i;
 
-  /* Create a filesytem and repository containing the Greek tree. */
+  /* Create a filesystem and repository containing the Greek tree. */
   SVN_ERR(create_greek_repos(&repos_url, "test-16k-repos", opts, pool));
 
   /* Check out the HEAD revision */
@@ -670,7 +671,7 @@ test_youngest_common_ancestor(const svn_
   const char *dest;
   svn_client__pathrev_t *yc_ancestor;
 
-  /* Create a filesytem and repository containing the Greek tree. */
+  /* Create a filesystem and repository containing the Greek tree. */
   SVN_ERR(create_greek_repos(&repos_url, "test-youngest-common-ancestor", 
opts, pool));
 
   SVN_ERR(svn_client_create_context(&ctx, pool));
@@ -735,7 +736,10 @@ test_foreign_repos_copy(const svn_test_o
   const char *repos2_url;
   const char *wc_path;
   svn_client_ctx_t *ctx;
-/* Create a filesytem and repository containing the Greek tree. */
+  svn_ra_session_t *ra_session;
+  svn_client__pathrev_t *loc;
+
+  /* Create a filesystem and repository containing the Greek tree. */
   SVN_ERR(create_greek_repos(&repos_url, "foreign-copy1", opts, pool));
   SVN_ERR(create_greek_repos(&repos2_url, "foreign-copy2", opts, pool));
 
@@ -756,19 +760,26 @@ test_foreign_repos_copy(const svn_test_o
   SVN_ERR(svn_client_checkout3(NULL, repos_url, wc_path, &peg_rev, &rev,
                                svn_depth_infinity, FALSE, FALSE, ctx, pool));
 
-  SVN_ERR(svn_client__copy_foreign(svn_path_url_add_component2(repos2_url, "A",
-                                                               pool),
-                                   svn_dirent_join(wc_path, "A-copied", pool),
-                                   &peg_rev, &rev, svn_depth_infinity, FALSE, 
FALSE,
-                                   ctx, pool));
-
-
-  SVN_ERR(svn_client__copy_foreign(svn_path_url_add_component2(repos2_url,
-                                                               "iota",
-                                                               pool),
-                                   svn_dirent_join(wc_path, "iota-copied", 
pool),
-                                   &peg_rev, &rev, svn_depth_infinity, FALSE, 
FALSE,
-                                   ctx, pool));
+  SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
+                                            repos2_url, NULL, &peg_rev, &rev,
+                                            ctx, pool));
+
+  loc->url = svn_path_url_add_component2(repos2_url, "A", pool);
+  SVN_WC__CALL_WITH_WRITE_LOCK(
+    svn_client__repos_to_wc_copy_by_editor(NULL /*sleep*/, svn_node_dir,
+                             loc->url, loc->rev,
+                             svn_dirent_join(wc_path, "A-copied", pool),
+                             ra_session, ctx, pool),
+    ctx->wc_ctx, wc_path, FALSE, pool);
+
+  SVN_ERR(svn_ra_reparent(ra_session, repos2_url, pool));
+  loc->url = svn_path_url_add_component2(repos2_url, "iota", pool);
+  SVN_WC__CALL_WITH_WRITE_LOCK(
+    svn_client__repos_to_wc_copy_by_editor(NULL /*sleep*/, svn_node_file,
+                             loc->url, loc->rev,
+                             svn_dirent_join(wc_path, "iota-copied", pool),
+                             ra_session, ctx, pool),
+    ctx->wc_ctx, wc_path, FALSE, pool);
 
   return SVN_NO_ERROR;
 }
@@ -787,7 +798,7 @@ test_suggest_mergesources(const svn_test
 
   peg_rev.kind = svn_opt_revision_unspecified;
 
-  /* Create a filesytem and repository containing the Greek tree. */
+  /* Create a filesystem and repository containing the Greek tree. */
   SVN_ERR(create_greek_repos(&repos_url, "mergesources", opts, pool));
 
   SVN_ERR(svn_client_create_context(&ctx, pool));
@@ -928,7 +939,7 @@ test_remote_only_status(const svn_test_o
 
   SVN_ERR(svn_stream_mark(contentstream, &start, pool));
 
-  /* Create a filesytem and repository containing the Greek tree. */
+  /* Create a filesystem and repository containing the Greek tree. */
   SVN_ERR(create_greek_repos(&repos_url, "test-remote-only-status", opts, 
pool));
 
   SVN_ERR(svn_client_create_context(&ctx, pool));
@@ -1096,7 +1107,7 @@ test_copy_pin_externals(const svn_test_o
     { NULL },
   };
 
-  /* Create a filesytem and repository containing the Greek tree. */
+  /* Create a filesystem and repository containing the Greek tree. */
   SVN_ERR(create_greek_repos(&repos_url, "pin-externals", opts, pool));
 
   wc_path = svn_test_data_path("pin-externals-working-copy", pool);
@@ -1323,7 +1334,7 @@ test_copy_pin_externals_select_subtree(c
     { NULL },
   };
 
-  /* Create a filesytem and repository containing the Greek tree. */
+  /* Create a filesystem and repository containing the Greek tree. */
   SVN_ERR(create_greek_repos(&repos_url, "pin-externals-select-subtree",
                              opts, pool));
 


Reply via email to