@@ -60,30 +60,65 @@ protected function failureDescription(mixed $other): string
60
60
return 'string matches format description ' ;
61
61
}
62
62
63
+ /**
64
+ * Returns a useful diff with the 'actual' differences
65
+ *
66
+ * The expected string can contain placeholders like %s and %d.
67
+ * By using 'diff' such placeholders compared to the real output are
68
+ * always objectively different, although we don't want to show them as different.
69
+ *
70
+ * This method removes the objective differences by figuring out if an objective
71
+ * difference is allowed by a placeholder.
72
+ *
73
+ * The final result should only contain the differences that caused the failing test.
74
+ */
63
75
protected function additionalFailureDescription (mixed $ other ): string
64
76
{
65
- $ from = explode ("\n" , $ this ->formatDescription );
66
- $ to = explode ("\n" , $ this ->convertNewlines ($ other ));
67
-
68
- foreach ($ from as $ index => $ line ) {
69
- if (isset ($ to [$ index ]) && $ line !== $ to [$ index ]) {
70
- $ line = $ this ->regularExpressionForFormatDescription ($ line );
77
+ $ expected = explode ("\n" , $ this ->formatDescription );
78
+ $ output = explode ("\n" , $ this ->convertNewlines ($ other ));
79
+
80
+ for ($ oIndex = 0 , $ eIndex = 0 , $ length = count ($ output ); $ oIndex < $ length ; $ oIndex ++) {
81
+ $ multiLineMatch = false ;
82
+ if (isset ($ expected [$ eIndex ]) && $ expected [$ eIndex ] !== $ output [$ oIndex ]) {
83
+ $ regEx = $ this ->regularExpressionForFormatDescription ($ expected [$ eIndex ]);
84
+ $ compareTo = $ output [$ oIndex ];
85
+ $ matches = [];
86
+
87
+ // if we do a multiline match we have to consider all following lines as well
88
+ if ($ this ->isMultilineMatch ($ expected [$ eIndex ])) {
89
+ $ multiLineMatch = true ;
90
+ $ compareTo = implode ("\n" , array_slice ($ output , $ oIndex ));
91
+ }
71
92
72
- if (preg_match ($ line , $ to [$ index ]) > 0 ) {
73
- $ from [$ index ] = $ to [$ index ];
93
+ if (preg_match ($ regEx , $ compareTo , $ matches ) > 0 ) {
94
+ $ lines = 1 ;
95
+ // if we matched multiple lines we have to sync $expected and $output
96
+ if ($ multiLineMatch ) {
97
+ $ lines = count (explode ("\n" , $ matches [0 ]));
98
+ }
99
+ // we sync at least one line
100
+ $ expected [$ eIndex ] = $ output [$ oIndex ];
101
+ // for multiline matches we sync the matched lines to $expected
102
+ for ($ i = 1 ; $ i < $ lines ; $ i ++) {
103
+ ++$ eIndex ;
104
+ ++$ oIndex ;
105
+ array_splice ($ expected , $ eIndex , 0 , [$ output [$ oIndex ]]);
106
+ }
74
107
}
75
108
}
109
+ $ eIndex ++;
76
110
}
111
+ $ expectedString = implode ("\n" , $ expected );
112
+ $ outputString = implode ("\n" , $ output );
77
113
78
- $ from = implode ("\n" , $ from );
79
- $ to = implode ("\n" , $ to );
80
-
81
- return $ this ->differ ()->diff ($ from , $ to );
114
+ return $ this ->differ ()->diff ($ expectedString , $ outputString );
82
115
}
83
116
84
117
private function regularExpressionForFormatDescription (string $ string ): string
85
118
{
86
- $ string = strtr (
119
+ // only add the end of string check ($) for single line comparisons
120
+ $ endOfLine = $ this ->isMultilineMatch ($ string ) ? '' : '$ ' ;
121
+ $ string = strtr (
87
122
preg_quote ($ string , '/ ' ),
88
123
[
89
124
'%% ' => '% ' ,
@@ -101,8 +136,7 @@ private function regularExpressionForFormatDescription(string $string): string
101
136
'%0 ' => '\x00 ' ,
102
137
],
103
138
);
104
-
105
- return '/^ ' . $ string . '$/s ' ;
139
+ return '/^ ' . $ string . $ endOfLine . '/s ' ;
106
140
}
107
141
108
142
private function convertNewlines (string $ text ): string
@@ -114,4 +148,9 @@ private function differ(): Differ
114
148
{
115
149
return new Differ (new UnifiedDiffOutputBuilder ("--- Expected \n+++ Actual \n" ));
116
150
}
151
+
152
+ private function isMultilineMatch (string $ line ): bool
153
+ {
154
+ return preg_match ('#%a#i ' , $ line ) > 0 ;
155
+ }
117
156
}
0 commit comments