diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java deleted file mode 100644 index 280c40c55..000000000 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright (C) 2016-2021 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.uhabits; - -import android.appwidget.*; -import android.content.*; -import android.content.res.*; -import android.os.*; -import android.util.*; - -import androidx.annotation.*; -import androidx.test.filters.*; -import androidx.test.platform.app.*; -import androidx.test.uiautomator.*; - -import junit.framework.*; - -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.tasks.*; -import org.isoron.uhabits.core.utils.*; -import org.isoron.uhabits.inject.*; -import org.isoron.uhabits.utils.*; -import org.junit.*; - -import java.io.*; -import java.time.*; -import java.util.*; -import java.util.concurrent.*; - -import static androidx.test.platform.app.InstrumentationRegistry.*; -import static androidx.test.uiautomator.UiDevice.*; -import static org.hamcrest.CoreMatchers.*; -import static org.hamcrest.MatcherAssert.*; - -@MediumTest -public class BaseAndroidTest extends TestCase -{ - // 8:00am, January 25th, 2015 (UTC) - public static final long FIXED_LOCAL_TIME = 1422172800000L; - - protected Context testContext; - - protected Context targetContext; - - protected Preferences prefs; - - protected HabitList habitList; - - protected TaskRunner taskRunner; - - protected HabitFixtures fixtures; - - protected CountDownLatch latch; - - protected HabitsApplicationTestComponent appComponent; - - protected ModelFactory modelFactory; - - protected HabitsActivityTestComponent component; - - private boolean isDone = false; - - private UiDevice device; - - @Override - @Before - public void setUp() - { - if (Looper.myLooper() == null) Looper.prepare(); - device = getInstance(getInstrumentation()); - - targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - testContext = InstrumentationRegistry.getInstrumentation().getContext(); - - DateUtils.setFixedLocalTime(FIXED_LOCAL_TIME); - DateUtils.setStartDayOffset(0, 0); - setResolution(2.0f); - setTheme(R.style.AppBaseTheme); - setLocale("en", "US"); - - latch = new CountDownLatch(1); - - Context context = targetContext.getApplicationContext(); - File dbFile = DatabaseUtils.getDatabaseFile(context); - appComponent = DaggerHabitsApplicationTestComponent - .builder() - .appContextModule(new AppContextModule(context)) - .habitsModule(new HabitsModule(dbFile)) - .build(); - - HabitsApplication.Companion.setComponent(appComponent); - prefs = appComponent.getPreferences(); - habitList = appComponent.getHabitList(); - taskRunner = appComponent.getTaskRunner(); - modelFactory = appComponent.getModelFactory(); - - prefs.clear(); - - fixtures = new HabitFixtures(modelFactory, habitList); - fixtures.purgeHabits(appComponent.getHabitList()); - Habit habit = fixtures.createEmptyHabit(); - - component = DaggerHabitsActivityTestComponent - .builder() - .activityContextModule(new ActivityContextModule(targetContext)) - .habitsApplicationComponent(appComponent) - .build(); - } - - protected void assertWidgetProviderIsInstalled(Class componentClass) - { - ComponentName provider = - new ComponentName(targetContext, componentClass); - AppWidgetManager manager = AppWidgetManager.getInstance(targetContext); - - List installedProviders = new LinkedList<>(); - for (AppWidgetProviderInfo info : manager.getInstalledProviders()) - installedProviders.add(info.provider); - - assertThat(installedProviders, hasItems(provider)); - } - - protected void awaitLatch() throws InterruptedException - { - assertTrue(latch.await(1, TimeUnit.SECONDS)); - } - - protected void setLocale(@NonNull String language, @NonNull String country) - { - Locale locale = new Locale(language, country); - Locale.setDefault(locale); - Resources res = targetContext.getResources(); - Configuration config = res.getConfiguration(); - config.setLocale(locale); - } - - protected void setResolution(float r) - { - DisplayMetrics dm = targetContext.getResources().getDisplayMetrics(); - dm.density = r; - dm.scaledDensity = r; - InterfaceUtils.setFixedResolution(r); - } - - protected void runConcurrently(Runnable... runnableList) throws Exception - { - isDone = false; - ExecutorService executor = Executors.newFixedThreadPool(100); - List futures = new LinkedList<>(); - for (Runnable r : runnableList) - futures.add(executor.submit(() -> - { - while (!isDone) r.run(); - return null; - })); - - Thread.sleep(3000); - isDone = true; - executor.shutdown(); - for(Future f : futures) f.get(); - while (!executor.isTerminated()) Thread.sleep(50); - } - - protected void setTheme(@StyleRes int themeId) - { - targetContext.setTheme(themeId); - StyledResources.setFixedTheme(themeId); - } - - protected void sleep(int time) - { - try - { - Thread.sleep(time); - } - catch (InterruptedException e) - { - fail(); - } - } - - public long timestamp(int year, int month, int day) - { - GregorianCalendar cal = DateUtils.getStartOfTodayCalendar(); - cal.set(year, month, day); - return cal.getTimeInMillis(); - } - - protected void startTracing() - { - File dir = new AndroidDirFinder(targetContext).getFilesDir("Profile"); - assertNotNull(dir); - String tracePath = dir.getAbsolutePath() + "/performance.trace"; - Log.d("PerformanceTest", String.format("Saving trace file to %s", tracePath)); - Debug.startMethodTracingSampling(tracePath, 0, 1000); - } - - protected void stopTracing() - { - Debug.stopMethodTracing(); - } - - protected Timestamp day(int offset) - { - return DateUtils.getToday().minus(offset); - } - - - public void setSystemTime(String tz, - int year, - int javaMonth, - int day, - int hourOfDay, - int minute) throws Exception - { - GregorianCalendar cal = new GregorianCalendar(); - cal.set(Calendar.SECOND, 0); - cal.set(year, javaMonth, day, hourOfDay, minute); - cal.setTimeZone(TimeZone.getTimeZone(tz)); - setSystemTime(cal); - } - - private void setSystemTime(GregorianCalendar cal) throws Exception - { - ZoneId tz = cal.getTimeZone().toZoneId(); - - // Set time zone (temporary) - String command = String.format("service call alarm 3 s16 %s", tz); - device.executeShellCommand(command); - - // Set time zone (permanent) - command = String.format("setprop persist.sys.timezone %s", tz); - device.executeShellCommand(command); - - // Set time - String date = String.format("%02d%02d%02d%02d%02d.%02d", - cal.get(Calendar.MONTH) + 1, - cal.get(Calendar.DAY_OF_MONTH), - cal.get(Calendar.HOUR_OF_DAY), - cal.get(Calendar.MINUTE), - cal.get(Calendar.YEAR), - cal.get(Calendar.SECOND)); - - // Set time (method 1) - // Run twice to override daylight saving time - device.executeShellCommand("date " + date); - device.executeShellCommand("date " + date); - - // Set time (method 2) - // Run in addition to the method above because one of these mail fail, depending - // on the Android API version. - command = String.format("date -u @%d", cal.getTimeInMillis() / 1000); - device.executeShellCommand(command); - - // Wait for system events to settle - Thread.sleep(1000); - } - - private GregorianCalendar savedCalendar = null; - - public void saveSystemTime() - { - savedCalendar = new GregorianCalendar(); - } - - public void restoreSystemTime() throws Exception - { - if (savedCalendar == null) throw new NullPointerException(); - setSystemTime(savedCalendar); - } -} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.kt new file mode 100644 index 000000000..61e9e121e --- /dev/null +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.kt @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2016-2021 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ +package org.isoron.uhabits + +import android.appwidget.AppWidgetManager +import android.content.ComponentName +import android.content.Context +import android.os.Looper +import androidx.annotation.StyleRes +import androidx.test.filters.MediumTest +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import junit.framework.TestCase +import org.hamcrest.CoreMatchers +import org.hamcrest.MatcherAssert +import org.isoron.uhabits.core.models.HabitList +import org.isoron.uhabits.core.models.ModelFactory +import org.isoron.uhabits.core.models.Timestamp +import org.isoron.uhabits.core.preferences.Preferences +import org.isoron.uhabits.core.tasks.TaskRunner +import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday +import org.isoron.uhabits.core.utils.DateUtils.Companion.setFixedLocalTime +import org.isoron.uhabits.core.utils.DateUtils.Companion.setStartDayOffset +import org.isoron.uhabits.inject.ActivityContextModule +import org.isoron.uhabits.inject.AppContextModule +import org.isoron.uhabits.inject.HabitsModule +import org.isoron.uhabits.utils.DatabaseUtils.getDatabaseFile +import org.isoron.uhabits.utils.InterfaceUtils.setFixedResolution +import org.isoron.uhabits.utils.StyledResources.Companion.setFixedTheme +import org.isoron.uhabits.widgets.BaseWidgetProvider +import org.junit.Before +import java.util.Calendar +import java.util.GregorianCalendar +import java.util.LinkedList +import java.util.Locale +import java.util.TimeZone +import java.util.concurrent.CountDownLatch + +@MediumTest +abstract class BaseAndroidTest : TestCase() { + @JvmField + protected var testContext: Context = InstrumentationRegistry.getInstrumentation().context + + @JvmField + protected var targetContext: Context = + InstrumentationRegistry.getInstrumentation().targetContext + protected lateinit var prefs: Preferences + + protected lateinit var habitList: HabitList + protected lateinit var taskRunner: TaskRunner + protected lateinit var fixtures: HabitFixtures + protected lateinit var latch: CountDownLatch + protected lateinit var appComponent: HabitsApplicationTestComponent + protected lateinit var modelFactory: ModelFactory + protected lateinit var component: HabitsActivityTestComponent + private lateinit var device: UiDevice + + @Before + public override fun setUp() { + if (Looper.myLooper() == null) Looper.prepare() + device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + setFixedLocalTime(FIXED_LOCAL_TIME) + setStartDayOffset(0, 0) + setResolution(2.0f) + setTheme(R.style.AppBaseTheme) + setLocale("en", "US") + latch = CountDownLatch(1) + val context = targetContext.applicationContext + val dbFile = getDatabaseFile(context) + appComponent = DaggerHabitsApplicationTestComponent + .builder() + .appContextModule(AppContextModule(context)) + .habitsModule(HabitsModule(dbFile)) + .build() + HabitsApplication.component = appComponent + prefs = appComponent.preferences + habitList = appComponent.habitList + taskRunner = appComponent.taskRunner + modelFactory = appComponent.modelFactory + prefs.clear() + fixtures = HabitFixtures(modelFactory, habitList) + fixtures.purgeHabits(appComponent.habitList) + fixtures.createEmptyHabit() + component = DaggerHabitsActivityTestComponent + .builder() + .activityContextModule(ActivityContextModule(targetContext)) + .habitsApplicationComponent(appComponent) + .build() + } + + protected fun assertWidgetProviderIsInstalled(componentClass: Class?) { + val provider = ComponentName(targetContext, componentClass!!) + val manager = AppWidgetManager.getInstance(targetContext) + val installedProviders: MutableList = LinkedList() + for (info in manager.installedProviders) installedProviders.add(info.provider) + MatcherAssert.assertThat>( + installedProviders, + CoreMatchers.hasItems(provider) + ) + } + + protected fun setLocale(language: String, country: String) { + val locale = Locale(language, country) + Locale.setDefault(locale) + val res = targetContext.resources + val config = res.configuration + config.setLocale(locale) + } + + protected fun setResolution(r: Float) { + val dm = targetContext.resources.displayMetrics + dm.density = r + dm.scaledDensity = r + setFixedResolution(r) + } + + protected fun setTheme(@StyleRes themeId: Int) { + targetContext.setTheme(themeId) + setFixedTheme(themeId) + } + + protected fun sleep(time: Int) { + try { + Thread.sleep(time.toLong()) + } catch (e: InterruptedException) { + fail() + } + } + + protected fun day(offset: Int): Timestamp { + return getToday().minus(offset) + } + + @Throws(Exception::class) + fun setSystemTime( + tz: String?, + year: Int, + javaMonth: Int, + day: Int, + hourOfDay: Int, + minute: Int + ) { + val cal = GregorianCalendar() + cal[Calendar.SECOND] = 0 + cal[year, javaMonth, day, hourOfDay] = minute + cal.timeZone = TimeZone.getTimeZone(tz) + setSystemTime(cal) + } + + @Throws(Exception::class) + private fun setSystemTime(cal: GregorianCalendar) { + val tz = cal.timeZone.toZoneId() + + // Set time zone (temporary) + var command = String.format("service call alarm 3 s16 %s", tz) + device.executeShellCommand(command) + + // Set time zone (permanent) + command = String.format("setprop persist.sys.timezone %s", tz) + device.executeShellCommand(command) + + // Set time + val date = String.format( + "%02d%02d%02d%02d%02d.%02d", + cal[Calendar.MONTH] + 1, + cal[Calendar.DAY_OF_MONTH], + cal[Calendar.HOUR_OF_DAY], + cal[Calendar.MINUTE], + cal[Calendar.YEAR], + cal[Calendar.SECOND] + ) + + // Set time (method 1) + // Run twice to override daylight saving time + device.executeShellCommand("date $date") + device.executeShellCommand("date $date") + + // Set time (method 2) + // Run in addition to the method above because one of these mail fail, depending + // on the Android API version. + command = String.format("date -u @%d", cal.timeInMillis / 1000) + device.executeShellCommand(command) + + // Wait for system events to settle + Thread.sleep(1000) + } + + private var savedCalendar: GregorianCalendar? = null + fun saveSystemTime() { + savedCalendar = GregorianCalendar() + } + + @Throws(Exception::class) + fun restoreSystemTime() { + if (savedCalendar == null) throw NullPointerException() + setSystemTime(savedCalendar!!) + } + + companion object { + // 8:00am, January 25th, 2015 (UTC) + const val FIXED_LOCAL_TIME = 1422172800000L + } +}