blob: 4d08e2db5b1fbbca6ebb5317f9cbcca21c4f7a63 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* Validates that diagnostic constants in CarService and Vehicle HAL have the same value
* This is an important assumption to validate because we do not perform any mapping between
* the two layers, instead relying on the constants on both sides having identical values.
*/
@RunWith(AndroidJUnit4.class)
@MediumTest
public class CarDiagnosticConstantsTest extends TestCase {
static final String TAG = CarDiagnosticConstantsTest.class.getSimpleName();
static class MismatchException extends Exception {
private static String dumpClass(Class<?> clazz) {
StringBuilder builder = new StringBuilder(clazz.getName() + "{\n");
Arrays.stream(clazz.getFields()).forEach((Field field) -> {
builder.append('\t').append(field.toString()).append('\n');
});
return builder.append('}').toString();
}
private static void logClasses(Class<?> clazz1, Class<?> clazz2) {
Log.d(TAG, "MismatchException. class1: " + dumpClass(clazz1));
Log.d(TAG, "MismatchException. class2: " + dumpClass(clazz2));
}
MismatchException(String message) {
super(message);
}
static MismatchException fieldValueMismatch(Class<?> clazz1, Class<?> clazz2, String name,
int value1, int value2) {
logClasses(clazz1, clazz2);
return new MismatchException("In comparison of " + clazz1 + " and " + clazz2 +
" field " + name + " had different values " + value1 + " vs. " + value2);
}
static MismatchException fieldsOnlyInClass1(Class<?> clazz1, Class<?> clazz2,
Map<String, Integer> fields) {
logClasses(clazz1, clazz2);
return new MismatchException("In comparison of " + clazz1 + " and " + clazz2 +
" some fields were only found in the first class:\n" +
fields.keySet().stream().reduce("",
(String s, String t) -> s + "\n" + t));
}
static MismatchException fieldOnlyInClass2(Class<?> clazz1, Class<?> clazz2, String field) {
logClasses(clazz1, clazz2);
return new MismatchException("In comparison of " + clazz1 + " and " + clazz2 +
" field " + field + " was not found in both classes");
}
}
static boolean isPublicStaticFinalInt(Field field) {
final int modifiers = field.getModifiers();
final boolean isPublic = (modifiers & Modifier.PUBLIC) == Modifier.PUBLIC;
final boolean isStatic = (modifiers & Modifier.STATIC) == Modifier.STATIC;
final boolean isFinal = (modifiers & Modifier.FINAL) == Modifier.FINAL;
if (isPublic && isStatic && isFinal) {
return field.getType() == int.class;
}
return false;
}
static void validateMatch(Class<?> clazz1, Class<?> clazz2) throws Exception {
Map<String, Integer> fields = new HashMap<>();
// add all the fields in the first class to a map
Arrays.stream(clazz1.getFields()).filter(
CarDiagnosticConstantsTest::isPublicStaticFinalInt).forEach( (Field field) -> {
final String name = field.getName();
try {
fields.put(name, field.getInt(null));
} catch (IllegalAccessException e) {
// this will practically never happen because we checked that it is a
// public static final field before reading from it
Log.wtf(TAG, String.format("attempt to access field %s threw exception",
field.toString()), e);
}
});
// check for all fields in the second class, and remove matches from the map
for (Field field2 : clazz2.getFields()) {
if (isPublicStaticFinalInt(field2)) {
final String name = field2.getName();
if (fields.containsKey(name)) {
try {
final int value2 = field2.getInt(null);
final int value1 = fields.getOrDefault(name, value2+1);
if (value2 != value1) {
throw MismatchException.fieldValueMismatch(clazz1, clazz2,
field2.getName(), value1, value2);
}
fields.remove(name);
} catch (IllegalAccessException e) {
// this will practically never happen because we checked that it is a
// public static final field before reading from it
Log.wtf(TAG, String.format("attempt to access field %s threw exception",
field2.toString()), e);
throw e;
}
} else {
throw MismatchException.fieldOnlyInClass2(clazz1, clazz2, name);
}
}
}
// if anything is left, we didn't find some fields in the second class
if (!fields.isEmpty()) {
throw MismatchException.fieldsOnlyInClass1(clazz1, clazz2, fields);
}
}
@Test
public void testFuelSystemStatus() throws Exception {
validateMatch(android.hardware.automotive.vehicle.V2_0.Obd2FuelSystemStatus.class,
android.car.diagnostic.CarDiagnosticEvent.FuelSystemStatus.class);
}
@Test public void testFuelType() throws Exception {
validateMatch(android.hardware.automotive.vehicle.V2_0.Obd2FuelType.class,
android.car.diagnostic.CarDiagnosticEvent.FuelType.class);
}
@Test public void testSecondaryAirStatus() throws Exception {
validateMatch(android.hardware.automotive.vehicle.V2_0.Obd2SecondaryAirStatus.class,
android.car.diagnostic.CarDiagnosticEvent.SecondaryAirStatus.class);
}
@Test public void testIgnitionMonitors() throws Exception {
validateMatch(android.hardware.automotive.vehicle.V2_0.Obd2CommonIgnitionMonitors.class,
android.car.diagnostic.CarDiagnosticEvent.CommonIgnitionMonitors.class);
validateMatch(android.hardware.automotive.vehicle.V2_0.Obd2CompressionIgnitionMonitors.class,
android.car.diagnostic.CarDiagnosticEvent.CompressionIgnitionMonitors.class);
validateMatch(android.hardware.automotive.vehicle.V2_0.Obd2SparkIgnitionMonitors.class,
android.car.diagnostic.CarDiagnosticEvent.SparkIgnitionMonitors.class);
}
}