@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
P=/sdcard/Android/data/org.isoron.uhabits/cache/Failed/
|
||||||
|
|
||||||
|
adb pull $P Failed/
|
||||||
|
adb shell rm -r $P
|
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 9.8 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 17 KiB |
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* 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.unit.views;
|
||||||
|
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.helpers.DateHelper;
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.unit.models.HabitFixtures;
|
||||||
|
import org.isoron.uhabits.views.CheckmarkView;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@SmallTest
|
||||||
|
public class CheckmarkViewTest extends ViewTest
|
||||||
|
{
|
||||||
|
private CheckmarkView view;
|
||||||
|
private Habit habit;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup()
|
||||||
|
{
|
||||||
|
super.setup();
|
||||||
|
|
||||||
|
habit = HabitFixtures.createNonDailyHabit();
|
||||||
|
view = new CheckmarkView(targetContext);
|
||||||
|
view.setHabit(habit);
|
||||||
|
measureView(dpToPixels(100), dpToPixels(200), view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void render_checked() throws IOException
|
||||||
|
{
|
||||||
|
assertRenders(view, "CheckmarkView/checked.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void render_unchecked() throws IOException
|
||||||
|
{
|
||||||
|
habit.repetitions.toggle(DateHelper.getStartOfToday());
|
||||||
|
view.refreshData();
|
||||||
|
|
||||||
|
assertRenders(view, "CheckmarkView/unchecked.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void render_implicitlyChecked() throws IOException
|
||||||
|
{
|
||||||
|
long today = DateHelper.getStartOfToday();
|
||||||
|
long day = DateHelper.millisecondsInOneDay;
|
||||||
|
habit.repetitions.toggle(today);
|
||||||
|
habit.repetitions.toggle(today - day);
|
||||||
|
habit.repetitions.toggle(today - 2 * day);
|
||||||
|
view.refreshData();
|
||||||
|
|
||||||
|
assertRenders(view, "CheckmarkView/implicitly_checked.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void render_largeSize() throws IOException
|
||||||
|
{
|
||||||
|
measureView(dpToPixels(300), dpToPixels(300), view);
|
||||||
|
assertRenders(view, "CheckmarkView/large_size.png");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* 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.unit.views;
|
||||||
|
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.helpers.ColorHelper;
|
||||||
|
import org.isoron.uhabits.views.RingView;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@SmallTest
|
||||||
|
public class RingViewTest extends ViewTest
|
||||||
|
{
|
||||||
|
private RingView view;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup()
|
||||||
|
{
|
||||||
|
super.setup();
|
||||||
|
|
||||||
|
view = new RingView(targetContext);
|
||||||
|
view.setLabel("Hello world");
|
||||||
|
view.setPercentage(0.6f);
|
||||||
|
view.setColor(ColorHelper.palette[0]);
|
||||||
|
view.setMaxDiameter(dpToPixels(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void render_base() throws IOException
|
||||||
|
{
|
||||||
|
measureView(dpToPixels(100), dpToPixels(100), view);
|
||||||
|
assertRenders(view, "RingView/render.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void render_withLongLabel() throws IOException
|
||||||
|
{
|
||||||
|
view.setLabel("The quick brown fox jumps over the lazy fox");
|
||||||
|
|
||||||
|
measureView(dpToPixels(100), dpToPixels(100), view);
|
||||||
|
assertRenders(view, "RingView/renderLongLabel.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void render_withDifferentParams() throws IOException
|
||||||
|
{
|
||||||
|
view.setLabel("Habit Strength");
|
||||||
|
view.setPercentage(0.25f);
|
||||||
|
view.setMaxDiameter(dpToPixels(50));
|
||||||
|
view.setColor(ColorHelper.palette[5]);
|
||||||
|
|
||||||
|
measureView(dpToPixels(200), dpToPixels(200), view);
|
||||||
|
assertRenders(view, "RingView/renderDifferentParams.png");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||||
|
*
|
||||||
|
* 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.unit.views;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.helpers.DialogHelper;
|
||||||
|
import org.junit.Before;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import static junit.framework.Assert.fail;
|
||||||
|
|
||||||
|
public class ViewTest
|
||||||
|
{
|
||||||
|
protected static final double SIMILARITY_CUTOFF = 0.02;
|
||||||
|
public static final int HISTOGRAM_BIN_SIZE = 8;
|
||||||
|
|
||||||
|
protected Context testContext;
|
||||||
|
protected Context targetContext;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup()
|
||||||
|
{
|
||||||
|
targetContext = InstrumentationRegistry.getTargetContext();
|
||||||
|
testContext = InstrumentationRegistry.getContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void measureView(int width, int height, View view)
|
||||||
|
{
|
||||||
|
int specWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
|
||||||
|
int specHeight = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
|
||||||
|
|
||||||
|
view.measure(specWidth, specHeight);
|
||||||
|
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertRenders(View view, String expectedImagePath) throws IOException
|
||||||
|
{
|
||||||
|
StringBuilder errorMessage = new StringBuilder();
|
||||||
|
expectedImagePath = getVersionedViewAssetPath(expectedImagePath);
|
||||||
|
|
||||||
|
view.setDrawingCacheEnabled(true);
|
||||||
|
view.buildDrawingCache();
|
||||||
|
Bitmap actual = view.getDrawingCache();
|
||||||
|
Bitmap expected = getBitmapFromAssets(expectedImagePath);
|
||||||
|
|
||||||
|
int width = actual.getWidth();
|
||||||
|
int height = actual.getHeight();
|
||||||
|
Bitmap scaledExpected = Bitmap.createScaledBitmap(expected, width, height, true);
|
||||||
|
|
||||||
|
double distance;
|
||||||
|
boolean similarEnough = true;
|
||||||
|
|
||||||
|
if ((distance = compareHistograms(getHistogram(actual), getHistogram(scaledExpected))) > SIMILARITY_CUTOFF)
|
||||||
|
{
|
||||||
|
similarEnough = false;
|
||||||
|
errorMessage.append(String.format(
|
||||||
|
"Rendered image has wrong histogram (distance=%f). ",
|
||||||
|
distance));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!similarEnough)
|
||||||
|
{
|
||||||
|
saveBitmap(expectedImagePath, ".scaledExpected", scaledExpected);
|
||||||
|
String path = saveBitmap(expectedImagePath, ".actual", actual);
|
||||||
|
errorMessage.append(String.format("Actual rendered image " + "saved to %s", path));
|
||||||
|
fail(errorMessage.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
actual.recycle();
|
||||||
|
expected.recycle();
|
||||||
|
scaledExpected.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap getBitmapFromAssets(String path) throws IOException
|
||||||
|
{
|
||||||
|
InputStream stream = testContext.getAssets().open(path);
|
||||||
|
return BitmapFactory.decodeStream(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getVersionedViewAssetPath(String path)
|
||||||
|
{
|
||||||
|
String result = null;
|
||||||
|
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= 21)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String vpath = "views-v21/" + path;
|
||||||
|
testContext.getAssets().open(vpath);
|
||||||
|
result = vpath;
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(result == null)
|
||||||
|
result = "views/" + path;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String saveBitmap(String filename, String suffix, Bitmap bitmap)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
String absolutePath = String.format("%s/Failed/%s", targetContext.getExternalCacheDir(),
|
||||||
|
filename.replaceAll("\\.png$", suffix + ".png"));
|
||||||
|
new File(absolutePath).getParentFile().mkdirs();
|
||||||
|
FileOutputStream out = new FileOutputStream(absolutePath);
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
|
||||||
|
|
||||||
|
return absolutePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int[][] getHistogram(Bitmap bitmap)
|
||||||
|
{
|
||||||
|
int histogram[][] = new int[4][256 / HISTOGRAM_BIN_SIZE];
|
||||||
|
|
||||||
|
for(int x = 0; x < bitmap.getWidth(); x++)
|
||||||
|
{
|
||||||
|
for(int y = 0; y < bitmap.getHeight(); y++)
|
||||||
|
{
|
||||||
|
int color = bitmap.getPixel(x, y);
|
||||||
|
int[] argb = new int[]{
|
||||||
|
(color >> 24) & 0xff, //alpha
|
||||||
|
(color >> 16) & 0xff, //red
|
||||||
|
(color >> 8) & 0xff, //green
|
||||||
|
(color ) & 0xff //blue
|
||||||
|
};
|
||||||
|
|
||||||
|
histogram[0][argb[0] / HISTOGRAM_BIN_SIZE]++;
|
||||||
|
histogram[1][argb[1] / HISTOGRAM_BIN_SIZE]++;
|
||||||
|
histogram[2][argb[2] / HISTOGRAM_BIN_SIZE]++;
|
||||||
|
histogram[3][argb[3] / HISTOGRAM_BIN_SIZE]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return histogram;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double compareHistograms(int[][] actualHistogram, int[][] expectedHistogram)
|
||||||
|
{
|
||||||
|
long diff = 0;
|
||||||
|
long total = 0;
|
||||||
|
|
||||||
|
for(int i = 0; i < 256 / HISTOGRAM_BIN_SIZE; i ++)
|
||||||
|
{
|
||||||
|
diff += Math.abs(actualHistogram[0][i] - expectedHistogram[0][i]);
|
||||||
|
diff += Math.abs(actualHistogram[1][i] - expectedHistogram[1][i]);
|
||||||
|
diff += Math.abs(actualHistogram[2][i] - expectedHistogram[2][i]);
|
||||||
|
diff += Math.abs(actualHistogram[3][i] - expectedHistogram[3][i]);
|
||||||
|
|
||||||
|
total += actualHistogram[0][i];
|
||||||
|
total += actualHistogram[1][i];
|
||||||
|
total += actualHistogram[2][i];
|
||||||
|
total += actualHistogram[3][i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (double) diff / total / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int dpToPixels(int dp)
|
||||||
|
{
|
||||||
|
return (int) DialogHelper.dpToPixels(targetContext, dp);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
PACKAGE_NAME=org.isoron.uhabits
|
||||||
|
OUTPUT_DIR=app/build/outputs
|
||||||
|
LOG=${OUTPUT_DIR}/test.log
|
||||||
|
|
||||||
|
info() {
|
||||||
|
local COLOR='\033[1;32m'
|
||||||
|
local NC='\033[0m'
|
||||||
|
echo -e " $COLOR*$NC $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
info "Cleaning output directory..."
|
||||||
|
rm -rf ${OUTPUT_DIR}
|
||||||
|
mkdir -p ${OUTPUT_DIR}
|
||||||
|
|
||||||
|
info "Building and installing APK..."
|
||||||
|
./gradlew assembleDebug assembleAndroidTest >> $LOG 2>> $LOG || exit 1
|
||||||
|
adb install -r ${OUTPUT_DIR}/apk/app-debug.apk >> $LOG 2>> $LOG || exit 1
|
||||||
|
adb install -r ${OUTPUT_DIR}/apk/app-debug-androidTest-unaligned.apk \
|
||||||
|
>> $LOG 2>> $LOG || exit 1
|
||||||
|
|
||||||
|
info "Granting permission to disable animations..."
|
||||||
|
adb shell pm grant org.isoron.uhabits android.permission.SET_ANIMATION_SCALE \
|
||||||
|
>> $LOG 2>> $LOG || exit 1
|
||||||
|
|
||||||
|
info "Running tests..."
|
||||||
|
adb shell am instrument \
|
||||||
|
-e coverage true $* \
|
||||||
|
-w ${PACKAGE_NAME}.test/android.support.test.runner.AndroidJUnitRunner \
|
||||||
|
| tee ${OUTPUT_DIR}/runner.txt \
|
||||||
|
| tee -a $LOG
|
||||||
|
grep -q "FAILURES\!\!\!" ${OUTPUT_DIR}/runner.txt && failed=1
|
||||||
|
|
||||||
|
info "Fetching failed generated files..."
|
||||||
|
mkdir -p ${OUTPUT_DIR}/failed
|
||||||
|
adb pull /sdcard/Android/data/${PACKAGE_NAME}/cache/Failed \
|
||||||
|
${OUTPUT_DIR}/failed >> $LOG 2>> $LOG
|
||||||
|
adb shell rm -r /sdcard/Android/data/${PACKAGE_NAME}/cache/ >> $LOG 2>> $LOG
|
||||||
|
|
||||||
|
info "Fetching logcat..."
|
||||||
|
adb logcat -d > ${OUTPUT_DIR}/logcat.txt
|
||||||
|
|
||||||
|
info "Building coverage report..."
|
||||||
|
mkdir -p ${OUTPUT_DIR}/code-coverage/connected/
|
||||||
|
adb pull /data/data/${PACKAGE_NAME}/files/coverage.ec \
|
||||||
|
${OUTPUT_DIR}/code-coverage/connected/ >> $LOG 2>> $LOG
|
||||||
|
./gradlew app:createDebugCoverageReport \
|
||||||
|
-x app:connectedDebugAndroidTest >> $LOG 2>> $LOG
|
||||||
|
|
||||||
|
info "Uninstalling test APK..."
|
||||||
|
adb uninstall ${PACKAGE_NAME}.test >> $LOG 2>> $LOG
|
||||||
|
|
||||||
|
exit $failed
|