outsourced helper and test-connector
This commit is contained in:
		
							parent
							
								
									020dacdad4
								
							
						
					
					
						commit
						2ea163a5cf
					
				
							
								
								
									
										140
									
								
								tests-lib/helper.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								tests-lib/helper.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,140 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					import _Y from '../../yjs/src/y.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import yMemory from '../../y-memory/src/Memory.js'
 | 
				
			||||||
 | 
					import yArray from '../../y-array/src/y-array.js'
 | 
				
			||||||
 | 
					import yMap from '../../y-map/src/Map.js'
 | 
				
			||||||
 | 
					import yTest from './test-connector.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Chance from 'chance'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export let Y = _Y
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Y.extend(yMemory, yArray, yMap, yTest)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function garbageCollectAllUsers (t, users) {
 | 
				
			||||||
 | 
					  await flushAll(t, users)
 | 
				
			||||||
 | 
					  await Promise.all(users.map(u => u.db.emptyGarbageCollector()))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function compareUsers (t, users) {
 | 
				
			||||||
 | 
					  var unsynced = users.filter(u => !u.connector.isSynced)
 | 
				
			||||||
 | 
					  unsynced.forEach(u => u.reconnect())
 | 
				
			||||||
 | 
					  if (users[0].connector.testRoom != null) {
 | 
				
			||||||
 | 
					    // flush for sync if test-connector
 | 
				
			||||||
 | 
					    await users[0].connector.testRoom.flushAll(users)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  await Promise.all(unsynced.map(u => {
 | 
				
			||||||
 | 
					    return new Promise(function (resolve) {
 | 
				
			||||||
 | 
					      u.connector.whenSynced(resolve)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }))
 | 
				
			||||||
 | 
					  await flushAll(t, users)
 | 
				
			||||||
 | 
					  // types must be equal before garbage collect
 | 
				
			||||||
 | 
					  var userTypeContents = users.map(u => u.share.array._content.map(c => c.val || JSON.stringify(c.type)))
 | 
				
			||||||
 | 
					  var data = await Promise.all(users.map(async (u) => {
 | 
				
			||||||
 | 
					    var data = {}
 | 
				
			||||||
 | 
					    await u.db.garbageCollect()
 | 
				
			||||||
 | 
					    await u.db.garbageCollect()
 | 
				
			||||||
 | 
					    u.db.requestTransaction(function * () {
 | 
				
			||||||
 | 
					      data.os = yield * this.getOperationsUntransformed()
 | 
				
			||||||
 | 
					      data.os = data.os.untransformed.map((op) => {
 | 
				
			||||||
 | 
					        return Y.Struct[op.struct].encode(op)
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      data.ds = yield * this.getDeleteSet()
 | 
				
			||||||
 | 
					      data.ss = yield * this.getStateSet()
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    await u.db.whenTransactionsFinished()
 | 
				
			||||||
 | 
					    return data
 | 
				
			||||||
 | 
					  }))
 | 
				
			||||||
 | 
					  for (var i = 0; i < data.length - 1; i++) {
 | 
				
			||||||
 | 
					    await t.asyncGroup(async () => {
 | 
				
			||||||
 | 
					      t.compare(userTypeContents[i], userTypeContents[i + 1], 'types')
 | 
				
			||||||
 | 
					      t.compare(data[i].os, data[i + 1].os, 'os')
 | 
				
			||||||
 | 
					      t.compare(data[i].ds, data[i + 1].ds, 'ds')
 | 
				
			||||||
 | 
					      t.compare(data[i].ss, data[i + 1].ss, 'ss')
 | 
				
			||||||
 | 
					    }, `Compare user${i} with user${i + 1}`)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function initArrays (t, opts) {
 | 
				
			||||||
 | 
					  var result = {
 | 
				
			||||||
 | 
					    users: []
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  var share = Object.assign({ flushHelper: 'Map', array: 'Array' }, opts.share)
 | 
				
			||||||
 | 
					  var chance = opts.chance || new Chance(t.getSeed() * 1000000000)
 | 
				
			||||||
 | 
					  var connector = Object.assign({ room: 'debugging_' + t.name, testContext: t, chance }, opts.connector)
 | 
				
			||||||
 | 
					  for (let i = 0; i < opts.users; i++) {
 | 
				
			||||||
 | 
					    let y = await Y({
 | 
				
			||||||
 | 
					      connector: connector,
 | 
				
			||||||
 | 
					      db: opts.db,
 | 
				
			||||||
 | 
					      share: share
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    result.users.push(y)
 | 
				
			||||||
 | 
					    for (let name in share) {
 | 
				
			||||||
 | 
					      result[name + i] = y.share[name]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  result.array0.delete(0, result.array0.length)
 | 
				
			||||||
 | 
					  if (result.users[0].connector.testRoom != null) {
 | 
				
			||||||
 | 
					    // flush for sync if test-connector
 | 
				
			||||||
 | 
					    await result.users[0].connector.testRoom.flushAll(result.users)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  await Promise.all(result.users.map(u => {
 | 
				
			||||||
 | 
					    return new Promise(function (resolve) {
 | 
				
			||||||
 | 
					      u.connector.whenSynced(resolve)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }))
 | 
				
			||||||
 | 
					  await flushAll(t, result.users)
 | 
				
			||||||
 | 
					  return result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function flushAll (t, users) {
 | 
				
			||||||
 | 
					  users = users.filter(u => u.connector.isSynced)
 | 
				
			||||||
 | 
					  if (users.length === 0) {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  await wait(0)
 | 
				
			||||||
 | 
					  if (users[0].connector.testRoom != null) {
 | 
				
			||||||
 | 
					    // use flushAll method specified in Test Connector
 | 
				
			||||||
 | 
					    await users[0].connector.testRoom.flushAll(users)
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    // flush for any connector
 | 
				
			||||||
 | 
					    await Promise.all(users.map(u => { return u.db.whenTransactionsFinished() }))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var flushCounter = users[0].share.flushHelper.get('0') || 0
 | 
				
			||||||
 | 
					    flushCounter++
 | 
				
			||||||
 | 
					    await Promise.all(users.map(async (u, i) => {
 | 
				
			||||||
 | 
					      // wait for all users to set the flush counter to the same value
 | 
				
			||||||
 | 
					      await new Promise(resolve => {
 | 
				
			||||||
 | 
					        function observer () {
 | 
				
			||||||
 | 
					          var allUsersReceivedUpdate = true
 | 
				
			||||||
 | 
					          for (var i = 0; i < users.length; i++) {
 | 
				
			||||||
 | 
					            if (u.share.flushHelper.get(i + '') !== flushCounter) {
 | 
				
			||||||
 | 
					              allUsersReceivedUpdate = false
 | 
				
			||||||
 | 
					              break
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          if (allUsersReceivedUpdate) {
 | 
				
			||||||
 | 
					            resolve()
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        u.share.flushHelper.observe(observer)
 | 
				
			||||||
 | 
					        u.share.flushHelper.set(i + '', flushCounter)
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function flushSome (t, users) {
 | 
				
			||||||
 | 
					  if (users[0].connector.testRoom == null) {
 | 
				
			||||||
 | 
					    // if not test-connector, wait for some time for operations to arrive
 | 
				
			||||||
 | 
					    await wait(100)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function wait (t) {
 | 
				
			||||||
 | 
					  return new Promise(function (resolve) {
 | 
				
			||||||
 | 
					    setTimeout(resolve, t != null ? t : 100)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										151
									
								
								tests-lib/test-connector.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								tests-lib/test-connector.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,151 @@
 | 
				
			|||||||
 | 
					/* global Y */
 | 
				
			||||||
 | 
					import { wait } from './helper.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var rooms = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class TestRoom {
 | 
				
			||||||
 | 
					  constructor (roomname) {
 | 
				
			||||||
 | 
					    this.room = roomname
 | 
				
			||||||
 | 
					    this.users = {}
 | 
				
			||||||
 | 
					    this.nextUserId = 0
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  join (connector) {
 | 
				
			||||||
 | 
					    if (connector.userId == null) {
 | 
				
			||||||
 | 
					      connector.setUserId('' + (this.nextUserId++))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    Object.keys(this.users).forEach(uid => {
 | 
				
			||||||
 | 
					      this.users[uid].userJoined(connector.userId, 'master')
 | 
				
			||||||
 | 
					      connector.userJoined(uid, 'master')
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    this.users[connector.userId] = connector
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  leave (connector) {
 | 
				
			||||||
 | 
					    delete this.users[connector.userId]
 | 
				
			||||||
 | 
					    Object.keys(this.users).forEach(uid => {
 | 
				
			||||||
 | 
					      this.users[uid].userLeft(connector.userId)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  send (sender, receiver, m) {
 | 
				
			||||||
 | 
					    m = JSON.parse(JSON.stringify(m))
 | 
				
			||||||
 | 
					    var user = this.users[receiver]
 | 
				
			||||||
 | 
					    if (user != null) {
 | 
				
			||||||
 | 
					      user.receiveMessage(sender, m)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  broadcast (sender, m) {
 | 
				
			||||||
 | 
					    Object.keys(this.users).forEach(receiver => {
 | 
				
			||||||
 | 
					      this.send(sender, receiver, m)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  async flushAll (users) {
 | 
				
			||||||
 | 
					    let flushing = true
 | 
				
			||||||
 | 
					    let allUserIds = Object.keys(this.users)
 | 
				
			||||||
 | 
					    if (users == null) {
 | 
				
			||||||
 | 
					      users = allUserIds.map(id => this.users[id].y)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    while (flushing) {
 | 
				
			||||||
 | 
					      let res = await Promise.all(allUserIds.map(id => this.users[id]._flushAll(users)))
 | 
				
			||||||
 | 
					      flushing = res.some(status => status === 'flushing')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getTestRoom (roomname) {
 | 
				
			||||||
 | 
					  if (rooms[roomname] == null) {
 | 
				
			||||||
 | 
					    rooms[roomname] = new TestRoom(roomname)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return rooms[roomname]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function extendTestConnector (Y) {
 | 
				
			||||||
 | 
					  class TestConnector extends Y.AbstractConnector {
 | 
				
			||||||
 | 
					    constructor (y, options) {
 | 
				
			||||||
 | 
					      if (options === undefined) {
 | 
				
			||||||
 | 
					        throw new Error('Options must not be undefined!')
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (options.room == null) {
 | 
				
			||||||
 | 
					        throw new Error('You must define a room name!')
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      options.role = 'slave'
 | 
				
			||||||
 | 
					      super(y, options)
 | 
				
			||||||
 | 
					      this.options = options
 | 
				
			||||||
 | 
					      this.room = options.room
 | 
				
			||||||
 | 
					      this.chance = options.chance
 | 
				
			||||||
 | 
					      this.testRoom = getTestRoom(this.room)
 | 
				
			||||||
 | 
					      this.testRoom.join(this)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    disconnect () {
 | 
				
			||||||
 | 
					      this.testRoom.leave(this)
 | 
				
			||||||
 | 
					      return super.disconnect()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    reconnect () {
 | 
				
			||||||
 | 
					      this.testRoom.join(this)
 | 
				
			||||||
 | 
					      return super.reconnect()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    send (uid, message) {
 | 
				
			||||||
 | 
					      this.testRoom.send(this.userId, uid, message)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    broadcast (message) {
 | 
				
			||||||
 | 
					      this.testRoom.broadcast(this.userId, message)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    async whenSynced (f) {
 | 
				
			||||||
 | 
					      var synced = false
 | 
				
			||||||
 | 
					      var periodicFlushTillSync = () => {
 | 
				
			||||||
 | 
					        if (synced) {
 | 
				
			||||||
 | 
					          f()
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          this.testRoom.flushAll([this.y]).then(function () {
 | 
				
			||||||
 | 
					            setTimeout(periodicFlushTillSync, 10)
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      periodicFlushTillSync()
 | 
				
			||||||
 | 
					      return super.whenSynced(function () {
 | 
				
			||||||
 | 
					        synced = true
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    receiveMessage (sender, m) {
 | 
				
			||||||
 | 
					      if (this.userId !== sender && this.connections[sender] != null) {
 | 
				
			||||||
 | 
					        var buffer = this.connections[sender].buffer
 | 
				
			||||||
 | 
					        if (buffer == null) {
 | 
				
			||||||
 | 
					          buffer = this.connections[sender].buffer = []
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        buffer.push(m)
 | 
				
			||||||
 | 
					        if (this.chance.bool({likelihood: 30})) {
 | 
				
			||||||
 | 
					          // flush 1/2 with 30% chance
 | 
				
			||||||
 | 
					          var flushLength = Math.round(buffer.length / 2)
 | 
				
			||||||
 | 
					          buffer.splice(0, flushLength).forEach(m => {
 | 
				
			||||||
 | 
					            super.receiveMessage(sender, m)
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    async _flushAll (flushUsers) {
 | 
				
			||||||
 | 
					      if (flushUsers.some(u => u.connector.userId === this.userId)) {
 | 
				
			||||||
 | 
					        // this one needs to sync with every other user
 | 
				
			||||||
 | 
					        flushUsers = Object.keys(this.connections).map(id => this.testRoom.users[id].y)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      var finished = []
 | 
				
			||||||
 | 
					      for (let i = 0; i < flushUsers.length; i++) {
 | 
				
			||||||
 | 
					        let userId = flushUsers[i].connector.userId
 | 
				
			||||||
 | 
					        if (userId === this.userId) continue
 | 
				
			||||||
 | 
					        let buffer = this.connections[userId].buffer
 | 
				
			||||||
 | 
					        if (buffer != null) {
 | 
				
			||||||
 | 
					          var messages = buffer.splice(0)
 | 
				
			||||||
 | 
					          for (let j = 0; j < messages.length; j++) {
 | 
				
			||||||
 | 
					            let p = super.receiveMessage(userId, messages[j])
 | 
				
			||||||
 | 
					            finished.push(p)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      await Promise.all(finished)
 | 
				
			||||||
 | 
					      await this.y.db.whenTransactionsFinished()
 | 
				
			||||||
 | 
					      return finished.length > 0 ? 'flushing' : 'done'
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  Y.extend('test', TestConnector)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (typeof Y !== 'undefined') {
 | 
				
			||||||
 | 
					  extendTestConnector(Y)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user