@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Expand Differential test coverage to include moves, shields, and more

Summary:
See D11468 and D11465. Fixes T5163. Fixes T4105. This makes it practical to test shields, unshielding, moves, etc.

This fixes the issue in D11468, where line maps from whitespace-ignored hunks could have fewer lines than line maps from whitespace-respected hunks, causing a warning.

This encodes the behavior which D11465 changed, making it the canon behavior. Specifically, we do **not** show a shield. I think this is correct. It seems misleading to show "the contents of this file were not changed", because they were changed in both the sense that the file was completely removed, and also changed in the sense that the content itself was (or may have been) changed at the destination. Instead, we just show nothing.

Test Plan:
- Added test coverage.
- Ran tests.
- Used `arc diff --raw --browse` to verify that web behavior was consistent with CLI/test behavior.

Reviewers: joshuaspence, btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T4105, T5163

Differential Revision: https://secure.phabricator.com/D11970

+270 -51
+74 -22
src/applications/differential/__tests__/DifferentialParseRenderTestCase.php
··· 2 2 3 3 final class DifferentialParseRenderTestCase extends PhabricatorTestCase { 4 4 5 + private function getTestDataDirectory() { 6 + return dirname(__FILE__).'/data/'; 7 + } 8 + 5 9 public function testParseRender() { 6 - $dir = dirname(__FILE__).'/data/'; 10 + $dir = $this->getTestDataDirectory(); 7 11 foreach (Filesystem::listDirectory($dir, $show_hidden = false) as $file) { 8 12 if (!preg_match('/\.diff$/', $file)) { 9 13 continue; ··· 22 26 } 23 27 24 28 foreach (array('one', 'two') as $type) { 25 - $parser = $this->buildChangesetParser($type, $data, $file); 26 - $actual = $parser->render(null, null, array()); 29 + $this->runParser($type, $data, $file, 'expect'); 30 + $this->runParser($type, $data, $file, 'unshielded'); 31 + $this->runParser($type, $data, $file, 'whitespace'); 32 + } 33 + } 34 + } 27 35 28 - $expect = Filesystem::readFile($dir.$file.'.'.$type.'.expect'); 29 - $this->assertEqual($expect, (string)$actual, $file.'.'.$type); 36 + private function runParser($type, $data, $file, $extension) { 37 + $dir = $this->getTestDataDirectory(); 38 + $test_file = $dir.$file.'.'.$type.'.'.$extension; 39 + if (!Filesystem::pathExists($test_file)) { 40 + return; 41 + } 42 + 43 + $unshielded = false; 44 + $whitespace = false; 45 + switch ($extension) { 46 + case 'unshielded': 47 + $unshielded = true; 48 + break; 49 + case 'whitespace'; 50 + $unshielded = true; 51 + $whitespace = true; 52 + break; 53 + } 54 + 55 + $parsers = $this->buildChangesetParsers($type, $data, $file); 56 + $actual = $this->renderParsers($parsers, $unshielded, $whitespace); 57 + $expect = Filesystem::readFile($test_file); 58 + 59 + $this->assertEqual($expect, $actual, basename($test_file)); 60 + } 61 + 62 + private function renderParsers(array $parsers, $unshield, $whitespace) { 63 + $result = array(); 64 + foreach ($parsers as $parser) { 65 + if ($unshield) { 66 + $s_range = 0; 67 + $e_range = 0xFFFF; 68 + } else { 69 + $s_range = null; 70 + $e_range = null; 30 71 } 72 + 73 + if ($whitespace) { 74 + $parser->setWhitespaceMode( 75 + DifferentialChangesetParser::WHITESPACE_SHOW_ALL); 76 + } 77 + 78 + $result[] = $parser->render($s_range, $e_range, array()); 31 79 } 80 + return implode(str_repeat('~', 80)."\n", $result); 32 81 } 33 82 34 - private function buildChangesetParser($type, $data, $file) { 83 + private function buildChangesetParsers($type, $data, $file) { 35 84 $parser = new ArcanistDiffParser(); 36 85 $changes = $parser->parseDiff($data); 37 86 38 87 $diff = DifferentialDiff::newFromRawChanges( 39 88 PhabricatorUser::getOmnipotentUser(), 40 89 $changes); 41 - if (count($diff->getChangesets()) !== 1) { 42 - throw new Exception("Expected one changeset: {$file}"); 43 - } 44 90 45 - $changeset = head($diff->getChangesets()); 91 + $changesets = $diff->getChangesets(); 46 92 47 93 $engine = new PhabricatorMarkupEngine(); 48 94 $engine->setViewer(new PhabricatorUser()); 49 95 50 - $cparser = new DifferentialChangesetParser(); 51 - $cparser->setUser(new PhabricatorUser()); 52 - $cparser->setDisableCache(true); 53 - $cparser->setChangeset($changeset); 54 - $cparser->setMarkupEngine($engine); 96 + $parsers = array(); 97 + foreach ($changesets as $changeset) { 98 + $cparser = new DifferentialChangesetParser(); 99 + $cparser->setUser(new PhabricatorUser()); 100 + $cparser->setDisableCache(true); 101 + $cparser->setChangeset($changeset); 102 + $cparser->setMarkupEngine($engine); 55 103 56 - if ($type == 'one') { 57 - $cparser->setRenderer(new DifferentialChangesetOneUpTestRenderer()); 58 - } else if ($type == 'two') { 59 - $cparser->setRenderer(new DifferentialChangesetTwoUpTestRenderer()); 60 - } else { 61 - throw new Exception("Unknown renderer type '{$type}'!"); 104 + if ($type == 'one') { 105 + $cparser->setRenderer(new DifferentialChangesetOneUpTestRenderer()); 106 + } else if ($type == 'two') { 107 + $cparser->setRenderer(new DifferentialChangesetTwoUpTestRenderer()); 108 + } else { 109 + throw new Exception( 110 + pht('Unknown renderer type "%s"!', $type)); 111 + } 112 + 113 + $parsers[] = $cparser; 62 114 } 63 115 64 - return $cparser; 116 + return $parsers; 65 117 } 66 118 67 119 }
+11 -9
src/applications/differential/__tests__/data/fruit.diff.one.expect
··· 1 1 CTYPE 1 1 (unforced) 2 2 - 3 + fruit 3 4 - 4 - N 1 + apple~ 5 - N 2 + banana~ 6 - N 3 + cherry~ 7 - N 4 + date~ 8 - N 5 + elderberry~ 9 - N 6 + fig~ 10 - N 7 + grape~ 11 - N 8 + honeydew~ 12 - N 9 + ~ 5 + N 1 + apple\n~ 6 + N 2 + banana\n~ 7 + N 3 + cherry\n~ 8 + N 4 + date\n~ 9 + N 5 + elderberry\n~ 10 + N 6 + fig\n~ 11 + N 7 + grape\n~ 12 + N 8 + honeydew\n~ 13 + N 9 + \n~ 14 + O X <EMPTY>
+10 -9
src/applications/differential/__tests__/data/fruit.diff.two.expect
··· 1 1 CTYPE 1 1 (unforced) 2 2 - 3 + fruit 3 4 - 4 5 O - . ~ 5 - N 1 + apple~ 6 + N 1 + apple\n~ 6 7 O - . ~ 7 - N 2 + banana~ 8 + N 2 + banana\n~ 8 9 O - . ~ 9 - N 3 + cherry~ 10 + N 3 + cherry\n~ 10 11 O - . ~ 11 - N 4 + date~ 12 + N 4 + date\n~ 12 13 O - . ~ 13 - N 5 + elderberry~ 14 + N 5 + elderberry\n~ 14 15 O - . ~ 15 - N 6 + fig~ 16 + N 6 + fig\n~ 16 17 O - . ~ 17 - N 7 + grape~ 18 + N 7 + grape\n~ 18 19 O - . ~ 19 - N 8 + honeydew~ 20 + N 8 + honeydew\n~ 20 21 O - . ~ 21 - N 9 + ~ 22 + N 9 + \n~
+10
src/applications/differential/__tests__/data/generated.diff
··· 1 + diff --git a/GENERATED b/GENERATED 2 + index 5dcff7f..eff82ef 100644 3 + --- a/GENERATED 4 + +++ b/GENERATED 5 + @@ -1,4 +1,4 @@ 6 + @generated 7 + 8 + -This is a generated file. 9 + +This is a generated file, full of generated code. 10 +
+5
src/applications/differential/__tests__/data/generated.diff.one.expect
··· 1 + CTYPE 2 1 (unforced) 2 + GENERATED 3 + GENERATED 4 + - 5 + SHIELD (default) This file contains generated code, which does not normally need to be reviewed.
+6
src/applications/differential/__tests__/data/generated.diff.one.unshielded
··· 1 + N 1 . @generated\n~ 2 + O 2 - \n~ 3 + N 2 + \n~ 4 + O 3 - This is a generated file.\n~ 5 + N 3 + This is a generated file{(, full of generated code)}.\n~ 6 + N 4 . \n~
+5
src/applications/differential/__tests__/data/generated.diff.two.expect
··· 1 + CTYPE 2 1 (unforced) 2 + GENERATED 3 + GENERATED 4 + - 5 + SHIELD (default) This file contains generated code, which does not normally need to be reviewed.
+8
src/applications/differential/__tests__/data/generated.diff.two.unshielded
··· 1 + O 1 . @generated\n~ 2 + N 1 . @generated\n~ 3 + O 2 - \n~ 4 + N 2 + \n~ 5 + O 3 - This is a generated file.\n~ 6 + N 3 + This is a generated file{(, full of generated code)}.\n~ 7 + O 4 . \n~ 8 + N 4 . \n~
+4
src/applications/differential/__tests__/data/move-unedited.diff
··· 1 + diff --git a/SRC b/DST 2 + similarity index 100% 3 + rename from SRC 4 + rename to DST
+10
src/applications/differential/__tests__/data/move-unedited.diff.one.expect
··· 1 + CTYPE 6 1 (unforced) 2 + SRC 3 + DST 4 + - 5 + SHIELD (none) The contents of this file were not changed. 6 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 7 + CTYPE 4 1 (forced) 8 + - 9 + SRC 10 + DST
+10
src/applications/differential/__tests__/data/move-unedited.diff.two.expect
··· 1 + CTYPE 6 1 (unforced) 2 + SRC 3 + DST 4 + - 5 + SHIELD (none) The contents of this file were not changed. 6 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 7 + CTYPE 4 1 (forced) 8 + - 9 + SRC 10 + DST
+12
src/applications/differential/__tests__/data/move.diff
··· 1 + diff --git a/SRC b/DST 2 + similarity index 57% 3 + rename from SRC 4 + rename to DST 5 + index 5d5c76f..f7bd789 100644 6 + --- a/SRC 7 + +++ b/DST 8 + @@ -1,3 +1,3 @@ 9 + Apples 10 + -Bananas 11 + +Blueberries 12 + Cherries
+13
src/applications/differential/__tests__/data/move.diff.one.expect
··· 1 + CTYPE 6 1 (unforced) 2 + SRC 3 + DST 4 + - 5 + N 1 . Apples\n~ 6 + O 2 - B{(anana)}s\n~ 7 + N 2 + B{(lueberrie)}s\n~ 8 + N 3 . Cherries\n~ 9 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 10 + CTYPE 4 1 (forced) 11 + - 12 + SRC 13 + DST
+15
src/applications/differential/__tests__/data/move.diff.two.expect
··· 1 + CTYPE 6 1 (unforced) 2 + SRC 3 + DST 4 + - 5 + O 1 . Apples\n~ 6 + N 1 . Apples\n~ 7 + O 2 - B{(anana)}s\n~ 8 + N 2 + B{(lueberrie)}s\n~ 9 + O 3 . Cherries\n~ 10 + N 3 . Cherries\n~ 11 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 12 + CTYPE 4 1 (forced) 13 + - 14 + SRC 15 + DST
+7
src/applications/differential/__tests__/data/whitespace.diff
··· 1 + diff --git a/WHITESPACE b/WHITESPACE 2 + index 4505d51..bca7deb 100644 3 + --- a/WHITESPACE 4 + +++ b/WHITESPACE 5 + @@ -1 +1 @@ 6 + --=[-Rocket-Ship> 7 + + -=[-Rocket-Ship>
+5
src/applications/differential/__tests__/data/whitespace.diff.one.expect
··· 1 + CTYPE 2 1 (unforced) 2 + WHITESPACE 3 + WHITESPACE 4 + - 5 + SHIELD (whitespace) This file was changed only by adding or removing whitespace.
+2
src/applications/differential/__tests__/data/whitespace.diff.one.whitespace
··· 1 + O 1 - -=[-Rocket-Ship>\n~ 2 + N 1 + {( )}-=[-Rocket-Ship>\n~
+5
src/applications/differential/__tests__/data/whitespace.diff.two.expect
··· 1 + CTYPE 2 1 (unforced) 2 + WHITESPACE 3 + WHITESPACE 4 + - 5 + SHIELD (whitespace) This file was changed only by adding or removing whitespace.
+2
src/applications/differential/__tests__/data/whitespace.diff.two.whitespace
··· 1 + O 1 - -=[-Rocket-Ship>\n~ 2 + N 1 + {( )}-=[-Rocket-Ship>\n~
+11 -8
src/applications/differential/parser/DifferentialChangesetParser.php
··· 612 612 $moveaway = false; 613 613 $changetype = $this->changeset->getChangeType(); 614 614 if ($changetype == DifferentialChangeType::TYPE_MOVE_AWAY) { 615 - // sometimes we show moved files as unchanged, sometimes deleted, 616 - // and sometimes inconsistent with what actually happened at the 617 - // destination of the move. Rather than make a false claim, 618 - // omit the 'not changed' notice if this is the source of a move 619 - $unchanged = false; 620 615 $moveaway = true; 621 616 } 622 617 ··· 776 771 pht( 777 772 'This file contains generated code, which does not normally '. 778 773 'need to be reviewed.')); 774 + } else if ($this->isMoveAway()) { 775 + // We put an empty shield on these files. Normally, they do not have 776 + // any diff content anyway. However, if they come through `arc`, they 777 + // may have content. We don't want to show it (it's not useful) and 778 + // we bailed out of fully processing it earlier anyway. 779 + 780 + // We could show a message like "this file was moved", but we show 781 + // that as a change header anyway, so it would be redundant. Instead, 782 + // just render an empty shield to skip rendering the diff body. 783 + $shield = ''; 779 784 } else if ($this->isUnchanged()) { 780 785 $type = 'text'; 781 786 if (!$rows) { ··· 791 796 $shield = $renderer->renderShield( 792 797 pht('The contents of this file were not changed.'), 793 798 $type); 794 - } else if ($this->isMoveAway()) { 795 - $shield = null; 796 799 } else if ($this->isWhitespaceOnly()) { 797 800 $shield = $renderer->renderShield( 798 801 pht('This file was changed only by adding or removing whitespace.'), ··· 809 812 } 810 813 } 811 814 812 - if ($shield) { 815 + if ($shield !== null) { 813 816 return $renderer->renderChangesetTable($shield); 814 817 } 815 818
+2 -2
src/applications/differential/parser/DifferentialHunkParser.php
··· 96 96 public function setOldLineTypeMap(array $map) { 97 97 $lines = $this->getOldLines(); 98 98 foreach ($lines as $key => $data) { 99 - $lines[$key]['type'] = $map[$data['line']]; 99 + $lines[$key]['type'] = idx($map, $data['line']); 100 100 } 101 101 $this->oldLines = $lines; 102 102 return $this; ··· 117 117 public function setNewLineTypeMap(array $map) { 118 118 $lines = $this->getNewLines(); 119 119 foreach ($lines as $key => $data) { 120 - $lines[$key]['type'] = $map[$data['line']]; 120 + $lines[$key]['type'] = idx($map, $data['line']); 121 121 } 122 122 $this->newLines = $lines; 123 123 return $this;
+43 -1
src/applications/differential/render/DifferentialChangesetTestRenderer.php
··· 7 7 $changeset = $this->getChangeset(); 8 8 9 9 $old = nonempty($changeset->getOldFile(), '-'); 10 + $current = nonempty($changeset->getFilename(), '-'); 10 11 $away = nonempty(implode(', ', $changeset->getAwayPaths()), '-'); 12 + 11 13 $ctype = $changeset->getChangeType(); 12 14 $ftype = $changeset->getFileType(); 13 15 $force = ($force ? '(forced)' : '(unforced)'); 14 16 15 17 return "CTYPE {$ctype} {$ftype} {$force}\n". 16 18 "{$old}\n". 19 + "{$current}\n". 17 20 "{$away}\n"; 18 21 } 19 22 ··· 47 50 48 51 $out = array(); 49 52 53 + $any_old = false; 54 + $any_new = false; 50 55 $primitives = $this->buildPrimitives($range_start, $range_len); 51 56 foreach ($primitives as $p) { 52 57 $type = $p['type']; 53 58 switch ($type) { 54 59 case 'old': 55 60 case 'new': 61 + if ($type == 'old') { 62 + $any_old = true; 63 + } 64 + if ($type == 'new') { 65 + $any_new = true; 66 + } 56 67 $num = nonempty($p['line'], '-'); 57 68 $render = $p['render']; 58 69 $htype = nonempty($p['htype'], '.'); 59 70 60 71 // TODO: This should probably happen earlier, whenever we deal with 61 72 // \r and \t normalization? 62 - $render = rtrim($render, "\r\n"); 73 + $render = str_replace( 74 + array( 75 + "\r", 76 + "\n", 77 + ), 78 + array( 79 + '\\r', 80 + '\\n', 81 + ), 82 + $render); 83 + 84 + $render = str_replace( 85 + array( 86 + '<span class="bright">', 87 + '</span>', 88 + ), 89 + array( 90 + '{(', 91 + ')}', 92 + ), 93 + $render); 94 + 95 + $render = html_entity_decode($render); 96 + 63 97 $t = ($type == 'old') ? 'O' : 'N'; 64 98 65 99 $out[] = "{$t} {$num} {$htype} {$render}~"; ··· 68 102 $out[] = $type; 69 103 break; 70 104 } 105 + } 106 + 107 + if (!$any_old) { 108 + $out[] = 'O X <EMPTY>'; 109 + } 110 + 111 + if (!$any_new) { 112 + $out[] = 'N X <EMPTY>'; 71 113 } 72 114 73 115 $out = implode("\n", $out)."\n";