3939import static org .elasticsearch .xpack .esql .core .util .NumericUtils .unsignedLongAsNumber ;
4040import static org .elasticsearch .xpack .esql .core .util .SpatialCoordinateTypes .CARTESIAN ;
4141import static org .elasticsearch .xpack .esql .core .util .SpatialCoordinateTypes .GEO ;
42+ import static org .hamcrest .MatcherAssert .assertThat ;
4243import static org .hamcrest .Matchers .instanceOf ;
4344import static org .junit .Assert .assertEquals ;
44- import static org .junit .Assert .assertThat ;
4545import static org .junit .Assert .fail ;
4646
4747public final class CsvAssert {
@@ -197,11 +197,7 @@ public static void assertData(
197197 for (int row = 0 ; row < expectedValues .size (); row ++) {
198198 try {
199199 if (row >= actualValues .size ()) {
200- if (dataFailures .isEmpty ()) {
201- fail ("Expected more data but no more entries found after [" + row + "]" );
202- } else {
203- dataFailure (dataFailures , "Expected more data but no more entries found after [" + row + "]\n " );
204- }
200+ dataFailure ("Expected more data but no more entries found after [" + row + "]" , dataFailures , expected , actualValues );
205201 }
206202
207203 if (logger != null ) {
@@ -250,13 +246,17 @@ public static void assertData(
250246 dataFailures .add (new DataFailure (row , column , transformedExpected , transformedActual ));
251247 }
252248 if (dataFailures .size () > 10 ) {
253- dataFailure (dataFailures );
249+ dataFailure ("" , dataFailures , expected , actualValues );
254250 }
255251 }
256252
257- var delta = actualRow .size () - expectedRow .size ();
258- if (delta > 0 ) {
259- fail ("Plan has extra columns, returned [" + actualRow .size () + "], expected [" + expectedRow .size () + "]" );
253+ if (actualRow .size () != expectedRow .size ()) {
254+ dataFailure (
255+ "Plan has extra columns, returned [" + actualRow .size () + "], expected [" + expectedRow .size () + "]" ,
256+ dataFailures ,
257+ expected ,
258+ actualValues
259+ );
260260 }
261261 } catch (AssertionError ae ) {
262262 if (logger != null && row + 1 < actualValues .size ()) {
@@ -267,21 +267,59 @@ public static void assertData(
267267 }
268268 }
269269 if (dataFailures .isEmpty () == false ) {
270- dataFailure (dataFailures );
270+ dataFailure ("" , dataFailures , expected , actualValues );
271271 }
272272 if (expectedValues .size () < actualValues .size ()) {
273- fail (
274- "Elasticsearch still has data after [" + expectedValues .size () + "] entries:\n " + row (actualValues , expectedValues .size ())
275- );
273+ dataFailure ("Elasticsearch still has data after [" + expectedValues .size () + "] entries" , dataFailures , expected , actualValues );
276274 }
277275 }
278276
279- private static void dataFailure (List <DataFailure > dataFailures ) {
280- dataFailure (dataFailures , "" );
277+ private static void dataFailure (
278+ String description ,
279+ List <DataFailure > dataFailures ,
280+ ExpectedResults expectedValues ,
281+ List <List <Object >> actualValues
282+ ) {
283+ var expected = pipeTable ("Expected:" , expectedValues .columnNames (), expectedValues .values (), 25 );
284+ var actual = pipeTable ("Actual:" , expectedValues .columnNames (), actualValues , 25 );
285+ fail (description + System .lineSeparator () + describeFailures (dataFailures ) + actual + expected );
286+ }
287+
288+ private static String pipeTable (String description , List <String > headers , List <List <Object >> values , int maxRows ) {
289+ int [] width = new int [headers .size ()];
290+ for (int i = 0 ; i < width .length ; i ++) {
291+ width [i ] = headers .get (i ).length ();
292+ for (List <Object > row : values ) {
293+ width [i ] = Math .max (width [i ], String .valueOf (row .get (i )).length ());
294+ }
295+ }
296+
297+ var result = new StringBuilder ().append (System .lineSeparator ()).append (description ).append (System .lineSeparator ());
298+ for (int c = 0 ; c < width .length ; c ++) {
299+ appendValue (result , headers .get (c ), width [c ]);
300+ }
301+ result .append ('|' ).append (System .lineSeparator ());
302+ for (int r = 0 ; r < Math .min (maxRows , values .size ()); r ++) {
303+ for (int c = 0 ; c < width .length ; c ++) {
304+ appendValue (result , values .get (r ).get (c ), width [c ]);
305+ }
306+ result .append ('|' ).append (System .lineSeparator ());
307+ }
308+ if (values .size () > maxRows ) {
309+ result .append ("..." ).append (System .lineSeparator ());
310+ }
311+ return result .toString ();
312+ }
313+
314+ private static void appendValue (StringBuilder result , Object value , int width ) {
315+ result .append ('|' ).append (value );
316+ for (int i = 0 ; i < width - String .valueOf (value ).length (); i ++) {
317+ result .append (' ' );
318+ }
281319 }
282320
283- private static void dataFailure (List <DataFailure > dataFailures , String prefixError ) {
284- fail ( prefixError + "Data mismatch:\n " + dataFailures .stream ().map (f -> {
321+ private static String describeFailures (List <DataFailure > dataFailures ) {
322+ return "Data mismatch:" + System . lineSeparator () + dataFailures .stream ().map (f -> {
285323 Description description = new StringDescription ();
286324 ListMatcher expected ;
287325 if (f .expected instanceof List <?> e ) {
@@ -299,7 +337,7 @@ private static void dataFailure(List<DataFailure> dataFailures, String prefixErr
299337 expected .describeMismatch (actualList , description );
300338 String prefix = "row " + f .row + " column " + f .column + ":" ;
301339 return prefix + description .toString ().replace ("\n " , "\n " + prefix );
302- }).collect (Collectors .joining (" \n " )));
340+ }).collect (Collectors .joining (System . lineSeparator ( )));
303341 }
304342
305343 private static Comparator <List <Object >> resultRowComparator (List <Type > types ) {
0 commit comments