mirror of https://github.com/iSoron/uhabits.git
parent
df755d30ee
commit
21fa636e0c
@ -1,289 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<ComponentName> 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<Future> 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<out BaseWidgetProvider?>?) {
|
||||
val provider = ComponentName(targetContext, componentClass!!)
|
||||
val manager = AppWidgetManager.getInstance(targetContext)
|
||||
val installedProviders: MutableList<ComponentName> = LinkedList()
|
||||
for (info in manager.installedProviders) installedProviders.add(info.provider)
|
||||
MatcherAssert.assertThat<List<ComponentName>>(
|
||||
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
|
||||
}
|
||||
}
|
Loading…
Reference in new issue