package com.ldbc.driver.validation; import com.google.common.collect.Lists; import com.ldbc.driver.Db; import com.ldbc.driver.Operation; import com.ldbc.driver.SerializingMarshallingException; import com.ldbc.driver.Workload; import com.ldbc.driver.WorkloadException; import com.ldbc.driver.util.MapUtils; import com.ldbc.driver.util.Tuple; import com.ldbc.driver.util.Tuple2; import com.ldbc.driver.util.Tuple3; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.type.TypeReference; import org.codehaus.jackson.util.DefaultPrettyPrinter; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import static java.lang.String.format; public class DbValidationResult { private final Db db; private final Set<Class> missingHandlersForOperationTypes; private final List<Tuple2<Operation,String>> unableToExecuteOperations; private final List<Tuple3<Operation,Object,Object>> incorrectResultsForOperations; private final Map<Class,Integer> successfullyExecutedOperationsPerOperationType; private final Map<Class,Integer> totalOperationsPerOperationType; private final ObjectMapper objectMapper; private static final TypeReference<List<Map<String,Object>>> TYPE_REFERENCE = new TypeReference<List<Map<String,Object>>>() { }; private final DefaultPrettyPrinter defaultPrettyPrinter; DbValidationResult( Db db ) { this.db = db; this.missingHandlersForOperationTypes = new HashSet<>(); this.unableToExecuteOperations = new ArrayList<>(); this.incorrectResultsForOperations = new ArrayList<>(); this.successfullyExecutedOperationsPerOperationType = new HashMap<>(); this.totalOperationsPerOperationType = new HashMap<>(); this.objectMapper = new ObjectMapper(); this.defaultPrettyPrinter = new DefaultPrettyPrinter(); this.defaultPrettyPrinter.indentArraysWith( new DefaultPrettyPrinter.Lf2SpacesIndenter() ); } void reportMissingHandlerForOperation( Operation operation ) { missingHandlersForOperationTypes.add( operation.getClass() ); incrementOperationCountPerOperationType( operation.getClass() ); } void reportUnableToExecuteOperation( Operation operation, String errorMessage ) { unableToExecuteOperations.add( Tuple.tuple2( operation, errorMessage ) ); incrementOperationCountPerOperationType( operation.getClass() ); } void reportIncorrectResultForOperation( Operation operation, Object expectedResult, Object actualResult ) { incorrectResultsForOperations.add( Tuple.tuple3( operation, expectedResult, actualResult ) ); incrementOperationCountPerOperationType( operation.getClass() ); } void reportSuccessfulExecution( Operation operation ) { if ( false == successfullyExecutedOperationsPerOperationType.containsKey( operation.getClass() ) ) { successfullyExecutedOperationsPerOperationType.put( operation.getClass(), 0 ); } int successfullyExecutedOperationsForOperationType = successfullyExecutedOperationsPerOperationType.get( operation.getClass() ); successfullyExecutedOperationsForOperationType++; successfullyExecutedOperationsPerOperationType .put( operation.getClass(), successfullyExecutedOperationsForOperationType ); incrementOperationCountPerOperationType( operation.getClass() ); } private void incrementOperationCountPerOperationType( Class operationType ) { Integer count = totalOperationsPerOperationType.get( operationType ); if ( null == count ) { totalOperationsPerOperationType.put( operationType, 1 ); } else { totalOperationsPerOperationType.put( operationType, count + 1 ); } } public boolean isSuccessful() { return missingHandlersForOperationTypes.isEmpty() && unableToExecuteOperations.isEmpty() && incorrectResultsForOperations.isEmpty(); } public String actualResultsForFailedOperationsAsJsonString( Workload workload ) throws WorkloadException { StringBuilder sb = new StringBuilder(); sb.append( "[" ); for ( int i = 0; i < incorrectResultsForOperations.size() - 1; i++ ) { Operation operation = incorrectResultsForOperations.get( i )._1(); Object actualResult = incorrectResultsForOperations.get( i )._3(); sb.append( operationAndResultAsJsonMapString( operation, actualResult, workload ) ).append( "," ); } if ( incorrectResultsForOperations.size() >= 1 ) { Operation operation = incorrectResultsForOperations.get( incorrectResultsForOperations.size() - 1 )._1(); Object actualResult = incorrectResultsForOperations.get( incorrectResultsForOperations.size() - 1 )._3(); sb.append( operationAndResultAsJsonMapString( operation, actualResult, workload ) ); } sb.append( "]" ); try { return objectMapper.writer( defaultPrettyPrinter ) .writeValueAsString( objectMapper.readValue( sb.toString(), TYPE_REFERENCE ) ); } catch ( IOException e ) { throw new WorkloadException( "Error encountered while trying to pretty print JSON output", e ); } } public String expectedResultsForFailedOperationsAsJsonString( Workload workload ) throws WorkloadException { StringBuilder sb = new StringBuilder(); sb.append( "[" ); for ( int i = 0; i < incorrectResultsForOperations.size() - 1; i++ ) { Operation operation = incorrectResultsForOperations.get( i )._1(); Object expectedResult = incorrectResultsForOperations.get( i )._2(); sb.append( operationAndResultAsJsonMapString( operation, expectedResult, workload ) ).append( "," ); } if ( incorrectResultsForOperations.size() >= 1 ) { Operation operation = incorrectResultsForOperations.get( incorrectResultsForOperations.size() - 1 )._1(); Object expectedResult = incorrectResultsForOperations.get( incorrectResultsForOperations.size() - 1 )._2(); sb.append( operationAndResultAsJsonMapString( operation, expectedResult, workload ) ); } sb.append( "]" ); try { return objectMapper.writer( defaultPrettyPrinter ) .writeValueAsString( objectMapper.readValue( sb.toString(), TYPE_REFERENCE ) ); } catch ( IOException e ) { throw new WorkloadException( "Error encountered while trying to pretty print JSON output", e ); } } private String operationAndResultAsJsonMapString( Operation operation, Object result, Workload workload ) throws WorkloadException { String serializedOperation; try { serializedOperation = workload.serializeOperation( operation ); } catch ( SerializingMarshallingException e ) { throw new WorkloadException( format( "Error occurred while serializing operation\nOperation: %s", operation ), e ); } String serializedResult; try { serializedResult = operation.serializeResult( result ); } catch ( SerializingMarshallingException e ) { throw new WorkloadException( format( "Error occurred while serializing operation result\nResult: %s", result.toString() ), e ); } return "{\"operation\":" + serializedOperation + ",\"result\":" + serializedResult + "}"; } public String resultMessage() { int padRightDistance = 15; StringBuilder sb = new StringBuilder(); sb.append( "Validation Result: " ).append( (isSuccessful()) ? "PASS" : "FAIL" ).append( "\n" ); sb.append( "Database: " ).append( db.getClass().getName() ).append( "\n" ); sb.append( " ***\n" ); sb.append( " Successfully executed " ).append( successfullyExecutedOperationsPerOperationType.size() ) .append( " operation types\n" ); for ( Class operationType : sort( totalOperationsPerOperationType.keySet() ) ) { sb.append( " " ). append( (successfullyExecutedOperationsPerOperationType.containsKey( operationType )) ? successfullyExecutedOperationsPerOperationType.get( operationType ) : 0 ).append( " / " ). append( format( "%1$-" + padRightDistance + "s", totalOperationsPerOperationType.get( operationType ) ) ). append( operationType.getSimpleName() ). append( "\n" ); } sb.append( " ***\n" ); sb.append( " Missing handler implementations for " ).append( missingHandlersForOperationTypes.size() ) .append( " operation types\n" ); for ( Class operationType : sort( missingHandlersForOperationTypes ) ) { sb.append( " " ).append( format( "%1$-" + padRightDistance + "s", operationType.getName() ) ) .append( "\n" ); } sb.append( " ***\n" ); sb.append( " Unable to execute " ).append( unableToExecuteOperations.size() ).append( " operations\n" ); Map<Class,Integer> unableToExecuteOperationsGrouping = unableToExecuteOperationsGrouping( unableToExecuteOperations ); for ( Map.Entry<Class,Integer> failedOperationType : MapUtils .sortedEntrySet( unableToExecuteOperationsGrouping ) ) { sb. append( failedOperationType.getKey().getSimpleName() ). append( " " ).append( failedOperationType.getValue() ).append( "\n" ); } sb.append( " ***\n" ); sb.append( " Incorrect results for " ).append( incorrectResultsForOperations.size() ) .append( " operations\n" ); Map<Class,Integer> incorrectResultsForOperationsGrouping = incorrectResultsForOperationsGrouping( incorrectResultsForOperations ); for ( Map.Entry<Class,Integer> failedOperationType : MapUtils .sortedEntrySet( incorrectResultsForOperationsGrouping ) ) { sb. append( failedOperationType.getKey().getSimpleName() ). append( " " ).append( failedOperationType.getValue() ).append( "\n" ); } sb.append( " ***\n" ); return sb.toString(); } private <T> List<T> sort( Iterable<T> unsorted ) { List<T> sorted = Lists.newArrayList( unsorted ); Collections.sort( sorted, new DefaultComparator<T>() ); return sorted; } private Map<Class,Integer> unableToExecuteOperationsGrouping( List<Tuple2<Operation,String>> unableToExecuteOperations ) { Map<Class,Integer> grouping = new HashMap<>(); for ( Tuple2<Operation,String> failedOperation : unableToExecuteOperations ) { Class operationType = failedOperation._1().getClass(); if ( grouping.containsKey( operationType ) ) { int count = grouping.get( operationType ); grouping.put( operationType, count + 1 ); } else { grouping.put( operationType, 1 ); } } return grouping; } private Map<Class,Integer> incorrectResultsForOperationsGrouping( List<Tuple3<Operation,Object,Object>> incorrectResultsForOperations ) { Map<Class,Integer> grouping = new HashMap<>(); for ( Tuple3<Operation,Object,Object> failedOperation : incorrectResultsForOperations ) { Class operationType = failedOperation._1().getClass(); if ( grouping.containsKey( operationType ) ) { int count = grouping.get( operationType ); grouping.put( operationType, count + 1 ); } else { grouping.put( operationType, 1 ); } } return grouping; } private static class DefaultComparator<T> implements Comparator<T> { @Override public int compare( T o1, T o2 ) { if ( o1 instanceof Comparable ) { return ((Comparable) o1).compareTo( o2 ); } else { return o1.toString().compareTo( o2.toString() ); } } } }