summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk4
-rw-r--r--proguard.flags4
-rw-r--r--src/com/android/calculator2/BoundedRational.java10
-rw-r--r--tests/Android.mk20
-rw-r--r--tests/AndroidManifest.xml33
-rw-r--r--tests/README.txt21
-rw-r--r--tests/src/com/android/calculator2/BRTest.java195
-rw-r--r--tests/src/com/android/calculator2/CalculatorHitSomeButtons.java164
8 files changed, 448 insertions, 3 deletions
diff --git a/Android.mk b/Android.mk
index d6b1c0c..2f3e84d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -27,6 +27,10 @@ LOCAL_SDK_VERSION := current
LOCAL_PACKAGE_NAME := ExactCalculator
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+
LOCAL_AAPT_FLAGS := --rename-manifest-package com.android.exactcalculator
include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/proguard.flags b/proguard.flags
new file mode 100644
index 0000000..dfd6702
--- /dev/null
+++ b/proguard.flags
@@ -0,0 +1,4 @@
+# Some small BoundedRational methods like equals() are not used by the
+# calculator, but crucial for testing.
+
+-keepclassmembers class com.android.calculator2.BoundedRational { *; }
diff --git a/src/com/android/calculator2/BoundedRational.java b/src/com/android/calculator2/BoundedRational.java
index 43572d5..4a71dee 100644
--- a/src/com/android/calculator2/BoundedRational.java
+++ b/src/com/android/calculator2/BoundedRational.java
@@ -62,9 +62,13 @@ public class BoundedRational {
}
// Debug or log messages only, not pretty.
+ public String toString() {
+ return mNum.toString() + "/" + mDen.toString();
+ }
+
public static String toString(BoundedRational r) {
if (r == null) return "not a small rational";
- return r.mNum.toString() + "/" + r.mDen.toString();
+ return r.toString();
}
// Primarily for debugging; clearly not exact
@@ -113,7 +117,7 @@ public class BoundedRational {
}
public int signum() {
- return mDen.signum() * mDen.signum();
+ return mNum.signum() * mDen.signum();
}
public boolean equals(BoundedRational r) {
@@ -184,7 +188,7 @@ public class BoundedRational {
if (!num_sqrt.multiply(num_sqrt).equals(r.mNum)) return null;
final BigInteger den_sqrt = BigInteger.valueOf(Math.round(Math.sqrt(
r.mDen.doubleValue())));
- if (!num_sqrt.multiply(den_sqrt).equals(r.mDen)) return null;
+ if (!den_sqrt.multiply(den_sqrt).equals(r.mDen)) return null;
return new BoundedRational(num_sqrt, den_sqrt);
}
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000..f4a73e7
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,20 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SDK_VERSION := current
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+# LOCAL_JAVA_LIBRARIES := android.test.runner
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := ExactCalculatorTests
+
+LOCAL_INSTRUMENTATION_FOR := ExactCalculator
+
+LOCAL_AAPT_FLAGS := --rename-manifest-package com.android.exactcalculator.tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
new file mode 100644
index 0000000..a6183f0
--- /dev/null
+++ b/tests/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.calculator2.tests">
+
+ <uses-sdk
+ android:minSdkVersion="21" />
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.exactcalculator"
+ android:label="BoundedRational and Calculator Functional Test">
+ </instrumentation>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+</manifest>
diff --git a/tests/README.txt b/tests/README.txt
new file mode 100644
index 0000000..40b4f8a
--- /dev/null
+++ b/tests/README.txt
@@ -0,0 +1,21 @@
+Run on Android with
+
+1) Build the tests.
+2) Install the calculator with
+adb install <tree root>/out/target/product/generic/data/app/ExactCalculatorTests/ExactCalculator.apk
+3) adb install <tree root>/out/target/product/generic/data/app/ExactCalculatorTests/ExactCalculatorTests.apk
+4) adb shell am instrument -w com.android.exactcalculator.tests/android.test.InstrumentationTestRunner
+
+There are two kinds of tests:
+
+1. A superficial test of calculator functionality through the UI.
+This is a resurrected version of a test that appeared in KitKat.
+It's currently mostly a placeholder and some basic infrastructure for
+future tests.
+
+2. A test of the BoundedRationals library that mostly checks for agreement
+with the constructive reals (CR) package. (The BoundedRationals package
+is used by the calculator mostly to identify exact results, i.e.
+terminating decimal expansions. But it's also used to optimize CR
+computations, and bugs in BoundedRational could result in incorrect
+outputs.)
diff --git a/tests/src/com/android/calculator2/BRTest.java b/tests/src/com/android/calculator2/BRTest.java
new file mode 100644
index 0000000..bd7070a
--- /dev/null
+++ b/tests/src/com/android/calculator2/BRTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// A test for BoundedRationals package.
+
+package com.android.calculator2;
+
+import com.hp.creals.CR;
+import com.hp.creals.UnaryCRFunction;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import java.math.BigInteger;
+
+public class BRTest extends TestCase {
+ private static void check(boolean x, String s) {
+ if (!x) throw new AssertionFailedError(s);
+ }
+ final static int TEST_PREC = -100; // 100 bits to the right of
+ // binary point.
+ private static void checkEq(BoundedRational x, CR y, String s) {
+ check(x.CRValue().compareTo(y, TEST_PREC) == 0, s);
+ }
+ private static void checkWeakEq(BoundedRational x, CR y, String s) {
+ if (x != null) checkEq(x, y, s);
+ }
+
+ private final static UnaryCRFunction ASIN = UnaryCRFunction.asinFunction;
+ private final static UnaryCRFunction ACOS = UnaryCRFunction.acosFunction;
+ private final static UnaryCRFunction ATAN = UnaryCRFunction.atanFunction;
+ private final static UnaryCRFunction TAN = UnaryCRFunction.tanFunction;
+ private final static BoundedRational BR_0 = new BoundedRational(0);
+ private final static BoundedRational BR_M1 = new BoundedRational(-1);
+ private final static BoundedRational BR_2 = new BoundedRational(2);
+ private final static BoundedRational BR_M2 = new BoundedRational(-2);
+ private final static BoundedRational BR_15 = new BoundedRational(15);
+ private final static BoundedRational BR_390 = new BoundedRational(390);
+ private final static BoundedRational BR_M390 = new BoundedRational(-390);
+ private final static CR CR_1 = CR.valueOf(1);
+
+ private final static CR RADIANS_PER_DEGREE = CR.PI.divide(CR.valueOf(180));
+ private final static CR DEGREES_PER_RADIAN = CR.valueOf(180).divide(CR.PI);
+ private final static CR LN10 = CR.valueOf(10).ln();
+
+ private static CR toRadians(CR x) {
+ return x.multiply(RADIANS_PER_DEGREE);
+ }
+
+ private static CR fromRadians(CR x) {
+ return x.multiply(DEGREES_PER_RADIAN);
+ }
+
+ // We assume that x is simple enough that we don't overflow bounds.
+ private static void checkBR(BoundedRational x) {
+ check(x != null, "test data should not be null");
+ CR xAsCR = x.CRValue();
+ checkEq(BoundedRational.add(x, BoundedRational.ONE), xAsCR.add(CR_1),
+ "add 1:" + x);
+ checkEq(BoundedRational.subtract(x, BoundedRational.MINUS_THIRTY),
+ xAsCR.subtract(CR.valueOf(-30)), "sub -30:" + x);
+ checkEq(BoundedRational.multiply(x, BR_15),
+ xAsCR.multiply(CR.valueOf(15)), "multiply 15:" + x);
+ checkEq(BoundedRational.divide(x, BR_15),
+ xAsCR.divide(CR.valueOf(15)), "divide 15:" + x);
+ checkWeakEq(BoundedRational.sin(x), xAsCR.sin(), "sin:" + x);
+ checkWeakEq(BoundedRational.cos(x), xAsCR.cos(), "cos:" + x);
+ checkWeakEq(BoundedRational.tan(x), TAN.execute(xAsCR), "tan:" + x);
+ checkWeakEq(BoundedRational.degreeSin(x), toRadians(xAsCR).sin(),
+ "degree sin:" + x);
+ checkWeakEq(BoundedRational.degreeCos(x), toRadians(xAsCR).cos(),
+ "degree cos:" + x);
+ BigInteger big_x = BoundedRational.asBigInteger(x);
+ long long_x = (big_x == null? 0 : big_x.longValue());
+ try {
+ checkWeakEq(BoundedRational.degreeTan(x),
+ TAN.execute(toRadians(xAsCR)), "degree tan:" + x);
+ check((long_x - 90) % 180 != 0, "missed undefined tan: " + x);
+ } catch (ArithmeticException ignored) {
+ check((long_x - 90) % 180 == 0, "exception on defined tan: " + x);
+ }
+ if (x.compareTo(BoundedRational.ONE) <= 0
+ && x.compareTo(BoundedRational.MINUS_ONE) >= 0) {
+ checkWeakEq(BoundedRational.asin(x), ASIN.execute(xAsCR),
+ "asin:" + x);
+ checkWeakEq(BoundedRational.acos(x), ACOS.execute(xAsCR),
+ "acos:" + x);
+ checkWeakEq(BoundedRational.degreeAsin(x),
+ fromRadians(ASIN.execute(xAsCR)), "degree asin:" + x);
+ checkWeakEq(BoundedRational.degreeAcos(x),
+ fromRadians(ACOS.execute(xAsCR)), "degree acos:" + x);
+ }
+ checkWeakEq(BoundedRational.atan(x), fromRadians(ATAN.execute(xAsCR)),
+ "atan:" + x);
+ checkWeakEq(BoundedRational.degreeAtan(x),
+ fromRadians(ATAN.execute(xAsCR)), "degree atan:" + x);
+ if (x.signum() > 0) {
+ checkWeakEq(BoundedRational.ln(x), xAsCR.ln(), "ln:" + x);
+ checkWeakEq(BoundedRational.log(x), xAsCR.ln().divide(LN10),
+ "log:" + x);
+ checkWeakEq(BoundedRational.sqrt(x), xAsCR.sqrt(), "sqrt:" + x);
+ checkEq(BoundedRational.pow(x, BR_15),
+ xAsCR.ln().multiply(CR.valueOf(15)).exp(),
+ "pow(x,15):" + x);
+ }
+ }
+
+ public void testBR() {
+ checkEq(BR_0, CR.valueOf(0), "0");
+ checkEq(BR_390, CR.valueOf(390), "390");
+ checkEq(BR_15, CR.valueOf(15), "15");
+ checkEq(BR_M390, CR.valueOf(-390), "-390");
+ checkEq(BR_M1, CR.valueOf(-1), "-1");
+ checkEq(BR_2, CR.valueOf(2), "2");
+ checkEq(BR_M2, CR.valueOf(-2), "-2");
+ check(BR_0.signum() == 0, "signum(0)");
+ check(BR_M1.signum() == -1, "signum(-1)");
+ check(BR_2.signum() == 1, "signum(2)");
+ // We check values that include all interesting degree values.
+ BoundedRational r = BR_M390;
+ while (!r.equals(BR_390)) {
+ check(r != null, "loop counter overflowed!");
+ checkBR(r);
+ r = BoundedRational.add(r, BR_15);
+ }
+ checkBR(BoundedRational.HALF);
+ checkBR(BoundedRational.MINUS_HALF);
+ checkBR(BoundedRational.ONE);
+ checkBR(BoundedRational.MINUS_ONE);
+ checkBR(new BoundedRational(1000));
+ checkBR(new BoundedRational(100));
+ checkBR(new BoundedRational(4,9));
+ check(BoundedRational.sqrt(new BoundedRational(4,9)) != null,
+ "sqrt(4/9) is null");
+ checkBR(BoundedRational.negate(new BoundedRational(4,9)));
+ checkBR(new BoundedRational(5,9));
+ checkBR(new BoundedRational(5,10));
+ checkBR(new BoundedRational(5,10));
+ checkBR(new BoundedRational(4,13));
+ checkBR(new BoundedRational(36));
+ checkBR(BoundedRational.negate(new BoundedRational(36)));
+ check(BoundedRational.pow(null, BR_15) == null, "pow(null, 15)");
+ }
+
+ public void testBRexceptions() {
+ try {
+ BoundedRational.ln(BR_M1);
+ check(false, "ln(-1)");
+ } catch (ArithmeticException ignored) {}
+ try {
+ BoundedRational.log(BR_M2);
+ check(false, "log(-2)");
+ } catch (ArithmeticException ignored) {}
+ try {
+ BoundedRational.sqrt(BR_M1);
+ check(false, "sqrt(-1)");
+ } catch (ArithmeticException ignored) {}
+ try {
+ BoundedRational.asin(BR_M2);
+ check(false, "asin(-2)");
+ } catch (ArithmeticException ignored) {}
+ try {
+ BoundedRational.degreeAcos(BR_2);
+ check(false, "degree acos(2)");
+ } catch (ArithmeticException ignored) {}
+ }
+
+ public void testBROverflow() {
+ BoundedRational sum = new BoundedRational(0);
+ long i;
+ for (i = 1; i < 1000; ++i) {
+ sum = BoundedRational.add(sum,
+ BoundedRational.inverse(new BoundedRational(i)));
+ if (sum == null) break;
+ }
+ // Experimentally, this overflows at 139, which seems
+ // plausible based on the Wolfram Alpha result.
+ // This test is robust against minor changes in MAX_SIZE.
+ check(i > 100, "Harmonic series overflowed at " + i);
+ check(i < 1000, "Harmonic series didn't overflow");
+ }
+}
diff --git a/tests/src/com/android/calculator2/CalculatorHitSomeButtons.java b/tests/src/com/android/calculator2/CalculatorHitSomeButtons.java
new file mode 100644
index 0000000..d26a5cb
--- /dev/null
+++ b/tests/src/com/android/calculator2/CalculatorHitSomeButtons.java
@@ -0,0 +1,164 @@
+/**
+ * Copyright (c) 2008, Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.calculator2;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.app.Instrumentation.ActivityMonitor;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.graphics.Rect;
+import android.test.TouchUtils;
+
+import com.android.calculator2.Calculator;
+import com.android.calculator2.R;
+import com.android.calculator2.CalculatorResult;
+
+/**
+ * Instrumentation tests for poking some buttons
+ *
+ */
+
+public class CalculatorHitSomeButtons extends ActivityInstrumentationTestCase <Calculator>{
+ public boolean setup = false;
+ private static final String TAG = "CalculatorTests";
+ Calculator mActivity = null;
+ Instrumentation mInst = null;
+
+ public CalculatorHitSomeButtons() {
+ super("com.android.calculator2", Calculator.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mInst = getInstrumentation();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+/*
+ @LargeTest
+ public void testPressSomeKeys() {
+ Log.v(TAG, "Pressing some keys!");
+
+ // Make sure that we clear the output
+ press(KeyEvent.KEYCODE_ENTER);
+ press(KeyEvent.KEYCODE_CLEAR);
+
+ // 3 + 4 * 5 => 23
+ press(KeyEvent.KEYCODE_3);
+ press(KeyEvent.KEYCODE_PLUS);
+ press(KeyEvent.KEYCODE_4);
+ press(KeyEvent.KEYCODE_9 | KeyEvent.META_SHIFT_ON);
+ press(KeyEvent.KEYCODE_5);
+ press(KeyEvent.KEYCODE_ENTER);
+
+ checkDisplay("23");
+ }
+*/
+
+ @LargeTest
+ public void testTapSomeButtons() {
+ // TODO: This probably makes way too many hardcoded assumptions about locale.
+ // The calculator will need a routine to internationalize the output.
+ // We should use that here, too.
+ Log.v(TAG, "Tapping some buttons!");
+
+ // Make sure that we clear the output
+ tap(R.id.eq);
+ tap(R.id.del);
+
+ // 567 / 3 => 189
+ tap(R.id.digit_5);
+ tap(R.id.digit_6);
+ tap(R.id.digit_7);
+ tap(R.id.op_div);
+ tap(R.id.digit_3);
+ tap(R.id.eq);
+
+ checkDisplay("189");
+
+ // make sure we can continue calculations also
+ // 189 - 789 => -600
+ tap(R.id.op_sub);
+ tap(R.id.digit_7);
+ tap(R.id.digit_8);
+ tap(R.id.digit_9);
+ tap(R.id.eq);
+
+ // Careful: the first digit in the expected value is \u2212, not "-" (a hyphen)
+ checkDisplay(mActivity.getString(R.string.op_sub) + "600");
+ }
+
+ // helper functions
+ private void press(int keycode) {
+ mInst.sendKeyDownUpSync(keycode);
+ }
+
+ private boolean tap(int id) {
+ View view = mActivity.findViewById(id);
+ if(view != null) {
+ TouchUtils.clickView(this, view);
+ return true;
+ }
+ return false;
+ }
+
+ private void checkDisplay(final String s) {
+ mInst.waitForIdle(new Runnable () {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(20); // Wait for background computation
+ } catch(InterruptedException ignored) {
+ fail("Unexpected interrupt");
+ }
+ mInst.waitForIdle(new Runnable () {
+ @Override
+ public void run() {
+ assertEquals(displayVal(), s);
+ }
+ });
+ }
+ });
+ }
+
+ private String displayVal() {
+ CalculatorResult display = (CalculatorResult) mActivity.findViewById(R.id.result);
+ assertNotNull(display);
+
+ EditText box = (EditText) display;
+ assertNotNull(box);
+
+ return box.getText().toString();
+ }
+}
+