1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-04 12:00:25 +00:00

Merge pull request #110829 from m4gr3d/javaclasswrapper_regression_tests

Add Android instrumented tests to the `app` module
This commit is contained in:
Thaddeus Crews
2025-10-24 11:23:08 -05:00
176 changed files with 999 additions and 45 deletions

View File

@@ -6,7 +6,7 @@ exclude: |
.*thirdparty/.*|
.*-(dll|dylib|so)_wrap\.[ch]|
platform/android/java/editor/src/main/java/com/android/.*|
platform/android/java/lib/src/com/google/.*
platform/android/java/lib/src/main/java/com/google/.*
)$
repos:
@@ -148,9 +148,9 @@ repos:
(?x)^(
core/math/bvh_.*\.inc|
platform/(?!android|ios|linuxbsd|macos|web|windows)\w+/.*|
platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView\.java|
platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper\.java|
platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix\.java
platform/android/java/lib/src/main/java/org/godotengine/godot/gl/GLSurfaceView\.java|
platform/android/java/lib/src/main/java/org/godotengine/godot/gl/EGLLogWrapper\.java|
platform/android/java/lib/src/main/java/org/godotengine/godot/utils/ProcessPhoenix\.java
)$
- id: header-guards

View File

@@ -123,17 +123,17 @@ Copyright: 2020, Manuel Prandini
License: Expat
Files: platform/android/java/editor/src/main/java/com/android/*
platform/android/java/lib/aidl/com/android/*
platform/android/java/lib/res/layout/status_bar_ongoing_event_progress_bar.xml
platform/android/java/lib/src/com/google/android/*
platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java
platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java
platform/android/java/lib/src/main/aidl/com/android/*
platform/android/java/lib/src/main/res/layout/status_bar_ongoing_event_progress_bar.xml
platform/android/java/lib/src/main/java/com/google/android/*
platform/android/java/lib/src/main/java/org/godotengine/godot/input/InputManagerCompat.java
platform/android/java/lib/src/main/java/org/godotengine/godot/input/InputManagerV16.java
Comment: The Android Open Source Project
Copyright: 2008-2016, The Android Open Source Project
2002, Google, Inc.
License: Apache-2.0
Files: platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java
Files: platform/android/java/lib/src/main/java/org/godotengine/godot/utils/ProcessPhoenix.java
Comment: ProcessPhoenix
Copyright: 2015, Jake Wharton
License: Apache-2.0

View File

@@ -40,7 +40,7 @@
* - Are added to the Error enum in core/error/error_list.h
* - Have a description added to error_names in core/error/error_list.cpp
* - Are bound with BIND_CORE_ENUM_CONSTANT() in core/core_constants.cpp
* - Have a matching Android version in platform/android/java/lib/src/org/godotengine/godot/error/Error.kt
* - Have a matching Android version in platform/android/java/lib/src/main/java/org/godotengine/godot/error/Error.kt
*/
enum Error {

View File

@@ -221,7 +221,7 @@ static const char *MISMATCHED_VERSIONS_MESSAGE = "Android build version mismatch
static const char *GDEXTENSION_LIBS_PATH = "libs/gdextensionlibs.json";
// This template string must be in sync with the content of 'platform/android/java/lib/res/mipmap-anydpi-v26/icon.xml'.
// This template string must be in sync with the content of 'platform/android/java/lib/src/main/java/res/mipmap-anydpi-v26/icon.xml'.
static const String ICON_XML_TEMPLATE =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
@@ -278,7 +278,7 @@ static const LauncherIcon LAUNCHER_ADAPTIVE_ICON_MONOCHROMES[ICON_DENSITIES_COUN
static const int EXPORT_FORMAT_APK = 0;
static const int EXPORT_FORMAT_AAB = 1;
static const char *APK_ASSETS_DIRECTORY = "assets";
static const char *APK_ASSETS_DIRECTORY = "src/main/assets";
static const char *AAB_ASSETS_DIRECTORY = "assetPackInstallTime/src/main/assets";
static const int DEFAULT_MIN_SDK_VERSION = 24; // Should match the value in 'platform/android/java/app/config.gradle#minSdk'

View File

@@ -59,7 +59,7 @@ static const int APP_CATEGORY_VIDEO = 8;
static const int APP_CATEGORY_UNDEFINED = 9;
// Supported XR modes.
// This should match the entries in 'platform/android/java/lib/src/org/godotengine/godot/xr/XRMode.java'
// This should match the entries in 'platform/android/java/lib/src/main/java/org/godotengine/godot/xr/XRMode.java'
static const int XR_MODE_REGULAR = 0;
static const int XR_MODE_OPENXR = 1;

View File

@@ -34,6 +34,11 @@ configurations {
}
dependencies {
// Android instrumented test dependencies
androidTestImplementation "androidx.test.ext:junit:1.3.0"
androidTestImplementation "androidx.test.espresso:espresso-core:3.7.0"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:1.3.11"
implementation "androidx.fragment:fragment:$versions.fragmentVersion"
implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion"
@@ -114,6 +119,8 @@ android {
targetSdkVersion getExportTargetSdkVersion()
missingDimensionStrategy 'products', 'template'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
lintOptions {
@@ -214,23 +221,26 @@ android {
flavorDimensions 'edition'
productFlavors {
// Product flavor for the standard (no .net support) builds.
standard {
getIsDefault().set(true)
}
// Product flavor for the Mono (.net) builds.
mono {}
// Product flavor used for running instrumented tests.
instrumented {
applicationIdSuffix ".instrumented"
versionNameSuffix "-instrumented"
}
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
res.srcDirs = ['res']
aidl.srcDirs = ['aidl']
assets.srcDirs = ['assets']
}
debug.jniLibs.srcDirs = ['libs/debug', 'libs/debug/vulkan_validation_layers']
dev.jniLibs.srcDirs = ['libs/dev']
release.jniLibs.srcDirs = ['libs/release']
main.res.srcDirs += ['res']
debug.jniLibs.srcDirs += ['libs/debug', 'libs/debug/vulkan_validation_layers']
dev.jniLibs.srcDirs += ['libs/dev']
release.jniLibs.srcDirs += ['libs/release']
}
applicationVariants.all { variant ->

View File

@@ -0,0 +1,76 @@
/**************************************************************************/
/* GodotAppTest.kt */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
package com.godot.game
import android.util.Log
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.godot.game.test.GodotAppInstrumentedTestPlugin
import org.godotengine.godot.plugin.GodotPluginRegistry
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
/**
* This instrumented test will launch the `instrumented` version of GodotApp and run a set of tests against it.
*/
@RunWith(AndroidJUnit4::class)
class GodotAppTest {
companion object {
private val TAG = GodotAppTest::class.java.simpleName
}
@get:Rule
val godotAppRule = ActivityScenarioRule(GodotApp::class.java)
/**
* Runs the JavaClassWrapper tests via the GodotAppInstrumentedTestPlugin.
*/
@Test
fun runJavaClassWrapperTests() {
val testPlugin = GodotPluginRegistry.getPluginRegistry()
.getPlugin("GodotAppInstrumentedTestPlugin") as GodotAppInstrumentedTestPlugin?
assertNotNull(testPlugin)
Log.d(TAG, "Waiting for the Godot main loop to start...")
testPlugin.waitForGodotMainLoopStarted()
Log.d(TAG, "Running JavaClassWrapper tests...")
val result = testPlugin.runJavaClassWrapperTests()
assertNotNull(result)
result.exceptionOrNull()?.let { throw it }
assertTrue(result.isSuccess)
Log.d(TAG, "Passed ${result.getOrNull()} tests")
}
}

View File

@@ -0,0 +1,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<meta-data
android:name="org.godotengine.plugin.v2.GodotAppInstrumentedTestPlugin"
android:value="com.godot.game.test.GodotAppInstrumentedTestPlugin"/>
</application>
</manifest>

View File

@@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

View File

@@ -0,0 +1,3 @@
# Godot 4+ specific ignores
/android/
/.godot/editor

View File

@@ -0,0 +1,17 @@
list=[{
"base": &"RefCounted",
"class": &"BaseTest",
"icon": "",
"is_abstract": true,
"is_tool": false,
"language": &"GDScript",
"path": "res://test/base_test.gd"
}, {
"base": &"BaseTest",
"class": &"JavaClassWrapperTests",
"icon": "",
"is_abstract": false,
"is_tool": false,
"language": &"GDScript",
"path": "res://test/javaclasswrapper/java_class_wrapper_tests.gd"
}]

View File

@@ -0,0 +1,2 @@
source_md5="4cdc64b13a9af63279c486903c9b54cc"
dest_md5="ddbdfc47e6405ad8d8e9e6a88a32824e"

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 813 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H447l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c3 34 55 34 58 0v-86c-3-34-55-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 996 B

View File

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://srnrli5m8won"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@@ -0,0 +1,60 @@
extends Node2D
var _plugin_name = "GodotAppInstrumentedTestPlugin"
var _android_plugin
func _ready():
if Engine.has_singleton(_plugin_name):
_android_plugin = Engine.get_singleton(_plugin_name)
_android_plugin.connect("launch_tests", _launch_tests)
else:
printerr("Couldn't find plugin " + _plugin_name)
get_tree().quit()
func _launch_tests(test_label: String) -> void:
var test_instance: BaseTest = null
match test_label:
"javaclasswrapper_tests":
test_instance = JavaClassWrapperTests.new()
if test_instance:
test_instance.__reset_tests()
test_instance.run_tests()
var incomplete_tests = test_instance._test_started - test_instance._test_completed
_android_plugin.onTestsCompleted(test_label, test_instance._test_completed, test_instance._test_assert_failures + incomplete_tests)
else:
_android_plugin.onTestsFailed(test_label, "Unable to launch tests")
func _on_plugin_toast_button_pressed() -> void:
if _android_plugin:
_android_plugin.helloWorld()
func _on_vibration_button_pressed() -> void:
var android_runtime = Engine.get_singleton("AndroidRuntime")
if android_runtime:
print("Checking if the device supports vibration")
var vibrator_service = android_runtime.getApplicationContext().getSystemService("vibrator")
if vibrator_service:
if vibrator_service.hasVibrator():
print("Vibration is supported on device! Vibrating now...")
var VibrationEffect = JavaClassWrapper.wrap("android.os.VibrationEffect")
var effect = VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE)
vibrator_service.vibrate(effect)
else:
printerr("Vibration is not supported on device")
else:
printerr("Unable to retrieve the vibrator service")
else:
printerr("Couldn't find AndroidRuntime singleton")
func _on_gd_script_toast_button_pressed() -> void:
var android_runtime = Engine.get_singleton("AndroidRuntime")
if android_runtime:
var activity = android_runtime.getActivity()
var toastCallable = func ():
var ToastClass = JavaClassWrapper.wrap("android.widget.Toast")
ToastClass.makeText(activity, "Toast from GDScript", ToastClass.LENGTH_LONG).show()
activity.runOnUiThread(android_runtime.createRunnableFromGodotCallable(toastCallable))

View File

@@ -0,0 +1 @@
uid://bv6y7in6otgcm

View File

@@ -0,0 +1,34 @@
[gd_scene load_steps=2 format=3 uid="uid://cg3hylang5fxn"]
[ext_resource type="Script" uid="uid://bv6y7in6otgcm" path="res://main.gd" id="1_j0gfq"]
[node name="Main" type="Node2D"]
script = ExtResource("1_j0gfq")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
offset_left = 68.0
offset_top = 102.0
offset_right = 506.0
offset_bottom = 408.0
theme_override_constants/separation = 25
[node name="PluginToastButton" type="Button" parent="VBoxContainer"]
custom_minimum_size = Vector2(0, 50)
layout_mode = 2
text = "Plugin Toast
"
[node name="VibrationButton" type="Button" parent="VBoxContainer"]
custom_minimum_size = Vector2(0, 50)
layout_mode = 2
text = "Vibration"
[node name="GDScriptToastButton" type="Button" parent="VBoxContainer"]
custom_minimum_size = Vector2(0, 50)
layout_mode = 2
text = "GDScript Toast
"
[connection signal="pressed" from="VBoxContainer/PluginToastButton" to="." method="_on_plugin_toast_button_pressed"]
[connection signal="pressed" from="VBoxContainer/VibrationButton" to="." method="_on_vibration_button_pressed"]
[connection signal="pressed" from="VBoxContainer/GDScriptToastButton" to="." method="_on_gd_script_toast_button_pressed"]

View File

@@ -0,0 +1,26 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="Godot App Instrumentation Tests"
run/main_scene="res://main.tscn"
config/features=PackedStringArray("4.5", "GL Compatibility")
config/icon="res://icon.svg"
[debug]
settings/stdout/verbose_stdout=true
[rendering]
renderer/rendering_method="gl_compatibility"
renderer/rendering_method.mobile="gl_compatibility"
textures/vram_compression/import_etc2_astc=true

View File

@@ -0,0 +1,44 @@
@abstract class_name BaseTest
var _test_started := 0
var _test_completed := 0
var _test_assert_passes := 0
var _test_assert_failures := 0
@abstract func run_tests()
func __exec_test(test_func: Callable):
_test_started += 1
test_func.call()
_test_completed += 1
func __reset_tests():
_test_started = 0
_test_completed = 0
_test_assert_passes = 0
_test_assert_failures = 0
func __get_stack_frame():
for s in get_stack():
if not s.function.begins_with('__') and s.function != "assert_equal":
return s
return null
func __assert_pass():
_test_assert_passes += 1
pass
func __assert_fail():
_test_assert_failures += 1
var s = __get_stack_frame()
if s != null:
print_rich ("[color=red] == FAILURE: In function %s() from '%s' on line %s[/color]" % [s.function, s.source, s.line])
else:
print_rich ("[color=red] == FAILURE (run with --debug to get more information!) ==[/color]")
func assert_equal(actual, expected):
if actual == expected:
__assert_pass()
else:
__assert_fail()
print (" |-> Expected '%s' but got '%s'" % [expected, actual])

View File

@@ -0,0 +1 @@
uid://mofa8j0d801f

View File

@@ -0,0 +1,136 @@
class_name JavaClassWrapperTests
extends BaseTest
func run_tests():
print("JavaClassWrapper tests starting..")
__exec_test(test_exceptions)
__exec_test(test_multiple_signatures)
__exec_test(test_array_arguments)
__exec_test(test_array_return)
__exec_test(test_dictionary)
__exec_test(test_object_overload)
__exec_test(test_variant_conversion_safe_from_stack_overflow)
print("JavaClassWrapper tests finished.")
print("Tests started: " + str(_test_started))
print("Tests completed: " + str(_test_completed))
func test_exceptions() -> void:
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
#print(TestClass.get_java_method_list())
assert_equal(JavaClassWrapper.get_exception(), null)
assert_equal(TestClass.testExc(27), 0)
assert_equal(str(JavaClassWrapper.get_exception()), '<JavaObject:java.lang.NullPointerException "java.lang.NullPointerException">')
assert_equal(JavaClassWrapper.get_exception(), null)
func test_multiple_signatures() -> void:
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
var ai := [1, 2]
assert_equal(TestClass.testMethod(1, ai), "IntArray: [1, 2]")
var astr := ["abc"]
assert_equal(TestClass.testMethod(2, astr), "IntArray: [0]")
var atstr: Array[String] = ["abc"]
assert_equal(TestClass.testMethod(3, atstr), "StringArray: [abc]")
var TestClass2: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass2')
var aobjl: Array[Object] = [
TestClass2.TestClass2(27),
TestClass2.TestClass2(135),
]
assert_equal(TestClass.testMethod(3, aobjl), "testObjects: 27 135")
func test_array_arguments() -> void:
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
assert_equal(TestClass.testArgBoolArray([true, false, true]), "[true, false, true]")
assert_equal(TestClass.testArgByteArray(PackedByteArray([1, 2, 3])), "[1, 2, 3]")
assert_equal(TestClass.testArgCharArray("abc".to_utf16_buffer()), "abc");
assert_equal(TestClass.testArgShortArray(PackedInt32Array([27, 28, 29])), "[27, 28, 29]")
assert_equal(TestClass.testArgShortArray([27, 28, 29]), "[27, 28, 29]")
assert_equal(TestClass.testArgIntArray(PackedInt32Array([7, 8, 9])), "[7, 8, 9]")
assert_equal(TestClass.testArgIntArray([7, 8, 9]), "[7, 8, 9]")
assert_equal(TestClass.testArgLongArray(PackedInt64Array([17, 18, 19])), "[17, 18, 19]")
assert_equal(TestClass.testArgLongArray([17, 18, 19]), "[17, 18, 19]")
assert_equal(TestClass.testArgFloatArray(PackedFloat32Array([17.1, 18.2, 19.3])), "[17.1, 18.2, 19.3]")
assert_equal(TestClass.testArgFloatArray([17.1, 18.2, 19.3]), "[17.1, 18.2, 19.3]")
assert_equal(TestClass.testArgDoubleArray(PackedFloat64Array([37.1, 38.2, 39.3])), "[37.1, 38.2, 39.3]")
assert_equal(TestClass.testArgDoubleArray([37.1, 38.2, 39.3]), "[37.1, 38.2, 39.3]")
func test_array_return() -> void:
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
#print(TestClass.get_java_method_list())
assert_equal(TestClass.testRetBoolArray(), [true, false, true])
assert_equal(TestClass.testRetWrappedBoolArray(), [true, false, true])
assert_equal(TestClass.testRetByteArray(), PackedByteArray([1, 2, 3]))
assert_equal(TestClass.testRetWrappedByteArray(), PackedByteArray([1, 2, 3]))
assert_equal(TestClass.testRetCharArray().get_string_from_utf16(), "abc")
assert_equal(TestClass.testRetWrappedCharArray().get_string_from_utf16(), "abc")
assert_equal(TestClass.testRetShortArray(), PackedInt32Array([11, 12, 13]))
assert_equal(TestClass.testRetWrappedShortArray(), PackedInt32Array([11, 12, 13]))
assert_equal(TestClass.testRetIntArray(), PackedInt32Array([21, 22, 23]))
assert_equal(TestClass.testRetWrappedIntArray(), PackedInt32Array([21, 22, 23]))
assert_equal(TestClass.testRetLongArray(), PackedInt64Array([41, 42, 43]))
assert_equal(TestClass.testRetWrappedLongArray(), PackedInt64Array([41, 42, 43]))
assert_equal(TestClass.testRetFloatArray(), PackedFloat32Array([31.1, 32.2, 33.3]))
assert_equal(TestClass.testRetWrappedFloatArray(), PackedFloat32Array([31.1, 32.2, 33.3]))
assert_equal(TestClass.testRetDoubleArray(), PackedFloat64Array([41.1, 42.2, 43.3]))
assert_equal(TestClass.testRetWrappedDoubleArray(), PackedFloat64Array([41.1, 42.2, 43.3]))
var obj_array = TestClass.testRetObjectArray()
assert_equal(str(obj_array[0]), '<JavaObject:com.godot.game.test.javaclasswrapper.TestClass2 "51">')
assert_equal(str(obj_array[1]), '<JavaObject:com.godot.game.test.javaclasswrapper.TestClass2 "52">')
assert_equal(TestClass.testRetStringArray(), PackedStringArray(["I", "am", "String"]))
assert_equal(TestClass.testRetCharSequenceArray(), PackedStringArray(["I", "am", "CharSequence"]))
func test_dictionary():
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
assert_equal(TestClass.testDictionary({a = 1, b = 2}), "{a=1, b=2}")
assert_equal(TestClass.testRetDictionary(), {a = 1, b = 2})
assert_equal(TestClass.testRetDictionaryArray(), [{a = 1, b = 2}])
assert_equal(TestClass.testDictionaryNested({a = 1, b = [2, 3], c = 4}), "{a: 1, b: [2, 3], c: 4}")
func test_object_overload():
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
var TestClass2: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass2')
var TestClass3: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass3')
var t2 = TestClass2.TestClass2(33)
var t3 = TestClass3.TestClass3("thirty three")
assert_equal(TestClass.testObjectOverload(t2), "TestClass2: 33")
assert_equal(TestClass.testObjectOverload(t3), "TestClass3: thirty three")
var arr_of_t2 = [t2, TestClass2.TestClass2(34)]
var arr_of_t3 = [t3, TestClass3.TestClass3("thirty four")]
assert_equal(TestClass.testObjectOverloadArray(arr_of_t2), "TestClass2: [33, 34]")
assert_equal(TestClass.testObjectOverloadArray(arr_of_t3), "TestClass3: [thirty three, thirty four]")
func test_variant_conversion_safe_from_stack_overflow():
var TestClass: JavaClass = JavaClassWrapper.wrap('com.godot.game.test.javaclasswrapper.TestClass')
var arr: Array = [42]
var dict: Dictionary = {"arr": arr}
arr.append(dict)
# The following line will crash with stack overflow if not handled property:
TestClass.testDictionary(dict)

View File

@@ -0,0 +1,144 @@
/**************************************************************************/
/* GodotAppInstrumentedTestPlugin.kt */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
package com.godot.game.test
import android.util.Log
import android.widget.Toast
import org.godotengine.godot.Godot
import org.godotengine.godot.plugin.GodotPlugin
import org.godotengine.godot.plugin.UsedByGodot
import org.godotengine.godot.plugin.SignalInfo
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CountDownLatch
/**
* [GodotPlugin] used to drive instrumented tests.
*/
class GodotAppInstrumentedTestPlugin(godot: Godot) : GodotPlugin(godot) {
companion object {
private val TAG = GodotAppInstrumentedTestPlugin::class.java.simpleName
private const val MAIN_LOOP_STARTED_LATCH_KEY = "main_loop_started_latch"
private const val JAVACLASSWRAPPER_TESTS = "javaclasswrapper_tests"
private val LAUNCH_TESTS_SIGNAL = SignalInfo("launch_tests", String::class.java)
private val SIGNALS = setOf(
LAUNCH_TESTS_SIGNAL
)
}
private val testResults = ConcurrentHashMap<String, Result<Any>>()
private val latches = ConcurrentHashMap<String, CountDownLatch>()
init {
// Add a countdown latch that is triggered when `onGodotMainLoopStarted` is fired.
// This will be used by tests to wait until the engine is ready.
latches[MAIN_LOOP_STARTED_LATCH_KEY] = CountDownLatch(1)
}
override fun getPluginName() = "GodotAppInstrumentedTestPlugin"
override fun getPluginSignals() = SIGNALS
override fun onGodotMainLoopStarted() {
super.onGodotMainLoopStarted()
latches.remove(MAIN_LOOP_STARTED_LATCH_KEY)?.countDown()
}
/**
* Used by the instrumented test to wait until the Godot main loop is up and running.
*/
internal fun waitForGodotMainLoopStarted() {
// Wait on the CountDownLatch for `onGodotMainLoopStarted`
try {
latches[MAIN_LOOP_STARTED_LATCH_KEY]?.await()
} catch (e: InterruptedException) {
Log.e(TAG, "Unable to wait for Godot main loop started event.", e)
}
}
/**
* This launches the JavaClassWrapper tests, and wait until the tests are complete before returning.
*/
internal fun runJavaClassWrapperTests(): Result<Any>? {
return launchTests(JAVACLASSWRAPPER_TESTS)
}
private fun launchTests(testLabel: String): Result<Any>? {
val latch = latches.getOrPut(testLabel) { CountDownLatch(1) }
emitSignal(LAUNCH_TESTS_SIGNAL.name, testLabel)
return try {
latch.await()
val result = testResults.remove(testLabel)
result
} catch (e: InterruptedException) {
Log.e(TAG, "Unable to wait for completion for $testLabel", e)
null
}
}
/**
* Callback invoked from gdscript when the tests are completed.
*/
@UsedByGodot
fun onTestsCompleted(testLabel: String, passes: Int, failures: Int) {
Log.d(TAG, "$testLabel tests completed")
val result = if (failures == 0) {
Result.success(passes)
} else {
Result.failure(AssertionError("$failures tests failed!"))
}
completeTest(testLabel, result)
}
@UsedByGodot
fun onTestsFailed(testLabel: String, failureMessage: String) {
Log.d(TAG, "$testLabel tests failed")
val result: Result<Any> = Result.failure(AssertionError(failureMessage))
completeTest(testLabel, result)
}
private fun completeTest(testKey: String, result: Result<Any>) {
testResults[testKey] = result
latches.remove(testKey)?.countDown()
}
@UsedByGodot
fun helloWorld() {
runOnHostThread {
Toast.makeText(activity, "Toast from Android plugin", Toast.LENGTH_LONG).show()
Log.v(pluginName, "Hello World")
}
}
}

View File

@@ -0,0 +1,261 @@
/**************************************************************************/
/* TestClass.kt */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
package com.godot.game.test.javaclasswrapper
import org.godotengine.godot.Dictionary
import kotlin.collections.contentToString
import kotlin.collections.joinToString
class TestClass {
companion object {
@JvmStatic
fun stringify(value: Any?): String {
return when (value) {
null -> "null"
is Map<*, *> -> {
val entries = value.entries.joinToString(", ") { (k, v) -> "${stringify(k)}: ${stringify(v)}" }
"{$entries}"
}
is List<*> -> value.joinToString(prefix = "[", postfix = "]") { stringify(it) }
is Array<*> -> value.joinToString(prefix = "[", postfix = "]") { stringify(it) }
is IntArray -> value.joinToString(prefix = "[", postfix = "]")
is LongArray -> value.joinToString(prefix = "[", postfix = "]")
is FloatArray -> value.joinToString(prefix = "[", postfix = "]")
is DoubleArray -> value.joinToString(prefix = "[", postfix = "]")
is BooleanArray -> value.joinToString(prefix = "[", postfix = "]")
is CharArray -> value.joinToString(prefix = "[", postfix = "]")
else -> value.toString()
}
}
@JvmStatic
fun testDictionary(d: Dictionary): String {
return d.toString()
}
@JvmStatic
fun testDictionaryNested(d: Dictionary): String {
return stringify(d)
}
@JvmStatic
fun testRetDictionary(): Dictionary {
var d = Dictionary()
d.putAll(mapOf("a" to 1, "b" to 2))
return d
}
@JvmStatic
fun testRetDictionaryArray(): Array<Dictionary> {
var d = Dictionary()
d.putAll(mapOf("a" to 1, "b" to 2))
return arrayOf(d)
}
@JvmStatic
fun testMethod(int: Int, array: IntArray): String {
return "IntArray: " + array.contentToString()
}
@JvmStatic
fun testMethod(int: Int, vararg args: String): String {
return "StringArray: " + args.contentToString()
}
@JvmStatic
fun testMethod(int: Int, objects: Array<TestClass2>): String {
return "testObjects: " + objects.joinToString(separator = " ") { it.getValue().toString() }
}
@JvmStatic
fun testExc(i: Int): Int {
val s: String? = null
s!!.length
return i
}
@JvmStatic
fun testArgBoolArray(a: BooleanArray): String {
return a.contentToString();
}
@JvmStatic
fun testArgByteArray(a: ByteArray): String {
return a.contentToString();
}
@JvmStatic
fun testArgCharArray(a: CharArray): String {
return a.joinToString("")
}
@JvmStatic
fun testArgShortArray(a: ShortArray): String {
return a.contentToString();
}
@JvmStatic
fun testArgIntArray(a: IntArray): String {
return a.contentToString();
}
@JvmStatic
fun testArgLongArray(a: LongArray): String {
return a.contentToString();
}
@JvmStatic
fun testArgFloatArray(a: FloatArray): String {
return a.contentToString();
}
@JvmStatic
fun testArgDoubleArray(a: DoubleArray): String {
return a.contentToString();
}
@JvmStatic
fun testRetBoolArray(): BooleanArray {
return booleanArrayOf(true, false, true)
}
@JvmStatic
fun testRetByteArray(): ByteArray {
return byteArrayOf(1, 2, 3)
}
@JvmStatic
fun testRetCharArray(): CharArray {
return "abc".toCharArray()
}
@JvmStatic
fun testRetShortArray(): ShortArray {
return shortArrayOf(11, 12, 13)
}
@JvmStatic
fun testRetIntArray(): IntArray {
return intArrayOf(21, 22, 23)
}
@JvmStatic
fun testRetLongArray(): LongArray {
return longArrayOf(41, 42, 43)
}
@JvmStatic
fun testRetFloatArray(): FloatArray {
return floatArrayOf(31.1f, 32.2f, 33.3f)
}
@JvmStatic
fun testRetDoubleArray(): DoubleArray {
return doubleArrayOf(41.1, 42.2, 43.3)
}
@JvmStatic
fun testRetWrappedBoolArray(): Array<Boolean> {
return arrayOf(true, false, true)
}
@JvmStatic
fun testRetWrappedByteArray(): Array<Byte> {
return arrayOf(1, 2, 3)
}
@JvmStatic
fun testRetWrappedCharArray(): Array<Char> {
return arrayOf('a', 'b', 'c')
}
@JvmStatic
fun testRetWrappedShortArray(): Array<Short> {
return arrayOf(11, 12, 13)
}
@JvmStatic
fun testRetWrappedIntArray(): Array<Int> {
return arrayOf(21, 22, 23)
}
@JvmStatic
fun testRetWrappedLongArray(): Array<Long> {
return arrayOf(41, 42, 43)
}
@JvmStatic
fun testRetWrappedFloatArray(): Array<Float> {
return arrayOf(31.1f, 32.2f, 33.3f)
}
@JvmStatic
fun testRetWrappedDoubleArray(): Array<Double> {
return arrayOf(41.1, 42.2, 43.3)
}
@JvmStatic
fun testRetObjectArray(): Array<TestClass2> {
return arrayOf(TestClass2(51), TestClass2(52));
}
@JvmStatic
fun testRetStringArray(): Array<String> {
return arrayOf("I", "am", "String")
}
@JvmStatic
fun testRetCharSequenceArray(): Array<CharSequence> {
return arrayOf("I", "am", "CharSequence")
}
@JvmStatic
fun testObjectOverload(a: TestClass2): String {
return "TestClass2: $a"
}
@JvmStatic
fun testObjectOverload(a: TestClass3): String {
return "TestClass3: $a"
}
@JvmStatic
fun testObjectOverloadArray(a: Array<TestClass2>): String {
return "TestClass2: " + a.contentToString()
}
@JvmStatic
fun testObjectOverloadArray(a: Array<TestClass3>): String {
return "TestClass3: " + a.contentToString()
}
}
}

View File

@@ -0,0 +1,40 @@
/**************************************************************************/
/* TestClass2.kt */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
package com.godot.game.test.javaclasswrapper
class TestClass2(private val value: Int) {
fun getValue(): Int {
return value
}
override fun toString(): String {
return value.toString()
}
}

View File

@@ -0,0 +1,40 @@
/**************************************************************************/
/* TestClass3.kt */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
package com.godot.game.test.javaclasswrapper
class TestClass3(private val value: String) {
fun getValue(): String {
return value
}
override fun toString(): String {
return value
}
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="godot_project_name_string">Godot App Instrumented Tests</string>
</resources>

View File

@@ -83,4 +83,13 @@ public class GodotApp extends GodotActivity {
super.onGodotMainLoopStarted();
runOnUiThread(updateWindowAppearance);
}
@Override
public void onGodotForceQuit(Godot instance) {
if (!BuildConfig.FLAVOR.equals("instrumented")) {
// For instrumented builds, we disable force-quitting to allow the instrumented tests to complete
// successfully, otherwise they fail when the process crashes.
super.onGodotForceQuit(instance);
}
}
}

View File

@@ -77,23 +77,14 @@ android {
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
test.java.srcDirs = ['srcTest/java']
res.srcDirs = ['res']
aidl.srcDirs = ['aidl']
assets.srcDirs = ['assets']
}
debug.jniLibs.srcDirs = ['libs/debug']
dev.jniLibs.srcDirs = ['libs/dev']
release.jniLibs.srcDirs = ['libs/release']
debug.jniLibs.srcDirs += ['libs/debug']
dev.jniLibs.srcDirs += ['libs/dev']
release.jniLibs.srcDirs += ['libs/release']
// Editor jni library
editorRelease.jniLibs.srcDirs = ['libs/tools/release']
editorDebug.jniLibs.srcDirs = ['libs/tools/debug']
editorDev.jniLibs.srcDirs = ['libs/tools/dev']
editorRelease.jniLibs.srcDirs += ['libs/tools/release']
editorDebug.jniLibs.srcDirs += ['libs/tools/debug']
editorDev.jniLibs.srcDirs += ['libs/tools/dev']
}
libraryVariants.all { variant ->

View File

@@ -110,7 +110,7 @@ public class DownloadThread {
* headers, or destination filename.
*/
private class StopRequest extends Throwable {
private static final long serialVersionUID = 6338592678988347973L;
public int mFinalStatus;

View File

@@ -133,7 +133,7 @@ public class LicenseChecker implements ServiceConnection {
* <p>
* source string: "com.android.vending.licensing.ILicensingService"
* <p>
*
*
* @param callback
*/
public synchronized void checkAccess(LicenseCheckerCallback callback) {

Some files were not shown because too many files have changed in this diff Show More