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