Converting a Go string to a string suitable use a specialized function, UTF16Encode, that can encode the string directly to a malloc'ed buffer. That way, only two copies are made when strings are passed from Go to Java; once for UTF-8 to UTF-16 encoding and once for the creation of the Java String. This CL implements the same optimization in the other direction, with a UTF-16 to UTF-8 decoder implemented in C. Unfortunately, while calling into a Go decoder also saves the extra copy, the Cgo overhead makes the calls much slower for short strings. To alleviate the risk of introducing decoding bugs, I've added the tests from the encoding/utf16 package to SeqTest. As a sideeffect, both Java and ObjC now always copy strings, regardless of the argument mode. The cpy argument can therefore be removed from the string conversion functions. Furthermore, the modeRetained and modeReturned modes can be collapsed into just one. While we're here, delete a leftover function from seq/strings.go that wasn't removed when the old seq buffers went away. Benchmarks, as compared with benchstat over 5 runs: name old time/op new time/op delta JavaStringShort 11.4µs ±13% 11.6µs ± 4% ~ (p=0.859 n=10+5) JavaStringShortDirect 19.5µs ± 9% 20.3µs ± 2% +3.68% (p=0.019 n=9+5) JavaStringLong 103µs ± 8% 24µs ± 4% -77.13% (p=0.001 n=9+5) JavaStringLongDirect 113µs ± 9% 32µs ± 7% -71.63% (p=0.001 n=9+5) JavaStringShortUnicode 11.1µs ±16% 10.7µs ± 5% ~ (p=0.190 n=9+5) JavaStringShortUnicodeDirect 19.6µs ± 7% 20.2µs ± 1% +2.78% (p=0.029 n=9+5) JavaStringLongUnicode 97.1µs ± 9% 28.0µs ± 5% -71.17% (p=0.001 n=9+5) JavaStringLongUnicodeDirect 105µs ±10% 34µs ± 5% -67.23% (p=0.002 n=8+5) JavaStringRetShort 14.2µs ± 2% 13.9µs ± 1% -2.15% (p=0.006 n=8+5) JavaStringRetShortDirect 20.8µs ± 2% 20.4µs ± 2% ~ (p=0.065 n=8+5) JavaStringRetLong 42.2µs ± 9% 42.4µs ± 3% ~ (p=0.190 n=9+5) JavaStringRetLongDirect 51.2µs ±21% 50.8µs ± 8% ~ (p=0.518 n=9+5) GoStringShort 23.4µs ± 7% 22.5µs ± 3% -3.55% (p=0.019 n=9+5) GoStringLong 51.9µs ± 9% 53.1µs ± 3% ~ (p=0.240 n=9+5) GoStringShortUnicode 24.2µs ± 6% 22.8µs ± 1% -5.54% (p=0.002 n=9+5) GoStringLongUnicode 58.6µs ± 8% 57.6µs ± 3% ~ (p=0.518 n=9+5) GoStringRetShort 27.6µs ± 1% 23.2µs ± 2% -15.87% (p=0.003 n=7+5) GoStringRetLong 129µs ±12% 33µs ± 2% -74.03% (p=0.001 n=10+5) Change-Id: Icb9481981493ffca8defed9fb80a9433d6048937 Reviewed-on: https://go-review.googlesource.com/20250 Reviewed-by: David Crawshaw <crawshaw@golang.org>
476 lines
13 KiB
Java
476 lines
13 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.Testpkg;
|
|
|
|
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());
|
|
|
|
Testpkg.S s0 = Testpkg.getStructVar();
|
|
assertEquals("var StructVar", "a struct var", s0.String());
|
|
Testpkg.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() {
|
|
Testpkg.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 extends Testpkg.I.Stub {
|
|
public void E() throws Exception {
|
|
throw new Exception("my exception from E");
|
|
}
|
|
|
|
boolean calledF;
|
|
public void F() {
|
|
calledF = true;
|
|
}
|
|
|
|
public Testpkg.I I() {
|
|
return this;
|
|
}
|
|
|
|
public Testpkg.S S() {
|
|
return Testpkg.New();
|
|
}
|
|
|
|
public String StoString(Testpkg.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";
|
|
Testpkg.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++) {
|
|
Testpkg.S s = Testpkg.CallS(obj);
|
|
runGC();
|
|
}
|
|
}
|
|
|
|
public void testInterfaceMethodTakesStructPointer() {
|
|
final AnI obj = new AnI();
|
|
Testpkg.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 extends Testpkg.I.Stub {
|
|
public void F() { countI++; }
|
|
|
|
public void E() throws Exception {}
|
|
public Testpkg.I I() { return null; }
|
|
public Testpkg.S S() { return null; }
|
|
public String StoString(Testpkg.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() {
|
|
Testpkg.Node a = Testpkg.NewNode("A");
|
|
Testpkg.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() {
|
|
Testpkg.Interface intf = Testpkg.NewConcrete();
|
|
}
|
|
|
|
public void testErrorField() {
|
|
final String want = "an error message";
|
|
Testpkg.Node n = Testpkg.NewNode("ErrTest");
|
|
n.setErr(want);
|
|
String got = n.getErr();
|
|
assertEquals("want back the error message we set", want, got);
|
|
}
|
|
|
|
//test if we have JNI local reference table overflow error
|
|
public void testLocalReferenceOverflow() {
|
|
Testpkg.CallWithCallback(new Testpkg.GoCallback.Stub() {
|
|
|
|
@Override
|
|
public void VarUpdate() {
|
|
//do nothing
|
|
}
|
|
});
|
|
}
|
|
|
|
public void testNullReferences() {
|
|
assertTrue(Testpkg.CallWithNull(null, new Testpkg.NullTest.Stub() {
|
|
public Testpkg.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 Testpkg.B.Stub() {
|
|
@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 Testpkg.Receiver.Stub() {
|
|
@Override public void Hello(String msg) {
|
|
}
|
|
});
|
|
}
|
|
}
|