diff options
-rw-r--r-- | ChangeLog | 34 | ||||
-rw-r--r-- | plugin/icedteanp/IcedTeaJavaRequestProcessor.cc | 7 | ||||
-rw-r--r-- | plugin/icedteanp/IcedTeaNPPlugin.cc | 118 | ||||
-rw-r--r-- | plugin/icedteanp/IcedTeaNPPlugin.h | 9 | ||||
-rw-r--r-- | tests/cpp-unit-tests/IcedTeaJavaRequestProcessorTest.cc | 416 | ||||
-rw-r--r-- | tests/cpp-unit-tests/IcedTeaNPPluginTest.cc | 32 | ||||
-rw-r--r-- | tests/cpp-unit-tests/IcedTeaPluginUtilsTest.cc | 4 | ||||
-rw-r--r-- | tests/cpp-unit-tests/IcedTeaScriptablePluginObjectTest.cc | 33 | ||||
-rw-r--r-- | tests/cpp-unit-tests/MemoryLeakDetector.h | 2 | ||||
-rw-r--r-- | tests/cpp-unit-tests/browser_mock.cc | 56 | ||||
-rw-r--r-- | tests/cpp-unit-tests/browser_mock.h | 4 | ||||
-rw-r--r-- | tests/cpp-unit-tests/browser_mock_npidentifier.cc | 117 | ||||
-rw-r--r-- | tests/cpp-unit-tests/browser_mock_npidentifier.h | 57 | ||||
-rw-r--r-- | tests/cpp-unit-tests/checked_allocations.h | 6 | ||||
-rw-r--r-- | tests/cpp-unit-tests/main.cc | 27 |
15 files changed, 835 insertions, 87 deletions
@@ -1,5 +1,39 @@ 2013-08-23 Adam Domurad <[email protected]> + Spawn Java side during C++ unit tests. Many new tests. + * plugin/icedteanp/IcedTeaJavaRequestProcessor.cc + (hasPackage): Minor cleanup. + * plugin/icedteanp/IcedTeaNPPlugin.cc + (initialize_data_directory): New, extracted function. + (NP_Initialize): Calls extracted function. + * plugin/icedteanp/IcedTeaNPPlugin.h: Expose more functions for + testing purposes. + * tests/cpp-unit-tests/IcedTeaNPPluginTest.cc + (get_scriptable_package_object): Test binding of java package + (get_scriptable_java_object): Test binding of java object + * tests/cpp-unit-tests/IcedTeaPluginUtilsTest.cc + (NPIdentifierAsString): Update to create npidentifier properly. + * tests/cpp-unit-tests/IcedTeaScriptablePluginObjectTest.cc + (getProperty): Test loading java.lang.Integer.MAX_VALUE from C++. + * tests/cpp-unit-tests/MemoryLeakDetector.h + (reset_global_state): Made public + * tests/cpp-unit-tests/checked_allocations.h + (SafeAllocator): New, typedef for allocator that avoids leak detection. + * tests/cpp-unit-tests/browser_mock.cc + (browsermock_setup_functions): Renamed to (browsermock_create_table). + (browsermock_create_table): Now returns browser table, additional + object release and identifier methods added. + * tests/cpp-unit-tests/browser_mock.h: Update for rename. + * tests/cpp-unit-tests/main.cc: Now clears state via + (reset_global_state) + * tests/cpp-unit-tests/IcedTeaJavaRequestProcessorTest.cc: New, + contains unit tests that cover all of JavaRequestProcessor's methods. + * tests/cpp-unit-tests/browser_mock_npidentifier.cc: Allocation-safe + npidentifier mocking, adheres to NPAPI spec. + * tests/cpp-unit-tests/browser_mock_npidentifier.h: Same. + +2013-08-23 Adam Domurad <[email protected]> + * plugin/icedteanp/IcedTeaNPPlugin.cc: Refactor plugin data creation. * plugin/icedteanp/IcedTeaNPPlugin.h: Same. diff --git a/plugin/icedteanp/IcedTeaJavaRequestProcessor.cc b/plugin/icedteanp/IcedTeaJavaRequestProcessor.cc index f7b7332..0d65bd4 100644 --- a/plugin/icedteanp/IcedTeaJavaRequestProcessor.cc +++ b/plugin/icedteanp/IcedTeaJavaRequestProcessor.cc @@ -1279,7 +1279,6 @@ JavaRequestProcessor::hasPackage(int plugin_instance_id, { JavaResultData* java_result; JavaRequestProcessor* java_request = new JavaRequestProcessor(); - std::string message = std::string(); std::string plugin_instance_id_str = std::string(); IcedTeaPluginUtilities::itoa(plugin_instance_id, &plugin_instance_id_str); @@ -1288,11 +1287,9 @@ JavaRequestProcessor::hasPackage(int plugin_instance_id, this->instance = 0; // context is always 0 (needed for java-side backwards compat.) this->reference = IcedTeaPluginUtilities::getReference(); + std::string message; IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message); - message.append(" HasPackage "); - message.append(plugin_instance_id_str); - message.append(" "); - message.append(java_result->return_string->c_str()); + message += " HasPackage " + plugin_instance_id_str + " " + *java_result->return_string; postAndWaitForResponse(message); diff --git a/plugin/icedteanp/IcedTeaNPPlugin.cc b/plugin/icedteanp/IcedTeaNPPlugin.cc index 7515f1b..bfce1ae 100644 --- a/plugin/icedteanp/IcedTeaNPPlugin.cc +++ b/plugin/icedteanp/IcedTeaNPPlugin.cc @@ -190,8 +190,6 @@ static pthread_t plugin_request_processor_thread2; static pthread_t plugin_request_processor_thread3; // Static instance helper functions. -// Have the browser allocate a new ITNPPluginData structure. -static ITNPPluginData* plugin_data_new (); // Retrieve the current document's documentbase. static std::string plugin_get_documentbase (NPP instance); // Callback used to monitor input pipe status. @@ -202,16 +200,12 @@ static gboolean plugin_in_pipe_callback (GIOChannel* source, static gboolean plugin_out_pipe_callback (GIOChannel* source, GIOCondition condition, gpointer plugin_data); -static NPError plugin_start_appletviewer (ITNPPluginData* data); std::string plugin_parameters_string (int argc, char* argn[], char* argv[]); static void plugin_stop_appletviewer (); -// Uninitialize ITNPPluginData structure -static void plugin_data_destroy (NPP instance); NPError get_cookie_info(const char* siteAddr, char** cookieString, uint32_t* len); NPError get_proxy_info(const char* siteAddr, char** proxy, uint32_t* len); void consume_message(gchar* message); -void start_jvm_if_needed(); static void appletviewer_monitor(GPid pid, gint status, gpointer data); void plugin_send_initialization_message(char* instance, gulong handle, int width, int height, @@ -922,7 +916,7 @@ set_cookie_info(const char* siteAddr, const char* cookieString, uint32_t len) // HELPER FUNCTIONS -static ITNPPluginData* +ITNPPluginData* plugin_data_new () { PLUGIN_DEBUG ("plugin_data_new\n"); @@ -1366,7 +1360,7 @@ plugin_test_appletviewer () return error; } -static NPError +NPError plugin_start_appletviewer (ITNPPluginData* data) { PLUGIN_DEBUG ("plugin_start_appletviewer\n"); @@ -1707,7 +1701,7 @@ static void appletviewer_monitor(GPid pid, gint status, gpointer data) PLUGIN_DEBUG ("appletviewer_monitor return\n"); } -static void +void plugin_data_destroy (NPP instance) { PLUGIN_DEBUG ("plugin_data_destroy\n"); @@ -1792,6 +1786,61 @@ initialize_plugin_table(NPPluginFuncs* pluginTable) return true; } +// Make sure the plugin data directory exists, creating it if necessary. +NPError +initialize_data_directory() +{ + const char* tmpdir_env = getenv("TMPDIR"); + if (tmpdir_env != NULL && g_file_test (tmpdir_env, + (GFileTest) (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) + { + data_directory = tmpdir_env; + } + else if (g_file_test (P_tmpdir, + (GFileTest) (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) + { + data_directory = P_tmpdir; + } + else + { + // If TMPDIR and P_tmpdir do not exist, try /tmp directly + data_directory = "/tmp"; + } + + data_directory += "/icedteaplugin-"; + if (getenv("USER") != NULL) + data_directory += getenv("USER"); + + // Now create a icedteaplugin subdir + if (!g_file_test (data_directory.c_str(), + (GFileTest) (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) + { + int file_error = 0; + + file_error = g_mkdir (data_directory.c_str(), 0700); + if (file_error != 0) + { + PLUGIN_ERROR_THREE ("Failed to create data directory", + data_directory.c_str(), + strerror (errno)); + return NPERR_GENERIC_ERROR; + } + } + + + // If data directory doesn't exist by this point, bail + if (!g_file_test (data_directory.c_str(), + (GFileTest) (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) + { + PLUGIN_ERROR_THREE ("Temp directory does not exist: ", + data_directory.c_str(), + strerror (errno)); + return NPERR_GENERIC_ERROR; + } + + return NPERR_NO_ERROR; +} + // FACTORY FUNCTIONS // Provides the browser with pointers to the plugin functions that we @@ -1860,56 +1909,7 @@ NP_Initialize (NPNetscapeFuncs* browserTable, NPPluginFuncs* pluginTable) NPError np_error = NPERR_NO_ERROR; - // Make sure the plugin data directory exists, creating it if - // necessary. - - const char* tmpdir_env = getenv("TMPDIR"); - if (tmpdir_env != NULL && g_file_test (tmpdir_env, - (GFileTest) (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) - { - data_directory = tmpdir_env; - } - else if (g_file_test (P_tmpdir, - (GFileTest) (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) - { - data_directory = P_tmpdir; - } - else - { - // If TMPDIR and P_tmpdir do not exist, try /tmp directly - data_directory = "/tmp"; - } - - data_directory += "/icedteaplugin-"; - if (getenv("USER") != NULL) - data_directory += getenv("USER"); - - // Now create a icedteaplugin subdir - if (!g_file_test (data_directory.c_str(), - (GFileTest) (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) - { - int file_error = 0; - - file_error = g_mkdir (data_directory.c_str(), 0700); - if (file_error != 0) - { - PLUGIN_ERROR_THREE ("Failed to create data directory", - data_directory.c_str(), - strerror (errno)); - return NPERR_GENERIC_ERROR; - } - } - - - // If data directory doesn't exist by this point, bail - if (!g_file_test (data_directory.c_str(), - (GFileTest) (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) - { - PLUGIN_ERROR_THREE ("Temp directory does not exist: ", - data_directory.c_str(), - strerror (errno)); - return NPERR_GENERIC_ERROR; - } + initialize_data_directory(); // Set appletviewer_executable. PLUGIN_DEBUG("Executing java at %s\n", get_plugin_executable().c_str()); diff --git a/plugin/icedteanp/IcedTeaNPPlugin.h b/plugin/icedteanp/IcedTeaNPPlugin.h index ff5e1eb..db6c421 100644 --- a/plugin/icedteanp/IcedTeaNPPlugin.h +++ b/plugin/icedteanp/IcedTeaNPPlugin.h @@ -99,6 +99,13 @@ struct ITNPPluginData } }; +// Have the browser allocate a new ITNPPluginData structure. +ITNPPluginData* plugin_data_new (); +void plugin_data_destroy (NPP instance); + +NPError initialize_data_directory(); +void start_jvm_if_needed(); + // Condition on which the queue processor waits extern pthread_cond_t cond_message_available; @@ -144,4 +151,6 @@ NPObject* get_scriptable_object(NPP instance); /* Creates a new scriptable plugin object and returns it */ NPObject* allocate_scriptable_object(NPP npp, NPClass *aClass); +NPError plugin_start_appletviewer (ITNPPluginData* data); + #endif /* __ICEDTEANPPLUGIN_H__ */ diff --git a/tests/cpp-unit-tests/IcedTeaJavaRequestProcessorTest.cc b/tests/cpp-unit-tests/IcedTeaJavaRequestProcessorTest.cc new file mode 100644 index 0000000..517d5d0 --- /dev/null +++ b/tests/cpp-unit-tests/IcedTeaJavaRequestProcessorTest.cc @@ -0,0 +1,416 @@ +/* Copyright (C) 2013 Red Hat + + This file is part of IcedTea. + + IcedTea is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + IcedTea is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with IcedTea; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. + + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License cover the whole + combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent + modules, and to copy and distribute the resulting executable under + terms of your choice, provided that you also meet, for each linked + independent module, the terms and conditions of the license of that + module. An independent module is a module which is not derived from + or based on this library. If you modify this library, you may extend + this exception to your version of the library, but you are not + obligated to do so. If you do not wish to do so, delete this + exception statement from your version. */ + +#include <cstdio> + +#include <vector> +#include <string> + +#include <npapi.h> + +#include <UnitTest++.h> + +#include "MemoryLeakDetector.h" + +#include "IcedTeaJavaRequestProcessor.h" + +/****************************************************************************** + * Simple helper methods to keep the tests clean. * + ******************************************************************************/ + +static const char* TEST_SOURCE = "[System]"; + +static std::string checked_return(JavaResultData* result) { + CHECK(!result->error_occurred); + return *result->return_string; +} + +// Packages + +static bool jrp_has_package(std::string package_name) { + JavaRequestProcessor processor; + JavaResultData* result = processor.hasPackage(0, package_name); + CHECK(!result->error_occurred); + return (result->return_identifier != 0); +} + +// Classes + +static std::string jrp_find_class(std::string name) { + return checked_return( + JavaRequestProcessor().findClass(0, name) + ); +} + +// Object creation + +static std::string jrp_new_object_with_constructor(std::string class_id, + std::string method_id, + std::vector<std::string> args = std::vector<std::string>()) { + return checked_return( + JavaRequestProcessor().newObjectWithConstructor(TEST_SOURCE, + class_id, method_id, args) + ); +} + +static std::string jrp_new_array(std::string class_id, std::string len) { + return checked_return( + JavaRequestProcessor().newArray(class_id, len) + ); +} + +static std::string jrp_new_string(std::string str) { + return checked_return( + JavaRequestProcessor().newString(str) + ); +} + +static std::string jrp_new_object(std::string class_id, + std::vector<std::string> args = std::vector<std::string>()) { + return checked_return( + JavaRequestProcessor().newObject(TEST_SOURCE, class_id, args) + ); +} + +static std::string jrp_get_value(std::string object_id) { + return checked_return( + JavaRequestProcessor().getValue(object_id) + ); +} + +// Inheritance + +static bool jrp_is_instance_of(std::string object_id, std::string class_id) { + JavaRequestProcessor processor; + JavaResultData* result = processor.isInstanceOf(object_id, class_id); + + CHECK(!result->error_occurred); + return (result->return_identifier != 0); +} + + +// Java methods operations. + +static std::string jrp_get_method_id(std::string class_id, + std::string method_name, + std::vector<std::string> args = std::vector<std::string>()) { + return checked_return( + JavaRequestProcessor().getMethodID(class_id, + browser_functions.getstringidentifier(method_name.c_str()), args) + ); +} + +static std::string jrp_get_static_method_id(std::string class_id, + std::string method_name, + std::vector<std::string> args = std::vector<std::string>()) { + return checked_return( + JavaRequestProcessor().getStaticMethodID(class_id, + browser_functions.getstringidentifier(method_name.c_str()), args) + ); +} + +static std::string jrp_call_method(std::string object_id, + std::string method_name, std::vector<std::string> args = std::vector<std::string>()) { + return checked_return( + JavaRequestProcessor().callMethod(TEST_SOURCE, object_id, + method_name, args) + ); +} + +static std::string jrp_call_static_method(std::string class_id, + std::string method_name, std::vector<std::string> args = std::vector<std::string>()) { + return checked_return( + JavaRequestProcessor().callStaticMethod(TEST_SOURCE, class_id, + method_name, args) + ); +} + +static std::string jrp_get_string(std::string object_id) { + return checked_return( + JavaRequestProcessor().getString(object_id) + ); +} + +static std::string jrp_get_class_id(std::string object_id) { + return checked_return( + JavaRequestProcessor().getClassID(object_id) + ); +} + + +// Java field operations. + +static std::string jrp_get_field(std::string object_id, + std::string field_name) { + return checked_return( + JavaRequestProcessor().getField(TEST_SOURCE, + jrp_get_class_id(object_id), object_id, field_name) + ); +} + +static std::string jrp_get_field_id(std::string class_id, + std::string field_name) { + return checked_return( + JavaRequestProcessor().getFieldID(class_id, field_name) + ); +} + +static std::string jrp_get_static_field_id(std::string class_id, + std::string field_name) { + return checked_return( + JavaRequestProcessor().getStaticFieldID(class_id, field_name) + ); +} + +static std::string jrp_get_static_field(std::string class_id, + std::string field_name) { + return checked_return( + JavaRequestProcessor().getStaticField(TEST_SOURCE, class_id, field_name) + ); +} + +static std::string jrp_set_field(std::string object_id, std::string field_name, + std::string value_id) { + return checked_return( + JavaRequestProcessor().setField(TEST_SOURCE, + jrp_get_class_id(object_id), object_id, field_name, value_id) + ); +} + +static std::string jrp_set_static_field(std::string class_id, std::string field_name, + std::string value_id) { + return checked_return( + JavaRequestProcessor().setStaticField(TEST_SOURCE, class_id, field_name, value_id) + ); +} + +// Java array operations. + +static std::string jrp_set_slot(std::string object_id, std::string index, + std::string value_id) { + return checked_return( + JavaRequestProcessor().setSlot(object_id, index, value_id) + ); +} + +static std::string jrp_get_slot(std::string object_id, std::string index) { + return checked_return( + JavaRequestProcessor().getSlot(object_id, index) + ); +} + +static std::string jrp_get_array_length(std::string object_id) { + return checked_return( + JavaRequestProcessor().getArrayLength(object_id) + ); +} + +// Result of toString() + +static std::string jrp_get_to_string_value(std::string object_id) { + return checked_return( + JavaRequestProcessor().getToStringValue(object_id) + ); +} + +/****************************************************************************** + * Compound helper methods. * + ******************************************************************************/ + +static NPP_t dummy_npp = {0,0}; + +static std::string create_java_integer(int value) { + // Prepare a java integer object with the value 1 + NPVariant integer_variant; + std::string integer_id; + INT32_TO_NPVARIANT(value, integer_variant); + createJavaObjectFromVariant(&dummy_npp, integer_variant, &integer_id); + return integer_id; +} + +static std::string create_null() { + // Prepare a null object + NPVariant null_variant; + std::string null_id; + NULL_TO_NPVARIANT(null_variant); + createJavaObjectFromVariant(&dummy_npp, null_variant, &null_id); + return null_id; +} + +static NPVariant java_result_to_variant(std::string object_id) { + NPVariant variant; + IcedTeaPluginUtilities::javaResultToNPVariant(&dummy_npp, &object_id, &variant); + return variant; +} + +/* Call the no-argument constructor of an object */ +static std::string jrp_noarg_construct(std::string classname) { + std::string class_id = jrp_find_class(classname); + std::string constructor_id = jrp_get_method_id(class_id, "<init>"); + return jrp_new_object_with_constructor(class_id, constructor_id); +} + + +/****************************************************************************** + * Test cases. Note that the tests exercise a variety of functions to first * + * create the appropriate conditions for the intended test. * + ******************************************************************************/ + +SUITE(JavaRequestProcessor) { + + TEST(callMethod) { + std::string object_id = jrp_noarg_construct("java.lang.Object"); + std::string tostring_result = jrp_get_string( + jrp_call_method(object_id, "toString")); + const char substr[] = "java.lang.Object@"; + // Check that the result of toString is as expected + CHECK(strncmp(tostring_result.c_str(), substr, strlen(substr)) == 0); + } + + /* Create a java.awt.Point, since it is one of the few standard classes with public fields. */ + TEST(getField_and_setField) { + std::string object_id = jrp_noarg_construct("java.awt.Point"); + + // Set the field 'x' to 1 + jrp_set_field(object_id, "x", create_java_integer(1)); + + // Get the field 'x' + NPVariant field_value = java_result_to_variant(jrp_get_field(object_id, "x")); + + // Ensure that the received field is 1 + CHECK(NPVARIANT_IS_INT32(field_value) && NPVARIANT_TO_INT32(field_value) == 1); + } + + TEST(getStaticField_and_setStaticField) { + // One of the few classes with a public & non-final static field that we can tinker with. + // If it moves, this test will fail, in which-case this test should be updated to another appropriate field. + std::string class_id = jrp_find_class("net.sourceforge.jnlp.controlpanel.DebuggingPanel"); + std::string properties_id = jrp_get_static_field(class_id, "properties"); + + // Check that the field is initially a non-null object + NPVariant sh_variant = java_result_to_variant(properties_id); + CHECK(!NPVARIANT_IS_NULL(sh_variant) && NPVARIANT_IS_OBJECT(sh_variant)); + browser_functions.releasevariantvalue(&sh_variant); + + jrp_set_static_field(class_id, "properties", create_null()); + sh_variant = java_result_to_variant(jrp_get_static_field(class_id, "properties")); + CHECK(NPVARIANT_IS_NULL(sh_variant)); + + // Reset the field to its original contents + jrp_set_static_field(class_id, "properties", properties_id); + sh_variant = java_result_to_variant(jrp_get_static_field(class_id, "properties")); + CHECK(!NPVARIANT_IS_NULL(sh_variant) && NPVARIANT_IS_OBJECT(sh_variant)); + browser_functions.releasevariantvalue(&sh_variant); + } + + TEST(arrayIndexing) { + const int ARRAY_LEN = 1; + + // We will create an Integer array of ARRAY_LEN + std::vector<std::string> args; + args.push_back(jrp_find_class("java.lang.Integer")); + args.push_back(create_java_integer(ARRAY_LEN)); + + // Create an array 'the hard way' (ie not using 'newArray') to test more of the API + std::string array_id = jrp_call_static_method(jrp_find_class("java.lang.reflect.Array"), "newInstance", args); + + // Attempt to set the first element to 1 + jrp_set_slot(array_id, "0", create_java_integer(1)); + // Note we get an integer _object_, not a plain int literal + std::string integer_id = jrp_get_slot(array_id, "0"); + NPVariant unboxed_slot_value = java_result_to_variant(jrp_call_method(integer_id, "intValue")); + + // Ensure that the received slot is 1 + CHECK(NPVARIANT_IS_INT32(unboxed_slot_value) && NPVARIANT_TO_INT32(unboxed_slot_value) == 1); + } + + // Also exercises 'getToStringValue' + TEST(newObject) { + std::string object_id = jrp_new_object(jrp_find_class("java.lang.Object")); + const char substr[] = "java.lang.Object@"; + // Check that the result of toString is as expected + CHECK(strncmp(jrp_get_to_string_value(object_id).c_str(), substr, strlen(substr)) == 0); + } + + TEST(hasPackage) { + CHECK(jrp_has_package("java.lang")); + CHECK(!jrp_has_package("not.an.icedtea_web.package")); + } + + TEST(newArray) { + const char ARRAY_LEN[] = "10"; + std::string array_id = jrp_new_array(jrp_find_class("java.lang.Integer"), ARRAY_LEN); + CHECK_EQUAL(ARRAY_LEN, jrp_get_array_length(array_id)); + } + + TEST(newString) { + const char TEST_STRING[] = "foobar"; + std::string string_id = jrp_new_string(TEST_STRING); + CHECK_EQUAL(TEST_STRING, jrp_get_string(string_id)); + CHECK_EQUAL(TEST_STRING, jrp_get_to_string_value(string_id)); + } + + // Can only really do sanity checks with given API + TEST(getFieldID_getStaticFieldID) { + CHECK(!jrp_get_field_id(jrp_find_class("java.awt.Point"), "x").empty()); + CHECK(!jrp_get_static_field_id(jrp_find_class("java.lang.Integer"), "MAX_VALUE").empty()); + } + + // Can only really do sanity checks with given API + TEST(getStaticMethodID) { + std::string class_id = jrp_find_class("java.lang.Integer"); + + std::vector<std::string> argtypes; + argtypes.push_back("Ljava.lang.String;"); + + CHECK(!jrp_get_static_method_id(class_id, "valueOf", argtypes).empty()); + } + + TEST(isInstanceOf) { + std::string point_id = jrp_noarg_construct("java.awt.Point"); + std::string object_class_id = jrp_find_class("java.lang.Object"); + CHECK(jrp_is_instance_of(point_id, object_class_id)); + } + + // Wasn't sure what the point of this method is to be honest, but it is used. + // Here it simply returns back a passed string object. + TEST(getValue) { + const char TEST_STRING[] = "foobar"; + + std::string str = jrp_get_string(jrp_get_value(jrp_new_string(TEST_STRING))); + CHECK_EQUAL(TEST_STRING, str); + } +} diff --git a/tests/cpp-unit-tests/IcedTeaNPPluginTest.cc b/tests/cpp-unit-tests/IcedTeaNPPluginTest.cc index 1633d6b..6fc0bf9 100644 --- a/tests/cpp-unit-tests/IcedTeaNPPluginTest.cc +++ b/tests/cpp-unit-tests/IcedTeaNPPluginTest.cc @@ -40,7 +40,10 @@ #include <UnitTest++.h> +#include "MemoryLeakDetector.h" + #include "IcedTeaNPPlugin.h" +#include "IcedTeaScriptablePluginObject.h" #include "IcedTeaPluginUtils.h" TEST(NP_GetMIMEDescription) { @@ -51,19 +54,38 @@ TEST(NP_GetMIMEDescription) { /* Not normally exposed */ std::vector<std::string*>* get_jvm_args(); -extern gchar* in_pipe_name; -extern gchar* out_pipe_name; TEST(get_jvm_args) { - in_pipe_name = (gchar*)"inpipe"; - out_pipe_name = (gchar*)"outpipe"; - std::vector<std::string*>* args = get_jvm_args(); CHECK(args != NULL); IcedTeaPluginUtilities::freeStringPtrVector(args); } + +static IcedTeaScriptableJavaPackageObject* get_scriptable_package_object() { + NPP_t instance = { /*Plugin data*/plugin_data_new(), /* Browser data*/0 }; + + /* Get the packages object (since instance.pdata->is_applet_instance == false) */ + NPObject* obj = get_scriptable_object(&instance); + + /* Make sure we got an IcedTeaScriptableJavaPackageObject */ + CHECK(obj->_class->deallocate == IcedTeaScriptableJavaPackageObject::deAllocate); + + plugin_data_destroy(&instance); + return (IcedTeaScriptableJavaPackageObject*)obj; +} + +TEST(get_scriptable_object) { + MemoryLeakDetector leak_detector; + // We test without an applet context, pending mocking of applet instances. + IcedTeaScriptableJavaPackageObject* obj = get_scriptable_package_object(); // Calls get_scriptable_object + + browser_functions.releaseobject(obj); + + CHECK(leak_detector.memory_leaks() == 0); +} + TEST(NP_GetValue) { void* __unused = NULL; gchar* char_value = NULL; diff --git a/tests/cpp-unit-tests/IcedTeaPluginUtilsTest.cc b/tests/cpp-unit-tests/IcedTeaPluginUtilsTest.cc index 7df0a1e..ac0e0d3 100644 --- a/tests/cpp-unit-tests/IcedTeaPluginUtilsTest.cc +++ b/tests/cpp-unit-tests/IcedTeaPluginUtilsTest.cc @@ -86,11 +86,11 @@ TEST(NPVariantStringCopy) { } TEST(NPIdentifierAsString) { - // NB: Mocked definition of 'utf8fromidentifier' simply reads NPIdentifier as a char* string. const char test_string[] = "foobar"; MemoryLeakDetector leak_detector; /* Ensure destruction */{ - std::string str = IcedTeaPluginUtilities::NPIdentifierAsString((NPIdentifier)test_string); + std::string str = IcedTeaPluginUtilities::NPIdentifierAsString( + browser_functions.getstringidentifier(test_string)); CHECK_EQUAL(test_string, str); } CHECK_EQUAL(0, leak_detector.memory_leaks()); diff --git a/tests/cpp-unit-tests/IcedTeaScriptablePluginObjectTest.cc b/tests/cpp-unit-tests/IcedTeaScriptablePluginObjectTest.cc index 9de0bbd..bb2b376 100644 --- a/tests/cpp-unit-tests/IcedTeaScriptablePluginObjectTest.cc +++ b/tests/cpp-unit-tests/IcedTeaScriptablePluginObjectTest.cc @@ -95,4 +95,37 @@ SUITE(IcedTeaScriptableJavaPackageObject) { browser_functions.releaseobject(obj); CHECK(leak_detector.memory_leaks() == 0); } + + static NPVariant np_checked_get(NPObject* obj, const char* identifier) { + NPVariant result; + CHECK(browser_functions.getproperty(&dummy_npp, obj, browser_functions.getstringidentifier(identifier), &result)); + return result; + } + + /* NOTICE: Requires icedtea-web Java-side to be running! + * Loads java.lang.Integer.MAX_VALUE */ + TEST(getProperty) { + MemoryLeakDetector leak_detector; + /* Ensure destruction */{ + /* Get the 'root' package */ + NPObject* obj = IcedTeaScriptableJavaPackageObject::get_scriptable_java_package_object(&dummy_npp, ""); + // Look up java.lang.Integer.MAX_VALUE + NPVariant java_result = np_checked_get(obj, "java"); + NPVariant lang_result = np_checked_get(NPVARIANT_TO_OBJECT(java_result), "lang"); + NPVariant integer_result = np_checked_get(NPVARIANT_TO_OBJECT(lang_result), "Integer"); + NPVariant max_value_result = np_checked_get(NPVARIANT_TO_OBJECT(integer_result), "MAX_VALUE"); + + // Check that it is indeed equal to 2147483647 + CHECK(NPVARIANT_IS_INT32(max_value_result)); + CHECK_EQUAL(2147483647, NPVARIANT_TO_INT32(max_value_result)); + + browser_functions.releasevariantvalue(&java_result); + browser_functions.releasevariantvalue(&lang_result); + browser_functions.releasevariantvalue(&integer_result); + browser_functions.releasevariantvalue(&max_value_result); + + browser_functions.releaseobject(obj); + } + CHECK(leak_detector.memory_leaks() == 0); + } } diff --git a/tests/cpp-unit-tests/MemoryLeakDetector.h b/tests/cpp-unit-tests/MemoryLeakDetector.h index 2bd5e9c..d457cb7 100644 --- a/tests/cpp-unit-tests/MemoryLeakDetector.h +++ b/tests/cpp-unit-tests/MemoryLeakDetector.h @@ -42,6 +42,7 @@ #define MEMORYLEAKDETECTOR_H_ #include <cstdio> +#include "browser_mock.h" #include "checked_allocations.h" #include "IcedTeaPluginUtils.h" @@ -69,7 +70,6 @@ public: return cpp_leaks + npapi_leaks; } -private: static void reset_global_state() { /* Clears allocations caused by storeInstanceID */ IcedTeaPluginUtilities::clearInstanceIDs(); diff --git a/tests/cpp-unit-tests/browser_mock.cc b/tests/cpp-unit-tests/browser_mock.cc index 8724687..32027f3 100644 --- a/tests/cpp-unit-tests/browser_mock.cc +++ b/tests/cpp-unit-tests/browser_mock.cc @@ -36,17 +36,26 @@ // Browser mock functions. Add more as needed. +#include <new> +#include <map> #include <cstring> + +#include <npapi.h> + #include "checked_allocations.h" #include "UnitTest++.h" #include "browser_mock.h" +#include "browser_mock_npidentifier.h" -#include "IcedTeaNPPlugin.h" +// 'Browser' global data +// Stores NPAPI allocations static AllocationSet __allocations; +// Mocked functions + // It is expected that these will only run during a unit test static void* mock_memalloc(uint32_t size) { void* mem = malloc(size); @@ -78,6 +87,15 @@ static void mock_releaseobject(NPObject* obj) { } } +static void mock_releasevariantvalue(NPVariant* variant) { + if (variant->type == NPVariantType_String) { + /* Only string and object values require freeing */ + mock_memfree((void*)variant->value.stringValue.UTF8Characters); + } else if (variant->type == NPVariantType_Object) { + mock_releaseobject(variant->value.objectValue); + } +} + static NPObject* mock_createobject(NPP instance, NPClass* np_class) { NPObject* obj; if (np_class->allocate) { @@ -90,17 +108,26 @@ static NPObject* mock_createobject(NPP instance, NPClass* np_class) { return obj; } -static NPUTF8* mock_utf8fromidentifier(NPIdentifier id) { - // Treat NPIdentifier (== void pointer) as a pointer to characters for simplicity - const NPUTF8* str = (const NPUTF8*) id; - // We expect this string to be freed with 'memfree' - NPUTF8* copy = (NPUTF8*) mock_memalloc(strlen(str) + 1); - memcpy(copy, str, strlen(str) + 1); - return copy; +static bool mock_getproperty(NPP npp, NPObject* obj, NPIdentifier property_name, NPVariant* result) { + if (obj->_class->getProperty) { + return obj->_class->getProperty(obj, property_name, result); + } else { + return false; + } } -void browsermock_setup_functions() { +static bool mock_setproperty(NPP npp, NPObject* obj, NPIdentifier property_name, const NPVariant* value) { + if (obj->_class->setProperty) { + return obj->_class->setProperty(obj, property_name, value); + } else { + return false; + } +} + +NPNetscapeFuncs browsermock_create_table() { + NPNetscapeFuncs browser_functions; memset(&browser_functions, 0, sizeof(NPNetscapeFuncs)); + browser_functions.size = sizeof(NPNetscapeFuncs); browser_functions.memalloc = &mock_memalloc; browser_functions.memfree = &mock_memfree; @@ -108,7 +135,16 @@ void browsermock_setup_functions() { browser_functions.createobject = &mock_createobject; browser_functions.retainobject = &mock_retainobject; browser_functions.releaseobject = &mock_releaseobject; - browser_functions.utf8fromidentifier = &mock_utf8fromidentifier; + browser_functions.releasevariantvalue = &mock_releasevariantvalue; + + browser_functions.setproperty = &mock_setproperty; + browser_functions.getproperty = &mock_getproperty; + + browser_functions.utf8fromidentifier = &browsermock_utf8fromidentifier; + browser_functions.getstringidentifier = &browsermock_getstringidentifier; + browser_functions.identifierisstring = &browsermock_identifierisstring; + + return browser_functions; } void browsermock_clear_state() { diff --git a/tests/cpp-unit-tests/browser_mock.h b/tests/cpp-unit-tests/browser_mock.h index d936215..ac38813 100644 --- a/tests/cpp-unit-tests/browser_mock.h +++ b/tests/cpp-unit-tests/browser_mock.h @@ -43,7 +43,9 @@ #ifndef __BROWSER_MOCK_H__ #define __BROWSER_MOCK_H__ -void browsermock_setup_functions(); +#include <npfunctions.h> + +NPNetscapeFuncs browsermock_create_table(); void browsermock_clear_state(); int browsermock_unfreed_allocations(); diff --git a/tests/cpp-unit-tests/browser_mock_npidentifier.cc b/tests/cpp-unit-tests/browser_mock_npidentifier.cc new file mode 100644 index 0000000..736d9a5 --- /dev/null +++ b/tests/cpp-unit-tests/browser_mock_npidentifier.cc @@ -0,0 +1,117 @@ +/* Copyright (C) 2013 Red Hat + + This file is part of IcedTea. + + IcedTea is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + IcedTea is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with IcedTea; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. + + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License cover the whole + combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent + modules, and to copy and distribute the resulting executable under + terms of your choice, provided that you also meet, for each linked + independent module, the terms and conditions of the license of that + module. An independent module is a module which is not derived from + or based on this library. If you modify this library, you may extend + this exception to your version of the library, but you are not + obligated to do so. If you do not wish to do so, delete this + exception statement from your version. */ + +#include <cstdlib> + +#include <map> + +#include "IcedTeaNPPlugin.h" +#include "checked_allocations.h" +#include "browser_mock_npidentifier.h" + +struct MockedNPIdentifier_t; // foward declare +typedef std::basic_string<char, std::char_traits<char>, SafeAllocator> SafeString; +typedef std::map<int, MockedNPIdentifier_t*, std::less<int>, SafeAllocator> SafeIntToIDMap; +typedef std::map<SafeString, MockedNPIdentifier_t*, std::less<SafeString>, SafeAllocator> SafeStringToIDMap; + + +// Handles creation of NPIdentifier + +// Carefully avoids operator new so as not to interfere with leak detection. +// This mimics browser internal state. +struct MockedNPIdentifier_t { + SafeString string; + int integer; + bool is_integer; // If false, it is a string + + // Carefully avoids operator new so as not to interfere with leak detection + static MockedNPIdentifier_t* safe_allocate(SafeString str) { + MockedNPIdentifier_t* mem = (MockedNPIdentifier_t*)malloc(sizeof(MockedNPIdentifier_t)); + new (&mem->string) SafeString(str); + mem->integer = -1; + mem->is_integer = false; + return mem; + } + + // Carefully avoids operator new so as not to interfere with leak detection + static MockedNPIdentifier_t* safe_allocate(int i) { + MockedNPIdentifier_t* mem = (MockedNPIdentifier_t*) malloc( + sizeof(MockedNPIdentifier_t)); + new (&mem->string) SafeString(); + mem->integer = i; + mem->is_integer = true; + return mem; + } +}; + +// Mimics global browser data. OK if not cleared in-between tests, does not change semantics. +// Used to ensure NPIdentifiers are unique. Never freed. +static SafeIntToIDMap __np_int_identifiers; +static SafeStringToIDMap __np_string_identifiers; + +// Carefully avoids operator new so as not to interfere with leak detection +NPIdentifier browsermock_getstringidentifier(const NPUTF8* name) { + SafeString safe_copy(name); + if (__np_string_identifiers.find(safe_copy) == __np_string_identifiers.end()) { + __np_string_identifiers[safe_copy] = MockedNPIdentifier_t::safe_allocate(safe_copy); + } + return __np_string_identifiers[safe_copy]; +} + +// Carefully avoids operator new so as not to interfere with leak detection +NPIdentifier browsermock_getintidentifier(int i) { + if (__np_int_identifiers.find(i) == __np_int_identifiers.end()) { + __np_int_identifiers[i] = MockedNPIdentifier_t::safe_allocate(i); + } + return __np_int_identifiers[i]; +} + +bool browsermock_identifierisstring(NPIdentifier identifier) { + MockedNPIdentifier_t* contents = (MockedNPIdentifier_t*)identifier; + return !contents->is_integer; +} + +NPUTF8* browsermock_utf8fromidentifier(NPIdentifier identifier) { + MockedNPIdentifier_t* contents = (MockedNPIdentifier_t*)identifier; + if (contents->is_integer) { + return NULL; + } + + // We expect this string to be freed with 'memfree' + NPUTF8* copy = (NPUTF8*) browser_functions.memalloc(contents->string.size() + 1); + memcpy(copy, contents->string.c_str(), contents->string.size() + 1); + return copy; +} diff --git a/tests/cpp-unit-tests/browser_mock_npidentifier.h b/tests/cpp-unit-tests/browser_mock_npidentifier.h new file mode 100644 index 0000000..d477fef --- /dev/null +++ b/tests/cpp-unit-tests/browser_mock_npidentifier.h @@ -0,0 +1,57 @@ +/* Copyright (C) 2013 Red Hat + + This file is part of IcedTea. + + IcedTea is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + IcedTea is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with IcedTea; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. + + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License cover the whole + combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent + modules, and to copy and distribute the resulting executable under + terms of your choice, provided that you also meet, for each linked + independent module, the terms and conditions of the license of that + module. An independent module is a module which is not derived from + or based on this library. If you modify this library, you may extend + this exception to your version of the library, but you are not + obligated to do so. If you do not wish to do so, delete this + exception statement from your version. */ + +/* + * browser_mock_npidentifier.h: + * Handles NPAPI functions related to NPIdentifier, NPIdentifier has the following constraints: + * - Unique for each integer & string + * - Should not interfere with leak detection, so use malloc-based allocators (specified by templates) everywhere. + */ + +#ifndef __BROWSER_MOCK_NPIDENTIFIER_H__ +#define __BROWSER_MOCK_NPIDENTIFIER_H__ + +#include <string> +#include <npfunctions.h> + +NPIdentifier browsermock_getstringidentifier(const NPUTF8* name); +NPIdentifier browsermock_getintidentifier(int i); + +bool browsermock_identifierisstring(NPIdentifier identifier); + +NPUTF8* browsermock_utf8fromidentifier(NPIdentifier identifier); + +#endif // __BROWSER_MOCK_NPIDENTIFIER_H__ diff --git a/tests/cpp-unit-tests/checked_allocations.h b/tests/cpp-unit-tests/checked_allocations.h index 0c9ce04..299c769 100644 --- a/tests/cpp-unit-tests/checked_allocations.h +++ b/tests/cpp-unit-tests/checked_allocations.h @@ -39,6 +39,7 @@ #ifndef CHECKED_ALLOCATIONS_H_ #define CHECKED_ALLOCATIONS_H_ +#include <string> #include <set> #include <cstdio> #include <exception> @@ -46,8 +47,9 @@ #include <cstdlib> #include <ext/malloc_allocator.h> //GNU extension -// Plays nice with custom-defined operator new -typedef std::set<void*, std::less<void*>, __gnu_cxx::malloc_allocator<void*> > AllocationSet; +// Classes that play nice with custom-defined operator new by using 'vanilla' malloc +typedef __gnu_cxx::malloc_allocator<void*> SafeAllocator; +typedef std::set<void*, std::less<void*>, SafeAllocator> AllocationSet; int cpp_unfreed_allocations(); diff --git a/tests/cpp-unit-tests/main.cc b/tests/cpp-unit-tests/main.cc index d7e71f6..c050bbb 100644 --- a/tests/cpp-unit-tests/main.cc +++ b/tests/cpp-unit-tests/main.cc @@ -42,7 +42,11 @@ #include <UnitTest++.h> #include <TestReporter.h> +#include <npfunctions.h> + +#include "IcedTeaNPPlugin.h" #include "browser_mock.h" +#include "MemoryLeakDetector.h" #include "checked_allocations.h" using namespace UnitTest; @@ -56,6 +60,12 @@ static std::string full_testname(const TestDetails& details) { } } +// Important for testing purposes of eg leaks between tests +static void reset_global_state() { + browsermock_clear_state(); + MemoryLeakDetector::reset_global_state(); +} + class IcedteaWebUnitTestReporter: public TestReporter { public: @@ -66,7 +76,7 @@ public: } virtual void ReportTestStart(const TestDetails& test) { - browsermock_clear_state(); + reset_global_state(); pretest_allocs = cpp_unfreed_allocations(); did_finish_correctly = true; } @@ -84,6 +94,7 @@ public: virtual void ReportTestFinish(const TestDetails& details, float secondsElapsed) { + reset_global_state(); int posttest_allocs = cpp_unfreed_allocations(); std::string testname = full_testname(details); @@ -126,8 +137,20 @@ static int run_icedtea_web_unit_tests() { True() /*All tests*/, 0 /*No time limit*/); } +/* Spawns the Java-side of the plugin, create request processing threads, + * and sets up a mocked 'browser' environment. */ +static void initialize_plugin_components() { + NPNetscapeFuncs mocked_browser_functions = browsermock_create_table(); + NPPluginFuncs unused_plugin_functions; + memset(&unused_plugin_functions, 0, sizeof(NPPluginFuncs)); + unused_plugin_functions.size = sizeof(NPPluginFuncs); + + NP_Initialize (&mocked_browser_functions, &unused_plugin_functions); + start_jvm_if_needed(); +} + int main() { - browsermock_setup_functions(); + initialize_plugin_components(); int exitcode = run_icedtea_web_unit_tests(); |