Convert BaseViewTest

pull/716/head
Quentin Hibon 5 years ago
parent 22dcd9f7ae
commit 0fc9bb57ae

@ -1,198 +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.graphics.*;
import android.view.*;
import android.widget.*;
import androidx.annotation.*;
import androidx.test.platform.app.*;
import org.isoron.uhabits.utils.*;
import org.isoron.uhabits.widgets.*;
import java.io.*;
import java.util.*;
import static android.view.View.MeasureSpec.*;
public class BaseViewTest extends BaseAndroidTest
{
public double similarityCutoff = 0.00018;
@Override
public void setUp()
{
super.setUp();
}
protected void assertRenders(View view, String expectedImagePath)
throws IOException
{
Bitmap actual = renderView(view);
if(actual == null) throw new IllegalStateException("actual is null");
assertRenders(actual, expectedImagePath);
}
protected void assertRenders(Bitmap actual, String expectedImagePath) throws IOException {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
expectedImagePath = "views/" + expectedImagePath;
try
{
Bitmap expected = getBitmapFromAssets(expectedImagePath);
double distance = distance(actual, expected);
if (distance > similarityCutoff)
{
saveBitmap(expectedImagePath, ".expected", expected);
String path = saveBitmap(expectedImagePath, "", actual);
fail(String.format("Image differs from expected " +
"(distance=%f). Actual rendered " +
"image saved to %s", distance, path));
}
expected.recycle();
}
catch (IOException e)
{
String path = saveBitmap(expectedImagePath, "", actual);
fail(String.format("Could not open expected image. Actual " +
"rendered image saved to %s", path));
throw e;
}
}
@NonNull
protected FrameLayout convertToView(BaseWidget widget,
int width,
int height)
{
widget.setDimensions(
new WidgetDimensions(width, height, width, height));
FrameLayout view = new FrameLayout(targetContext);
RemoteViews remoteViews = widget.getPortraitRemoteViews();
view.addView(remoteViews.apply(targetContext, view));
measureView(view, width, height);
return view;
}
protected float dpToPixels(int dp)
{
return InterfaceUtils.dpToPixels(targetContext, dp);
}
protected void measureView(View view, float width, float height)
{
int specWidth = makeMeasureSpec((int) width, View.MeasureSpec.EXACTLY);
int specHeight = makeMeasureSpec((int) height, View.MeasureSpec.EXACTLY);
view.setLayoutParams(new ViewGroup.LayoutParams((int) width, (int) height));
view.measure(specWidth, specHeight);
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
}
protected void skipAnimation(View view)
{
ViewPropertyAnimator animator = view.animate();
animator.setDuration(0);
animator.start();
}
private int[] colorToArgb(int c1)
{
return new int[]{
(c1 >> 24) & 0xff, //alpha
(c1 >> 16) & 0xff, //red
(c1 >> 8) & 0xff, //green
(c1) & 0xff //blue
};
}
private double distance(Bitmap b1, Bitmap b2)
{
if (b1.getWidth() != b2.getWidth()) return 1.0;
if (b1.getHeight() != b2.getHeight()) return 1.0;
Random random = new Random();
double distance = 0.0;
for (int x = 0; x < b1.getWidth(); x++)
{
for (int y = 0; y < b1.getHeight(); y++)
{
if (random.nextInt(4) != 0) continue;
int[] argb1 = colorToArgb(b1.getPixel(x, y));
int[] argb2 = colorToArgb(b2.getPixel(x, y));
distance += Math.abs(argb1[0] - argb2[0]);
distance += Math.abs(argb1[1] - argb2[1]);
distance += Math.abs(argb1[2] - argb2[2]);
distance += Math.abs(argb1[3] - argb2[3]);
}
}
distance /= 255.0 * 16 * b1.getWidth() * b1.getHeight();
return distance;
}
private Bitmap getBitmapFromAssets(String path) throws IOException
{
InputStream stream = testContext.getAssets().open(path);
return BitmapFactory.decodeStream(stream);
}
private String saveBitmap(String filename, String suffix, Bitmap bitmap)
throws IOException
{
File dir = FileUtils.getSDCardDir("test-screenshots");
if (dir == null)
dir = new AndroidDirFinder(targetContext).getFilesDir("test-screenshots");
if (dir == null) throw new RuntimeException(
"Could not find suitable dir for screenshots");
filename = filename.replaceAll("\\.png$", suffix + ".png");
String absolutePath =
String.format("%s/%s", dir.getAbsolutePath(), filename);
File parent = new File(absolutePath).getParentFile();
if (!parent.exists() && !parent.mkdirs()) throw new RuntimeException(
String.format("Could not create dir: %s",
parent.getAbsolutePath()));
FileOutputStream out = new FileOutputStream(absolutePath);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
return absolutePath;
}
public Bitmap renderView(View view)
{
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
if(view.isLayoutRequested())
measureView(view, width, height);
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
view.invalidate();
view.draw(canvas);
return bitmap;
}
}

