Testing against ROS 3.0.0. (#1712)

* Testing against ROS 3.0.0.
* Adding Realm.Sync.Subscription.removeAllListeners. Refactor partial sync tests.
* Refactoring partial sync tests.
This commit is contained in:
Kenneth Geisshirt 2018-03-21 18:18:37 +01:00 committed by GitHub
parent 65e8962674
commit c370ef4d86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 230 additions and 306 deletions

View File

@ -1,10 +1,27 @@
X.Y.Z Release notes
=============================================================
### Compatibility
* Sync protocol: 24
* Server-side history format: 4
* File format: 7
* Realm Object Server: 3.0.0 or later
### Breaking changes
* None.
### Enhancements
* Added `Realm.Sync.Subscription.removeAllListeners()`.
### Internal
* Tested with Realm Object Server 3.0.0.
2.3.1 Release notes (2018-3-16)
=============================================================
### Compatibility
* Sync protocol: 24
* Server-side history format: 4
* File format: 7
* Realm Object Server: 3.0.0-alpha.8
* Realm Object Server: 3.0.0-alpha.8 or later
### Breaking changes
* None.
@ -16,7 +33,8 @@
* [Sync] Avoid hammering the ROS authentication service when large numbers of Realms are opened at once.
### Internal
* None.
* Tested with Realm Object Server 3.0.0-rc.1.
2.3.0 Release notes (2018-3-13)
=============================================================

View File

@ -2,4 +2,4 @@ PACKAGE_NAME=realm-js
VERSION=2.3.1
REALM_CORE_VERSION=5.4.0
REALM_SYNC_VERSION=3.0.0
REALM_OBJECT_SERVER_VERSION=3.0.0-alpha.8
REALM_OBJECT_SERVER_VERSION=3.0.0

View File

@ -524,6 +524,11 @@ class Subscription {
* @throws {Error} If `callback` is not a function.
*/
removeListener(callback) {}
/**
* Remove all listeners from the subscription instance.
*/
removeAllListeners() {}
}
/**

View File

@ -172,12 +172,13 @@ module.exports = function(realmConstructor) {
let url = new URL(user.server);
let secure = (url.protocol === 'https:')?'s':'';
let port = (url.port === undefined)?'9080':url.port
let realmUrl = `realm${secure}://${url.hostname}:${port}/~/default`;
let realmUrl = `realm${secure}://${url.hostname}:${port}/default`;
let config = {
sync: {
user,
url: realmUrl
user: user,
url: realmUrl,
partial: true
}
};
return config;

1
lib/index.d.ts vendored
View File

@ -413,6 +413,7 @@ declare namespace Realm.Sync {
unsubscribe(): void;
addListener(subscruptionCallback: SubscriptionNotificationCallback): void;
removeListener(subscruptionCallback: SubscriptionNotificationCallback): void;
removeAllListeners(): void;
}
enum SubscriptionState {

View File

@ -98,7 +98,7 @@ function print_error() {
function validateRefresh(user, localRealmPath, response, json) {
let session = user._sessionForOnDiskPath(localRealmPath);
if (!session) {
print_error(`Unhandled session token refresh error: could not look up session at path ${localRealmPath}`);
print_error(`Unhandled session token refresh error: could not look up session for user ${user.identity} at path ${localRealmPath}`);
return;
}
@ -108,7 +108,7 @@ function validateRefresh(user, localRealmPath, response, json) {
if (errorHandler) {
errorHandler(session, error);
} else {
print_error('Unhandled session token refresh error', error);
print_error(`Unhandled session token refresh error for user ${user.identity} at path ${localRealmPath}`, error);
}
return;
}

View File

@ -591,6 +591,7 @@ public:
static void unsubscribe(ContextType, ObjectType, Arguments, ReturnValue &);
static void add_listener(ContextType, ObjectType, Arguments, ReturnValue &);
static void remove_listener(ContextType, ObjectType, Arguments, ReturnValue &);
static void remove_all_listeners(ContextType, ObjectType, Arguments, ReturnValue &);
PropertyMap<T> const properties = {
{"state", {wrap<get_state>, nullptr}},
@ -601,6 +602,7 @@ public:
{"unsubscribe", wrap<unsubscribe>},
{"addListener", wrap<add_listener>},
{"removeListener", wrap<remove_listener>},
{"removeAllListeners", wrap<remove_all_listeners>},
};
};
@ -676,6 +678,12 @@ void SubscriptionClass<T>::remove_listener(ContextType ctx, ObjectType this_obje
tokens.erase(std::remove_if(tokens.begin(), tokens.end(), compare), tokens.end());
}
template<typename T>
void SubscriptionClass<T>::remove_all_listeners(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
auto subscription = get_internal<T, SubscriptionClass<T>>(this_object);
subscription->m_notification_tokens.clear();
}
template<typename T>
class SyncClass : public ClassDefinition<T, void*> {

@ -1 +1 @@
Subproject commit b250563ea1eb9f32ec7dbd76f2c6f8f1a26914cc
Subproject commit f2a536d29de48e34e60799a5bf3f36e13806387e

View File

@ -11,7 +11,8 @@ const Realm = require(realmModule);
function createObjects(user) {
const config = {
sync: { user,
sync: {
user: user,
url: `realm://localhost:9080/~/${realmName}`,
error: err => console.log(err)
},
@ -19,7 +20,6 @@ function createObjects(user) {
};
const realm = new Realm(config);
realm.write(() => {
for (let i = 1; i <= 3; i++) {
realm.create('Dog', { name: `Lassy ${i}` });

View File

@ -0,0 +1,72 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
/*
* This script creates 3 new objects into a new realm. These are objects are validated to exists by the download api tests.
*/
'use strict';
console.log("partial-sync-api-helper started");
const username = process.argv[2];
const realmModule = process.argv[3];
const Realm = require(realmModule);
function createObjects(user) {
const config = {
sync: {
user,
url: `realm://localhost:9080/default`,
partial: true,
error: err => console.log('partial-sync-api-helper', err)
},
schema: [{ name: 'Dog', properties: { name: 'string' } }]
};
const realm = new Realm(config);
realm.write(() => {
for (let i = 1; i <= 3; i++) {
realm.create('Dog', { name: `Lassy ${i}` });
}
});
let session = realm.syncSession;
return new Promise((resolve, reject) => {
let callback = (transferred, total) => {
if (transferred === total) {
session.removeProgressNotification(callback);
resolve(realm);
}
}
session.addProgressNotification('upload', 'forCurrentlyOutstandingWork', callback);
});
}
let registrationError;
Realm.Sync.User.register('http://localhost:9080', username, 'password')
.catch((error) => {
registrationError = JSON.stringify(error);
return Realm.Sync.User.login('http://localhost:9080', username, 'password')
})
.catch((error) => {
const loginError = JSON.stringify(error);
console.error(`partial-sync-api-helper failed:\n User.register() error:\n${registrationError}\n User.login() error:\n${registrationError}`);
process.exit(-2);
})
.then((user) => createObjects(user))
.then(() => process.exit(0));

View File

@ -61,10 +61,10 @@ function subscribe(results) {
const subscription = results.subscribe();
return new Promise((resolve, reject) => {
subscription.addListener((subscription, state) => {
if (state == Realm.Sync.SubscriptionState.Complete) {
if (state === Realm.Sync.SubscriptionState.Complete) {
resolve();
}
else if (state == Realm.Sync.SubscriptionState.Error) {
else if (state === Realm.Sync.SubscriptionState.Error) {
reject();
}
});
@ -185,7 +185,7 @@ module.exports = {
}
}
],
sync: {user, url, partial: true}
sync: {user: user, url: url, partial: true}
};
};
let owner, otherUser
@ -193,16 +193,12 @@ module.exports = {
.register('http://localhost:9080', uuid(), 'password')
.then(user => {
owner = user;
new Realm({sync: {user, url: 'realm://localhost:9080/~/test'}}).close();
new Realm({sync: {user, url: 'realm://localhost:9080/default', partial: true}}).close();
return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password')
})
.then(user => {
.then((user) => {
otherUser = user;
return owner.applyPermissions({userId: otherUser.identity},
`/${owner.identity}/test`, 'read')
})
.then(() => {
let realm = new Realm(config(owner, 'realm://localhost:9080/~/test'));
let realm = new Realm(config(owner, 'realm://localhost:9080/default'));
realm.write(() => {
let user = realm.create(Realm.Permissions.User, {id: otherUser.identity})
let role = realm.create(Realm.Permissions.Role, {name: 'reader'})
@ -215,7 +211,7 @@ module.exports = {
});
return waitForUpload(realm).then(() => realm.close());
})
.then(() => Realm.open(config(otherUser, `realm://localhost:9080/${owner.identity}/test`)))
.then(() => Realm.open(config(otherUser, `realm://localhost:9080/default`)))
.then((realm) => subscribe(realm.objects('Object')).then(() => realm))
.then((realm) => {
// Should have full access to the Realm as a whole

View File

@ -166,77 +166,6 @@ module.exports = {
});
},
testDefaultRealm() {
if (!isNodeProccess) {
return;
}
const username = uuid();
const realmName = 'default';
const expectedObjectsCount = 3;
let user;
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password'))
.then(u => {
user = u;
return Realm.open(Realm.automaticSyncConfiguration());
})
.then(realm => {
let actualObjectsCount = realm.objects('Dog').length;
TestCase.assertEqual(actualObjectsCount, expectedObjectsCount, "Synced realm does not contain the expected objects count");
const session = realm.syncSession;
TestCase.assertInstanceOf(session, Realm.Sync.Session);
TestCase.assertEqual(session.user.identity, user.identity);
TestCase.assertEqual(session.state, 'active');
});
},
testDefaultRealmFromUser() {
if (!isNodeProccess) {
return;
}
const username = uuid();
const realmName = 'default';
const expectedObjectsCount = 3;
let user;
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password'))
.then(u => {
user = u;
return Realm.open(Realm.automaticSyncConfiguration(user));
})
.then(realm => {
let actualObjectsCount = realm.objects('Dog').length;
TestCase.assertEqual(actualObjectsCount, expectedObjectsCount, "Synced realm does not contain the expected objects count");
const session = realm.syncSession;
TestCase.assertInstanceOf(session, Realm.Sync.Session);
TestCase.assertEqual(session.user.identity, user.identity);
TestCase.assertEqual(session.state, 'active');
});
},
testDefaultRealmInvalidArguments() {
if (!isNodeProccess) {
return;
}
const username = uuid();
const realmName = 'default';
const expectedObjectsCount = 3;
let user;
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password'))
.then(u => {
TestCase.assertThrows(() => Realm.automaticSyncConfiguration('foo', 'bar')); // too many arguments
})
},
testRealmOpenWithExistingLocalRealm() {
if (!isNodeProccess) {
return;
@ -793,254 +722,148 @@ module.exports = {
});
},
testOpenPartialSyncUrl() {
// All tests releated to partial sync is assemble in one big test.
// Since it is the same instance of ROS running, it is virtually impossible
// to reset the state between the tests.
// In the future we should away from this style of testing.
testPartialSync() {
if (!isNodeProccess) {
return;
}
var user;
var realm;
const username = uuid();
return Realm.Sync.User.register('http://localhost:9080', username, 'password')
.then(user => {
const expectedObjectsCount = 3;
function __partialIsAllowed() {
// test: __partial is allowed
let config1 = {
sync: {
user: user,
url: `realm://localhost:9080/~/default/__partial/`,
url: `realm://localhost:9080/default/__partial/`,
partial: true,
_disablePartialSyncUrlChecks: true
}
};
const realm = new Realm(config1);
TestCase.assertFalse(realm.isClosed);
realm.close();
};
let config2 = {
sync: {
user: user,
url: `realm://localhost:9080/~/default/__partial/`, // <--- not allowed URL
partial: true,
}
};
TestCase.assertThrows(() => new Realm(config2));
});
},
function __partialIsNotAllowed() {
let config2 = {
sync: {
user: user,
url: `realm://localhost:9080/default/__partial/`, // <--- not allowed URL
partial: true,
}
};
TestCase.assertThrows(() => new Realm(config2));
};
testPartialSyncAnonymous_SubscriptionListener() {
// FIXME: try to enable for React Native
if (!isNodeProccess) {
return;
}
function shouldFail() {
let config = {
sync: {
user: user,
url: 'realm://localhost:9080/~/default',
partial: false, // <---- calling subscribe should fail
error: (session, error) => console.log(error)
},
schema: [{ name: 'Dog', properties: { name: 'string' } }]
};
const username = uuid();
const realmName = uuid();
Realm.deleteFile(config);
const realm = new Realm(config);
TestCase.assertEqual(realm.objects('Dog').length, 0);
TestCase.assertThrows(function () { var subscription = realm.objects('Dog').filtered("name == 'Lassy 1'").subscribe(); } );
realm.close();
};
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password'))
.then(user => {
let config = {
sync: {
user: user,
url: `realm://localhost:9080/~/${realmName}`,
partial: true,
error: (session, error) => console.log(error)
},
schema: [{ name: 'Dog', properties: { name: 'string' } }]
};
function defaultRealmInvalidArguments() {
TestCase.assertThrows(() => Realm.automaticSyncConfiguration('foo', 'bar')); // too many arguments
};
Realm.deleteFile(config);
const realm = new Realm(config);
TestCase.assertEqual(realm.objects('Dog').length, 0);
var results = realm.objects('Dog').filtered("name == 'Lassy 1'");
var subscription = results.subscribe();
TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating);
return new Promise((resolve, reject) => {
subscription.addListener((subscription, state) => {
if (state == Realm.Sync.SubscriptionState.Complete) {
TestCase.assertEqual(results.length, 1);
TestCase.assertTrue(results[0].name === 'Lassy 1', "The object is not synced correctly");
resolve();
}
});
setTimeout(function() {
reject("listener never called");
}, 5000);
});
});
},
testPartialSyncAnonymous_ResultsListener() {
// FIXME: try to enable for React Native
if (!isNodeProccess) {
return;
}
return runOutOfProcess(__dirname + '/partial-sync-api-helper.js', username, REALM_MODULE_PATH)
.then(() => {
return Realm.Sync.User.login('http://localhost:9080', username, 'password').then((u) => {
user = u;
const username = uuid();
const realmName = uuid();
__partialIsAllowed();
__partialIsNotAllowed();
shouldFail();
defaultRealmInvalidArguments();
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password'))
.then(user => {
let config = {
sync: {
user: user,
url: `realm://localhost:9080/~/${realmName}`,
partial: true,
error: (session, error) => console.log(error)
},
schema: [{ name: 'Dog', properties: { name: 'string' } }]
};
return new Promise((resolve, reject) => {
let config = Realm.automaticSyncConfiguration();
config.schema = [{ name: 'Dog', properties: { name: 'string' } }];
Realm.deleteFile(config);
Realm.deleteFile(config);
const realm = new Realm(config);
TestCase.assertEqual(realm.objects('Dog').length, 0);
var results = realm.objects('Dog').filtered("name == 'Lassy 1'");
var subscription = results.subscribe();
TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating);
return new Promise((resolve, reject) => {
results.addListener((collection, changes) => {
if (subscription.state === Realm.Sync.SubscriptionState.Complete) {
TestCase.assertEqual(collection.length, 1);
TestCase.assertTrue(collection[0].name === 'Lassy 1', "The object is not synced correctly");
resolve();
}
});
setTimeout(function() {
reject("listener never called");
}, 5000);
});
});
},
realm = new Realm(config);
const session = realm.syncSession;
TestCase.assertInstanceOf(session, Realm.Sync.Session);
TestCase.assertEqual(session.user.identity, user.identity);
TestCase.assertEqual(session.state, 'active');
testPartialSyncMultipleSubscriptions() {
// FIXME: try to enable for React Native
if (!isNodeProccess) {
return;
}
var results1 = realm.objects('Dog').filtered("name == 'Lassy 1'");
var results2 = realm.objects('Dog').filtered("name == 'Lassy 2'");
const username = uuid();
const realmName = uuid();
var subscription1 = results1.subscribe();
TestCase.assertEqual(subscription1.state, Realm.Sync.SubscriptionState.Creating);
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password'))
.then(user => {
let config = {
sync: {
user: user,
url: `realm://localhost:9080/~/${realmName}`,
partial: true,
error: (session, error) => console.log(error)
},
schema: [{ name: 'Dog', properties: { name: 'string' } }]
};
var subscription2 = results2.subscribe();
TestCase.assertEqual(subscription2.state, Realm.Sync.SubscriptionState.Creating);
Realm.deleteFile(config);
const realm = new Realm(config);
TestCase.assertEqual(realm.objects('Dog').length, 0);
var results1 = realm.objects('Dog').filtered("name == 'Lassy 1'");
var results2 = realm.objects('Dog').filtered("name == 'Lassy 2'");
var subscription1 = results1.subscribe();
var subscription2 = results2.subscribe();
let called1 = false;
let called2 = false;
return new Promise((resolve, reject) => {
let called1 = false;
let called2 = false;
results1.addListener((collection, changeset) => {
if (subscription1.state == Realm.Sync.SubscriptionState.Complete) {
TestCase.assertEqual(collection.length, 1);
TestCase.assertTrue(collection[0].name === 'Lassy 1', "The object is not synced correctly");
called1 = true;
if (called1 && called2) {
resolve();
subscription1.addListener((subscription, state) => {
if (state === Realm.Sync.SubscriptionState.Complete) {
results1.addListener((collection, changeset) => {
TestCase.assertEqual(collection.length, 1);
TestCase.assertTrue(collection[0].name === 'Lassy 1', "The object is not synced correctly");
results1.removeAllListeners();
subscription1.unsubscribe();
called1 = true;
});
} else if (state === Realm.Sync.SubscriptionState.Invalidated) {
subscription1.removeAllListeners();
if (called1 && called2) {
realm.close();
resolve('Done');
}
}
}
});
results2.addListener((collection, changeset) => {
if (subscription2.state == Realm.Sync.SubscriptionState.Complete) {
TestCase.assertEqual(collection.length, 1);
TestCase.assertTrue(collection[0].name === 'Lassy 2', "The object is not synced correctly");
called2 = true;
if (called1 && called2) {
resolve();
});
subscription2.addListener((subscription, state) => {
if (state === Realm.Sync.SubscriptionState.Complete) {
results2.addListener((collection, changeset) => {
TestCase.assertEqual(collection.length, 1);
TestCase.assertTrue(collection[0].name === 'Lassy 2', "The object is not synced correctly");
results2.removeAllListeners();
subscription2.unsubscribe();
called2 = true;
});
} else if (state === Realm.Sync.SubscriptionState.Invalidated) {
subscription2.removeAllListeners();
if (called1 && called2) {
realm.close();
resolve('Done');
}
}
}
});
});
setTimeout(function() {
reject("listener never called");
}, 5000);
setTimeout(() => {
reject("listeners never called");
}, 15000);
});
});
});
},
testPartialSyncFailing() {
// FIXME: try to enable for React Native
if (!isNodeProccess) {
return;
}
const username = uuid();
const realmName = uuid();
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password'))
.then(user => {
let config = {
sync: {
user: user,
url: `realm://localhost:9080/~/${realmName}`,
partial: false, // <---- calling subscribe should fail
error: (session, error) => console.log(error)
},
schema: [{ name: 'Dog', properties: { name: 'string' } }]
};
Realm.deleteFile(config);
const realm = new Realm(config);
TestCase.assertEqual(realm.objects('Dog').length, 0);
TestCase.assertThrows(function () { var subscription = realm.objects('Dog').filtered("name == 'Lassy 1'").subscribe(); } );
});
},
testPartialSyncUnsubscribe() {
// FIXME: try to enable for React Native
if (!isNodeProccess) {
return;
}
const username = uuid();
const realmName = uuid();
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => Realm.Sync.User.login('http://localhost:9080', username, 'password'))
.then(user => {
let config = {
sync: {
user: user,
url: `realm://localhost:9080/~/${realmName}`,
partial: true,
error: (session, error) => console.log(error)
},
schema: [{ name: 'Dog', properties: { name: 'string' } }]
};
Realm.deleteFile(config);
const realm = new Realm(config);
var results = realm.objects('Dog').filtered("name == 'Lassy 1'");
var subscription = results.subscribe();
TestCase.assertEqual(subscription.state, Realm.Sync.SubscriptionState.Creating);
return new Promise((resolve, reject) => {
results.addListener((collection, changes) => {
if (subscription.state === Realm.Sync.SubscriptionState.Complete) {
subscription.unsubscribe();
}
if (subscription.state === Realm.Sync.SubscriptionState.Invalidated) {
resolve();
}
});
setTimeout(function() {
reject("listener never called");
}, 5000);
});
});
},
testClientReset() {
// FIXME: try to enable for React Native