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