From c54e4379b976dee2b11938f8d3936ae443a6c2c9 Mon Sep 17 00:00:00 2001 From: Tim O'Brien Date: Thu, 30 Nov 2023 23:22:31 +1100 Subject: [PATCH 1/6] add delete object references in tests --- jnigi_test.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/jnigi_test.go b/jnigi_test.go index 08a12f1..4a24b3d 100644 --- a/jnigi_test.go +++ b/jnigi_test.go @@ -59,6 +59,7 @@ func PTestBasic(t *testing.T) { if err := obj.CallMethod(env, "hashCode", &v); err != nil { t.Fatal(err) } + env.DeleteLocalRef(obj) // byte array argument, byte array method var testStr string = "hello world" @@ -66,6 +67,7 @@ func PTestBasic(t *testing.T) { if err != nil { t.Fatal(err) } + defer env.DeleteLocalRef(str) var goBytes []byte if err := str.CallMethod(env, "getBytes", &goBytes, env.GetUTF8String()); err != nil { t.Fatal(err) @@ -79,6 +81,8 @@ func PTestBasic(t *testing.T) { if err := str.CallMethod(env, "substring", str2, 6); err != nil { t.Fatal(err) } + defer env.DeleteLocalRef(str2) + var dummy []byte if err := str2.CallMethod(env, "getBytes", &dummy); err != nil { t.Fatal(err) @@ -111,6 +115,8 @@ func PTestBasic(t *testing.T) { if err != nil { t.Fatal(err) } + defer env.DeleteLocalRef(pt) + err = pt.SetField(env, "x", 5) if err != nil { t.Fatal(err) @@ -133,6 +139,7 @@ func PTestBasic(t *testing.T) { if err != nil { t.Fatal(err) } + defer env.DeleteLocalRef(str) if err := str.CallMethod(env, "getBytes", &goBytes, env.GetUTF8String()); err != nil { t.Fatal(err) @@ -170,6 +177,8 @@ func PTestAttach(t *testing.T) { }() <-x + env.DeleteGlobalRef(gObj) + env.DeleteLocalRef(obj) } func PTestObjectArrays(t *testing.T) { @@ -178,11 +187,12 @@ func PTestObjectArrays(t *testing.T) { if err != nil { t.Fatal(err) } - + defer env.DeleteLocalRef(str) regex, err := env.NewObject("java/lang/String", []byte("X")) if err != nil { t.Fatal(err) } + defer env.DeleteLocalRef(regex) v := NewObjectArrayRef("java/lang/String") if err := str.CallMethod(env, "split", v, regex); err != nil { @@ -208,10 +218,12 @@ func PTestObjectArrays(t *testing.T) { if err := array.CallMethod(env, "getClass", class); err != nil { t.Fatal(err) } + defer env.DeleteLocalRef(class) jClassName := NewObjectRef("java/lang/String") if err := class.CallMethod(env, "getName", jClassName); err != nil { t.Fatal(err) } + defer env.DeleteLocalRef(jClassName) var className []byte if err := jClassName.CallMethod(env, "getBytes", &className); err != nil { t.Fatal(err) @@ -251,6 +263,7 @@ func PTestConvert(t *testing.T) { if err != nil { t.Fatal(err) } + defer env.DeleteLocalRef(str) var firstWord GoString if err := str.CallMethod(env, "substring", &firstWord, 0, 4); err != nil { @@ -266,11 +279,14 @@ func PTestInstanceOf(t *testing.T) { if err != nil { t.Fatal(err) } + defer env.DeleteLocalRef(alist) str, err := env.NewObject("java/lang/String") if err != nil { t.Fatal(err) } + defer env.DeleteLocalRef(str) + var dummy bool if err := alist.CallMethod(env, "add", &dummy, str.Cast("java/lang/Object")); err != nil { t.Fatal(err) @@ -280,6 +296,7 @@ func PTestInstanceOf(t *testing.T) { if err := alist.CallMethod(env, "get", obj, 0); err != nil { t.Fatal(err) } + defer env.DeleteLocalRef(obj) if isInstance, err := obj.IsInstanceOf(env, "java/lang/String"); err != nil { t.Fatal(err) @@ -300,12 +317,14 @@ func PTestByteArray(t *testing.T) { if !assert.Equal(t, "hello", toGoStr(t, str)) { t.Fail() } + env.DeleteLocalRef(str) testStr := "hello world" str, err = env.NewObject("java/lang/String", []byte(testStr)) if err != nil { t.Fatal(err) } + defer env.DeleteLocalRef(str) arr := NewArrayRef(Byte | Array) if err := str.CallMethod(env, "getBytes", arr, env.GetUTF8String()); err != nil { From 808910705b1143508af955c55c32bca5e62eb6d7 Mon Sep 17 00:00:00 2001 From: Tim O'Brien Date: Thu, 7 Dec 2023 11:32:59 +1100 Subject: [PATCH 2/6] adding coverage in tests --- jnigi_test.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/jnigi_test.go b/jnigi_test.go index 4a24b3d..cc43d31 100644 --- a/jnigi_test.go +++ b/jnigi_test.go @@ -104,11 +104,18 @@ func PTestBasic(t *testing.T) { } // get static field + err = env.SetStaticField("java/util/Calendar", "APRIL", 5) + if err != nil { + t.Fatal(err) + } var calPos int err = env.GetStaticField("java/util/Calendar", "APRIL", &calPos) if err != nil { t.Fatal(err) } + if !assert.Equal(t, 5, calPos) { + t.Fail() + } // set/get object field pt, err := env.NewObject("java/awt/Point") @@ -332,11 +339,17 @@ func PTestByteArray(t *testing.T) { } ba2 := env.NewByteArrayFromObject(arr.ObjectRef) - bytes = ba2.GetCritical(env) + + bytes = ba2.CopyBytes(env) if !assert.Equal(t, "hello world", string(bytes)) { t.Fail() } - ba2.ReleaseCritical(env, bytes) + + ba3 := env.NewByteArrayFromSlice([]byte("hello world!")) + bytes = ba3.CopyBytes(env) + if !assert.Equal(t, "hello world!", string(bytes)) { + t.Fail() + } } func PTestGetJVM(t *testing.T) { From a85984abb2b3f80f56709ab784b3ef9da6f645dd Mon Sep 17 00:00:00 2001 From: Tim O'Brien Date: Fri, 8 Dec 2023 09:20:46 +1100 Subject: [PATCH 3/6] go fmt --- cwrapper.go | 4 ++-- windows.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cwrapper.go b/cwrapper.go index c059fec..27d213b 100644 --- a/cwrapper.go +++ b/cwrapper.go @@ -639,8 +639,8 @@ const ( JNI_VERSION_1_4 = C.JNI_VERSION_1_4 JNI_VERSION_1_6 = C.JNI_VERSION_1_6 JNI_VERSION_1_8 = C.JNI_VERSION_1_8 - JNI_VERSION_9 = C.JNI_VERSION_9 - JNI_VERSION_10 = C.JNI_VERSION_10 + JNI_VERSION_9 = C.JNI_VERSION_9 + JNI_VERSION_10 = C.JNI_VERSION_10 DEFAULT_VERSION = JNI_VERSION_1_6 ) diff --git a/windows.go b/windows.go index 888efd4..efc41bc 100644 --- a/windows.go +++ b/windows.go @@ -32,8 +32,8 @@ import "C" import ( "errors" - "unsafe" "golang.org/x/sys/windows" + "unsafe" ) func jni_GetDefaultJavaVMInitArgs(args unsafe.Pointer) jint { From de5801930ad50846681542e819f951b8ce869b48 Mon Sep 17 00:00:00 2001 From: Tim O'Brien Date: Fri, 8 Dec 2023 11:58:10 +1100 Subject: [PATCH 4/6] Add testing of RegisterNative * Add Java Class with native function in java/testing * Add callback function in jnigi_testing_c_callback.go * Add script testing.sh to compile java and run tests --- .gitignore | 1 + Dockerfile | 2 +- java/test/javac.sh | 4 ++++ java/test/src/local/JnigiTesting.java | 5 ++++ jnigi_test.go | 28 +++++++++++++++++++++- jnigi_test_c_callback.go | 34 +++++++++++++++++++++++++++ testing.sh | 4 ++++ 7 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100755 java/test/javac.sh create mode 100644 java/test/src/local/JnigiTesting.java create mode 100644 jnigi_test_c_callback.go create mode 100755 testing.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b9be3eb --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +java/test/out/* \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 30e1559..5c69dab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,6 @@ RUN go mod download && go mod verify COPY . . -CMD go test +CMD ./testing.sh diff --git a/java/test/javac.sh b/java/test/javac.sh new file mode 100755 index 0000000..5986091 --- /dev/null +++ b/java/test/javac.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +cd $(dirname $0) +javac -d out -cp src/ src/local/* \ No newline at end of file diff --git a/java/test/src/local/JnigiTesting.java b/java/test/src/local/JnigiTesting.java new file mode 100644 index 0000000..a19a25e --- /dev/null +++ b/java/test/src/local/JnigiTesting.java @@ -0,0 +1,5 @@ +package local; + +public class JnigiTesting { + public native java.lang.String Greet(java.lang.String x); +} diff --git a/jnigi_test.go b/jnigi_test.go index cc43d31..2f3c8e6 100644 --- a/jnigi_test.go +++ b/jnigi_test.go @@ -6,6 +6,8 @@ package jnigi import ( "github.com/stretchr/testify/assert" + "os" + "path/filepath" "runtime" "strings" "testing" @@ -28,6 +30,7 @@ func TestAll(t *testing.T) { PTestPushPopLocalFrame(t) PTestHandleException(t) PTestCast(t) + PTestRegisterNative(t) PTestDestroy(t) } @@ -39,7 +42,8 @@ func PTestInit(t *testing.T) { t.Fatal(err) } runtime.LockOSThread() - jvm2, e2, err := CreateJVM(NewJVMInitArgs(false, true, DEFAULT_VERSION, []string{"-Xcheck:jni"})) + cwd, _ := os.Getwd() + jvm2, e2, err := CreateJVM(NewJVMInitArgs(false, true, DEFAULT_VERSION, []string{"-Xcheck:jni", "-Djava.class.path=" + filepath.Join(cwd, "java/test/out")})) if err != nil { t.Fatal(err) } @@ -514,6 +518,28 @@ func PTestCast(t *testing.T) { } } +func PTestRegisterNative(t *testing.T) { + if err := env.RegisterNative("local/JnigiTesting", "Greet", ObjectType("java/lang/String"), []interface{}{ObjectType("java/lang/String")}, c_go_callback_Greet); err != nil { + t.Fatal(err) + } + objRef, err := env.NewObject("local/JnigiTesting") + if err != nil { + t.Fatal(err) + } + nameRef, err := env.NewObject("java/lang/String", []byte("World")) + if err != nil { + t.Fatal(err) + } + strRef := NewObjectRef("java/lang/String") + if err := objRef.CallMethod(env, "Greet", strRef, nameRef); err != nil { + t.Fatal(err) + } + goStr := toGoStr(t, strRef) + if !assert.Equal(t, "Hello World!", goStr) { + t.Fail() + } +} + func toGoStr(t *testing.T, o *ObjectRef) string { var goBytes []byte if err := o.CallMethod(env, "getBytes", &goBytes); err != nil { diff --git a/jnigi_test_c_callback.go b/jnigi_test_c_callback.go new file mode 100644 index 0000000..54a4c88 --- /dev/null +++ b/jnigi_test_c_callback.go @@ -0,0 +1,34 @@ +//go:build cgo_testing + +package jnigi + +import ( + "unsafe" +) + +/* +#include + +extern uintptr_t go_callback_Greet(void *env, uintptr_t obj, uintptr_t arg_0 ); + +*/ +import "C" + +//export go_callback_Greet +func go_callback_Greet(jenv unsafe.Pointer, jobj uintptr, arg_0 uintptr) uintptr { + env := WrapEnv(jenv) + env.ExceptionHandler = ThrowableToStringExceptionHandler + + strArgRef := WrapJObject(arg_0, "java/lang/String", false) + var goBytes []byte + if err := strArgRef.CallMethod(env, "getBytes", &goBytes); err != nil { + panic(err) + } + retRef, err := env.NewObject("java/lang/String", []byte("Hello "+string(goBytes)+"!")) + if err != nil { + panic(err) + } + return uintptr(retRef.JObject()) +} + +var c_go_callback_Greet = C.go_callback_Greet diff --git a/testing.sh b/testing.sh new file mode 100755 index 0000000..d03b4e2 --- /dev/null +++ b/testing.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +java/test/javac.sh && \ +go test -v --tags=cgo_testing From 3447665e335e2a29fc7d5c86aaee6e6a983bd88a Mon Sep 17 00:00:00 2001 From: Tim O'Brien Date: Fri, 8 Dec 2023 17:38:13 +1100 Subject: [PATCH 5/6] Update README, remove mention of go test since we have CI now. --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index a43b359..5415916 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,6 @@ This allows for Go to initiate the JVM or Java to start a Go runtime respectivel Docs: https://pkg.go.dev/tekao.net/jnigi -## Pull Requests -**Please make sure**: `go test` works, add any doc comments for function signature changes. - ## v1 As of 2021-12-05 the master branch will be version 2. Packages that used JNIGI before this should update their go.mod to set v1 as the version. Or update their code to be compatible with version 2. From d868b470ed4bf09846cc0055d4f4667816d11e03 Mon Sep 17 00:00:00 2001 From: Tim O'Brien Date: Fri, 12 Jan 2024 22:18:28 +1100 Subject: [PATCH 6/6] Add test for CallNonVirtualMethod Redirect standard err on exception handler testing M java/test/javac.sh A java/test/src/local/JnigiTestBase.java A java/test/src/local/JnigiTestExtend.java M jnigi_test.go M testing.sh --- java/test/javac.sh | 2 +- java/test/src/local/JnigiTestBase.java | 7 +++ java/test/src/local/JnigiTestExtend.java | 7 +++ jnigi_test.go | 55 +++++++++++++++++++++--- testing.sh | 2 +- 5 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 java/test/src/local/JnigiTestBase.java create mode 100644 java/test/src/local/JnigiTestExtend.java diff --git a/java/test/javac.sh b/java/test/javac.sh index 5986091..14db6a2 100755 --- a/java/test/javac.sh +++ b/java/test/javac.sh @@ -1,4 +1,4 @@ #!/bin/bash cd $(dirname $0) -javac -d out -cp src/ src/local/* \ No newline at end of file +javac -d out -cp src src/local/* \ No newline at end of file diff --git a/java/test/src/local/JnigiTestBase.java b/java/test/src/local/JnigiTestBase.java new file mode 100644 index 0000000..2399127 --- /dev/null +++ b/java/test/src/local/JnigiTestBase.java @@ -0,0 +1,7 @@ +package local; + +public class JnigiTestBase { + public int meaning() { + return 42; + } +} diff --git a/java/test/src/local/JnigiTestExtend.java b/java/test/src/local/JnigiTestExtend.java new file mode 100644 index 0000000..8d09961 --- /dev/null +++ b/java/test/src/local/JnigiTestExtend.java @@ -0,0 +1,7 @@ +package local; + +public class JnigiTestExtend extends JnigiTestBase { + public int meaning() { + return 1337; + } +} diff --git a/jnigi_test.go b/jnigi_test.go index b33b835..565671a 100644 --- a/jnigi_test.go +++ b/jnigi_test.go @@ -5,12 +5,14 @@ package jnigi import ( - "github.com/stretchr/testify/assert" "os" "path/filepath" "runtime" "strings" + "syscall" "testing" + + "github.com/stretchr/testify/assert" ) var env *Env @@ -30,6 +32,7 @@ func TestAll(t *testing.T) { PTestPushPopLocalFrame(t) PTestHandleException(t) PTestCast(t) + PTestNonVirtual(t) PTestRegisterNative(t) PTestDestroy(t) } @@ -436,23 +439,49 @@ func PTestPushPopLocalFrame(t *testing.T) { } } +func runWithStderrRedir(f func() error) error { + // redirect standard err + stdErrFd := int(os.Stderr.Fd()) + newFd, err := syscall.Dup(stdErrFd) + if err != nil { + return err + } + devNull, err := os.OpenFile(os.DevNull, os.O_WRONLY, 000) + if err != nil { + return err + } + syscall.Dup2(int(devNull.Fd()), stdErrFd) + err = f() + syscall.Dup2(newFd, stdErrFd) + + return err +} + +func newObjWithStderrRedir(class string) error { + err := runWithStderrRedir(func() error { + _, err := env.NewObject(class) + return err + }) + return err +} + func PTestHandleException(t *testing.T) { jexceptErrMsg := "Java exception occurred. check stderr/logcat" - if _, err := env.NewObject("java/foo/bar"); err == nil { + if err := newObjWithStderrRedir("java/foo/bar"); err == nil { t.Fatal("did not return error") } else if !assert.Equal(t, jexceptErrMsg, err.Error()) { t.Fatal("did not return standard error") } env.ExceptionHandler = ThrowableToStringExceptionHandler - if _, err := env.NewObject("java/foo/bar"); err == nil { + if err := newObjWithStderrRedir("java/foo/bar"); err == nil { t.Fatal("did not return error") } else if !assert.Equal(t, "java.lang.NoClassDefFoundError: java/foo/bar", err.Error()) { t.Fatal("did not return standard error") } env.ExceptionHandler = ThrowableErrorExceptionHandler - if _, err := env.NewObject("java/foo/bar"); err == nil { + if err := newObjWithStderrRedir("java/foo/bar"); err == nil { t.Fatal("did not return error") } else { throwableError, ok := err.(ThrowableError) @@ -507,11 +536,12 @@ func PTestHandleException(t *testing.T) { env.ExceptionHandler = nil - if _, err := env.NewObject("java/foo/bar"); err == nil { + if err := newObjWithStderrRedir("java/foo/bar"); err == nil { t.Fatal("did not return error") } else if !assert.Equal(t, jexceptErrMsg, err.Error()) { t.Error("did not return standard error") } + } func PTestCast(t *testing.T) { @@ -527,6 +557,21 @@ func PTestCast(t *testing.T) { } } +func PTestNonVirtual(t *testing.T) { + obj, err := env.NewObject("local/JnigiTestExtend") + if err != nil { + t.Fatal(err) + } + var val int + if err := obj.CallNonvirtualMethod(env, "local/JnigiTestBase", "meaning", &val); err != nil { + t.Fatal(err) + } + + if !assert.Equal(t, 42, val) { + t.Fail() + } +} + func PTestRegisterNative(t *testing.T) { if err := env.RegisterNative("local/JnigiTesting", "Greet", ObjectType("java/lang/String"), []interface{}{ObjectType("java/lang/String")}, c_go_callback_Greet); err != nil { t.Fatal(err) diff --git a/testing.sh b/testing.sh index d03b4e2..c4266b7 100755 --- a/testing.sh +++ b/testing.sh @@ -1,4 +1,4 @@ #!/bin/bash java/test/javac.sh && \ -go test -v --tags=cgo_testing +go test --tags=cgo_testing