From aac7d1498a683a30a3cdcf44aebac72d52d1da44 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Thu, 14 Sep 2017 14:53:53 -0700 Subject: [PATCH 01/23] Throw a more appropriate error for some operations on closed Realms --- src/js_realm.hpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 931b4d3d..523d22ab 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -795,6 +795,7 @@ void RealmClass::create(ContextType ctx, FunctionType, ObjectType this_object validate_argument_count(argc, 2, 3); SharedRealm realm = *get_internal>(this_object); + realm->verify_open(); std::string object_type; auto &object_schema = validated_object_schema_for_value(ctx, realm, arguments[0], object_type); @@ -818,6 +819,7 @@ void RealmClass::delete_one(ContextType ctx, FunctionType, ObjectType this_ob validate_argument_count(argc, 1); SharedRealm realm = *get_internal>(this_object); + realm->verify_open(); if (!realm->is_in_transaction()) { throw std::runtime_error("Can only delete objects within a transaction."); } @@ -865,6 +867,7 @@ void RealmClass::delete_all(ContextType ctx, FunctionType, ObjectType this_ob validate_argument_count(argc, 0); SharedRealm realm = *get_internal>(this_object); + realm->verify_open(); if (!realm->is_in_transaction()) { throw std::runtime_error("Can only delete objects within a transaction."); @@ -887,7 +890,7 @@ void RealmClass::write(ContextType ctx, FunctionType, ObjectType this_object, try { Function::call(ctx, callback, this_object, 0, nullptr); } - catch (std::exception &e) { + catch (...) { realm->cancel_transaction(); throw; } @@ -927,9 +930,7 @@ void RealmClass::add_listener(ContextType ctx, FunctionType, ObjectType this_ auto callback = Value::validated_to_function(ctx, arguments[1]); SharedRealm realm = *get_internal>(this_object); - if (realm->is_closed()) { - throw ClosedRealmException(); - } + realm->verify_open(); get_delegate(realm.get())->add_notification(callback); } @@ -941,9 +942,7 @@ void RealmClass::remove_listener(ContextType ctx, FunctionType, ObjectType th auto callback = Value::validated_to_function(ctx, arguments[1]); SharedRealm realm = *get_internal>(this_object); - if (realm->is_closed()) { - throw ClosedRealmException(); - } + realm->verify_open(); get_delegate(realm.get())->remove_notification(callback); } @@ -955,9 +954,7 @@ void RealmClass::remove_all_listeners(ContextType ctx, FunctionType, ObjectTy } SharedRealm realm = *get_internal>(this_object); - if (realm->is_closed()) { - throw ClosedRealmException(); - } + realm->verify_open(); get_delegate(realm.get())->remove_all_notifications(); } From e564ad1de8278fe01fb444118222f381fe0d7401 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Thu, 14 Sep 2017 15:00:49 -0700 Subject: [PATCH 02/23] Improve the errors thrown for invalid schema definitions --- src/js_realm.hpp | 2 +- src/js_schema.hpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 523d22ab..929e47b6 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -398,7 +398,7 @@ void RealmClass::constructor(ContextType ctx, ObjectType this_object, size_t static const String schema_string = "schema"; ValueType schema_value = Object::get_property(ctx, object, schema_string); if (!Value::is_undefined(ctx, schema_value)) { - ObjectType schema_object = Value::validated_to_object(ctx, schema_value, "schema"); + ObjectType schema_object = Value::validated_to_array(ctx, schema_value, "schema"); config.schema.emplace(Schema::parse_schema(ctx, schema_object, defaults, constructors)); schema_updated = true; } diff --git a/src/js_schema.hpp b/src/js_schema.hpp index 44d1a629..c6b8b514 100644 --- a/src/js_schema.hpp +++ b/src/js_schema.hpp @@ -179,9 +179,9 @@ ObjectSchema Schema::parse_object_schema(ContextType ctx, ObjectType object_s ObjectDefaults object_defaults; ObjectSchema object_schema; - object_schema.name = Object::validated_get_string(ctx, object_schema_object, name_string); - - ObjectType properties_object = Object::validated_get_object(ctx, object_schema_object, properties_string, "ObjectSchema must have a 'properties' object."); + object_schema.name = Object::validated_get_string(ctx, object_schema_object, name_string, "ObjectSchema"); + + ObjectType properties_object = Object::validated_get_object(ctx, object_schema_object, properties_string, "ObjectSchema"); if (Value::is_array(ctx, properties_object)) { uint32_t length = Object::validated_get_length(ctx, properties_object); for (uint32_t i = 0; i < length; i++) { From b8fd4fb861bd7880681c31e7e088257554fa9d62 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Thu, 14 Sep 2017 16:01:59 -0700 Subject: [PATCH 03/23] Add the invalid value to the type error exception message --- src/js_object_accessor.hpp | 9 ++++----- src/js_realm_object.hpp | 29 +++++++++++++++-------------- src/js_types.hpp | 24 +++++++++++------------- 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/js_object_accessor.hpp b/src/js_object_accessor.hpp index c601aa91..338f905a 100644 --- a/src/js_object_accessor.hpp +++ b/src/js_object_accessor.hpp @@ -22,8 +22,6 @@ #include "js_realm_object.hpp" #include "js_schema.hpp" -#include "util/format.hpp" - namespace realm { class List; class Object; @@ -65,8 +63,9 @@ public: ValueType value = Object::get_property(m_ctx, object, prop_name); const auto& prop = m_object_schema.persisted_properties[prop_index]; if (!Value::is_valid_for_property(m_ctx, value, prop)) { - throw TypeErrorException(util::format("%1.%2", m_object_schema.name, prop.name), - js_type_name_for_property_type(prop.type)); + throw TypeErrorException(m_object_schema.name, prop.name, + js_type_name_for_property_type(prop.type), + print(value)); } return value; } @@ -121,7 +120,7 @@ public: void will_change(realm::Object&, realm::Property const&) { } void did_change() { } - std::string print(ValueType const&) { return "not implemented"; } + std::string print(ValueType const& v) { return Value::to_string(m_ctx, v); } private: ContextType m_ctx; diff --git a/src/js_realm_object.hpp b/src/js_realm_object.hpp index 1ca090ce..78238277 100644 --- a/src/js_realm_object.hpp +++ b/src/js_realm_object.hpp @@ -49,7 +49,7 @@ struct RealmObjectClass : ClassDefinition { static void get_property(ContextType, ObjectType, const String &, ReturnValue &); static bool set_property(ContextType, ObjectType, const String &, ValueType); static std::vector get_property_names(ContextType, ObjectType); - + static void is_valid(ContextType, FunctionType, ObjectType, size_t, const ValueType [], ReturnValue &); static void get_object_schema(ContextType, FunctionType, ObjectType, size_t, const ValueType [], ReturnValue &); static void linking_objects(ContextType, FunctionType, ObjectType, size_t, const ValueType [], ReturnValue &); @@ -73,13 +73,13 @@ template void RealmObjectClass::is_valid(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { return_value.set(get_internal>(this_object)->is_valid()); } - + template void RealmObjectClass::get_object_schema(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { auto object = get_internal>(this_object); return_value.set(Schema::object_for_object_schema(ctx, object->get_object_schema())); } - + template typename T::Object RealmObjectClass::create_instance(ContextType ctx, realm::Object realm_object) { static String prototype_string = "prototype"; @@ -100,7 +100,7 @@ typename T::Object RealmObjectClass::create_instance(ContextType ctx, realm:: if (result != object && !Value::is_null(ctx, result) && !Value::is_undefined(ctx, result)) { throw std::runtime_error("Realm object constructor must not return another value"); } - + return object; } @@ -127,12 +127,13 @@ bool RealmObjectClass::set_property(ContextType ctx, ObjectType object, const return false; } + NativeAccessor accessor(ctx, realm_object->realm(), realm_object->get_object_schema()); if (!Value::is_valid_for_property(ctx, value, *prop)) { - throw TypeErrorException(util::format("%1.%2", realm_object->get_object_schema().name, property_name), - js_type_name_for_property_type(prop->type)); + throw TypeErrorException(realm_object->get_object_schema().name, property_name, + js_type_name_for_property_type(prop->type), + accessor.print(value)); } - NativeAccessor accessor(ctx, realm_object->realm(), realm_object->get_object_schema()); realm_object->set_property_value(accessor, property_name, value, true); return true; } @@ -162,29 +163,29 @@ std::vector> RealmObjectClass::get_property_names(ContextType ctx, template void realm::js::RealmObjectClass::linking_objects(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 2); - + std::string object_type = Value::validated_to_string(ctx, arguments[0], "objectType"); std::string property_name = Value::validated_to_string(ctx, arguments[1], "property"); - + auto object = get_internal>(this_object); - + auto target_object_schema = object->realm()->schema().find(object_type); if (target_object_schema == object->realm()->schema().end()) { throw std::logic_error(util::format("Could not find schema for type '%1'", object_type)); } - + auto link_property = target_object_schema->property_for_name(property_name); if (!link_property) { throw std::logic_error(util::format("Type '%1' does not contain property '%2'", object_type, property_name)); } - + if (link_property->object_type != object->get_object_schema().name) { throw std::logic_error(util::format("'%1.%2' is not a relationship to '%3'", object_type, property_name, object->get_object_schema().name)); } - + realm::TableRef table = ObjectStore::table_for_object_type(object->realm()->read_group(), target_object_schema->name); auto row = object->row(); auto tv = row.get_table()->get_backlink_view(row.get_index(), table.get(), link_property->table_column); - + return_value.set(ResultsClass::create_instance(ctx, realm::Results(object->realm(), std::move(tv)))); } diff --git a/src/js_types.hpp b/src/js_types.hpp index 26acbee7..af30de83 100644 --- a/src/js_types.hpp +++ b/src/js_types.hpp @@ -26,6 +26,7 @@ #include #include +#include #include #if defined(__GNUC__) && !(defined(DEBUG) && DEBUG) @@ -80,18 +81,16 @@ struct Context { class TypeErrorException : public std::invalid_argument { public: - std::string const& prefix() const { return m_prefix; } - std::string const& type() const { return m_type; } + TypeErrorException(StringData object_type, StringData property, + std::string const& type, std::string const& value) + : std::invalid_argument(util::format("%1.%2 must be of type '%3', got (%4)", + object_type, property, type, value)) + {} - TypeErrorException(std::string prefix, std::string type) : - std::invalid_argument(prefix + " must be of type: " + type), - m_prefix(std::move(prefix)), - m_type(std::move(type)) - {} - -private: - std::string m_prefix; - std::string m_type; + TypeErrorException(const char *name, std::string const& type, std::string const& value) + : std::invalid_argument(util::format("%1 must be of type '%2', got (%3)", + name ? name : "JS value", type, value)) + {} }; template @@ -138,8 +137,7 @@ struct Value { #define VALIDATED(return_t, type) \ static return_t validated_to_##type(ContextType ctx, const ValueType &value, const char *name = nullptr) { \ if (!is_##type(ctx, value)) { \ - std::string prefix = name ? std::string("'") + name + "'" : "JS value"; \ - throw TypeErrorException(prefix, #type); \ + throw TypeErrorException(name, #type, to_string(ctx, value)); \ } \ return to_##type(ctx, value); \ } From 4ad75c9546e8341112f3c773d85b4b83f8481d53 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Tue, 5 Sep 2017 16:30:26 -0700 Subject: [PATCH 04/23] Don't discard the actual error message in validated_get_X --- src/js_schema.hpp | 3 ++- src/js_types.hpp | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/js_schema.hpp b/src/js_schema.hpp index c6b8b514..e32e2427 100644 --- a/src/js_schema.hpp +++ b/src/js_schema.hpp @@ -232,7 +232,8 @@ ObjectSchema Schema::parse_object_schema(ContextType ctx, ObjectType object_s } template -realm::Schema Schema::parse_schema(ContextType ctx, ObjectType schema_object, ObjectDefaultsMap &defaults, ConstructorMap &constructors) { +realm::Schema Schema::parse_schema(ContextType ctx, ObjectType schema_object, + ObjectDefaultsMap &defaults, ConstructorMap &constructors) { std::vector schema; uint32_t length = Object::validated_get_length(ctx, schema_object); diff --git a/src/js_types.hpp b/src/js_types.hpp index af30de83..1e32c2b6 100644 --- a/src/js_types.hpp +++ b/src/js_types.hpp @@ -20,6 +20,7 @@ #include "execution_context_id.hpp" #include "property.hpp" +#include "util/format.hpp" #include #include @@ -223,7 +224,7 @@ struct Object { return Value::validated_to_##type(ctx, get_property(ctx, object, key), std::string(key).c_str()); \ } \ catch (std::invalid_argument &e) { \ - throw message ? std::invalid_argument(message) : e; \ + throw message ? std::invalid_argument(util::format("Failed to read %1: %2", message, e.what())) : e; \ } \ } \ static return_t validated_get_##type(ContextType ctx, const ObjectType &object, uint32_t index, const char *message = nullptr) { \ @@ -231,7 +232,7 @@ struct Object { return Value::validated_to_##type(ctx, get_property(ctx, object, index)); \ } \ catch (std::invalid_argument &e) { \ - throw message ? std::invalid_argument(message) : e; \ + throw message ? std::invalid_argument(util::format("Failed to read %1: %2", message, e.what())) : e; \ } \ } From 53ca09839e77f5c0188a5a43e1cb4175f0e51b6a Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Thu, 14 Sep 2017 15:50:11 -0700 Subject: [PATCH 05/23] Check the exception message in all Realm tests which assert an exception is thrown --- tests/js/asserts.js | 17 ++ tests/js/realm-tests.js | 517 +++++++++++++++++----------------------- 2 files changed, 238 insertions(+), 296 deletions(-) diff --git a/tests/js/asserts.js b/tests/js/asserts.js index a3a3026e..95f3378c 100644 --- a/tests/js/asserts.js +++ b/tests/js/asserts.js @@ -120,6 +120,23 @@ module.exports = { } }, + assertThrowsContaining: function(func, expectedMessage) { + var caught = false; + try { + func(); + } + catch (e) { + caught = true; + if (!e.message.includes(expectedMessage)) { + throw new TestFailureError(`Expected exception "${expectedMessage}" not thrown - instead caught: "${e}"`); + } + } + + if (!caught) { + throw new TestFailureError(`Expected exception "${expectedMessage}" not thrown`); + } + }, + assertTrue: function(condition, errorMessage) { if (!condition) { throw new TestFailureError(errorMessage || `Condition ${condition} expected to be true`); diff --git a/tests/js/realm-tests.js b/tests/js/realm-tests.js index fb52b048..0ef1a435 100644 --- a/tests/js/realm-tests.js +++ b/tests/js/realm-tests.js @@ -18,9 +18,9 @@ 'use strict'; -var Realm = require('realm'); -var TestCase = require('./asserts'); -var schemas = require('./schemas'); +const Realm = require('realm'); +const TestCase = require('./asserts'); +const schemas = require('./schemas'); let pathSeparator = '/'; if (typeof process === 'object' && process.platform === 'win32') { @@ -29,7 +29,7 @@ if (typeof process === 'object' && process.platform === 'win32') { module.exports = { testRealmConstructor: function() { - var realm = new Realm({schema: []}); + const realm = new Realm({schema: []}); TestCase.assertTrue(realm instanceof Realm); TestCase.assertEqual(typeof Realm, 'function'); @@ -37,52 +37,43 @@ module.exports = { }, testRealmConstructorPath: function() { - TestCase.assertThrows(function() { - new Realm(''); - }, 'Realm cannot be created with an invalid path'); - TestCase.assertThrows(function() { - new Realm('test1.realm', 'invalidArgument'); - }, 'Realm constructor can only have 0 or 1 argument(s)'); + TestCase.assertThrows(() => new Realm('')); // the message for this error is platform-specific + TestCase.assertThrowsContaining(() => new Realm('test1.realm', 'invalidArgument'), + "Invalid arguments when constructing 'Realm'"); - var defaultRealm = new Realm({schema: []}); + const defaultRealm = new Realm({schema: []}); TestCase.assertEqual(defaultRealm.path, Realm.defaultPath); - var defaultRealm2 = new Realm(); + const defaultRealm2 = new Realm(); TestCase.assertEqual(defaultRealm2.path, Realm.defaultPath); - var defaultDir = Realm.defaultPath.substring(0, Realm.defaultPath.lastIndexOf(pathSeparator) + 1) - var testPath = 'test1.realm'; - var realm = new Realm({schema: [], path: testPath}); + const defaultDir = Realm.defaultPath.substring(0, Realm.defaultPath.lastIndexOf(pathSeparator) + 1); + const testPath = 'test1.realm'; + const realm = new Realm({schema: [], path: testPath}); TestCase.assertEqual(realm.path, defaultDir + testPath); - var testPath2 = 'test2.realm'; - var realm2 = new Realm({schema: [], path: testPath2}); + const testPath2 = 'test2.realm'; + const realm2 = new Realm({schema: [], path: testPath2}); TestCase.assertEqual(realm2.path, defaultDir + testPath2); }, testRealmConstructorSchemaVersion: function() { - var defaultRealm = new Realm({schema: []}); + const defaultRealm = new Realm({schema: []}); TestCase.assertEqual(defaultRealm.schemaVersion, 0); - TestCase.assertThrows(function() { - new Realm({schemaVersion: 1, schema: []}); - }, "Realm already opened at a different schema version"); - + TestCase.assertThrowsContaining(() => new Realm({schemaVersion: 1, schema: []}), + "already opened with different schema version."); + TestCase.assertEqual(new Realm().schemaVersion, 0); TestCase.assertEqual(new Realm({schemaVersion: 0}).schemaVersion, 0); - var realm = new Realm({path: 'test1.realm', schema: [], schemaVersion: 1}); + let realm = new Realm({path: 'test1.realm', schema: [], schemaVersion: 1}); TestCase.assertEqual(realm.schemaVersion, 1); TestCase.assertEqual(realm.schema.length, 0); realm.close(); - // FIXME - enable once realm initialization supports schema comparison - // TestCase.assertThrows(function() { - // realm = new Realm({path: testPath, schema: [schemas.TestObject], schemaVersion: 1}); - // }, "schema changes require updating the schema version"); - realm = new Realm({path: 'test1.realm', schema: [schemas.TestObject], schemaVersion: 2}); - realm.write(function() { + realm.write(() => { realm.create('TestObject', {doubleCol: 1}); }); TestCase.assertEqual(realm.objects('TestObject')[0].doubleCol, 1); @@ -91,41 +82,31 @@ module.exports = { }, testRealmConstructorDynamicSchema: function() { - var realm = new Realm({schema: [schemas.TestObject]}); - realm.write(function() { + let realm = new Realm({schema: [schemas.TestObject]}); + realm.write(() => { realm.create('TestObject', [1]) }); realm.close(); realm = new Realm(); - var objects = realm.objects('TestObject'); + const objects = realm.objects('TestObject'); TestCase.assertEqual(objects.length, 1); TestCase.assertEqual(objects[0].doubleCol, 1.0); }, testRealmConstructorSchemaValidation: function() { - TestCase.assertThrows(function() { - new Realm({schema: schemas.AllTypes}); - }, 'The schema should be an array'); - - TestCase.assertThrows(function() { - new Realm({schema: ['SomeType']}); - }, 'The schema should be an array of objects'); - - TestCase.assertThrows(function() { - new Realm({schema: [{}]}); - }, 'The schema should be an array of ObjectSchema objects'); - - TestCase.assertThrows(function() { - new Realm({schema: [{name: 'SomeObject'}]}); - }, 'The schema should be an array of ObjectSchema objects'); - - TestCase.assertThrows(function() { - new Realm({schema: [{properties: {intCol: 'int'}}]}); - }, 'The schema should be an array of ObjectSchema objects'); + TestCase.assertThrowsContaining(() => new Realm({schema: schemas.AllTypes}), "schema must be of type 'array', got"); + TestCase.assertThrowsContaining(() => new Realm({schema: ['SomeType']}), + "Failed to read ObjectSchema: JS value must be of type 'object', got (SomeType)"); + TestCase.assertThrowsContaining(() => new Realm({schema: [{}]}), + "Failed to read ObjectSchema: name must be of type 'string', got (undefined)"); + TestCase.assertThrowsContaining(() => new Realm({schema: [{name: 'SomeObject'}]}), + "Failed to read ObjectSchema: properties must be of type 'object', got (undefined)"); + TestCase.assertThrowsContaining(() => new Realm({schema: [{properties: {intCol: 'int'}}]}), + "Failed to read ObjectSchema: name must be of type 'string', got (undefined)"); // linkingObjects property where the source property is missing - TestCase.assertThrows(function() { + TestCase.assertThrowsContaining(() => { new Realm({schema: [{ name: 'InvalidObject', properties: { @@ -135,7 +116,7 @@ module.exports = { }, "Property 'InvalidObject.nosuchproperty' declared as origin of linking objects property 'InvalidObject.linkingObjects' does not exist"); // linkingObjects property where the source property is not a link - TestCase.assertThrows(function() { + TestCase.assertThrowsContaining(() => { new Realm({schema: [{ name: 'InvalidObject', properties: { @@ -144,9 +125,9 @@ module.exports = { } }]}); }, "Property 'InvalidObject.integer' declared as origin of linking objects property 'InvalidObject.linkingObjects' is not a link") - + // linkingObjects property where the source property links elsewhere - TestCase.assertThrows(function() { + TestCase.assertThrowsContaining(() => { new Realm({schema: [{ name: 'InvalidObject', properties: { @@ -165,7 +146,7 @@ module.exports = { testRealmConstructorInMemory: function() { // open in-memory realm instance const realm1 = new Realm({inMemory: true, schema: [schemas.TestObject]}); - realm1.write(function() { + realm1.write(() => { realm1.create('TestObject', [1]) }); TestCase.assertEqual(realm1.inMemory, true); @@ -187,28 +168,25 @@ module.exports = { TestCase.assertEqual(realm3.schema.length, 0); // try to open the same realm in persistent mode (should fail as you cannot mix modes) - TestCase.assertThrows(function() { - const realm4 = new Realm({}); - }); + TestCase.assertThrowsContaining(() => new Realm({}), 'already opened with different inMemory settings.'); }, testRealmConstructorReadOnly: function() { - var realm = new Realm({schema: [schemas.TestObject]}); - realm.write(function() { + let realm = new Realm({schema: [schemas.TestObject]}); + realm.write(() => { realm.create('TestObject', [1]) }); TestCase.assertEqual(realm.readOnly, false); realm.close(); realm = new Realm({readOnly: true, schema: [schemas.TestObject]}); - var objects = realm.objects('TestObject'); + const objects = realm.objects('TestObject'); TestCase.assertEqual(objects.length, 1); TestCase.assertEqual(objects[0].doubleCol, 1.0); TestCase.assertEqual(realm.readOnly, true); - TestCase.assertThrows(function() { - realm.write(function() {}); - }); + TestCase.assertThrowsContaining(() => realm.write(() => {}), + "Can't perform transactions on read-only Realms."); realm.close(); realm = new Realm({readOnly: true}); @@ -217,12 +195,12 @@ module.exports = { }, testDefaultPath: function() { - var defaultPath = Realm.defaultPath; - var defaultRealm = new Realm({schema: []}); + const defaultPath = Realm.defaultPath; + let defaultRealm = new Realm({schema: []}); TestCase.assertEqual(defaultRealm.path, Realm.defaultPath); try { - var newPath = Realm.defaultPath.substring(0, defaultPath.lastIndexOf(pathSeparator) + 1) + 'default2.realm'; + const newPath = `${Realm.defaultPath.substring(0, defaultPath.lastIndexOf(pathSeparator) + 1)}default2.realm`; Realm.defaultPath = newPath; defaultRealm = new Realm({schema: []}); TestCase.assertEqual(defaultRealm.path, newPath, "should use updated default realm path"); @@ -234,8 +212,8 @@ module.exports = { testRealmSchemaVersion: function() { TestCase.assertEqual(Realm.schemaVersion(Realm.defaultPath), -1); - - var realm = new Realm({schema: []}); + + let realm = new Realm({schema: []}); TestCase.assertEqual(realm.schemaVersion, 0); TestCase.assertEqual(Realm.schemaVersion(Realm.defaultPath), 0); @@ -245,69 +223,64 @@ module.exports = { }, testRealmWrite: function() { - var realm = new Realm({schema: [schemas.IntPrimary, schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]}); - + const realm = new Realm({schema: [schemas.IntPrimary, schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]}); + // exceptions should be propogated - TestCase.assertThrows(function() { - realm.write(function() { - realm.invalid(); - }); - }); + TestCase.assertThrowsContaining(() => realm.write(() => { throw new Error('Inner exception message'); }), + 'Inner exception message'); // writes should be possible after caught exception - realm.write(function() { + realm.write(() => { realm.create('TestObject', {doubleCol: 1}); }); TestCase.assertEqual(1, realm.objects('TestObject').length); - realm.write(function() { + realm.write(() => { // nested transactions not supported - TestCase.assertThrows(function() { - realm.write(function() {}); - }); + TestCase.assertThrowsContaining(() => realm.write(() => {}), + 'The Realm is already in a write transaction'); }); }, testRealmCreate: function() { - var realm = new Realm({schema: [schemas.TestObject]}); + const realm = new Realm({schema: [schemas.TestObject]}); - TestCase.assertThrows(function() { - realm.create('TestObject', {doubleCol: 1}); - }, 'can only create inside a write transaction'); + TestCase.assertThrowsContaining(() => realm.create('TestObject', {doubleCol: 1}), + "Cannot modify managed objects outside of a write transaction."); - realm.write(function() { + realm.write(() => { realm.create('TestObject', {doubleCol: 1}); realm.create('TestObject', {doubleCol: 2}); }); - var objects = realm.objects('TestObject'); + const objects = realm.objects('TestObject'); TestCase.assertEqual(objects.length, 2, 'wrong object count'); TestCase.assertEqual(objects[0].doubleCol, 1, 'wrong object property value'); TestCase.assertEqual(objects[1].doubleCol, 2, 'wrong object property value'); }, testRealmCreatePrimaryKey: function() { - var realm = new Realm({schema: [schemas.IntPrimary]}); + const realm = new Realm({schema: [schemas.IntPrimary]}); - realm.write(function() { - var obj0 = realm.create('IntPrimaryObject', { + realm.write(() => { + const obj0 = realm.create('IntPrimaryObject', { primaryCol: 0, valueCol: 'val0', }); - TestCase.assertThrows(function() { + TestCase.assertThrowsContaining(() => { realm.create('IntPrimaryObject', { primaryCol: 0, valueCol: 'val0', }); - }, 'cannot create object with conflicting primary key'); + }, "Attempting to create an object of type 'IntPrimaryObject' with an existing primary key value '0'."); realm.create('IntPrimaryObject', { primaryCol: 1, valueCol: 'val1', }, true); - var objects = realm.objects('IntPrimaryObject'); + const objects = realm.objects('IntPrimaryObject'); TestCase.assertEqual(objects.length, 2); realm.create('IntPrimaryObject', { @@ -324,23 +297,23 @@ module.exports = { }, testRealmCreateOptionals: function() { - var realm = new Realm({schema: [schemas.NullableBasicTypes, schemas.LinkTypes, schemas.TestObject]}); - var basic, links; - realm.write(function() { + const realm = new Realm({schema: [schemas.NullableBasicTypes, schemas.LinkTypes, schemas.TestObject]}); + let basic, links; + realm.write(() => { basic = realm.create('NullableBasicTypesObject', {}); links = realm.create('LinkTypesObject', {}); }); - for (var name in schemas.NullableBasicTypes.properties) { - TestCase.assertEqual(basic[name], null); + for (const name in schemas.NullableBasicTypes.properties) { + TestCase.assertEqual(basic[name], null); } TestCase.assertEqual(links.objectCol, null); TestCase.assertEqual(links.arrayCol.length, 0); }, testRealmCreateUpsert: function() { - var realm = new Realm({schema: [schemas.IntPrimary, schemas.StringPrimary, schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]}); - realm.write(function() { - var values = { + const realm = new Realm({schema: [schemas.IntPrimary, schemas.StringPrimary, schemas.AllTypes, schemas.TestObject, schemas.LinkToAllTypes]}); + realm.write(() => { + const values = { primaryCol: '0', boolCol: true, intCol: 1, @@ -353,13 +326,12 @@ module.exports = { arrayCol: [], }; - var obj0 = realm.create('AllTypesObject', values); + const obj0 = realm.create('AllTypesObject', values); - TestCase.assertThrows(function() { - realm.create('AllTypesObject', values); - }, 'cannot create object with conflicting primary key'); + TestCase.assertThrowsContaining(() => realm.create('AllTypesObject', values), + "Attempting to create an object of type 'AllTypesObject' with an existing primary key value '0'."); - var obj1 = realm.create('AllTypesObject', { + const obj1 = realm.create('AllTypesObject', { primaryCol: '1', boolCol: false, intCol: 2, @@ -372,7 +344,7 @@ module.exports = { arrayCol: [{doubleCol: 2}], }, true); - var objects = realm.objects('AllTypesObject'); + const objects = realm.objects('AllTypesObject'); TestCase.assertEqual(objects.length, 2); realm.create('AllTypesObject', { @@ -427,7 +399,7 @@ module.exports = { TestCase.assertEqual(obj1.objectCol, null); // test with string primaries - var obj =realm.create('StringPrimaryObject', { + const obj =realm.create('StringPrimaryObject', { primaryCol: '0', valueCol: 0 }); @@ -442,12 +414,12 @@ module.exports = { }, testRealmWithIndexedProperties: function() { - var realm = new Realm({schema: [schemas.IndexedTypes]}); - realm.write(function() { + const realm = new Realm({schema: [schemas.IndexedTypes]}); + realm.write(() => { realm.create('IndexedTypesObject', {boolCol: true, intCol: 1, stringCol: '1', dateCol: new Date(1)}); }); - var NotIndexed = { + const NotIndexed = { name: 'NotIndexedObject', properties: { floatCol: {type: 'float', indexed: false} @@ -456,38 +428,38 @@ module.exports = { new Realm({schema: [NotIndexed], path: '1.realm'}); - var IndexedSchema = { + const IndexedSchema = { name: 'IndexedSchema', }; - TestCase.assertThrows(function() { + TestCase.assertThrowsContaining(() => { IndexedSchema.properties = { floatCol: {type: 'float', indexed: true} }; new Realm({schema: [IndexedSchema], path: '2.realm'}); - }); + }, "Property 'IndexedSchema.floatCol' of type 'float' cannot be indexed."); - TestCase.assertThrows(function() { + TestCase.assertThrowsContaining(() => { IndexedSchema.properties = { doubleCol: {type: 'double', indexed: true} } new Realm({schema: [IndexedSchema], path: '3.realm'}); - }); + }, "Property 'IndexedSchema.doubleCol' of type 'double' cannot be indexed."); - TestCase.assertThrows(function() { + TestCase.assertThrowsContaining(() => { IndexedSchema.properties = { dataCol: {type: 'data', indexed: true} } new Realm({schema: [IndexedSchema], path: '4.realm'}); - }); + }, "Property 'IndexedSchema.dataCol' of type 'data' cannot be indexed."); // primary key - IndexedSchema.properties = { boolCol: {type: 'bool', indexed: true} }; - IndexedSchema.primaryKey = 'boolCol'; + IndexedSchema.properties = { intCol: {type: 'int', indexed: true} }; + IndexedSchema.primaryKey = 'intCol'; // Test this doesn't throw new Realm({schema: [IndexedSchema], path: '5.realm'}); }, testRealmCreateWithDefaults: function() { - var realm = new Realm({schema: [schemas.DefaultValues, schemas.TestObject]}); + let realm = new Realm({schema: [schemas.DefaultValues, schemas.TestObject]}); - var createAndTestObject = function() { - var obj = realm.create('DefaultValuesObject', {}); - var properties = schemas.DefaultValues.properties; + const createAndTestObject = () => { + const obj = realm.create('DefaultValuesObject', {}); + const properties = schemas.DefaultValues.properties; TestCase.assertEqual(obj.boolCol, properties.boolCol.default); TestCase.assertEqual(obj.intCol, properties.intCol.default); @@ -510,17 +482,17 @@ module.exports = { }, testRealmCreateWithChangingDefaults: function() { - var objectSchema = { + const objectSchema = { name: 'IntObject', properties: { intCol: {type: 'int', default: 1}, } }; - var realm = new Realm({schema: [objectSchema]}); + let realm = new Realm({schema: [objectSchema]}); - var createAndTestObject = function() { - var object = realm.create('IntObject', {}); + const createAndTestObject = () => { + const object = realm.create('IntObject', {}); TestCase.assertEqual(object.intCol, objectSchema.properties.intCol.default); }; @@ -533,7 +505,7 @@ module.exports = { }, testRealmCreateWithConstructor: function() { - var customCreated = 0; + let customCreated = 0; function CustomObject() { customCreated++; @@ -548,9 +520,8 @@ module.exports = { function InvalidObject() { return {}; } - TestCase.assertThrows(function() { - new Realm({schema: [InvalidObject]}); - }); + TestCase.assertThrowsContaining(() => new Realm({schema: [InvalidObject]}), + "Realm object constructor must have a 'schema' property."); InvalidObject.schema = { name: 'InvalidObject', @@ -559,10 +530,10 @@ module.exports = { } }; - var realm = new Realm({schema: [CustomObject, InvalidObject]}); + let realm = new Realm({schema: [CustomObject, InvalidObject]}); - realm.write(function() { - var object = realm.create('CustomObject', {intCol: 1}); + realm.write(() => { + let object = realm.create('CustomObject', {intCol: 1}); TestCase.assertTrue(object instanceof CustomObject); TestCase.assertTrue(Object.getPrototypeOf(object) == CustomObject.prototype); TestCase.assertEqual(customCreated, 1); @@ -574,21 +545,21 @@ module.exports = { TestCase.assertEqual(customCreated, 2); }); - TestCase.assertThrows(function() { - realm.write(function() { + TestCase.assertThrowsContaining(() => { + realm.write(() => { realm.create('InvalidObject', {intCol: 1}); }); - }); + }, 'Realm object constructor must not return another value'); // Only the original constructor should be valid. function InvalidCustomObject() {} InvalidCustomObject.schema = CustomObject.schema; - TestCase.assertThrows(function() { - realm.write(function() { + TestCase.assertThrowsContaining(() => { + realm.write(() => { realm.create(InvalidCustomObject, {intCol: 1}); }); - }); + }, 'Constructor was not registered in the schema for this Realm'); // The constructor should still work when creating another Realm instance. realm = new Realm(); @@ -605,9 +576,9 @@ module.exports = { } }; - var realm = new Realm({schema: [CustomObject]}); - realm.write(function() { - var object = realm.create('CustomObject', {intCol: 1}); + let realm = new Realm({schema: [CustomObject]}); + realm.write(() => { + const object = realm.create('CustomObject', {intCol: 1}); TestCase.assertTrue(object instanceof CustomObject); }); @@ -615,30 +586,27 @@ module.exports = { NewCustomObject.schema = CustomObject.schema; realm = new Realm({schema: [NewCustomObject]}); - realm.write(function() { - var object = realm.create('CustomObject', {intCol: 1}); + realm.write(() => { + const object = realm.create('CustomObject', {intCol: 1}); TestCase.assertTrue(object instanceof NewCustomObject); }); }, testRealmDelete: function() { - var realm = new Realm({schema: [schemas.TestObject]}); + const realm = new Realm({schema: [schemas.TestObject]}); - realm.write(function() { - for (var i = 0; i < 10; i++) { + realm.write(() => { + for (let i = 0; i < 10; i++) { realm.create('TestObject', {doubleCol: i}); } }); - var objects = realm.objects('TestObject'); - TestCase.assertThrows(function() { - realm.delete(objects[0]); - }, 'can only delete in a write transaction'); + const objects = realm.objects('TestObject'); + TestCase.assertThrowsContaining(() => realm.delete(objects[0]), + "Can only delete objects within a transaction."); - realm.write(function() { - TestCase.assertThrows(function() { - realm.delete(); - }); + realm.write(() => { + TestCase.assertThrowsContaining(() => realm.delete(), 'Invalid arguments'); realm.delete(objects[0]); TestCase.assertEqual(objects.length, 9, 'wrong object count'); @@ -650,24 +618,23 @@ module.exports = { TestCase.assertEqual(objects[0].doubleCol, 7, "wrong property value"); TestCase.assertEqual(objects[1].doubleCol, 8, "wrong property value"); - var threeObjects = realm.objects('TestObject').filtered("doubleCol < 5"); + const threeObjects = realm.objects('TestObject').filtered("doubleCol < 5"); TestCase.assertEqual(threeObjects.length, 3, "wrong results count"); realm.delete(threeObjects); TestCase.assertEqual(objects.length, 4, 'wrong object count'); TestCase.assertEqual(threeObjects.length, 0, 'threeObject should have been deleted'); - var o = objects[0]; + const o = objects[0]; realm.delete(o); - TestCase.assertThrows(function() { - realm.delete(o); - }); + TestCase.assertThrowsContaining(() => realm.delete(o), + 'Object is invalid. Either it has been previously deleted or the Realm it belongs to has been closed.'); }); }, testDeleteAll: function() { - var realm = new Realm({schema: [schemas.TestObject, schemas.IntPrimary]}); + const realm = new Realm({schema: [schemas.TestObject, schemas.IntPrimary]}); - realm.write(function() { + realm.write(() => { realm.create('TestObject', {doubleCol: 1}); realm.create('TestObject', {doubleCol: 2}); realm.create('IntPrimaryObject', {primaryCol: 2, valueCol: 'value'}); @@ -676,11 +643,10 @@ module.exports = { TestCase.assertEqual(realm.objects('TestObject').length, 2); TestCase.assertEqual(realm.objects('IntPrimaryObject').length, 1); - TestCase.assertThrows(function() { - realm.deleteAll(); - }, 'can only deleteAll in a write transaction'); + TestCase.assertThrowsContaining(() => realm.deleteAll(), + "Can only delete objects within a transaction."); - realm.write(function() { + realm.write(() => { realm.deleteAll(); }); @@ -689,9 +655,9 @@ module.exports = { }, testRealmObjects: function() { - var realm = new Realm({schema: [schemas.PersonObject, schemas.DefaultValues, schemas.TestObject]}); + const realm = new Realm({schema: [schemas.PersonObject, schemas.DefaultValues, schemas.TestObject]}); - realm.write(function() { + realm.write(() => { realm.create('PersonObject', {name: 'Ari', age: 10}); realm.create('PersonObject', {name: 'Tim', age: 11}); realm.create('PersonObject', {name: 'Bjarne', age: 12}); @@ -699,77 +665,44 @@ module.exports = { }); // Should be able to pass constructor for getting objects. - var objects = realm.objects(schemas.PersonObject); + const objects = realm.objects(schemas.PersonObject); TestCase.assertTrue(objects[0] instanceof schemas.PersonObject); function InvalidPerson() {} InvalidPerson.schema = schemas.PersonObject.schema; - TestCase.assertThrows(function() { - realm.objects(); - }); - TestCase.assertThrows(function() { - realm.objects([]); - }); - TestCase.assertThrows(function() { - realm.objects('InvalidClass'); - }); - TestCase.assertThrows(function() { - realm.objects('PersonObject', 'truepredicate'); - }); - TestCase.assertThrows(function() { - realm.objects(InvalidPerson); - }); + TestCase.assertThrowsContaining(() => realm.objects(), 'Invalid arguments'); + TestCase.assertThrowsContaining(() => realm.objects([]), "objectType must be of type 'string', got ()"); + TestCase.assertThrowsContaining(() => realm.objects('InvalidClass'), "Object type 'InvalidClass' not found in schema."); + TestCase.assertThrowsContaining(() => realm.objects('PersonObject', 'truepredicate'), 'Invalid arguments'); + TestCase.assertThrowsContaining(() => realm.objects(InvalidPerson), + 'Constructor was not registered in the schema for this Realm'); - var person = realm.objects('PersonObject')[0]; - var listenerCallback = () => {}; + const person = realm.objects('PersonObject')[0]; + const listenerCallback = () => {}; realm.addListener('change', listenerCallback); // The tests below assert that everthing throws when // operating on a closed realm realm.close(); - TestCase.assertThrows(function() { - console.log("Name: ", person.name); - }); + TestCase.assertThrowsContaining(() => console.log("Name: ", person.name), + 'Accessing object of type PersonObject which has been invalidated or deleted'); - TestCase.assertThrows(function() { - realm.objects('PersonObject'); - }); - - TestCase.assertThrows(function() { - realm.addListener('change', () => {}); - }); - - TestCase.assertThrows(function() { - realm.create('PersonObject', {name: 'Ari', age: 10}); - }); - - TestCase.assertThrows(function() { - realm.delete(person); - }); - - TestCase.assertThrows(function() { - realm.deleteAll(); - }); - - TestCase.assertThrows(function() { - realm.write(() => {}); - }); - - TestCase.assertThrows(function() { - realm.removeListener('change', listenerCallback); - }); - - TestCase.assertThrows(function() { - realm.removeAllListeners(); - }); + TestCase.assertThrowsContaining(() => realm.objects('PersonObject'), 'Cannot access realm that has been closed'); + TestCase.assertThrowsContaining(() => realm.addListener('change', () => {}), 'Cannot access realm that has been closed'); + TestCase.assertThrowsContaining(() => realm.create('PersonObject', {name: 'Ari', age: 10}), 'Cannot access realm that has been closed'); + TestCase.assertThrowsContaining(() => realm.delete(person), 'Cannot access realm that has been closed'); + TestCase.assertThrowsContaining(() => realm.deleteAll(), 'Cannot access realm that has been closed'); + TestCase.assertThrowsContaining(() => realm.write(() => {}), 'Cannot access realm that has been closed'); + TestCase.assertThrowsContaining(() => realm.removeListener('change', listenerCallback), 'Cannot access realm that has been closed'); + TestCase.assertThrowsContaining(() => realm.removeAllListeners(), 'Cannot access realm that has been closed'); }, testRealmObjectForPrimaryKey: function() { - var realm = new Realm({schema: [schemas.IntPrimary, schemas.StringPrimary, schemas.TestObject]}); + const realm = new Realm({schema: [schemas.IntPrimary, schemas.StringPrimary, schemas.TestObject]}); - realm.write(function() { + realm.write(() => { realm.create('IntPrimaryObject', {primaryCol: 0, valueCol: 'val0'}); realm.create('IntPrimaryObject', {primaryCol: 1, valueCol: 'val1'}); @@ -789,36 +722,30 @@ module.exports = { TestCase.assertEqual(realm.objectForPrimaryKey('StringPrimaryObject', 'val0').valueCol, 0); TestCase.assertEqual(realm.objectForPrimaryKey('StringPrimaryObject', 'val1').valueCol, 1); - TestCase.assertThrows(function() { - realm.objectForPrimaryKey('TestObject', 0); - }); - TestCase.assertThrows(function() { - realm.objectForPrimaryKey(); - }); - TestCase.assertThrows(function() { - realm.objectForPrimaryKey('IntPrimary'); - }); - TestCase.assertThrows(function() { - realm.objectForPrimaryKey('InvalidClass', 0); - }); + TestCase.assertThrowsContaining(() => realm.objectForPrimaryKey('TestObject', 0), + "'TestObject' does not have a primary key defined"); + TestCase.assertThrowsContaining(() => realm.objectForPrimaryKey(), 'Invalid arguments'); + TestCase.assertThrowsContaining(() => realm.objectForPrimaryKey('IntPrimary'), "Invalid arguments"); + TestCase.assertThrowsContaining(() => realm.objectForPrimaryKey('InvalidClass', 0), + "Object type 'InvalidClass' not found in schema."); }, testNotifications: function() { - var realm = new Realm({schema: []}); - var notificationCount = 0; - var notificationName; + const realm = new Realm({schema: []}); + let notificationCount = 0; + let notificationName; - realm.addListener('change', function(realm, name) { + realm.addListener('change', (realm, name) => { notificationCount++; notificationName = name; }); TestCase.assertEqual(notificationCount, 0); - realm.write(function() {}); + realm.write(() => {}); TestCase.assertEqual(notificationCount, 1); TestCase.assertEqual(notificationName, 'change'); - var secondNotificationCount = 0; + let secondNotificationCount = 0; function secondNotification() { secondNotificationCount++; } @@ -827,39 +754,37 @@ module.exports = { realm.addListener('change', secondNotification); realm.addListener('change', secondNotification); - realm.write(function() {}); + realm.write(() => {}); TestCase.assertEqual(notificationCount, 2); TestCase.assertEqual(secondNotificationCount, 1); realm.removeListener('change', secondNotification); - realm.write(function() {}); + realm.write(() => {}); TestCase.assertEqual(notificationCount, 3); TestCase.assertEqual(secondNotificationCount, 1); realm.removeAllListeners(); - realm.write(function() {}); + realm.write(() => {}); TestCase.assertEqual(notificationCount, 3); TestCase.assertEqual(secondNotificationCount, 1); - TestCase.assertThrows(function() { - realm.addListener('invalid', function() {}); + TestCase.assertThrowsContaining(() => realm.addListener('invalid', () => {}), + "Only the 'change' notification name is supported."); + + realm.addListener('change', () => { + throw new Error('expected error message'); }); - realm.addListener('change', function() { - throw new Error('error'); - }); - - TestCase.assertThrows(function() { - realm.write(function() {}); - }); + TestCase.assertThrowsContaining(() => realm.write(() => {}), + 'expected error message'); }, testSchema: function() { - var originalSchema = [schemas.TestObject, schemas.BasicTypes, schemas.NullableBasicTypes, schemas.IndexedTypes, schemas.IntPrimary, + const originalSchema = [schemas.TestObject, schemas.BasicTypes, schemas.NullableBasicTypes, schemas.IndexedTypes, schemas.IntPrimary, schemas.PersonObject, schemas.LinkTypes, schemas.LinkingObjectsObject]; - - var schemaMap = {}; - originalSchema.forEach(function(objectSchema) { + + const schemaMap = {}; + originalSchema.forEach(objectSchema => { if (objectSchema.schema) { // for PersonObject schemaMap[objectSchema.schema.name] = objectSchema; } else { @@ -867,9 +792,9 @@ module.exports = { } }); - var realm = new Realm({schema: originalSchema}); + const realm = new Realm({schema: originalSchema}); - var schema = realm.schema; + const schema = realm.schema; TestCase.assertEqual(schema.length, originalSchema.length); function isString(val) { @@ -877,21 +802,21 @@ module.exports = { } function verifyObjectSchema(returned) { - var original = schemaMap[returned.name]; + let original = schemaMap[returned.name]; if (original.schema) { original = original.schema; } TestCase.assertEqual(returned.primaryKey, original.primaryKey); - for (var propName in returned.properties) { - var prop1 = returned.properties[propName]; - var prop2 = original.properties[propName]; + for (const propName in returned.properties) { + const prop1 = returned.properties[propName]; + const prop2 = original.properties[propName]; if (prop1.type == 'object') { - TestCase.assertEqual(prop1.objectType, isString(prop2) ? prop2 : prop2.objectType); + TestCase.assertEqual(prop1.objectType, isString(prop2) ? prop2 : prop2.objectType); TestCase.assertEqual(prop1.optional, true); } else if (prop1.type == 'list') { - TestCase.assertEqual(prop1.objectType, prop2.objectType); + TestCase.assertEqual(prop1.objectType, prop2.objectType); TestCase.assertEqual(prop1.optional, undefined); } else if (prop1.type == 'linking objects') { @@ -900,7 +825,7 @@ module.exports = { TestCase.assertEqual(prop1.optional, undefined); } else { - TestCase.assertEqual(prop1.type, isString(prop2) ? prop2 : prop2.type); + TestCase.assertEqual(prop1.type, isString(prop2) ? prop2 : prop2.type); TestCase.assertEqual(prop1.optional, prop2.optional || undefined); } @@ -908,7 +833,7 @@ module.exports = { } } - for (var i = 0; i < originalSchema.length; i++) { + for (let i = 0; i < originalSchema.length; i++) { verifyObjectSchema(schema[i]); } }, @@ -916,12 +841,12 @@ module.exports = { testCopyBundledRealmFiles: function() { Realm.copyBundledRealmFiles(); - var realm = new Realm({path: 'dates-v5.realm', schema: [schemas.DateObject]}); + let realm = new Realm({path: 'dates-v5.realm', schema: [schemas.DateObject]}); TestCase.assertEqual(realm.objects('Date').length, 2); TestCase.assertEqual(realm.objects('Date')[0].currentDate.getTime(), 1462500087955); - var newDate = new Date(1); - realm.write(function() { + const newDate = new Date(1); + realm.write(() => { realm.objects('Date')[0].currentDate = newDate; }); realm.close(); @@ -933,33 +858,33 @@ module.exports = { }, testErrorMessageFromInvalidWrite: function() { - var realm = new Realm({schema: [schemas.PersonObject]}); - - TestCase.assertThrowsException(function() { - realm.write(function () { - var p1 = realm.create('PersonObject', { name: 'Ari', age: 10 }); + const realm = new Realm({schema: [schemas.PersonObject]}); + + TestCase.assertThrowsException(() => { + realm.write(() => { + const p1 = realm.create('PersonObject', { name: 'Ari', age: 10 }); p1.age = "Ten"; }); - }, new Error("PersonObject.age must be of type: number")); + }, new Error("PersonObject.age must be of type 'number', got (Ten)")); }, testErrorMessageFromInvalidCreate: function() { - var realm = new Realm({schema: [schemas.PersonObject]}); + const realm = new Realm({schema: [schemas.PersonObject]}); - TestCase.assertThrowsException(function() { - realm.write(function () { - var p1 = realm.create('PersonObject', { name: 'Ari', age: 'Ten' }); + TestCase.assertThrowsException(() => { + realm.write(() => { + const p1 = realm.create('PersonObject', { name: 'Ari', age: 'Ten' }); }); - }, new Error("PersonObject.age must be of type: number")); + }, new Error("PersonObject.age must be of type 'number', got (Ten)")); }, testValidTypesForListProperties: function() { - var realm = new Realm({schema: [schemas.PersonObject]}); - realm.write(function () { - var p1 = realm.create('PersonObject', { name: 'Ari', age: 10 }); - var p2 = realm.create('PersonObject', { name: 'Harold', age: 55, children: realm.objects('PersonObject').filtered('age < 15') }); + const realm = new Realm({schema: [schemas.PersonObject]}); + realm.write(() => { + const p1 = realm.create('PersonObject', { name: 'Ari', age: 10 }); + const p2 = realm.create('PersonObject', { name: 'Harold', age: 55, children: realm.objects('PersonObject').filtered('age < 15') }); TestCase.assertEqual(p2.children.length, 1); - var p3 = realm.create('PersonObject', { name: 'Wendy', age: 52, children: p2.children }); + const p3 = realm.create('PersonObject', { name: 'Wendy', age: 52, children: p2.children }); TestCase.assertEqual(p3.children.length, 1); }); }, @@ -1007,13 +932,13 @@ module.exports = { }, testCompact: function() { - var wasCalled = false; + let wasCalled = false; const count = 1000; // create compactable Realm const realm1 = new Realm({schema: [schemas.StringOnly]}); realm1.write(() => { realm1.create('StringOnlyObject', { stringCol: 'A' }); - for (var i = 0; i < count; i++) { + for (let i = 0; i < count; i++) { realm1.create('StringOnlyObject', { stringCol: 'ABCDEFG' }); } realm1.create('StringOnlyObject', { stringCol: 'B' }); @@ -1021,7 +946,7 @@ module.exports = { realm1.close(); // open Realm and see if it is compacted - var shouldCompact = function(totalBytes, usedBytes) { + const shouldCompact = (totalBytes, usedBytes) => { wasCalled = true; const fiveHundredKB = 500*1024; return (totalBytes > fiveHundredKB) && (usedBytes / totalBytes) < 0.2; @@ -1049,9 +974,9 @@ module.exports = { testManualCompactInWrite: function() { const realm = new Realm({schema: [schemas.StringOnly]}); realm.write(() => { - TestCase.assertThrows(() => { + TestCase.assertThrowsContaining(() => { realm.compact(); - }); + }, 'Cannot compact a Realm within a transaction.'); }); TestCase.assertTrue(realm.empty); }, @@ -1059,14 +984,14 @@ module.exports = { testManualCompactMultipleInstances: function() { const realm1 = new Realm({schema: [schemas.StringOnly]}); const realm2 = new Realm({schema: [schemas.StringOnly]}); - TestCase.assertThrows(realm1.compact()); + TestCase.assertTrue(realm1.compact()); }, testRealmDeleteFileDefaultConfigPath: function() { const config = {schema: [schemas.TestObject]}; const realm = new Realm(config); - realm.write(function() { + realm.write(() => { realm.create('TestObject', {doubleCol: 1}); }); @@ -1084,7 +1009,7 @@ module.exports = { const config = {schema: [schemas.TestObject], path: 'test-realm-delete-file.realm'}; const realm = new Realm(config); - realm.write(function() { + realm.write(() => { realm.create('TestObject', {doubleCol: 1}); }); From e697ae3d5c7617cd7b3e496febcb033128c9c413 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 15 Sep 2017 13:12:44 -0700 Subject: [PATCH 06/23] Silence an unused variable warning when building with sync disabled --- src/js_realm.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 929e47b6..a313d9ae 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -754,6 +754,8 @@ void RealmClass::wait_for_download_completion(ContextType ctx, FunctionType, return; } } +#else + static_cast(config_object); #endif ValueType callback_arguments[1]; From 742ff99003ee95c2c94a1471bee79d4038a9812e Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 15 Sep 2017 13:16:42 -0700 Subject: [PATCH 07/23] Use the same error messages in the RPC code as the regular code --- lib/browser/index.js | 6 +++--- lib/browser/objects.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/browser/index.js b/lib/browser/index.js index 10bc7512..8ef9fc12 100644 --- a/lib/browser/index.js +++ b/lib/browser/index.js @@ -83,14 +83,14 @@ export default class Realm { if (typeof item == 'function') { let schema = item.schema; if (!schema || typeof schema != 'object') { - throw new Error("Realm object constructor must have 'schema' property"); + throw new Error("Realm object constructor must have a 'schema' property."); } let {name, properties} = schema; if (!name || typeof name != 'string') { - throw new Error("Realm object schema must have 'name' property"); + throw new Error(`Failed to read ObjectSchema: name must be of type 'string', got (${typeof name})`); } else if (!properties || typeof properties != 'object') { - throw new Error("Realm object schema must have 'properties' property"); + throw new Error(`Failed to read ObjectSchema: properties must be of type 'object', got (${typeof properties})`); } schemas.splice(i, 1, schema); diff --git a/lib/browser/objects.js b/lib/browser/objects.js index f0f5caf7..9c9ad87e 100644 --- a/lib/browser/objects.js +++ b/lib/browser/objects.js @@ -85,5 +85,5 @@ export function typeForConstructor(realmId, constructor) { } } - return null; + throw new Error("Constructor was not registered in the schema for this Realm") } From 0c9d7ca54e33301b0ef17a0133d74cb3ab450df0 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 15 Sep 2017 13:36:04 -0700 Subject: [PATCH 08/23] Improve error reporting for incorrect argument counts for Realm methods Report the expected and actual arg counts for too many arguments, and behave more like normal JS when too few are supplied (i.e. complain about the next argument being undefined rather than just saying 'Invalid arguments'). --- src/js_class.hpp | 23 ++++++ src/js_realm.hpp | 176 ++++++++++++++++++++-------------------- src/jsc/jsc_class.hpp | 15 ++++ src/node/node_class.hpp | 17 ++++ tests/js/realm-tests.js | 14 ++-- 5 files changed, 151 insertions(+), 94 deletions(-) diff --git a/src/js_class.hpp b/src/js_class.hpp index 70b28e2e..052ec8f5 100644 --- a/src/js_class.hpp +++ b/src/js_class.hpp @@ -33,6 +33,29 @@ using ConstructorType = void(typename T::Context, typename T::Object, size_t, co template using MethodType = void(typename T::Context, typename T::Function, typename T::Object, size_t, const typename T::Value[], ReturnValue &); +template +struct Arguments { + const typename T::Context ctx; + const size_t count; + const typename T::Value* const value; + + typename T::Value operator[](size_t index) const noexcept { + if (index >= count) { + return Value::from_undefined(ctx); + } + return value[index]; + } + + void validate_maximum(size_t max) const { + if (max < count) { + throw std::invalid_argument(util::format("Invalid arguments: at most %1 expected, but %2 supplied.", max, count)); + } + } +}; + +template +using ArgumentsMethodType = void(typename T::Context, typename T::Function, typename T::Object, Arguments, ReturnValue &); + template struct PropertyType { using GetterType = void(typename T::Context, typename T::Object, ReturnValue &); diff --git a/src/js_realm.hpp b/src/js_realm.hpp index a313d9ae..ee3e9daf 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -123,9 +123,7 @@ class RealmDelegate : public BindingContext { } ObjectType realm_object = create_object>(m_context, new SharedRealm(realm)); - ValueType arguments[2]; - arguments[0] = realm_object; - arguments[1] = Value::from_string(m_context, notification_name); + ValueType arguments[] = {realm_object, Value::from_string(m_context, notification_name)}; std::list> notifications_copy(m_notifications); for (auto &callback : notifications_copy) { @@ -148,6 +146,7 @@ class RealmClass : public ClassDefinition> { using FunctionType = typename T::Function; using ObjectType = typename T::Object; using ValueType = typename T::Value; + using Arguments = js::Arguments; using String = js::String; using Object = js::Object; using Value = js::Value; @@ -165,22 +164,22 @@ public: static FunctionType create_constructor(ContextType); // methods - static void objects(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void object_for_primary_key(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void create(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void delete_one(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void delete_all(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void write(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void begin_transaction(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue&); - static void commit_transaction(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue&); - static void cancel_transaction(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue&); - static void add_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void wait_for_download_completion(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void remove_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void remove_all_listeners(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void close(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void compact(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void delete_model(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void objects(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &); + static void object_for_primary_key(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &); + static void create(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &); + static void delete_one(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &); + static void delete_all(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &); + static void write(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &); + static void begin_transaction(ContextType, FunctionType, ObjectType, Arguments, ReturnValue&); + static void commit_transaction(ContextType, FunctionType, ObjectType, Arguments, ReturnValue&); + static void cancel_transaction(ContextType, FunctionType, ObjectType, Arguments, ReturnValue&); + static void add_listener(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &); + static void wait_for_download_completion(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &); + static void remove_listener(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &); + static void remove_all_listeners(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &); + static void close(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &); + static void compact(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &); + static void delete_model(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &); // properties static void get_empty(ContextType, ObjectType, ReturnValue &); @@ -198,10 +197,10 @@ public: static void constructor(ContextType, ObjectType, size_t, const ValueType[]); static SharedRealm create_shared_realm(ContextType, realm::Realm::Config, bool, ObjectDefaultsMap &&, ConstructorMap &&); - static void schema_version(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void clear_test_state(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void copy_bundled_realm_files(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - static void delete_file(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void schema_version(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &); + static void clear_test_state(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &); + static void copy_bundled_realm_files(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &); + static void delete_file(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &); // static properties static void get_default_path(ContextType, ObjectType, ReturnValue &); @@ -504,13 +503,13 @@ SharedRealm RealmClass::create_shared_realm(ContextType ctx, realm::Realm::Co } template -void RealmClass::schema_version(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 1, 2); +void RealmClass::schema_version(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(2); realm::Realm::Config config; - config.path = normalize_realm_path(Value::validated_to_string(ctx, arguments[0])); - if (argc == 2) { - auto encryption_key = Value::validated_to_binary(ctx, arguments[1], "encryptionKey"); + config.path = normalize_realm_path(Value::validated_to_string(ctx, args[0])); + if (args.count == 2) { + auto encryption_key = Value::validated_to_binary(ctx, args[1], "encryptionKey"); config.encryption_key.assign(encryption_key.data(), encryption_key.data() + encryption_key.size()); } @@ -525,23 +524,22 @@ void RealmClass::schema_version(ContextType ctx, FunctionType, ObjectType thi template -void RealmClass::clear_test_state(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 0); - +void RealmClass::clear_test_state(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(0); js::clear_test_state(); } template -void RealmClass::copy_bundled_realm_files(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 0); +void RealmClass::copy_bundled_realm_files(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(0); realm::copy_bundled_realm_files(); } template -void RealmClass::delete_file(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 1); +void RealmClass::delete_file(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); - ValueType value = arguments[0]; + ValueType value = args[0]; if (!Value::is_object(ctx, value)) { throw std::runtime_error("Invalid argument, expected a Realm configuration object"); } @@ -569,9 +567,9 @@ void RealmClass::delete_file(ContextType ctx, FunctionType, ObjectType this_o } template -void RealmClass::delete_model(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 1); - ValueType value = arguments[0]; +void RealmClass::delete_model(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); + ValueType value = args[0]; SharedRealm& realm = *get_internal>(this_object); @@ -643,14 +641,14 @@ void RealmClass::get_sync_session(ContextType ctx, ObjectType object, ReturnV #endif template -void RealmClass::wait_for_download_completion(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 2, 3); - auto config_object = Value::validated_to_object(ctx, arguments[0]); - auto callback_function = Value::validated_to_function(ctx, arguments[argc - 1]); +void RealmClass::wait_for_download_completion(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(3); + auto config_object = Value::validated_to_object(ctx, args[0]); + auto callback_function = Value::validated_to_function(ctx, args[1 + (args.count == 3)]); ValueType session_callback = Value::from_null(ctx); - if (argc == 3) { - session_callback = Value::validated_to_function(ctx, arguments[1]); + if (args.count == 3) { + session_callback = Value::validated_to_function(ctx, args[1]); } #if REALM_ENABLE_SYNC @@ -764,25 +762,25 @@ void RealmClass::wait_for_download_completion(ContextType ctx, FunctionType, } template -void RealmClass::objects(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 1); +void RealmClass::objects(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); SharedRealm realm = *get_internal>(this_object); std::string object_type; - validated_object_schema_for_value(ctx, realm, arguments[0], object_type); + validated_object_schema_for_value(ctx, realm, args[0], object_type); return_value.set(ResultsClass::create_instance(ctx, realm, object_type)); } template -void RealmClass::object_for_primary_key(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 2); +void RealmClass::object_for_primary_key(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(2); SharedRealm realm = *get_internal>(this_object); std::string object_type; - auto &object_schema = validated_object_schema_for_value(ctx, realm, arguments[0], object_type); + auto &object_schema = validated_object_schema_for_value(ctx, realm, args[0], object_type); NativeAccessor accessor(ctx, realm, object_schema); - auto realm_object = realm::Object::get_for_primary_key(accessor, realm, object_schema, arguments[1]); + auto realm_object = realm::Object::get_for_primary_key(accessor, realm, object_schema, args[1]); if (realm_object.is_valid()) { return_value.set(RealmObjectClass::create_instance(ctx, std::move(realm_object))); @@ -793,22 +791,22 @@ void RealmClass::object_for_primary_key(ContextType ctx, FunctionType, Object } template -void RealmClass::create(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 2, 3); +void RealmClass::create(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(3); SharedRealm realm = *get_internal>(this_object); realm->verify_open(); std::string object_type; - auto &object_schema = validated_object_schema_for_value(ctx, realm, arguments[0], object_type); + auto &object_schema = validated_object_schema_for_value(ctx, realm, args[0], object_type); - ObjectType object = Value::validated_to_object(ctx, arguments[1], "properties"); - if (Value::is_array(ctx, arguments[1])) { + ObjectType object = Value::validated_to_object(ctx, args[1], "properties"); + if (Value::is_array(ctx, args[1])) { object = Schema::dict_for_property_array(ctx, object_schema, object); } bool update = false; - if (argc == 3) { - update = Value::validated_to_boolean(ctx, arguments[2], "update"); + if (args.count == 3) { + update = Value::validated_to_boolean(ctx, args[2], "update"); } NativeAccessor accessor(ctx, realm, object_schema); @@ -817,8 +815,8 @@ void RealmClass::create(ContextType ctx, FunctionType, ObjectType this_object } template -void RealmClass::delete_one(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 1); +void RealmClass::delete_one(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); SharedRealm realm = *get_internal>(this_object); realm->verify_open(); @@ -826,7 +824,7 @@ void RealmClass::delete_one(ContextType ctx, FunctionType, ObjectType this_ob throw std::runtime_error("Can only delete objects within a transaction."); } - ObjectType arg = Value::validated_to_object(ctx, arguments[0]); + ObjectType arg = Value::validated_to_object(ctx, args[0], "object"); if (Object::template is_instance>(ctx, arg)) { auto object = get_internal>(arg); @@ -865,8 +863,8 @@ void RealmClass::delete_one(ContextType ctx, FunctionType, ObjectType this_ob } template -void RealmClass::delete_all(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 0); +void RealmClass::delete_all(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(0); SharedRealm realm = *get_internal>(this_object); realm->verify_open(); @@ -881,11 +879,11 @@ void RealmClass::delete_all(ContextType ctx, FunctionType, ObjectType this_ob } template -void RealmClass::write(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 1); +void RealmClass::write(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); SharedRealm realm = *get_internal>(this_object); - FunctionType callback = Value::validated_to_function(ctx, arguments[0]); + FunctionType callback = Value::validated_to_function(ctx, args[0]); realm->begin_transaction(); @@ -901,35 +899,35 @@ void RealmClass::write(ContextType ctx, FunctionType, ObjectType this_object, } template -void RealmClass::begin_transaction(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 0); +void RealmClass::begin_transaction(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(0); SharedRealm realm = *get_internal>(this_object); realm->begin_transaction(); } template -void RealmClass::commit_transaction(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 0); +void RealmClass::commit_transaction(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(0); SharedRealm realm = *get_internal>(this_object); realm->commit_transaction(); } template -void RealmClass::cancel_transaction(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 0); +void RealmClass::cancel_transaction(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(0); SharedRealm realm = *get_internal>(this_object); realm->cancel_transaction(); } template -void RealmClass::add_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 2); +void RealmClass::add_listener(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(2); - validated_notification_name(ctx, arguments[0]); - auto callback = Value::validated_to_function(ctx, arguments[1]); + validated_notification_name(ctx, args[0]); + auto callback = Value::validated_to_function(ctx, args[1]); SharedRealm realm = *get_internal>(this_object); realm->verify_open(); @@ -937,11 +935,11 @@ void RealmClass::add_listener(ContextType ctx, FunctionType, ObjectType this_ } template -void RealmClass::remove_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 2); +void RealmClass::remove_listener(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(2); - validated_notification_name(ctx, arguments[0]); - auto callback = Value::validated_to_function(ctx, arguments[1]); + validated_notification_name(ctx, args[0]); + auto callback = Value::validated_to_function(ctx, args[1]); SharedRealm realm = *get_internal>(this_object); realm->verify_open(); @@ -949,10 +947,10 @@ void RealmClass::remove_listener(ContextType ctx, FunctionType, ObjectType th } template -void RealmClass::remove_all_listeners(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 0, 1); - if (argc) { - validated_notification_name(ctx, arguments[0]); +void RealmClass::remove_all_listeners(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(1); + if (args.count) { + validated_notification_name(ctx, args[0]); } SharedRealm realm = *get_internal>(this_object); @@ -961,16 +959,16 @@ void RealmClass::remove_all_listeners(ContextType ctx, FunctionType, ObjectTy } template -void RealmClass::close(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 0); +void RealmClass::close(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(0); SharedRealm realm = *get_internal>(this_object); realm->close(); } template -void RealmClass::compact(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { - validate_argument_count(argc, 0); +void RealmClass::compact(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) { + args.validate_maximum(0); SharedRealm realm = *get_internal>(this_object); if (realm->is_in_transaction()) { diff --git a/src/jsc/jsc_class.hpp b/src/jsc/jsc_class.hpp index 6e6f7618..8accc341 100644 --- a/src/jsc/jsc_class.hpp +++ b/src/jsc/jsc_class.hpp @@ -30,7 +30,9 @@ template using ClassDefinition = js::ClassDefinition; using ConstructorType = js::ConstructorType; +using ArgumentsMethodType = js::ArgumentsMethodType; using MethodType = js::MethodType; +using Arguments = js::Arguments; using PropertyType = js::PropertyType; using IndexPropertyType = js::IndexPropertyType; using StringPropertyType = js::StringPropertyType; @@ -369,6 +371,19 @@ JSValueRef wrap(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, } } +template +JSValueRef wrap(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], JSValueRef* exception) { + jsc::ReturnValue return_value(ctx); + try { + F(ctx, function, this_object, jsc::Arguments{ctx, argc, arguments}, return_value); + return return_value; + } + catch (std::exception &e) { + *exception = jsc::Exception::value(ctx, e); + return nullptr; + } +} + template JSValueRef wrap(JSContextRef ctx, JSObjectRef object, JSStringRef property, JSValueRef* exception) { jsc::ReturnValue return_value(ctx); diff --git a/src/node/node_class.hpp b/src/node/node_class.hpp index 21cddad6..2b1c7557 100644 --- a/src/node/node_class.hpp +++ b/src/node/node_class.hpp @@ -31,6 +31,8 @@ using ClassDefinition = js::ClassDefinition; using ConstructorType = js::ConstructorType; using MethodType = js::MethodType; +using ArgumentsMethodType = js::ArgumentsMethodType; +using Arguments = js::Arguments; using PropertyType = js::PropertyType; using IndexPropertyType = js::IndexPropertyType; using StringPropertyType = js::StringPropertyType; @@ -308,6 +310,21 @@ void wrap(const v8::FunctionCallbackInfo& info) { } } +template +void wrap(const v8::FunctionCallbackInfo& info) { + v8::Isolate* isolate = info.GetIsolate(); + node::ReturnValue return_value(info.GetReturnValue()); + auto arguments = node::get_arguments(info); + + try { + F(isolate, info.Callee(), info.This(), node::Arguments{isolate, arguments.size(), arguments.data()}, return_value); + } + catch (std::exception &e) { + Nan::ThrowError(node::Exception::value(isolate, e)); + } +} + + template void wrap(v8::Local property, const v8::PropertyCallbackInfo& info) { v8::Isolate* isolate = info.GetIsolate(); diff --git a/tests/js/realm-tests.js b/tests/js/realm-tests.js index 0ef1a435..251e8e8d 100644 --- a/tests/js/realm-tests.js +++ b/tests/js/realm-tests.js @@ -606,7 +606,8 @@ module.exports = { "Can only delete objects within a transaction."); realm.write(() => { - TestCase.assertThrowsContaining(() => realm.delete(), 'Invalid arguments'); + TestCase.assertThrowsContaining(() => realm.delete(), + "object must be of type 'object', got (undefined)"); realm.delete(objects[0]); TestCase.assertEqual(objects.length, 9, 'wrong object count'); @@ -671,10 +672,11 @@ module.exports = { function InvalidPerson() {} InvalidPerson.schema = schemas.PersonObject.schema; - TestCase.assertThrowsContaining(() => realm.objects(), 'Invalid arguments'); + TestCase.assertThrowsContaining(() => realm.objects(), "objectType must be of type 'string', got (undefined)"); TestCase.assertThrowsContaining(() => realm.objects([]), "objectType must be of type 'string', got ()"); TestCase.assertThrowsContaining(() => realm.objects('InvalidClass'), "Object type 'InvalidClass' not found in schema."); - TestCase.assertThrowsContaining(() => realm.objects('PersonObject', 'truepredicate'), 'Invalid arguments'); + TestCase.assertThrowsContaining(() => realm.objects('PersonObject', 'truepredicate'), + "Invalid arguments: at most 1 expected, but 2 supplied."); TestCase.assertThrowsContaining(() => realm.objects(InvalidPerson), 'Constructor was not registered in the schema for this Realm'); @@ -724,8 +726,10 @@ module.exports = { TestCase.assertThrowsContaining(() => realm.objectForPrimaryKey('TestObject', 0), "'TestObject' does not have a primary key defined"); - TestCase.assertThrowsContaining(() => realm.objectForPrimaryKey(), 'Invalid arguments'); - TestCase.assertThrowsContaining(() => realm.objectForPrimaryKey('IntPrimary'), "Invalid arguments"); + TestCase.assertThrowsContaining(() => realm.objectForPrimaryKey(), + "objectType must be of type 'string', got (undefined)"); + TestCase.assertThrowsContaining(() => realm.objectForPrimaryKey('IntPrimaryObject'), + "Invalid null value for non-nullable primary key."); TestCase.assertThrowsContaining(() => realm.objectForPrimaryKey('InvalidClass', 0), "Object type 'InvalidClass' not found in schema."); }, From 70739d632b391e006b74f5213effb621ec337926 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Mon, 18 Sep 2017 11:58:37 -0700 Subject: [PATCH 09/23] Build realm from source for each test-runner test Without this node-pre-gyp will instead download the prebuilt binary and test that, and I couldn't find a way to persuade node-pre-gyp to "download" the locally built copy. --- package.json | 6 +++--- scripts/test.sh | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index fbf17cf4..782e6bdf 100644 --- a/package.json +++ b/package.json @@ -59,9 +59,9 @@ "jsdoc": "npm install && npm run jsdoc:clean && jsdoc -u docs/tutorials -p package.json -c docs/conf.json", "prenode-tests": "npm install --build-from-source && cd tests && npm install", "node-tests": "cd tests && npm run test && cd ..", - "test-runner:ava": "cd tests/test-runners/ava && npm install && npm test", - "test-runner:mocha": "cd tests/test-runners/mocha && npm install && npm test", - "test-runner:jest": "cd tests/test-runners/jest && npm install && npm test", + "test-runner:ava": "cd tests/test-runners/ava && npm install --build-from-source=realm && npm test", + "test-runner:mocha": "cd tests/test-runners/mocha && npm install --build-from-source=realm && npm test", + "test-runner:jest": "cd tests/test-runners/jest && npm install --build-from-source=realm && npm test", "test-runners": "npm run test-runner:ava && npm run test-runner:mocha && npm run test-runner:jest", "isMac": "node -p \"if (process.platform == 'darwin') { process.exit(0); } else { process.exit(-1); }\"", "testMac": "npm run isMac -s && echo this is mac || echo . ", diff --git a/scripts/test.sh b/scripts/test.sh index 76d34a0e..1f22aeec 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -396,8 +396,6 @@ case "$TARGET" in ;; "test-runners") npm run check-environment - # Create a fake realm module that points to the source root so that test-runner tests can require('realm') - npm install --build-from-source npm run test-runners ;; "all") From 604968a9870453d2ac2093fb35d1943138191458 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Mon, 18 Sep 2017 12:20:09 -0700 Subject: [PATCH 10/23] Ensure node 6.5.0 is installed on CI --- scripts/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test.sh b/scripts/test.sh index 1f22aeec..4a733543 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -241,7 +241,7 @@ elif [ -x "$(command -v brew)" ] && [ -f "$(brew --prefix nvm)/nvm.sh" ]; then # TODO: change the mac slaves to use manual nvm installation . "$(brew --prefix nvm)/nvm.sh" fi -[[ "$(command -v nvm)" ]] && nvm use 6.5.0 || true +[[ "$(command -v nvm)" ]] && nvm install 6.5.0 && nvm use 6.5.0 || true # Remove cached packages rm -rf ~/.yarn-cache/npm-realm-* From ada601354c3dd3b4d26ea57b8d4637ae2d16cd0d Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Mon, 18 Sep 2017 13:23:27 -0700 Subject: [PATCH 11/23] Fix some shellcheck warnings --- scripts/test.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/test.sh b/scripts/test.sh index 4a733543..777e0700 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -9,7 +9,7 @@ export NPM_CONFIG_PROGRESS=false TARGET=$1 CONFIGURATION=${2:-Release} -if echo $CONFIGURATION | grep -i "^Debug$" > /dev/null ; then +if echo "$CONFIGURATION" | grep -i "^Debug$" > /dev/null ; then CONFIGURATION="Debug" fi @@ -17,8 +17,6 @@ IOS_SIM_DEVICE=${IOS_SIM_DEVICE:-} # use preferentially, otherwise will be set a ios_sim_default_device_type=${IOS_SIM_DEVICE_TYPE:-iPhone 5s} ios_sim_default_ios_version=${IOS_SIM_OS:-iOS 10.1} -ACCEPTED_LICENSES='MIT, ISC, BSD, Apache-2.0, BSD-2-Clause, BSD-3-Clause, WTFPL, Unlicense, (MIT AND CC-BY-3.0)' - PATH="/opt/android-sdk-linux/platform-tools:$PATH" SRCROOT=$(cd "$(dirname "$0")/.." && pwd) XCPRETTY=$(which xcpretty || true) @@ -235,13 +233,15 @@ trap cleanup EXIT # Use a consistent version of Node if possible. if [ -f "$NVM_DIR/nvm.sh" ]; then - . "$NVM_DIR/nvm.sh" + . "$NVM_DIR/nvm.sh" || true elif [ -x "$(command -v brew)" ] && [ -f "$(brew --prefix nvm)/nvm.sh" ]; then # we must be on mac and nvm was installed with brew # TODO: change the mac slaves to use manual nvm installation - . "$(brew --prefix nvm)/nvm.sh" + . "$(brew --prefix nvm)/nvm.sh" || true +fi +if [[ "$(command -v nvm)" ]]; then + nvm install 6.5.0 fi -[[ "$(command -v nvm)" ]] && nvm install 6.5.0 && nvm use 6.5.0 || true # Remove cached packages rm -rf ~/.yarn-cache/npm-realm-* From 518991598074e36665425cf9ddb152f95d4bbd3b Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Mon, 18 Sep 2017 16:19:33 -0700 Subject: [PATCH 12/23] Skip sourcing nvm.sh if it's already available --- scripts/test.sh | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/scripts/test.sh b/scripts/test.sh index 777e0700..133d18a0 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -232,12 +232,16 @@ cleanup trap cleanup EXIT # Use a consistent version of Node if possible. -if [ -f "$NVM_DIR/nvm.sh" ]; then - . "$NVM_DIR/nvm.sh" || true -elif [ -x "$(command -v brew)" ] && [ -f "$(brew --prefix nvm)/nvm.sh" ]; then - # we must be on mac and nvm was installed with brew - # TODO: change the mac slaves to use manual nvm installation - . "$(brew --prefix nvm)/nvm.sh" || true +if [[ -z "$(command -v nvm)" ]]; then + if [ -f "$NVM_DIR/nvm.sh" ]; then + . "$NVM_DIR/nvm.sh" || true + elif [ -x "$(command -v brew)" ] && [ -f "$(brew --prefix nvm)/nvm.sh" ]; then + # we must be on mac and nvm was installed with brew + # TODO: change the mac slaves to use manual nvm installation + . "$(brew --prefix nvm)/nvm.sh" || true + elif [ -f "$HOME/.nvm/nvm.sh" ]; then + . ~/.nvm/nvm.sh + fi fi if [[ "$(command -v nvm)" ]]; then nvm install 6.5.0 From e1f3cd638f3aa59c1764c6a95c7faa4471ceb615 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Mon, 18 Sep 2017 19:16:28 -0700 Subject: [PATCH 13/23] Don't forward arguments to nvh.sh --- scripts/test.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/test.sh b/scripts/test.sh index 133d18a0..c7c44ba9 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -234,13 +234,13 @@ trap cleanup EXIT # Use a consistent version of Node if possible. if [[ -z "$(command -v nvm)" ]]; then if [ -f "$NVM_DIR/nvm.sh" ]; then - . "$NVM_DIR/nvm.sh" || true + . "$NVM_DIR/nvm.sh" '' || true elif [ -x "$(command -v brew)" ] && [ -f "$(brew --prefix nvm)/nvm.sh" ]; then # we must be on mac and nvm was installed with brew # TODO: change the mac slaves to use manual nvm installation - . "$(brew --prefix nvm)/nvm.sh" || true + . "$(brew --prefix nvm)/nvm.sh" '' || true elif [ -f "$HOME/.nvm/nvm.sh" ]; then - . ~/.nvm/nvm.sh + . ~/.nvm/nvm.sh '' fi fi if [[ "$(command -v nvm)" ]]; then From d04a45b8d18128f32710ca85817a73849a66e8b1 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Mon, 18 Sep 2017 21:08:19 -0700 Subject: [PATCH 14/23] Ignore errors when sourcing nvm.sh It fails spuriously if it fails to understand the version of system node. --- scripts/test.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/test.sh b/scripts/test.sh index c7c44ba9..66b66f34 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -233,6 +233,7 @@ trap cleanup EXIT # Use a consistent version of Node if possible. if [[ -z "$(command -v nvm)" ]]; then + set +e if [ -f "$NVM_DIR/nvm.sh" ]; then . "$NVM_DIR/nvm.sh" '' || true elif [ -x "$(command -v brew)" ] && [ -f "$(brew --prefix nvm)/nvm.sh" ]; then @@ -242,6 +243,7 @@ if [[ -z "$(command -v nvm)" ]]; then elif [ -f "$HOME/.nvm/nvm.sh" ]; then . ~/.nvm/nvm.sh '' fi + set -e fi if [[ "$(command -v nvm)" ]]; then nvm install 6.5.0 From f3f51ece763e52a99b40c2ab51970f7e7c3ff23a Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Mon, 18 Sep 2017 22:32:41 -0700 Subject: [PATCH 15/23] Separate build and test steps in xcode to reduce chance of hitting "Early unexpected exit" --- scripts/test.sh | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/scripts/test.sh b/scripts/test.sh index 66b66f34..c7be3683 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -122,13 +122,18 @@ xctest() { echo " done" # - Run the build and test + xcrun xcodebuild -scheme "$1" -configuration "$CONFIGURATION" -sdk iphonesimulator -destination id="$IOS_SIM_DEVICE" build || { + EXITCODE=$? + echo "*** Failure (exit code $EXITCODE). ***" + exit $EXITCODE + } if [ -n "$XCPRETTY" ]; then log_temp=$(mktemp build.log.XXXXXX) if [ -e "$log_temp" ]; then rm "$log_temp" fi - xcrun xcodebuild -scheme "$1" -configuration "$CONFIGURATION" -sdk iphonesimulator -destination name="iPhone 5s" build test 2>&1 | tee "$log_temp" | "$XCPRETTY" -c --no-utf --report junit --output build/reports/junit.xml || { - EXITCODE=$? + xcrun xcodebuild -scheme "$1" -configuration "$CONFIGURATION" -sdk iphonesimulator -destination name="iPhone 5s" test 2>&1 | tee "$log_temp" | "$XCPRETTY" -c --no-utf --report junit --output build/reports/junit.xml || { + EXITCODE=$? printf "*** Xcode Failure (exit code %s). The full xcode log follows: ***\n\n" "$EXITCODE" cat "$log_temp" printf "\n\n*** End Xcode Failure ***\n" @@ -136,11 +141,11 @@ xctest() { } rm "$log_temp" else - xcrun xcodebuild -scheme "$1" -configuration "$CONFIGURATION" -sdk iphonesimulator -destination id="$IOS_SIM_DEVICE" build test || { - EXITCODE=$? + xcrun xcodebuild -scheme "$1" -configuration "$CONFIGURATION" -sdk iphonesimulator -destination id="$IOS_SIM_DEVICE" test || { + EXITCODE=$? echo "*** Failure (exit code $EXITCODE). ***" exit $EXITCODE - } + } fi } From 8ad5c36cca14e156ee30360aeb8201a25d9b57f3 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Thu, 7 Sep 2017 11:28:24 -0700 Subject: [PATCH 16/23] Fix race conditions in testAddListener The test was resolving the promise long before it actually finished running, leading to it not testing what it was trying to test and sometimes crashing. --- tests/js/results-tests.js | 54 ++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/tests/js/results-tests.js b/tests/js/results-tests.js index 3e974910..efbd252a 100644 --- a/tests/js/results-tests.js +++ b/tests/js/results-tests.js @@ -383,23 +383,23 @@ module.exports = { TestCase.assertEqual(snapshot.length, 0); }); }, - + testResultsFindIndexOfObject: function() { var realm = new Realm({schema: [schemas.TestObject]}); - + var object1, object2, object3; realm.write(function() { object1 = realm.create('TestObject', {doubleCol: 1}); object2 = realm.create('TestObject', {doubleCol: 2}); object3 = realm.create('TestObject', {doubleCol: 2}); }); - + // Search in base table const objects = realm.objects('TestObject'); TestCase.assertEqual(objects.indexOf(object1), 0); TestCase.assertEqual(objects.indexOf(object2), 1); TestCase.assertEqual(objects.indexOf(object3), 2); - + // Search in filtered query const results = objects.filtered("doubleCol == 2"); TestCase.assertEqual(results.indexOf(object1), -1); @@ -408,7 +408,7 @@ module.exports = { const nonRealmObject = {test: "this is an object"}; TestCase.assertEqual(objects.indexOf(nonRealmObject), -1); - + // Searching for object from the wrong realm var realm2 = new Realm({path: '2.realm', schema: realm.schema}); var object4; @@ -421,29 +421,35 @@ module.exports = { }, testAddListener: function() { - return new Promise((resolve, _reject) => { - var realm = new Realm({ schema: [schemas.TestObject] }); - - realm.write(() => { - realm.create('TestObject', { doubleCol: 1 }); - realm.create('TestObject', { doubleCol: 2 }); - realm.create('TestObject', { doubleCol: 3 }); - }); + const realm = new Realm({ schema: [schemas.TestObject] }); + realm.write(() => { + realm.create('TestObject', { doubleCol: 1 }); + realm.create('TestObject', { doubleCol: 2 }); + realm.create('TestObject', { doubleCol: 3 }); + }); + let resolve, first = true; + return new Promise((r, _reject) => { + resolve = r; realm.objects('TestObject').addListener((testObjects, changes) => { - // TODO: First notification is empty, so perform these - // assertions on the second call. However, there is a race condition - // in React Native, so find a way to do this in a robust way. - //TestCase.assertEqual(testObjects.length, 4); - //TestCase.assertEqual(changes.insertions.length, 1); + if (first) { + TestCase.assertEqual(testObjects.length, 3); + TestCase.assertEqual(changes.insertions.length, 0); + } + else { + TestCase.assertEqual(testObjects.length, 4); + TestCase.assertEqual(changes.insertions.length, 1); + } + first = false; resolve(); }); - - realm.write(() => { - realm.create('TestObject', { doubleCol: 1 }); + }).then(() => { + return new Promise((r, _reject) => { + realm.write(() => { + realm.create('TestObject', { doubleCol: 1 }); + }); + resolve = r; }); - }) + }); } - - }; From e8e23bbee7824323939e9657c4007a12bef72556 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Tue, 19 Sep 2017 11:53:49 -0700 Subject: [PATCH 17/23] Make permission tests better handle server delays Retry a few times if the request hasn't been processed yet rather than hoping that a 100ms sleep will suffice. --- tests/js/permission-tests.js | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/tests/js/permission-tests.js b/tests/js/permission-tests.js index c1bf831e..5f33e95d 100644 --- a/tests/js/permission-tests.js +++ b/tests/js/permission-tests.js @@ -54,13 +54,25 @@ function wait(t) { return new Promise(resolve => setTimeout(resolve, t)); } +function repeatUntil(fn, predicate) { + let retries = 0 + function check() { + if (retries > 3) { + return Promise.reject(new Error("operation timed out")); + } + ++retries; + return fn().then(x => predicate(x) ? x : wait(100).then(check)); + } + return check; +} + module.exports = { testApplyAndGetGrantedPermissions() { return createUsersWithTestRealms(1) .then(([user]) => { return user.applyPermissions({ userId: '*' }, `/${user.identity}/test`, 'read') - .then(wait(100)) - .then(() => user.getGrantedPermissions('any')) + .then(repeatUntil(() => user.getGrantedPermissions('any'), + permissions => permissions.length > 1)) .then(permissions => { TestCase.assertEqual(permissions[1].path, `/${user.identity}/test`); TestCase.assertEqual(permissions[1].mayRead, true); @@ -77,17 +89,19 @@ module.exports = { .then(token => user2.acceptPermissionOffer(token)) .then(realmUrl => { TestCase.assertEqual(realmUrl, `/${user1.identity}/test`); - return user2.getGrantedPermissions('any') - .then(permissions => { - TestCase.assertEqual(permissions[1].path, `/${user1.identity}/test`); - TestCase.assertEqual(permissions[1].mayRead, true); - TestCase.assertEqual(permissions[1].mayWrite, false); - TestCase.assertEqual(permissions[1].mayManage, false); - }); + return realmUrl; + }) + .then(repeatUntil(() => user2.getGrantedPermissions('any'), + permissions => permissions.length > 1)) + .then(permissions => { + TestCase.assertEqual(permissions[1].path, `/${user1.identity}/test`); + TestCase.assertEqual(permissions[1].mayRead, true); + TestCase.assertEqual(permissions[1].mayWrite, false); + TestCase.assertEqual(permissions[1].mayManage, false); }); }); }, - + testInvalidatePermissionOffer() { return createUsersWithTestRealms(2) .then(([user1, user2]) => { From 41440125cb8fd909a022b9e707eeccc67cd3100b Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Tue, 19 Sep 2017 15:45:05 -0700 Subject: [PATCH 18/23] Ensure RN has an event loop running for async tests --- tests/react-test-app/ios/ReactTests/RealmReactTests.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/react-test-app/ios/ReactTests/RealmReactTests.m b/tests/react-test-app/ios/ReactTests/RealmReactTests.m index 746e31a6..27bacbe8 100644 --- a/tests/react-test-app/ios/ReactTests/RealmReactTests.m +++ b/tests/react-test-app/ios/ReactTests/RealmReactTests.m @@ -168,6 +168,7 @@ extern NSMutableArray *RCTGetModuleClasses(void); + (void)waitForCondition:(BOOL *)condition description:(NSString *)description { NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:30.0]; + RCTBridge *bridge = [self currentBridge]; while (!*condition) { if ([timeout timeIntervalSinceNow] < 0) { @@ -180,6 +181,7 @@ extern NSMutableArray *RCTGetModuleClasses(void); [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; [runLoop runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; [NSThread sleepForTimeInterval:0.01]; // Bad things may happen without some sleep. + [bridge.eventDispatcher sendAppEventWithName:@"realm-dummy" body:nil]; // Ensure RN has an event loop running } } } From c3afa0c30120730ea5777e1e5fab2ed72a79202d Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Tue, 19 Sep 2017 15:54:10 -0700 Subject: [PATCH 19/23] Disable testAddListener when running in chrome --- tests/js/results-tests.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/js/results-tests.js b/tests/js/results-tests.js index efbd252a..99ea0353 100644 --- a/tests/js/results-tests.js +++ b/tests/js/results-tests.js @@ -421,6 +421,11 @@ module.exports = { }, testAddListener: function() { + if (typeof navigator !== 'undefined' && /Chrome/.test(navigator.userAgent)) { // eslint-disable-line no-undef + // FIXME: async callbacks do not work correctly in Chrome debugging mode + return; + } + const realm = new Realm({ schema: [schemas.TestObject] }); realm.write(() => { realm.create('TestObject', { doubleCol: 1 }); From 17759c5ce987ec26de40193a373b56f478b5c6b5 Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Wed, 20 Sep 2017 10:24:45 +0530 Subject: [PATCH 20/23] Fix port conflict between RN >= 0.48 and RPC server (#1294) --- CHANGELOG.md | 12 ++++++++++++ react-native/android/publish_android_template | 8 ++++---- .../main/java/io/realm/react/RealmReactModule.java | 2 +- react-native/ios/RealmReact/RealmReact.mm | 2 +- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9501068d..5923f128 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +X.Y.Z Release notes +============================================================= +### Breaking changes +* None + +### Enhancements +* None + +### Bug fixes +* Fixed port conflict between RN >= 0.48 inspector proxy and RPC server used for Chrome debugging (#1294). + + 1.12.0 Release notes (2017-9-14) ============================================================= ### Breaking changes diff --git a/react-native/android/publish_android_template b/react-native/android/publish_android_template index c4c0670b..7d1e6686 100644 --- a/react-native/android/publish_android_template +++ b/react-native/android/publish_android_template @@ -22,17 +22,17 @@ apply plugin: 'com.android.library' task forwardDebugPort(type: Exec) { def adb = android.getAdbExe()?.toString() ?: 'false' - commandLine adb, 'forward', 'tcp:8082', 'tcp:8082' + commandLine adb, 'forward', 'tcp:8083', 'tcp:8083' ignoreExitValue true doLast { if (execResult.getExitValue() != 0) { logger.error( '===========================================================================\n' + - 'WARNING: Failed to automatically forward port 8082.\n' + - 'In order to use Realm in Chrome debugging mode, port 8082 must be forwarded\n' + + 'WARNING: Failed to automatically forward port 8083.\n' + + 'In order to use Realm in Chrome debugging mode, port 8083 must be forwarded\n' + 'from localhost to the device or emulator being used to run the application.\n' + 'You may need to add the appropriate flags to the command that failed:\n' + - ' adb forward tcp:8082 tcp:8082\n' + + ' adb forward tcp:8083 tcp:8083\n' + '===========================================================================\n' ) } diff --git a/react-native/android/src/main/java/io/realm/react/RealmReactModule.java b/react-native/android/src/main/java/io/realm/react/RealmReactModule.java index 696b978d..0ad49a2a 100644 --- a/react-native/android/src/main/java/io/realm/react/RealmReactModule.java +++ b/react-native/android/src/main/java/io/realm/react/RealmReactModule.java @@ -23,7 +23,7 @@ import java.util.Map; import fi.iki.elonen.NanoHTTPD; class RealmReactModule extends ReactContextBaseJavaModule { - private static final int DEFAULT_PORT = 8082; + private static final int DEFAULT_PORT = 8083; private static boolean sentAnalytics = false; private AndroidWebServer webServer; diff --git a/react-native/ios/RealmReact/RealmReact.mm b/react-native/ios/RealmReact/RealmReact.mm index 62be7893..26da98e2 100644 --- a/react-native/ios/RealmReact/RealmReact.mm +++ b/react-native/ios/RealmReact/RealmReact.mm @@ -38,7 +38,7 @@ #import "GCDWebServerErrorResponse.h" #import "rpc.hpp" -#define WEB_SERVER_PORT 8082 +#define WEB_SERVER_PORT 8083 using namespace realm::rpc; #endif From 494929a9a55ae2255fb6a2b39cec4aedbebcde4c Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Wed, 20 Sep 2017 14:32:25 +0530 Subject: [PATCH 21/23] Fix typo in Github issue template --- .github/ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 95a8b062..17ab0c7a 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -40,6 +40,6 @@ Full projects that we can compile and run ourselves are ideal! ## Version of Realm and Tooling - Realm JS SDK Version: ? -- Node or React Nattive: ? +- Node or React Native: ? - Client OS & Version: ? - Which debugger for React Native: ?/None From 275db8a631b6e1eb1cb208f386e495b8b75bfe04 Mon Sep 17 00:00:00 2001 From: blagoev Date: Wed, 20 Sep 2017 21:59:07 +0300 Subject: [PATCH 22/23] fix progress notifications registrations --- src/js_sync.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js_sync.hpp b/src/js_sync.hpp index ad136bf7..3542bcb6 100644 --- a/src/js_sync.hpp +++ b/src/js_sync.hpp @@ -361,7 +361,7 @@ void SessionClass::add_progress_notification(ContextType ctx, FunctionType, O progressFunc = std::move(progress_handler); - auto registrationToken = session->register_progress_notifier(std::move(progressFunc), notifierType, false); + auto registrationToken = session->register_progress_notifier(std::move(progressFunc), notifierType, is_streaming); auto syncSession = create_object>(ctx, new WeakSession(session)); PropertyAttributes attributes = ReadOnly | DontEnum | DontDelete; From f8f6661fe21b0abf8c876f1cef0a45ece85d5145 Mon Sep 17 00:00:00 2001 From: blagoev Date: Fri, 22 Sep 2017 13:14:02 +0300 Subject: [PATCH 23/23] Fix api doc error --- docs/sync.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/sync.js b/docs/sync.js index 74761220..cd8c1fd7 100644 --- a/docs/sync.js +++ b/docs/sync.js @@ -30,9 +30,6 @@ class Sync { * _Currently only the 'change' event is supported_ * @param {function(change_event)} change_callback - called when changes are made to any Realm which * match the given regular expression - * @param {bool} validate_ssl=true - Validate the server's SSL chertificate. - * @param {string} ssl_trust_certificate_path=None - Path to a trust/anchor certificate used by the - * client to verify the server certificate. */ static addListener(server_url, admin_user, regex, name, change_callback) {}