1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car; 18 19 import android.support.test.filters.MediumTest; 20 import android.support.test.runner.AndroidJUnit4; 21 import android.util.Log; 22 23 import junit.framework.TestCase; 24 25 import org.junit.Test; 26 import org.junit.runner.RunWith; 27 28 import java.lang.reflect.Field; 29 import java.lang.reflect.Modifier; 30 import java.util.Arrays; 31 import java.util.HashMap; 32 import java.util.Map; 33 34 /** 35 * Validates that diagnostic constants in CarService and Vehicle HAL have the same value 36 * This is an important assumption to validate because we do not perform any mapping between 37 * the two layers, instead relying on the constants on both sides having identical values. 38 */ 39 @RunWith(AndroidJUnit4.class) 40 @MediumTest 41 public class CarDiagnosticConstantsTest extends TestCase { 42 static final String TAG = CarDiagnosticConstantsTest.class.getSimpleName(); 43 44 static class MismatchException extends Exception { 45 private static String dumpClass(Class<?> clazz) { 46 StringBuilder builder = new StringBuilder(clazz.getName() + "{\n"); 47 Arrays.stream(clazz.getFields()).forEach((Field field) -> { 48 builder.append('\t').append(field.toString()).append('\n'); 49 }); 50 return builder.append('}').toString(); 51 } 52 53 private static void logClasses(Class<?> clazz1, Class<?> clazz2) { 54 Log.d(TAG, "MismatchException. class1: " + dumpClass(clazz1)); 55 Log.d(TAG, "MismatchException. class2: " + dumpClass(clazz2)); 56 } 57 58 MismatchException(String message) { 59 super(message); 60 } 61 62 static MismatchException fieldValueMismatch(Class<?> clazz1, Class<?> clazz2, String name, 63 int value1, int value2) { 64 logClasses(clazz1, clazz2); 65 return new MismatchException("In comparison of " + clazz1 + " and " + clazz2 + 66 " field " + name + " had different values " + value1 + " vs. " + value2); 67 } 68 69 static MismatchException fieldsOnlyInClass1(Class<?> clazz1, Class<?> clazz2, 70 Map<String, Integer> fields) { 71 logClasses(clazz1, clazz2); 72 return new MismatchException("In comparison of " + clazz1 + " and " + clazz2 + 73 " some fields were only found in the first class:\n" + 74 fields.keySet().stream().reduce("", 75 (String s, String t) -> s + "\n" + t)); 76 } 77 78 static MismatchException fieldOnlyInClass2(Class<?> clazz1, Class<?> clazz2, String field) { 79 logClasses(clazz1, clazz2); 80 return new MismatchException("In comparison of " + clazz1 + " and " + clazz2 + 81 " field " + field + " was not found in both classes"); 82 } 83 } 84 85 static boolean isPublicStaticFinalInt(Field field) { 86 final int modifiers = field.getModifiers(); 87 final boolean isPublic = (modifiers & Modifier.PUBLIC) == Modifier.PUBLIC; 88 final boolean isStatic = (modifiers & Modifier.STATIC) == Modifier.STATIC; 89 final boolean isFinal = (modifiers & Modifier.FINAL) == Modifier.FINAL; 90 if (isPublic && isStatic && isFinal) { 91 return field.getType() == int.class; 92 } 93 return false; 94 } 95 96 static void validateMatch(Class<?> clazz1, Class<?> clazz2) throws Exception { 97 Map<String, Integer> fields = new HashMap<>(); 98 99 // add all the fields in the first class to a map 100 Arrays.stream(clazz1.getFields()).filter( 101 CarDiagnosticConstantsTest::isPublicStaticFinalInt).forEach( (Field field) -> { 102 final String name = field.getName(); 103 try { 104 fields.put(name, field.getInt(null)); 105 } catch (IllegalAccessException e) { 106 // this will practically never happen because we checked that it is a 107 // public static final field before reading from it 108 Log.wtf(TAG, String.format("attempt to access field %s threw exception", 109 field.toString()), e); 110 } 111 }); 112 113 // check for all fields in the second class, and remove matches from the map 114 for (Field field2 : clazz2.getFields()) { 115 if (isPublicStaticFinalInt(field2)) { 116 final String name = field2.getName(); 117 if (fields.containsKey(name)) { 118 try { 119 final int value2 = field2.getInt(null); 120 final int value1 = fields.getOrDefault(name, value2+1); 121 if (value2 != value1) { 122 throw MismatchException.fieldValueMismatch(clazz1, clazz2, 123 field2.getName(), value1, value2); 124 } 125 fields.remove(name); 126 } catch (IllegalAccessException e) { 127 // this will practically never happen because we checked that it is a 128 // public static final field before reading from it 129 Log.wtf(TAG, String.format("attempt to access field %s threw exception", 130 field2.toString()), e); 131 throw e; 132 } 133 } else { 134 throw MismatchException.fieldOnlyInClass2(clazz1, clazz2, name); 135 } 136 } 137 } 138 139 // if anything is left, we didn't find some fields in the second class 140 if (!fields.isEmpty()) { 141 throw MismatchException.fieldsOnlyInClass1(clazz1, clazz2, fields); 142 } 143 } 144 145 @Test 146 public void testFuelSystemStatus() throws Exception { 147 validateMatch(android.hardware.automotive.vehicle.V2_0.Obd2FuelSystemStatus.class, 148 android.car.diagnostic.CarDiagnosticEvent.FuelSystemStatus.class); 149 } 150 151 @Test public void testFuelType() throws Exception { 152 validateMatch(android.hardware.automotive.vehicle.V2_0.Obd2FuelType.class, 153 android.car.diagnostic.CarDiagnosticEvent.FuelType.class); 154 } 155 156 @Test public void testSecondaryAirStatus() throws Exception { 157 validateMatch(android.hardware.automotive.vehicle.V2_0.Obd2SecondaryAirStatus.class, 158 android.car.diagnostic.CarDiagnosticEvent.SecondaryAirStatus.class); 159 } 160 161 @Test public void testIgnitionMonitors() throws Exception { 162 validateMatch(android.hardware.automotive.vehicle.V2_0.Obd2CommonIgnitionMonitors.class, 163 android.car.diagnostic.CarDiagnosticEvent.CommonIgnitionMonitors.class); 164 165 validateMatch(android.hardware.automotive.vehicle.V2_0.Obd2CompressionIgnitionMonitors.class, 166 android.car.diagnostic.CarDiagnosticEvent.CompressionIgnitionMonitors.class); 167 168 validateMatch(android.hardware.automotive.vehicle.V2_0.Obd2SparkIgnitionMonitors.class, 169 android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors.class); 170 } 171 } 172