| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.base.test; |
| |
| import android.content.Context; |
| import android.content.SharedPreferences; |
| import android.support.test.InstrumentationRegistry; |
| import android.support.v4.content.ContextCompat; |
| import android.text.TextUtils; |
| |
| import org.junit.rules.TestRule; |
| import org.junit.runner.Description; |
| import org.junit.runners.model.Statement; |
| |
| import org.chromium.base.ContextUtils; |
| import org.chromium.base.FileUtils; |
| import org.chromium.base.test.util.InMemorySharedPreferences; |
| import org.chromium.base.test.util.InMemorySharedPreferencesContext; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| |
| /** |
| * Holds setUp / tearDown logic common to all instrumentation tests. |
| */ |
| class BaseJUnit4TestRule implements TestRule { |
| @Override |
| public Statement apply(Statement base, Description description) { |
| return new Statement() { |
| @Override |
| public void evaluate() throws Throwable { |
| // Don't tests if there are prior on-disk shared prefs lying around. |
| checkOrDeleteOnDiskSharedPreferences(false); |
| |
| InMemorySharedPreferencesContext context = |
| BaseChromiumAndroidJUnitRunner.sInMemorySharedPreferencesContext; |
| if (context != null) { |
| // Reset Application context in case any tests have replaced it. |
| ContextUtils.initApplicationContextForTests(context); |
| // Ensure all tests start with empty (InMemory)SharedPreferences. |
| context.clearSharedPreferences(); |
| // Delete any files that leak state between tests. |
| clearDataDirectory(context); |
| } else { |
| // Delete any files that leak state between tests. |
| clearDataDirectory(InstrumentationRegistry.getTargetContext()); |
| } |
| |
| base.evaluate(); |
| |
| // Do not use try/finally so that preferences asserts do not mask prior exceptions. |
| checkOrDeleteOnDiskSharedPreferences(true); |
| } |
| }; |
| } |
| |
| private void checkOrDeleteOnDiskSharedPreferences(boolean check) { |
| File dataDir = ContextCompat.getDataDir(InstrumentationRegistry.getTargetContext()); |
| File prefsDir = new File(dataDir, "shared_prefs"); |
| File[] files = prefsDir.listFiles(); |
| if (files == null) { |
| return; |
| } |
| ArrayList<File> badFiles = new ArrayList<>(); |
| for (File f : files) { |
| // Multidex support library prefs need to stay or else multidex extraction will occur |
| // needlessly. |
| // WebView prefs need to stay because webview tests have no (good) way of hooking |
| // SharedPreferences for instantiated WebViews. |
| if (!f.getName().endsWith("multidex.version.xml") |
| && !f.getName().equals("WebViewChromiumPrefs.xml")) { |
| if (check) { |
| badFiles.add(f); |
| } else { |
| f.delete(); |
| } |
| } |
| } |
| if (!badFiles.isEmpty()) { |
| String errorMsg = "Found unexpected shared preferences file(s) after test ran.\n" |
| + "All code should use ContextUtils.getApplicationContext() when accessing " |
| + "SharedPreferences so that tests are hooked to use InMemorySharedPreferences." |
| + " This could also mean needing to override getSharedPreferences() on custom " |
| + "Context subclasses (e.g. ChromeBaseAppCompatActivity does this to make " |
| + "Preferences screens work).\n\n"; |
| |
| SharedPreferences testPrefs = ContextUtils.getApplicationContext().getSharedPreferences( |
| "test", Context.MODE_PRIVATE); |
| if (!(testPrefs instanceof InMemorySharedPreferences)) { |
| errorMsg += String.format( |
| "ContextUtils.getApplicationContext() was set to type \"%s\", which does " |
| + "not delegate to InMemorySharedPreferencesContext (this is " |
| + "likely the issues).\n\n", |
| ContextUtils.getApplicationContext().getClass().getName()); |
| } |
| |
| errorMsg += "Files:\n * " + TextUtils.join("\n * ", badFiles); |
| throw new AssertionError(errorMsg); |
| } |
| } |
| |
| private static void clearDataDirectory(Context targetContext) { |
| File dataDir = ContextCompat.getDataDir(targetContext); |
| File[] files = dataDir.listFiles(); |
| if (files == null) return; |
| for (File file : files) { |
| // Symlink to app's native libraries. |
| if (file.getName().equals("lib")) { |
| continue; |
| } |
| if (file.getName().equals("incremental-install-files")) { |
| continue; |
| } |
| // E.g. Legacy multidex files. |
| if (file.getName().equals("code_cache")) { |
| continue; |
| } |
| // SharedPreferences handled by checkOrDeleteOnDiskSharedPreferences(). |
| if (file.getName().equals("shared_prefs")) { |
| continue; |
| } |
| if (!FileUtils.recursivelyDeleteFile(file)) { |
| throw new RuntimeException("Could not delete file: " + file.getAbsolutePath()); |
| } |
| } |
| // We have to make sure the cache directory still exists, as the framework |
| // will try to create it otherwise and will fail for sandbox processes with |
| // a NullPointerException. |
| new File(dataDir, "cache").mkdir(); |
| } |
| } |