Home | History | Annotate | Download | only in car
      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