2
0
mirror of synced 2025-02-23 14:58:12 +00:00
mobile/bind/java/SeqTest.java
Elias Naur 89b8360218 bind: remove error wrappers to preserve error instance identity
CL 24800 changed the error representation from strings to objects.
However, since native errors types are not immediately compatible
across languages, wrapper types were introduced to bridge the gap.

This CL remove those wrappers and instead special case the error
proxy types to conform to their language error protocol.

Specifically:

 - The ObjC proxy for Go errors now extends NSError and calls
   initWithDomain to store the error message.
 - The Go proxy for ObjC NSError return the localizedDescription
    property for calls to Error.
 - The Java proxy for Go errors ow extends Exception and
   overrides getMessage() to return the error message.
 - The Go proxy for Java Exceptions returns getMessage whenever
   Error is called.

The end result is that error values behave more like normal objects
across the language boundary. In particular, instance identity is
now preserved: an error passed across the boundary and back will
result in the same instance.

There are two semantic changes that followed this change:

 - The domain for wrapped Go errors is now always "go".
   The domain wasn't useful before this CL: the domains were set to
   the package name of function or method where the error happened
   to cross the language boundary.
 - If a Go method that returns an error is implemented in ObjC, the
   implementation must now both return NO _and_ set the error result
   for the calling Go code to receive a non-nil error.
   Before this CL, because errors were always wrapped, a nil ObjC
   could be represented with a non-nil wrapper.

Change-Id: Idb415b6b13ecf79ccceb60f675059942bfc48fec
Reviewed-on: https://go-review.googlesource.com/29298
Reviewed-by: David Crawshaw <crawshaw@golang.org>
2016-10-04 09:11:42 +00:00

555 lines
16 KiB
Java

// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package go;
import android.test.InstrumentationTestCase;
import android.test.MoreAsserts;
import java.util.Arrays;
import java.util.Random;
import go.testpkg.*;
import go.secondpkg.Secondpkg;
public class SeqTest extends InstrumentationTestCase {
public SeqTest() {
}
public void testConst() {
assertEquals("const String", "a string", Testpkg.AString);
assertEquals("const Int", 7, Testpkg.AnInt);
assertEquals("const Bool", true, Testpkg.ABool);
assertEquals("const Float", 0.12345, Testpkg.AFloat, 0.0001);
assertEquals("const MinInt32", -1<<31, Testpkg.MinInt32);
assertEquals("const MaxInt32", (1<<31) - 1, Testpkg.MaxInt32);
assertEquals("const MinInt64", -1L<<63, Testpkg.MinInt64);
assertEquals("const MaxInt64", (1L<<63) - 1, Testpkg.MaxInt64);
assertEquals("const SmallestNonzeroFloat64", 4.940656458412465441765687928682213723651e-324, Testpkg.SmallestNonzeroFloat64, 1e-323);
assertEquals("const MaxFloat64", 1.797693134862315708145274237317043567981e+308, Testpkg.MaxFloat64, 0.0001);
assertEquals("const SmallestNonzeroFloat32", 1.401298464324817070923729583289916131280e-45, Testpkg.SmallestNonzeroFloat32, 1e-44);
assertEquals("const MaxFloat32", 3.40282346638528859811704183484516925440e+38, Testpkg.MaxFloat32, 0.0001);
assertEquals("const Log2E", 1/0.693147180559945309417232121458176568075500134360255254120680009, Testpkg.Log2E, 0.0001);
}
public void testRefMap() {
// Ensure that the RefMap.live count is kept in sync
// even a particular reference number is removed and
// added again
Seq.RefMap m = new Seq.RefMap();
Seq.Ref r = new Seq.Ref(1, null);
m.put(r.refnum, r);
m.remove(r.refnum);
m.put(r.refnum, r);
// Force the RefMap to grow, to activate the sanity
// checking of the live count in RefMap.grow.
for (int i = 2; i < 24; i++) {
m.put(i, new Seq.Ref(i, null));
}
}
public void testVar() {
assertEquals("var StringVar", "a string var", Testpkg.getStringVar());
String newStringVar = "a new string var";
Testpkg.setStringVar(newStringVar);
assertEquals("var StringVar", newStringVar, Testpkg.getStringVar());
assertEquals("var IntVar", 77, Testpkg.getIntVar());
long newIntVar = 777;
Testpkg.setIntVar(newIntVar);
assertEquals("var IntVar", newIntVar, Testpkg.getIntVar());
S s0 = Testpkg.getStructVar();
assertEquals("var StructVar", "a struct var", s0.string());
S s1 = Testpkg.new_();
Testpkg.setStructVar(s1);
assertEquals("var StructVar", s1.string(), Testpkg.getStructVar().string());
AnI obj = new AnI();
obj.name = "this is an I";
Testpkg.setInterfaceVar(obj);
assertEquals("var InterfaceVar", obj.string(), Testpkg.getInterfaceVar().string());
}
public void testAssets() {
// Make sure that a valid context is set before reading assets
Seq.setContext(getInstrumentation().getContext());
String want = "Hello, Assets.\n";
String got = Testpkg.readAsset();
assertEquals("Asset read", want, got);
}
public void testAdd() {
long res = Testpkg.add(3, 4);
assertEquals("Unexpected arithmetic failure", 7, res);
}
public void testBool() {
assertTrue(Testpkg.negate(false));
assertFalse(Testpkg.negate(true));
}
public void testShortString() {
String want = "a short string";
String got = Testpkg.strDup(want);
assertEquals("Strings should match", want, got);
want = "";
got = Testpkg.strDup(want);
assertEquals("Strings should match (empty string)", want, got);
got = Testpkg.strDup(null);
assertEquals("Strings should match (null string)", want, got);
}
public void testLongString() {
StringBuilder b = new StringBuilder();
for (int i = 0; i < 128*1024; i++) {
b.append("0123456789");
}
String want = b.toString();
String got = Testpkg.strDup(want);
assertEquals("Strings should match", want, got);
}
public void testUnicode() {
String[] tests = new String[]{
"abcxyz09{}",
"Hello, 世界",
"\uffff\uD800\uDC00\uD800\uDC01\uD808\uDF45\uDBFF\uDFFF",
// From Go std lib tests in unicode/utf16/utf16_test.go
"\u0001\u0002\u0003\u0004",
"\uffff\ud800\udc00\ud800\udc01\ud808\udf45\udbff\udfff",
"\ud800a",
"\udfff"
};
String[] wants = new String[]{
"abcxyz09{}",
"Hello, 世界",
"\uffff\uD800\uDC00\uD800\uDC01\uD808\uDF45\uDBFF\uDFFF",
"\u0001\u0002\u0003\u0004",
"\uffff\ud800\udc00\ud800\udc01\ud808\udf45\udbff\udfff",
"\ufffda",
"\ufffd"
};
for (int i = 0; i < tests.length; i++) {
String got = Testpkg.strDup(tests[i]);
String want = wants[i];
assertEquals("Strings should match", want, got);
}
}
public void testNilErr() throws Exception {
Testpkg.err(null); // returns nil, no exception
}
public void testErr() {
String msg = "Go errors are dropped into the confusing space of exceptions";
try {
Testpkg.err(msg);
fail("expected non-nil error to be turned into an exception");
} catch (Exception e) {
assertEquals("messages should match", msg, e.getMessage());
}
}
public void testByteArray() {
for (int i = 0; i < 2048; i++) {
if (i == 0) {
byte[] got = Testpkg.bytesAppend(null, null);
assertEquals("Bytes(null+null) should match", (byte[])null, got);
got = Testpkg.bytesAppend(new byte[0], new byte[0]);
assertEquals("Bytes(empty+empty) should match", (byte[])null, got);
continue;
}
byte[] want = new byte[i];
new Random().nextBytes(want);
byte[] s1 = null;
byte[] s2 = null;
if (i > 0) {
s1 = Arrays.copyOfRange(want, 0, 1);
}
if (i > 1) {
s2 = Arrays.copyOfRange(want, 1, i);
}
byte[] got = Testpkg.bytesAppend(s1, s2);
MoreAsserts.assertEquals("Bytes(len="+i+") should match", want, got);
}
}
// Test for golang.org/issue/9486.
public void testByteArrayAfterString() {
byte[] bytes = new byte[1024];
for (int i=0; i < bytes.length; i++) {
bytes[i] = 8;
}
String stuff = "stuff";
byte[] got = Testpkg.appendToString(stuff, bytes);
try {
byte[] s = stuff.getBytes("UTF-8");
byte[] want = new byte[s.length + bytes.length];
System.arraycopy(s, 0, want, 0, s.length);
System.arraycopy(bytes, 0, want, s.length, bytes.length);
MoreAsserts.assertEquals("Bytes should match", want, got);
} catch (Exception e) {
fail("Cannot perform the test: " + e.toString());
}
}
public void testGoRefGC() {
S s = Testpkg.new_();
runGC();
long collected = Testpkg.numSCollected();
assertEquals("Only S should be pinned", 0, collected);
s = null;
runGC();
collected = Testpkg.numSCollected();
assertEquals("S should be collected", 1, collected);
}
private class AnI implements I {
public void e() throws Exception {
throw new Exception("my exception from E");
}
boolean calledF;
public void f() {
calledF = true;
}
public I i() {
return this;
}
public S s() {
return Testpkg.new_();
}
public String stoString(S s) {
return s.string();
}
public long v() {
return 1234;
}
public long ve() throws Exception {
throw new Exception("my exception from VE");
}
public String name;
public String string() {
return name;
}
}
// TODO(hyangah): add tests for methods that take parameters.
public void testInterfaceMethodReturnsError() {
final AnI obj = new AnI();
try {
Testpkg.callE(obj);
fail("Expecting exception but none was thrown.");
} catch (Exception e) {
assertEquals("Error messages should match", "my exception from E", e.getMessage());
}
}
public void testInterfaceMethodVoid() {
final AnI obj = new AnI();
Testpkg.callF(obj);
assertTrue("Want AnI.F to be called", obj.calledF);
}
public void testInterfaceMethodReturnsInterface() {
AnI obj = new AnI();
obj.name = "testing AnI.I";
I i = Testpkg.callI(obj);
assertEquals("Want AnI.I to return itself", i.string(), obj.string());
runGC();
i = Testpkg.callI(obj);
assertEquals("Want AnI.I to return itself", i.string(), obj.string());
}
public void testInterfaceMethodReturnsStructPointer() {
final AnI obj = new AnI();
for (int i = 0; i < 5; i++) {
S s = Testpkg.callS(obj);
runGC();
}
}
public void testInterfaceMethodTakesStructPointer() {
final AnI obj = new AnI();
S s = Testpkg.callS(obj);
String got = obj.stoString(s);
String want = s.string();
assertEquals("Want AnI.StoString(s) to call s's String", want, got);
}
public void testInterfaceMethodReturnsInt() {
final AnI obj = new AnI();
assertEquals("Values must match", 1234, Testpkg.callV(obj));
}
public void testInterfaceMethodReturnsIntOrError() {
final AnI obj = new AnI();
try {
long v = Testpkg.callVE(obj);
fail("Expecting exception but none was thrown and got value " + v);
} catch (Exception e) {
assertEquals("Error messages should match", "my exception from VE", e.getMessage());
}
}
boolean finalizedAnI;
private class AnI_Traced extends AnI {
@Override
public void finalize() throws Throwable {
finalizedAnI = true;
super.finalize();
}
}
public void testJavaRefGC() {
finalizedAnI = false;
AnI obj = new AnI_Traced();
Testpkg.callF(obj);
assertTrue("want F to be called", obj.calledF);
Testpkg.callF(obj);
obj = null;
runGC();
assertTrue("want obj to be collected", finalizedAnI);
}
public void testJavaRefKeep() {
finalizedAnI = false;
AnI obj = new AnI_Traced();
Testpkg.callF(obj);
Testpkg.callF(obj);
obj = null;
runGC();
assertTrue("want obj not to be kept by Go", finalizedAnI);
finalizedAnI = false;
obj = new AnI_Traced();
Testpkg.keep(obj);
obj = null;
runGC();
assertFalse("want obj to be kept live by Go", finalizedAnI);
}
private int countI = 0;
private class CountI implements I {
public void f() { countI++; }
public void e() throws Exception {}
public I i() { return null; }
public S s() { return null; }
public String stoString(S s) { return ""; }
public long v() { return 0; }
public long ve() throws Exception { return 0; }
public String string() { return ""; }
}
public void testGoRefMapGrow() {
CountI obj = new CountI();
Testpkg.keep(obj);
// Push active references beyond base map size.
for (int i = 0; i < 24; i++) {
CountI o = new CountI();
Testpkg.callF(o);
if (i%3==0) {
Testpkg.keep(o);
}
}
runGC();
for (int i = 0; i < 128; i++) {
Testpkg.callF(new CountI());
}
Testpkg.callF(obj); // original object needs to work.
assertEquals(countI, 1+24+128);
}
private void runGC() {
System.gc();
System.runFinalization();
Testpkg.gc();
System.gc();
System.runFinalization();
}
public void testUnnamedParams() {
final String msg = "1234567";
assertEquals("want the length of \"1234567\" passed after unnamed params",
7, Testpkg.unnamedParams(10, 20, msg));
}
public void testPointerToStructAsField() {
Node a = Testpkg.newNode("A");
Node b = Testpkg.newNode("B");
a.setNext(b);
String got = a.string();
assertEquals("want Node A points to Node B", "A:B:<end>", got);
}
public void testImplementsInterface() {
Interface intf = Testpkg.newConcrete();
}
public void testErrorField() {
Node n = Testpkg.newNode("ErrTest");
Exception want = new Exception("an error message");
n.setErr(want);
Exception got = n.getErr();
assertTrue("want back the error we set", want == got);
String msg = Testpkg.errorMessage(want);
assertEquals("the error message must match", want.getMessage(), msg);
}
public void testErrorDup() {
Exception err = Testpkg.getGlobalErr();
assertTrue("the Go error instance must preserve its identity", Testpkg.isGlobalErr(err));
assertEquals("the Go error message must be preserved", "global err", err.getMessage());
}
//test if we have JNI local reference table overflow error
public void testLocalReferenceOverflow() {
Testpkg.callWithCallback(new GoCallback() {
@Override
public void varUpdate() {
//do nothing
}
});
}
public void testNullReferences() {
assertTrue(Testpkg.callWithNull(null, new NullTest() {
public NullTest null_() {
return null;
}
}));
assertEquals("Go nil interface is null", null, Testpkg.newNullInterface());
assertEquals("Go nil struct pointer is null", null, Testpkg.newNullStruct());
}
public void testPassByteArray() {
Testpkg.passByteArray(new B() {
@Override public void b(byte[] b) {
byte[] want = new byte[]{1, 2, 3, 4};
MoreAsserts.assertEquals("bytes should match", want, b);
}
});
}
public void testReader() {
byte[] b = new byte[8];
try {
long n = Testpkg.readIntoByteArray(b);
assertEquals("wrote to the entire byte array", b.length, n);
byte[] want = new byte[b.length];
for (int i = 0; i < want.length; i++)
want[i] = (byte)i;
MoreAsserts.assertEquals("bytes should match", want, b);
} catch (Exception e) {
fail("Failed to write: " + e.toString());
}
}
public void testGoroutineCallback() {
Testpkg.goroutineCallback(new Receiver() {
@Override public void hello(String msg) {
}
});
}
public void testImportedPkg() {
Testpkg.callImportedI(new go.secondpkg.I() {
@Override public long f(long i) {
return i;
}
});
assertEquals("imported string should match", Secondpkg.HelloString, Secondpkg.hello());
go.secondpkg.I i = Testpkg.newImportedI();
go.secondpkg.S s = Testpkg.newImportedS();
i = Testpkg.getImportedVarI();
s = Testpkg.getImportedVarS();
assertEquals("numbers should match", 8, i.f(8));
assertEquals("numbers should match", 8, s.f(8));
Testpkg.setImportedVarI(i);
Testpkg.setImportedVarS(s);
ImportedFields fields = Testpkg.newImportedFields();
i = fields.getI();
s = fields.getS();
fields.setI(i);
fields.setS(s);
Testpkg.withImportedI(i);
Testpkg.withImportedS(s);
go.secondpkg.IF f = new AnI();
f = Testpkg.new_();
go.secondpkg.Ser ser = Testpkg.newSer();
}
public void testRoundtripEquality() {
I want = new AnI();
assertTrue("java object passed through Go should not be wrapped", want == Testpkg.iDup(want));
InterfaceDupper idup = new InterfaceDupper(){
@Override public Interface iDup(Interface i) {
return i;
}
};
assertTrue("Go interface passed through Java should not be wrapped", Testpkg.callIDupper(idup));
ConcreteDupper cdup = new ConcreteDupper(){
@Override public Concrete cDup(Concrete c) {
return c;
}
};
assertTrue("Go struct passed through Java should not be wrapped", Testpkg.callCDupper(cdup));
}
public void testEmptyError() {
try {
Testpkg.emptyError();
fail("Empty error wasn't caught");
} catch (Exception e) {
}
EmptyErrorer empty = new EmptyErrorer() {
@Override public void emptyError() throws Exception {
throw new Exception("");
}
};
try {
Testpkg.callEmptyError(empty);
fail("Empty exception wasn't caught");
} catch (Exception e) {
}
}
public void testInitCaller() {
Testpkg.init();
InitCaller initer = Testpkg.newInitCaller();
initer.init();
}
}