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