@ -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
|