@ -0,0 +1,185 @@
/*
* 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.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.view.View
import android.view.View.MeasureSpec
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.test.platform.app.InstrumentationRegistry
import org.isoron.uhabits.utils.FileUtils.getSDCardDir
import org.isoron.uhabits.utils.InterfaceUtils.dpToPixels
import org.isoron.uhabits.widgets.BaseWidget
import org.isoron.uhabits.widgets.WidgetDimensions
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.util.Random
open class BaseViewTest : BaseAndroidTest() {
var similarityCutoff = 0.00018
override fun setUp() {
super.setUp()
}
@Throws(IOException::class)
protected fun assertRenders(view: View, expectedImagePath: String) {
val actual = renderView(view)
assertRenders(actual, expectedImagePath)
}
@Throws(IOException::class)
protected fun assertRenders(actual: Bitmap, expectedImagePath: String) {
var expectedImagePath = expectedImagePath
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
expectedImagePath = "views/$expectedImagePath"
try {
val expected = getBitmapFromAssets(expectedImagePath)
val distance = distance(actual, expected)
if (distance > similarityCutoff) {
saveBitmap(expectedImagePath, ".expected", expected)
val path = saveBitmap(expectedImagePath, "", actual)
fail(
String.format(
"Image differs from expected " +
"(distance=%f). Actual rendered " +
"image saved to %s",
distance,
path
)
)
}
expected.recycle()
} catch (e: IOException) {
val path = saveBitmap(expectedImagePath, "", actual)
fail(
String.format(
"Could not open expected image. Actual " +
"rendered image saved to %s",
path
)
)
throw e
}
}
protected fun convertToView(
widget: BaseWidget,
width: Int,
height: Int
): FrameLayout {
widget.setDimensions(
WidgetDimensions(width, height, width, height)
)
val view = FrameLayout(targetContext)
val remoteViews = widget.portraitRemoteViews
view.addView(remoteViews.apply(targetContext, view))
measureView(view, width.toFloat(), height.toFloat())
return view
}
protected fun dpToPixels(dp: Int): Float {
return dpToPixels(targetContext, dp.toFloat())
}
protected fun measureView(view: View, width: Float, height: Float) {
val specWidth = MeasureSpec.makeMeasureSpec(width.toInt(), MeasureSpec.EXACTLY)
val specHeight = MeasureSpec.makeMeasureSpec(height.toInt(), MeasureSpec.EXACTLY)
view.layoutParams = ViewGroup.LayoutParams(width.toInt(), height.toInt())
view.measure(specWidth, specHeight)
view.layout(0, 0, view.measuredWidth, view.measuredHeight)
}
protected fun skipAnimation(view: View) {
val animator = view.animate()
animator.duration = 0
animator.start()
}
private fun colorToArgb(c1: Int): IntArray {
return intArrayOf(
c1 shr 24 and 0xff, // alpha
c1 shr 16 and 0xff, // red
c1 shr 8 and 0xff, // green
c1 and 0xff // blue
)
}
private fun distance(b1: Bitmap, b2: Bitmap): Double {
if (b1.width != b2.width) return 1.0
if (b1.height != b2.height) return 1.0
val random = Random()
var distance = 0.0
for (x in 0 until b1.width) {
for (y in 0 until b1.height) {
if (random.nextInt(4) != 0) continue
val argb1 = colorToArgb(b1.getPixel(x, y))
val argb2 = colorToArgb(b2.getPixel(x, y))
distance += Math.abs(argb1[0] - argb2[0]).toDouble()
distance += Math.abs(argb1[1] - argb2[1]).toDouble()
distance += Math.abs(argb1[2] - argb2[2]).toDouble()
distance += Math.abs(argb1[3] - argb2[3]).toDouble()
}
}
distance /= 255.0 * 16 * b1.width * b1.height
return distance
}
@Throws(IOException::class)
private fun getBitmapFromAssets(path: String): Bitmap {
val stream = testContext.assets.open(path)
return BitmapFactory.decodeStream(stream)
}
@Throws(IOException::class)
private fun saveBitmap(filename: String, suffix: String, bitmap: Bitmap): String {
var filename = filename
var dir = getSDCardDir("test-screenshots")
if (dir == null) dir = AndroidDirFinder(targetContext).getFilesDir("test-screenshots")
if (dir == null) throw RuntimeException(
"Could not find suitable dir for screenshots"
)
filename = filename.replace("\\.png$".toRegex(), "$suffix.png")
val absolutePath = String.format("%s/%s", dir.absolutePath, filename)
val parent = File(absolutePath).parentFile
if (!parent.exists() && !parent.mkdirs()) throw RuntimeException(
String.format(
"Could not create dir: %s",
parent.absolutePath
)
)
val out = FileOutputStream(absolutePath)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
return absolutePath
}
fun renderView(view: View): Bitmap {
val width = view.measuredWidth
val height = view.measuredHeight
if (view.isLayoutRequested) measureView(view, width.toFloat(), height.toFloat())
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
view.invalidate()
view.draw(canvas)
return bitmap
}
}
Loading…
Cancel
Save