1 /* 2 * Copyright (C) 2021 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.telemetry; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import android.car.telemetry.MetricsConfigKey; 23 import android.os.PersistableBundle; 24 25 import org.junit.Before; 26 import org.junit.Test; 27 import org.junit.runner.RunWith; 28 import org.mockito.junit.MockitoJUnitRunner; 29 30 import java.io.ByteArrayOutputStream; 31 import java.io.File; 32 import java.io.FileInputStream; 33 import java.nio.charset.StandardCharsets; 34 import java.nio.file.Files; 35 import java.util.concurrent.TimeUnit; 36 37 @RunWith(MockitoJUnitRunner.class) 38 public class ResultStoreTest { 39 private static final PersistableBundle TEST_INTERIM_BUNDLE = new PersistableBundle(); 40 private static final PersistableBundle TEST_FINAL_BUNDLE = new PersistableBundle(); 41 private static final TelemetryProto.TelemetryError TEST_TELEMETRY_ERROR = 42 TelemetryProto.TelemetryError.newBuilder().setMessage("test error").build(); 43 44 private File mTestRootDir; 45 private File mTestInterimResultDir; 46 private File mTestErrorResultDir; 47 private File mTestFinalResultDir; 48 private ResultStore mResultStore; 49 50 @Before setUp()51 public void setUp() throws Exception { 52 TEST_INTERIM_BUNDLE.putString("test key", "interim value"); 53 TEST_FINAL_BUNDLE.putString("test key", "final value"); 54 55 mTestRootDir = Files.createTempDirectory("car_telemetry_test").toFile(); 56 mTestInterimResultDir = new File(mTestRootDir, ResultStore.INTERIM_RESULT_DIR); 57 mTestErrorResultDir = new File(mTestRootDir, ResultStore.ERROR_RESULT_DIR); 58 mTestFinalResultDir = new File(mTestRootDir, ResultStore.FINAL_RESULT_DIR); 59 60 mResultStore = new ResultStore(mTestRootDir); 61 } 62 63 @Test testConstructor_shouldCreateResultsFolder()64 public void testConstructor_shouldCreateResultsFolder() { 65 // constructor is called in setUp() 66 assertThat(mTestInterimResultDir.exists()).isTrue(); 67 assertThat(mTestFinalResultDir.exists()).isTrue(); 68 } 69 70 @Test testConstructor_shouldLoadInterimResultsIntoMemory()71 public void testConstructor_shouldLoadInterimResultsIntoMemory() throws Exception { 72 String testInterimFileName = "test_file_1"; 73 writeBundleToFile(mTestInterimResultDir, testInterimFileName, TEST_INTERIM_BUNDLE); 74 75 mResultStore = new ResultStore(mTestRootDir); 76 77 // should compare value instead of reference 78 assertThat(mResultStore.getInterimResult(testInterimFileName).toString()) 79 .isEqualTo(TEST_INTERIM_BUNDLE.toString()); 80 } 81 82 @Test testFlushToDisk_shouldRemoveStaleData()83 public void testFlushToDisk_shouldRemoveStaleData() throws Exception { 84 File staleTestFile1 = new File(mTestInterimResultDir, "stale_test_file_1"); 85 File staleTestFile2 = new File(mTestFinalResultDir, "stale_test_file_2"); 86 File activeTestFile3 = new File(mTestInterimResultDir, "active_test_file_3"); 87 writeBundleToFile(staleTestFile1, TEST_INTERIM_BUNDLE); 88 writeBundleToFile(staleTestFile2, TEST_FINAL_BUNDLE); 89 writeBundleToFile(activeTestFile3, TEST_INTERIM_BUNDLE); 90 long currTimeMs = System.currentTimeMillis(); 91 staleTestFile1.setLastModified(0L); // stale 92 staleTestFile2.setLastModified( 93 currTimeMs - TimeUnit.MILLISECONDS.convert(31, TimeUnit.DAYS)); // stale 94 activeTestFile3.setLastModified( 95 currTimeMs - TimeUnit.MILLISECONDS.convert(29, TimeUnit.DAYS)); // active 96 97 mResultStore.flushToDisk(); 98 99 assertThat(staleTestFile1.exists()).isFalse(); 100 assertThat(staleTestFile2.exists()).isFalse(); 101 assertThat(activeTestFile3.exists()).isTrue(); 102 } 103 104 105 @Test testPutInterimResultAndFlushToDisk_shouldReplaceExistingFile()106 public void testPutInterimResultAndFlushToDisk_shouldReplaceExistingFile() throws Exception { 107 String newKey = "new key"; 108 String newValue = "new value"; 109 String metricsConfigName = "my_metrics_config"; 110 writeBundleToFile(mTestInterimResultDir, metricsConfigName, TEST_INTERIM_BUNDLE); 111 TEST_INTERIM_BUNDLE.putString(newKey, newValue); 112 113 mResultStore.putInterimResult(metricsConfigName, TEST_INTERIM_BUNDLE); 114 mResultStore.flushToDisk(); 115 116 PersistableBundle bundle = readBundleFromFile(mTestInterimResultDir, metricsConfigName); 117 assertThat(bundle.getString(newKey)).isEqualTo(newValue); 118 assertThat(bundle.toString()).isEqualTo(TEST_INTERIM_BUNDLE.toString()); 119 } 120 121 @Test testPutInterimResultAndFlushToDisk_shouldWriteDirtyResultsOnly()122 public void testPutInterimResultAndFlushToDisk_shouldWriteDirtyResultsOnly() throws Exception { 123 File fileFoo = new File(mTestInterimResultDir, "foo"); 124 File fileBar = new File(mTestInterimResultDir, "bar"); 125 writeBundleToFile(fileFoo, TEST_INTERIM_BUNDLE); 126 writeBundleToFile(fileBar, TEST_INTERIM_BUNDLE); 127 mResultStore = new ResultStore(mTestRootDir); // re-load data 128 PersistableBundle newData = new PersistableBundle(); 129 newData.putDouble("pi", 3.1415926); 130 131 mResultStore.putInterimResult("bar", newData); // make bar dirty 132 fileFoo.delete(); // delete the clean file from the file system 133 mResultStore.flushToDisk(); // write dirty data 134 135 // foo is a clean file that should not be written in flushToDisk() 136 assertThat(fileFoo.exists()).isFalse(); 137 assertThat(readBundleFromFile(fileBar).toString()).isEqualTo(newData.toString()); 138 } 139 140 @Test testGetFinalResult_whenNoData_shouldReceiveNull()141 public void testGetFinalResult_whenNoData_shouldReceiveNull() throws Exception { 142 String metricsConfigName = "my_metrics_config"; 143 144 PersistableBundle bundle = mResultStore.getFinalResult(metricsConfigName, true); 145 146 assertThat(bundle).isNull(); 147 } 148 149 @Test testGetFinalResult_whenDataCorrupt_shouldReceiveNull()150 public void testGetFinalResult_whenDataCorrupt_shouldReceiveNull() throws Exception { 151 String metricsConfigName = "my_metrics_config"; 152 Files.write(new File(mTestFinalResultDir, metricsConfigName).toPath(), 153 "not a bundle".getBytes(StandardCharsets.UTF_8)); 154 155 PersistableBundle bundle = mResultStore.getFinalResult(metricsConfigName, true); 156 157 assertThat(bundle).isNull(); 158 } 159 160 @Test testGetFinalResult_whenDeleteFlagTrue_shouldDeleteData()161 public void testGetFinalResult_whenDeleteFlagTrue_shouldDeleteData() throws Exception { 162 String testFinalFileName = "my_metrics_config"; 163 writeBundleToFile(mTestFinalResultDir, testFinalFileName, TEST_FINAL_BUNDLE); 164 165 PersistableBundle bundle = mResultStore.getFinalResult(testFinalFileName, true); 166 167 // should compare value instead of reference 168 assertThat(bundle.toString()).isEqualTo(TEST_FINAL_BUNDLE.toString()); 169 assertThat(new File(mTestFinalResultDir, testFinalFileName).exists()).isFalse(); 170 } 171 172 @Test testGetFinalResult_whenDeleteFlagFalse_shouldNotDeleteData()173 public void testGetFinalResult_whenDeleteFlagFalse_shouldNotDeleteData() throws Exception { 174 String testFinalFileName = "my_metrics_config"; 175 writeBundleToFile(mTestFinalResultDir, testFinalFileName, TEST_FINAL_BUNDLE); 176 177 PersistableBundle bundle = mResultStore.getFinalResult(testFinalFileName, false); 178 179 // should compare value instead of reference 180 assertThat(bundle.toString()).isEqualTo(TEST_FINAL_BUNDLE.toString()); 181 assertThat(new File(mTestFinalResultDir, testFinalFileName).exists()).isTrue(); 182 } 183 184 @Test testGetError_whenNoError_shouldReceiveNull()185 public void testGetError_whenNoError_shouldReceiveNull() { 186 String metricsConfigName = "my_metrics_config"; 187 188 TelemetryProto.TelemetryError error = mResultStore.getError(metricsConfigName, true); 189 190 assertThat(error).isNull(); 191 } 192 193 @Test testGetError_shouldReceiveError()194 public void testGetError_shouldReceiveError() throws Exception { 195 String metricsConfigName = "my_metrics_config"; 196 // write serialized error object to file 197 Files.write( 198 new File(mTestErrorResultDir, metricsConfigName).toPath(), 199 TEST_TELEMETRY_ERROR.toByteArray()); 200 201 TelemetryProto.TelemetryError error = mResultStore.getError(metricsConfigName, true); 202 203 assertThat(error).isEqualTo(TEST_TELEMETRY_ERROR); 204 } 205 206 @Test testPutFinalResult_shouldWriteResultAndRemoveInterim()207 public void testPutFinalResult_shouldWriteResultAndRemoveInterim() throws Exception { 208 String metricsConfigName = "my_metrics_config"; 209 writeBundleToFile(mTestInterimResultDir, metricsConfigName, TEST_INTERIM_BUNDLE); 210 211 mResultStore.putFinalResult(metricsConfigName, TEST_FINAL_BUNDLE); 212 213 assertThat(mResultStore.getInterimResult(metricsConfigName)).isNull(); 214 assertThat(new File(mTestInterimResultDir, metricsConfigName).exists()).isFalse(); 215 assertThat(new File(mTestFinalResultDir, metricsConfigName).exists()).isTrue(); 216 } 217 218 @Test testPutError_shouldWriteErrorAndRemoveInterimResultFile()219 public void testPutError_shouldWriteErrorAndRemoveInterimResultFile() throws Exception { 220 String metricsConfigName = "my_metrics_config"; 221 writeBundleToFile(mTestInterimResultDir, metricsConfigName, TEST_INTERIM_BUNDLE); 222 223 mResultStore.putError(metricsConfigName, TEST_TELEMETRY_ERROR); 224 225 assertThat(new File(mTestInterimResultDir, metricsConfigName).exists()).isFalse(); 226 assertThat(new File(mTestErrorResultDir, metricsConfigName).exists()).isTrue(); 227 } 228 229 @Test testRemoveResult_whenInterimResult_shouldDelete()230 public void testRemoveResult_whenInterimResult_shouldDelete() throws Exception { 231 String metricsConfigName = "my_metrics_config"; 232 MetricsConfigKey key = new MetricsConfigKey(metricsConfigName, 1); 233 writeBundleToFile(mTestInterimResultDir, metricsConfigName, TEST_INTERIM_BUNDLE); 234 235 mResultStore.removeResult(key); 236 237 assertThat(new File(mTestInterimResultDir, metricsConfigName).exists()).isFalse(); 238 } 239 240 @Test testRemoveResult_whenFinalResult_shouldDelete()241 public void testRemoveResult_whenFinalResult_shouldDelete() throws Exception { 242 String metricsConfigName = "my_metrics_config"; 243 MetricsConfigKey key = new MetricsConfigKey(metricsConfigName, 1); 244 writeBundleToFile(mTestFinalResultDir, metricsConfigName, TEST_FINAL_BUNDLE); 245 246 mResultStore.removeResult(key); 247 248 assertThat(new File(mTestFinalResultDir, metricsConfigName).exists()).isFalse(); 249 } 250 251 @Test testRemoveAllResults_shouldDeleteAll()252 public void testRemoveAllResults_shouldDeleteAll() throws Exception { 253 mResultStore.putInterimResult("config 1", TEST_INTERIM_BUNDLE); 254 mResultStore.putFinalResult("config 2", TEST_FINAL_BUNDLE); 255 mResultStore.putError("config 3", TEST_TELEMETRY_ERROR); 256 mResultStore.flushToDisk(); 257 258 mResultStore.removeAllResults(); 259 260 assertThat(mTestInterimResultDir.listFiles()).isEmpty(); 261 assertThat(mTestFinalResultDir.listFiles()).isEmpty(); 262 } 263 writeBundleToFile( File dir, String fileName, PersistableBundle persistableBundle)264 private void writeBundleToFile( 265 File dir, String fileName, PersistableBundle persistableBundle) throws Exception { 266 writeBundleToFile(new File(dir, fileName), persistableBundle); 267 } 268 269 /** 270 * Writes a persistable bundle to the result directory with the given directory and file name, 271 * and verifies that it was successfully written. 272 */ writeBundleToFile( File file, PersistableBundle persistableBundle)273 private void writeBundleToFile( 274 File file, PersistableBundle persistableBundle) throws Exception { 275 try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { 276 persistableBundle.writeToStream(byteArrayOutputStream); 277 Files.write(file.toPath(), byteArrayOutputStream.toByteArray()); 278 } 279 assertWithMessage("bundle is not written to the result directory") 280 .that(file.exists()).isTrue(); 281 } 282 readBundleFromFile(File dir, String fileName)283 private PersistableBundle readBundleFromFile(File dir, String fileName) throws Exception { 284 return readBundleFromFile(new File(dir, fileName)); 285 } 286 287 /** Reads a persistable bundle from the given path. */ readBundleFromFile(File file)288 private PersistableBundle readBundleFromFile(File file) throws Exception { 289 try (FileInputStream fis = new FileInputStream(file)) { 290 return PersistableBundle.readFromStream(fis); 291 } 292 } 293 } 294