1<?php
2
3/**
4 * Test cases for @{class:ArcanistDiffParser}.
5 */
6final class ArcanistDiffParserTestCase extends PhutilTestCase {
7
8  public function testParser() {
9    $root = dirname(__FILE__).'/diff/';
10    foreach (Filesystem::listDirectory($root, $hidden = false) as $file) {
11      $this->parseDiff($root.$file);
12    }
13  }
14
15  private function parseDiff($diff_file) {
16    $contents = Filesystem::readFile($diff_file);
17    $file = basename($diff_file);
18
19    $parser = new ArcanistDiffParser();
20    $changes = $parser->parseDiff($contents);
21
22    switch ($file) {
23      case 'colorized.hggitdiff':
24        $this->assertEqual(1, count($changes));
25        break;
26      case 'basic-missing-both-newlines-plus.udiff':
27      case 'basic-missing-both-newlines.udiff':
28      case 'basic-missing-new-newline-plus.udiff':
29      case 'basic-missing-new-newline.udiff':
30      case 'basic-missing-old-newline-plus.udiff':
31      case 'basic-missing-old-newline.udiff':
32        $expect_old = strpos($file, '-old-') || strpos($file, '-both-');
33        $expect_new = strpos($file, '-new-') || strpos($file, '-both-');
34        $expect_two = strpos($file, '-plus');
35
36        $this->assertEqual(count($changes), $expect_two ? 2 : 1);
37        $change = reset($changes);
38        $this->assertTrue($change !== null);
39
40        $hunks = $change->getHunks();
41        $this->assertEqual(1, count($hunks));
42
43        $hunk = reset($hunks);
44        $this->assertEqual((bool)$expect_old, $hunk->getIsMissingOldNewline());
45        $this->assertEqual((bool)$expect_new, $hunk->getIsMissingNewNewline());
46        break;
47      case 'basic-binary.udiff':
48        $this->assertEqual(1, count($changes));
49        $change = reset($changes);
50        $this->assertEqual(
51          ArcanistDiffChangeType::FILE_BINARY,
52          $change->getFileType());
53        break;
54      case 'basic-multi-hunk.udiff':
55        $this->assertEqual(1, count($changes));
56        $change = reset($changes);
57        $hunks = $change->getHunks();
58        $this->assertEqual(4, count($hunks));
59        $this->assertEqual('right', $change->getCurrentPath());
60        $this->assertEqual('left', $change->getOldPath());
61        break;
62      case 'basic-multi-hunk-content.svndiff':
63        $this->assertEqual(1, count($changes));
64        $change = reset($changes);
65        $hunks = $change->getHunks();
66        $this->assertEqual(2, count($hunks));
67
68        $there_is_a_literal_trailing_space_here = ' ';
69
70        $corpus_0 = <<<EOCORPUS
71 asdfasdf
72+% quack
73 %
74-%
75 %%
76 %%
77 %%%
78
79EOCORPUS;
80        $corpus_1 = <<<EOCORPUS
81 %%%%%
82 %%%%%
83{$there_is_a_literal_trailing_space_here}
84-!
85+! quack
86
87EOCORPUS;
88        $this->assertEqual(
89          $corpus_0,
90          $hunks[0]->getCorpus());
91        $this->assertEqual(
92          $corpus_1,
93          $hunks[1]->getCorpus());
94        break;
95      case 'svn-ignore-whitespace-only.svndiff':
96        $this->assertEqual(2, count($changes));
97        $hunks = reset($changes)->getHunks();
98        $this->assertEqual(0, count($hunks));
99        break;
100      case 'svn-property-add.svndiff':
101        $this->assertEqual(1, count($changes));
102        $change = reset($changes);
103        $hunks = reset($changes)->getHunks();
104        $this->assertEqual(1, count($hunks));
105        $this->assertEqual(
106          array(
107            'duck' => 'quack',
108          ),
109          $change->getNewProperties());
110        break;
111      case 'svn-property-modify.svndiff':
112        $this->assertEqual(2, count($changes));
113
114        $change = array_shift($changes);
115        $this->assertEqual(0, count($change->getHunks()));
116        $this->assertEqual(
117          array(
118            'svn:ignore' => '*.phpz',
119          ),
120          $change->getOldProperties());
121        $this->assertEqual(
122          array(
123            'svn:ignore' => '*.php',
124          ),
125          $change->getNewProperties());
126
127        $change = array_shift($changes);
128        $this->assertEqual(0, count($change->getHunks()));
129        $this->assertEqual(
130          array(
131            'svn:special' => '*',
132          ),
133          $change->getOldProperties());
134        $this->assertEqual(
135          array(
136            'svn:special' => 'moo',
137          ),
138          $change->getNewProperties());
139        break;
140      case 'svn-property-delete.svndiff':
141        $this->assertEqual(1, count($changes));
142        $change = reset($changes);
143
144        $this->assertEqual(0, count($change->getHunks()));
145        $this->assertEqual(
146          $change->getOldProperties(),
147          array(
148            'svn:special' => '*',
149          ));
150        $this->assertEqual(
151          array(
152          ),
153          $change->getNewProperties());
154        break;
155      case 'svn-property-merged.svndiff':
156        $this->assertEqual(1, count($changes));
157        $change = reset($changes);
158
159        $this->assertEqual(count($change->getHunks()), 0);
160
161        $this->assertEqual(
162          $change->getOldProperties(),
163          array());
164        $this->assertEqual(
165          $change->getNewProperties(),
166          array());
167        break;
168      case 'svn-property-merge.svndiff':
169        $this->assertEqual(1, count($changes));
170        $change = reset($changes);
171
172        $this->assertEqual(count($change->getHunks()), 0);
173        $this->assertEqual(
174          $change->getOldProperties(),
175          array(
176          ));
177        $this->assertEqual(
178          $change->getNewProperties(),
179          array(
180            'svn:mergeinfo' => <<<EOTEXT
181Merged /tfb/branches/internmove/www/html/js/help/UIFaq.js:r83462-126155
182Merged /tfb/branches/ads-create-v3/www/html/js/help/UIFaq.js:r140558-142418
183EOTEXT
184          ));
185        break;
186      case 'svn-property-older-than-1.5.svndiff':
187        // In SVN 1.5, the format for property diffs changed to use the words
188        // "Added", "Deleted" and "Modified" instead of "Name". This is an old
189        // property change diff which uses "Name".
190        $this->assertEqual(1, count($changes));
191        $change = reset($changes);
192
193        $this->assertEqual(count($change->getHunks()), 0);
194        $this->assertEqual(
195          $change->getOldProperties(),
196          array(
197          ));
198        $this->assertEqual(
199          $change->getNewProperties(),
200          array(
201            'svn:executable' => '*',
202          ));
203        break;
204      case 'svn-binary-add.svndiff':
205        $this->assertEqual(1, count($changes));
206        $change = reset($changes);
207        $this->assertEqual(
208          ArcanistDiffChangeType::FILE_BINARY,
209          $change->getFileType());
210        $this->assertEqual(0, count($change->getHunks()));
211        $this->assertEqual(
212          array(
213            'svn:mime-type' => 'application/octet-stream',
214          ),
215          $change->getNewProperties());
216        break;
217      case 'svn-binary-diff.svndiff':
218      case 'svn-binary-diff-freebsd.svndiff':
219        $this->assertEqual(1, count($changes));
220        $change = reset($changes);
221        $this->assertEqual(
222          ArcanistDiffChangeType::FILE_BINARY,
223          $change->getFileType());
224        $this->assertEqual(count($change->getHunks()), 0);
225        break;
226      case 'git-delete-file.gitdiff':
227        $this->assertEqual(1, count($changes));
228        $change = reset($changes);
229        $this->assertEqual(
230          ArcanistDiffChangeType::TYPE_DELETE,
231          $change->getType());
232        $this->assertEqual(
233          'scripts/intern/test/testfile2',
234          $change->getCurrentPath());
235        $this->assertEqual(1, count($change->getHunks()));
236        break;
237      case 'git-binary-change.gitdiff':
238        $this->assertEqual(1, count($changes));
239        $change = reset($changes);
240        $this->assertEqual(
241          ArcanistDiffChangeType::FILE_BINARY,
242          $change->getFileType());
243        $this->assertEqual(0, count($change->getHunks()));
244        break;
245      case 'git-filemode-change.gitdiff':
246        $this->assertEqual(1, count($changes));
247        $change = reset($changes);
248        $this->assertEqual(1, count($change->getHunks()));
249        $this->assertEqual(
250          array(
251            'unix:filemode' => '100644',
252          ),
253          $change->getOldProperties());
254        $this->assertEqual(
255          array(
256            'unix:filemode' => '100755',
257          ),
258          $change->getNewProperties());
259        break;
260      case 'git-filemode-change-only.gitdiff':
261        $this->assertEqual(count($changes), 2);
262        $change = reset($changes);
263        $this->assertEqual(count($change->getHunks()), 0);
264        $this->assertEqual(
265          array(
266            'unix:filemode' => '100644',
267          ),
268          $change->getOldProperties());
269        $this->assertEqual(
270          array(
271            'unix:filemode' => '100755',
272          ),
273          $change->getNewProperties());
274        break;
275      case 'svn-empty-file.svndiff':
276        $this->assertEqual(2, count($changes));
277        $change = array_shift($changes);
278        $this->assertEqual(0, count($change->getHunks()));
279        break;
280      case 'git-ignore-whitespace-only.gitdiff':
281        $this->assertEqual(count($changes), 2);
282
283        $change = array_shift($changes);
284        $this->assertEqual(count($change->getHunks()), 0);
285        $this->assertEqual(
286          $change->getOldPath(),
287          'scripts/intern/test/testfile2');
288        $this->assertEqual(
289          $change->getCurrentPath(),
290          'scripts/intern/test/testfile2');
291
292        $change = array_shift($changes);
293        $this->assertEqual(count($change->getHunks()), 1);
294        $this->assertEqual(
295          $change->getOldPath(),
296          'scripts/intern/test/testfile3');
297        $this->assertEqual(
298          $change->getCurrentPath(),
299          'scripts/intern/test/testfile3');
300        break;
301      case 'git-move.gitdiff':
302      case 'git-move-edit.gitdiff':
303      case 'git-move-plus.gitdiff':
304
305        $extra_changeset = (bool)strpos($file, '-plus');
306        $has_hunk = (bool)strpos($file, '-edit');
307
308        $this->assertEqual($extra_changeset ? 3 : 2, count($changes));
309
310        $change = array_shift($changes);
311        $this->assertEqual($has_hunk ? 1 : 0,
312                          count($change->getHunks()));
313        $this->assertEqual(
314          $change->getType(),
315          ArcanistDiffChangeType::TYPE_MOVE_HERE);
316
317        $target = $change;
318
319        $change = array_shift($changes);
320        $this->assertEqual(0, count($change->getHunks()));
321        $this->assertEqual(
322          ArcanistDiffChangeType::TYPE_MOVE_AWAY,
323          $change->getType());
324
325        $this->assertEqual(
326          $change->getCurrentPath(),
327          $target->getOldPath());
328        $this->assertTrue(
329          in_array($target->getCurrentPath(), $change->getAwayPaths()));
330        break;
331      case 'git-merge-header.gitdiff':
332        $this->assertEqual(1, count($changes));
333        $change = reset($changes);
334        $this->assertEqual(
335          ArcanistDiffChangeType::TYPE_MESSAGE,
336          $change->getType());
337        $this->assertEqual(
338          '501f6d519703458471dbea6284ec5f49d1408598',
339          $change->getCommitHash());
340        break;
341      case 'git-new-file.gitdiff':
342        $this->assertEqual(1, count($changes));
343        $change = reset($changes);
344        $this->assertEqual(
345          ArcanistDiffChangeType::TYPE_ADD,
346          $change->getType());
347        break;
348      case 'git-copy.gitdiff':
349        $this->assertEqual(2, count($changes));
350
351        $change = array_shift($changes);
352        $this->assertEqual(0, count($change->getHunks()));
353        $this->assertEqual(
354          ArcanistDiffChangeType::TYPE_COPY_HERE,
355          $change->getType());
356        $this->assertEqual(
357          'flib/intern/widgets/ui/UIWidgetRSSBox.php',
358          $change->getCurrentPath());
359
360        $change = array_shift($changes);
361        $this->assertEqual(0, count($change->getHunks()));
362        $this->assertEqual(
363          ArcanistDiffChangeType::TYPE_COPY_AWAY,
364          $change->getType());
365        $this->assertEqual(
366          'lib/display/intern/ui/widget/UIWidgetRSSBox.php',
367          $change->getCurrentPath());
368
369        break;
370      case 'git-copy-plus.gitdiff':
371        $this->assertEqual(2, count($changes));
372
373        $change = array_shift($changes);
374        $this->assertEqual(3, count($change->getHunks()));
375        $this->assertEqual(
376          ArcanistDiffChangeType::TYPE_COPY_HERE,
377          $change->getType());
378        $this->assertEqual(
379          'flib/intern/widgets/ui/UIWidgetGraphConnect.php',
380          $change->getCurrentPath());
381
382        $change = array_shift($changes);
383        $this->assertEqual(0, count($change->getHunks()));
384        $this->assertEqual(
385          ArcanistDiffChangeType::TYPE_COPY_AWAY,
386          $change->getType());
387        $this->assertEqual(
388          'lib/display/intern/ui/widget/UIWidgetLunchtime.php',
389          $change->getCurrentPath());
390        break;
391      case 'svn-property-multiline.svndiff':
392        $this->assertEqual(1, count($changes));
393        $change = array_shift($changes);
394
395        $this->assertEqual(0, count($change->getHunks()));
396        $this->assertEqual(
397          array(
398            'svn:ignore' => 'tags',
399          ),
400          $change->getOldProperties());
401        $this->assertEqual(
402          array(
403            'svn:ignore' => "tags\nasdf\nlol\nwhat",
404          ),
405          $change->getNewProperties());
406        break;
407      case 'git-empty-files.gitdiff':
408        $this->assertEqual(2, count($changes));
409        while ($change = array_shift($changes)) {
410          $this->assertEqual(0, count($change->getHunks()));
411        }
412        break;
413      case 'git-mnemonicprefix.gitdiff':
414        // Check parsing of diffs created with `diff.mnemonicprefix`
415        // configuration option set to `true`.
416        $this->assertEqual(1, count($changes));
417        $this->assertEqual(1, count(reset($changes)->getHunks()));
418        break;
419      case 'git-commit.gitdiff':
420      case 'git-commit-logdecorate.gitdiff':
421        $this->assertEqual(1, count($changes));
422        $change = reset($changes);
423        $this->assertEqual(
424          ArcanistDiffChangeType::TYPE_MESSAGE,
425          $change->getType());
426        $this->assertEqual(
427          '76e2f1339c298c748aa0b52030799ed202a6537b',
428          $change->getCommitHash());
429        $this->assertEqual(
430          <<<EOTEXT
431
432Deprecating UIActionButton (Part 1)
433
434Summary: Replaces calls to UIActionButton with <ui:button>.  I tested most
435         of these calls, but there were some that I didn't know how to
436         reach, so if you are one of the owners of this code, please test
437         your feature in my sandbox: www.ngao.devrs013.facebook.com
438
439         @brosenthal, I removed some logic that was setting a disabled state
440         on a UIActionButton, which is actually a no-op.
441
442Reviewed By: brosenthal
443
444Other Commenters: sparker, egiovanola
445
446Test Plan: www.ngao.devrs013.facebook.com
447
448           Explicitly tested:
449           * ads creation flow (add keyword)
450           * ads manager (conversion tracking)
451           * help center (create a discussion)
452           * new user wizard (next step button)
453
454Revert: OK
455
456DiffCamp Revision: 94064
457
458git-svn-id: svn+ssh://tubbs/svnroot/tfb/trunk/www@223593 2c7ba8d8
459EOTEXT
460          , $change->getMetadata('message'));
461        break;
462      case 'git-binary.gitdiff':
463        $this->assertEqual(1, count($changes));
464        $change = reset($changes);
465        $this->assertEqual(
466          ArcanistDiffChangeType::TYPE_CHANGE,
467          $change->getType());
468        $this->assertEqual(
469          ArcanistDiffChangeType::FILE_BINARY,
470          $change->getFileType());
471        break;
472      case 'git-odd-filename.gitdiff':
473        $this->assertEqual(2, count($changes));
474        $change = reset($changes);
475        $this->assertEqual(
476          'old/'."\342\210\206".'.jpg',
477          $change->getOldPath());
478        $this->assertEqual(
479          'new/'."\342\210\206".'.jpg',
480          $change->getCurrentPath());
481        break;
482      case 'hg-binary-change.hgdiff':
483      case 'hg-solo-binary-change.hgdiff':
484        $this->assertEqual(1, count($changes));
485        $change = reset($changes);
486        $this->assertEqual(
487          ArcanistDiffChangeType::TYPE_ADD,
488          $change->getType());
489        $this->assertEqual(
490          ArcanistDiffChangeType::FILE_BINARY,
491          $change->getFileType());
492        break;
493      case 'hg-binary-delete.hgdiff':
494        $this->assertEqual(1, count($changes));
495        $change = reset($changes);
496        $this->assertEqual(
497          ArcanistDiffChangeType::TYPE_DELETE,
498          $change->getType());
499        $this->assertEqual(
500          ArcanistDiffChangeType::FILE_BINARY,
501          $change->getFileType());
502        break;
503      case 'git-replace-symlink.gitdiff':
504        $this->assertEqual(1, count($changes));
505        $change = array_shift($changes);
506        $this->assertEqual(
507          ArcanistDiffChangeType::TYPE_CHANGE,
508          $change->getType());
509        break;
510      case 'svn-1.7-property-added.svndiff':
511        $this->assertEqual(1, count($changes));
512        $change = head($changes);
513        $new_properties = $change->getNewProperties();
514        $this->assertEqual(2, count($new_properties));
515        $this->assertEqual('*', idx($new_properties, 'svn:executable'));
516        $this->assertEqual('text/html', idx($new_properties, 'svn:mime-type'));
517        break;
518      case 'hg-diff-range.hgdiff':
519        $this->assertEqual(1, count($changes));
520        $change = array_shift($changes);
521        $this->assertEqual(
522          'Test.java',
523          $change->getOldPath());
524        $this->assertEqual(
525          'Test.java',
526          $change->getCurrentPath());
527        break;
528      case 'hg-patch.hgdiff':
529        $this->assertEqual(1, count($changes));
530        break;
531      case 'hg-patch-git.hgdiff':
532        $this->assertEqual(1, count($changes));
533        break;
534      case 'custom-prefixes.gitdiff':
535        $this->assertEqual(1, count($changes));
536        $change = head($changes);
537        $this->assertEqual(
538          'file',
539          $change->getCurrentPath());
540        break;
541      case 'custom-prefixes-edit.gitdiff':
542        $this->assertEqual(1, count($changes));
543        $change = head($changes);
544        $this->assertEqual(
545          'file',
546          $change->getCurrentPath());
547        break;
548      case 'more-newlines.svndiff':
549        $this->assertEqual(1, count($changes));
550        break;
551      case 'suppress-blank-empty.gitdiff':
552        $this->assertEqual(1, count($changes));
553        break;
554      case 'svn-property-windows.svndiff':
555        $this->assertEqual(1, count($changes));
556        break;
557      case 'rcs-addline.rcsdiff':
558        $this->assertEqual(1, count($changes));
559        $change = array_shift($changes);
560        $this->assertEqual(
561          ArcanistDiffChangeType::TYPE_CHANGE,
562          $change->getType());
563        break;
564      case 'rcs-deleteline.rcsdiff':
565        $this->assertEqual(1, count($changes));
566        $change = array_shift($changes);
567        $this->assertEqual(
568          ArcanistDiffChangeType::TYPE_CHANGE,
569          $change->getType());
570        break;
571      case 'comment.svndiff':
572        $this->assertEqual(1, count($changes));
573        $change = array_shift($changes);
574        $this->assertEqual(
575          ArcanistDiffChangeType::TYPE_CHANGE,
576          $change->getType());
577        break;
578      case 'svnlook-basics.svndiff':
579      case 'svnlook-add.svndiff':
580      case 'svnlook-delete.svndiff':
581      case 'svnlook-copied.svndiff':
582        $this->assertEqual(1, count($changes));
583        break;
584      case 'git-format-patch.gitdiff':
585        $this->assertEqual(2, count($changes));
586
587        $change = array_shift($changes);
588        $this->assertEqual(
589          ArcanistDiffChangeType::TYPE_MESSAGE,
590          $change->getType());
591        $this->assertEqual('WIP', $change->getMetadata('message'));
592
593        $change = array_shift($changes);
594        $this->assertEqual(
595          ArcanistDiffChangeType::TYPE_CHANGE,
596          $change->getType());
597        break;
598      case 'svn-double-diff.svndiff':
599        $this->assertEqual(1, count($changes));
600
601        $change = array_shift($changes);
602        $hunks = $change->getHunks();
603        $this->assertEqual(1, count($hunks));
604        break;
605      case 'git-remove-spaces.gitdiff':
606        $this->assertEqual(1, count($changes));
607
608        $change = array_shift($changes);
609        $this->assertEqual('file with spaces.txt', $change->getOldPath());
610        break;
611      default:
612        throw new Exception(pht('No test block for diff file %s.', $diff_file));
613        break;
614    }
615  }
616
617  public function testGitCommonFilenameExtraction() {
618    static $tests = array(
619      'a/filename.c b/filename.c'         => 'filename.c',
620      "a/filename.c b/filename.c\n"       => 'filename.c',
621      "a/filename.c b/filename.c\r\n"     => 'filename.c',
622      'filename.c filename.c'             => 'filename.c',
623      '1/filename.c 2/filename.c'         => 'filename.c',
624      '"a/\\"quotes\\"" "b/\\"quotes\\""' => '"quotes"',
625      '"a/\\"quotes and spaces\\"" "b/\\"quotes and spaces\\""' =>
626        '"quotes and spaces"',
627      '"a/\\342\\230\\203" "b/\\342\\230\\203"' =>
628         "\xE2\x98\x83",
629      'a/Core Data/filename.c b/Core Data/filename.c' =>
630         'Core Data/filename.c',
631      'some file with spaces.c some file with spaces.c' =>
632         'some file with spaces.c',
633      '"foo bar.c" foo bar.c'        => 'foo bar.c',
634      '"a/foo bar.c" b/foo bar.c'    => 'foo bar.c',
635      'src/file dst/file'            => 'file',
636
637      // Renames are handled by the "rename from ..." lines later in
638      // the diff, for simplicity of parsing; this is also how git
639      // itself handles it.
640      'a/foo.c b/bar.c'              => null,
641      'a/foo bar.c b/baz troz.c'     => null,
642      '"a/foo bar.c" b/baz troz.c'   => null,
643      'a/foo bar.c "b/baz troz.c"'   => null,
644      '"a/foo bar.c" "b/baz troz.c"' => null,
645      'filename file with spaces.c filename file with spaces.c' =>
646        'filename file with spaces.c',
647    );
648
649    foreach ($tests as $input => $expect) {
650      $result = ArcanistDiffParser::extractGitCommonFilename($input);
651      $this->assertEqual(
652        $expect,
653        $result,
654        pht('Split: %s', $input));
655    }
656  }
657
658
659  public function runSingleRename($diffline, $from, $to, $old, $new) {
660    $str = "diff --git $diffline\nsimilarity index 95%\n"
661         ."rename from $from\nrename to $to\n";
662    $parser = new ArcanistDiffParser();
663    $changes = $parser->parseDiff($str);
664    $this->assertTrue(
665      $changes !== null,
666      pht("Parsed:\n%s", $str));
667    $this->assertEqual(
668      $old == $new ? 1 : 2, count($changes),
669      pht("Parsed one change:\n%s", $str));
670    $change = reset($changes);
671    $this->assertEqual(
672      array($old, $new),
673      array($change->getOldPath(), $change->getCurrentPath()),
674      pht('Split: %s', $diffline));
675  }
676
677  public function testGitRenames() {
678    $this->runSingleRename('a/old.c b/new.c',
679                           'old.c',                   'new.c',
680                           'old.c',                   'new.c');
681    $this->runSingleRename('old.c new.c',
682                           'old.c',                   'new.c',
683                           'old.c',                   'new.c');
684    $this->runSingleRename('1/old.c 2/new.c',
685                           'old.c',                   'new.c',
686                           'old.c',                   'new.c');
687    $this->runSingleRename('from/file.c to/file.c',
688                           'from/file.c',             'to/file.c',
689                           'from/file.c',             'to/file.c');
690    $this->runSingleRename('"a/\\"quotes1\\"" "b/\\"quotes2\\""',
691                           '"\\"quotes1\\""',         '"\\"quotes2\\""',
692                           '"quotes1"',               '"quotes2"');
693    $this->runSingleRename('"a/\\"quotes spaces1\\"" "b/\\"quotes spaces2\\""',
694                           '"\\"quotes spaces1\\""',  '"\\"quotes spaces2\\""',
695                           '"quotes spaces1"',        '"quotes spaces2"');
696    $this->runSingleRename('"a/\\342\\230\\2031" "b/\\342\\230\\2032"',
697                           '"\\342\\230\\2031"',      '"\\342\\230\\2032"',
698                           "\xE2\x98\x831",           "\xE2\x98\x832");
699    $this->runSingleRename('a/Core Data/old.c b/Core Data/new.c',
700                           'Core Data/old.c',         'Core Data/new.c',
701                           'Core Data/old.c',         'Core Data/new.c');
702    $this->runSingleRename('file with spaces.c file with spaces.c',
703                           'file with spaces.c', 'file with spaces.c',
704                           'file with spaces.c', 'file with spaces.c');
705    $this->runSingleRename('a/non-quoted filename.c "b/quoted filename.c"',
706                           'non-quoted filename.c',   '"quoted filename.c"',
707                           'non-quoted filename.c',   'quoted filename.c');
708    $this->runSingleRename('non-quoted filename.c "quoted filename.c"',
709                           'non-quoted filename.c',   '"quoted filename.c"',
710                           'non-quoted filename.c',   'quoted filename.c');
711    $this->runSingleRename('"a/quoted filename.c" b/non quoted filename.c',
712                           '"quoted filename.c"',     'non quoted filename.c',
713                           'quoted filename.c',       'non quoted filename.c');
714    $this->runSingleRename('"quoted filename.c" non-quoted filename.c',
715                           '"quoted filename.c"',     'non-quoted filename.c',
716                           'quoted filename.c',       'non-quoted filename.c');
717    $this->runSingleRename('old file with spaces.c new file with spaces.c',
718                           'old file with spaces.c',  'new file with spaces.c',
719                           'old file with spaces.c',  'new file with spaces.c');
720    $this->runSingleRename('old file old file',
721                           'old file old',            'file',
722                           'old file old',            'file');
723  }
724}
725