decaffeinate: Convert run.coffee to JS

This commit is contained in:
decaffeinate 2020-05-06 12:12:55 +02:00 committed by Tim Alby
parent b6cc463a1e
commit 3b6c0d8ca6

View file

@ -1,209 +1,284 @@
DocUpdaterClient = require "../../acceptance/coffee/helpers/DocUpdaterClient" /*
# MockTrackChangesApi = require "../../acceptance/js/helpers/MockTrackChangesApi" * decaffeinate suggestions:
# MockWebApi = require "../../acceptance/js/helpers/MockWebApi" * DS101: Remove unnecessary use of Array.from
assert = require "assert" * DS102: Remove unnecessary code created because of implicit returns
async = require "async" * DS202: Simplify dynamic range loops
* DS205: Consider reworking code to avoid use of IIFEs
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const DocUpdaterClient = require("../../acceptance/coffee/helpers/DocUpdaterClient");
// MockTrackChangesApi = require "../../acceptance/js/helpers/MockTrackChangesApi"
// MockWebApi = require "../../acceptance/js/helpers/MockWebApi"
const assert = require("assert");
const async = require("async");
insert = (string, pos, content) -> const insert = function(string, pos, content) {
result = string.slice(0, pos) + content + string.slice(pos) const result = string.slice(0, pos) + content + string.slice(pos);
return result return result;
};
transform = (op1, op2) -> const transform = function(op1, op2) {
if op2.p < op1.p if (op2.p < op1.p) {
return { return {
p: op1.p + op2.i.length p: op1.p + op2.i.length,
i: op1.i i: op1.i
};
} else {
return op1;
} }
else };
return op1
class StressTestClient class StressTestClient {
constructor: (@options = {}) -> constructor(options) {
@options.updateDelay ?= 200 if (options == null) { options = {}; }
@project_id = @options.project_id or DocUpdaterClient.randomId() this.options = options;
@doc_id = @options.doc_id or DocUpdaterClient.randomId() if (this.options.updateDelay == null) { this.options.updateDelay = 200; }
@pos = @options.pos or 0 this.project_id = this.options.project_id || DocUpdaterClient.randomId();
@content = @options.content or "" this.doc_id = this.options.doc_id || DocUpdaterClient.randomId();
this.pos = this.options.pos || 0;
this.content = this.options.content || "";
@client_id = DocUpdaterClient.randomId() this.client_id = DocUpdaterClient.randomId();
@version = @options.version or 0 this.version = this.options.version || 0;
@inflight_op = null this.inflight_op = null;
@charCode = 0 this.charCode = 0;
@counts = { this.counts = {
conflicts: 0 conflicts: 0,
local_updates: 0 local_updates: 0,
remote_updates: 0 remote_updates: 0,
max_delay: 0 max_delay: 0
};
DocUpdaterClient.subscribeToAppliedOps((channel, update) => {
update = JSON.parse(update);
if (update.error != null) {
console.error(new Error(`Error from server: '${update.error}'`));
return;
}
if (update.doc_id === this.doc_id) {
return this.processReply(update);
}
});
} }
DocUpdaterClient.subscribeToAppliedOps (channel, update) => sendUpdate() {
update = JSON.parse(update) const data = String.fromCharCode(65 + (this.charCode++ % 26));
if update.error? this.content = insert(this.content, this.pos, data);
console.error new Error("Error from server: '#{update.error}'") this.inflight_op = {
return i: data,
if update.doc_id == @doc_id p: this.pos++
@processReply(update) };
this.resendUpdate();
sendUpdate: () -> return this.inflight_op_sent = Date.now();
data = String.fromCharCode(65 + @charCode++ % 26)
@content = insert(@content, @pos, data)
@inflight_op = {
i: data
p: @pos++
} }
@resendUpdate()
@inflight_op_sent = Date.now()
resendUpdate: () -> resendUpdate() {
assert(@inflight_op?) assert(this.inflight_op != null);
DocUpdaterClient.sendUpdate( DocUpdaterClient.sendUpdate(
@project_id, @doc_id this.project_id, this.doc_id,
{ {
doc: @doc_id doc: this.doc_id,
op: [@inflight_op] op: [this.inflight_op],
v: @version v: this.version,
meta: meta: {
source: @client_id source: this.client_id
dupIfSource: [@client_id] },
dupIfSource: [this.client_id]
}
);
return this.update_timer = setTimeout(() => {
console.log(`[${new Date()}] \t[${this.client_id.slice(0,4)}] WARN: Resending update after 5 seconds`);
return this.resendUpdate();
}
, 5000);
} }
)
@update_timer = setTimeout () =>
console.log "[#{new Date()}] \t[#{@client_id.slice(0,4)}] WARN: Resending update after 5 seconds"
@resendUpdate()
, 5000
processReply: (update) -> processReply(update) {
if update.op.v != @version if (update.op.v !== this.version) {
if update.op.v < @version if (update.op.v < this.version) {
console.log "[#{new Date()}] \t[#{@client_id.slice(0,4)}] WARN: Duplicate ack (already seen version)" console.log(`[${new Date()}] \t[${this.client_id.slice(0,4)}] WARN: Duplicate ack (already seen version)`);
return return;
else } else {
console.error "[#{new Date()}] \t[#{@client_id.slice(0,4)}] ERROR: Version jumped ahead (client: #{@version}, op: #{update.op.v})" console.error(`[${new Date()}] \t[${this.client_id.slice(0,4)}] ERROR: Version jumped ahead (client: ${this.version}, op: ${update.op.v})`);
@version++ }
if update.op.meta.source == @client_id }
if @inflight_op? this.version++;
@counts.local_updates++ if (update.op.meta.source === this.client_id) {
@inflight_op = null if (this.inflight_op != null) {
clearTimeout @update_timer this.counts.local_updates++;
delay = Date.now() - @inflight_op_sent this.inflight_op = null;
@counts.max_delay = Math.max(@counts.max_delay, delay) clearTimeout(this.update_timer);
@continue() const delay = Date.now() - this.inflight_op_sent;
else this.counts.max_delay = Math.max(this.counts.max_delay, delay);
console.log "[#{new Date()}] \t[#{@client_id.slice(0,4)}] WARN: Duplicate ack" return this.continue();
else } else {
assert(update.op.op.length == 1) return console.log(`[${new Date()}] \t[${this.client_id.slice(0,4)}] WARN: Duplicate ack`);
@counts.remote_updates++ }
external_op = update.op.op[0] } else {
if @inflight_op? assert(update.op.op.length === 1);
@counts.conflicts++ this.counts.remote_updates++;
@inflight_op = transform(@inflight_op, external_op) let external_op = update.op.op[0];
external_op = transform(external_op, @inflight_op) if (this.inflight_op != null) {
if external_op.p < @pos this.counts.conflicts++;
@pos += external_op.i.length this.inflight_op = transform(this.inflight_op, external_op);
@content = insert(@content, external_op.p, external_op.i) external_op = transform(external_op, this.inflight_op);
}
if (external_op.p < this.pos) {
this.pos += external_op.i.length;
}
return this.content = insert(this.content, external_op.p, external_op.i);
}
}
continue: () -> continue() {
if @updateCount > 0 if (this.updateCount > 0) {
@updateCount-- this.updateCount--;
setTimeout () => return setTimeout(() => {
@sendUpdate() return this.sendUpdate();
, @options.updateDelay * ( 0.5 + Math.random() ) }
else , this.options.updateDelay * ( 0.5 + Math.random() ));
@updateCallback() } else {
return this.updateCallback();
}
}
runForNUpdates: (n, callback = (error) ->) -> runForNUpdates(n, callback) {
@updateCallback = callback if (callback == null) { callback = function(error) {}; }
@updateCount = n this.updateCallback = callback;
@continue() this.updateCount = n;
return this.continue();
}
check: (callback = (error) ->) -> check(callback) {
DocUpdaterClient.getDoc @project_id, @doc_id, (error, res, body) => if (callback == null) { callback = function(error) {}; }
throw error if error? return DocUpdaterClient.getDoc(this.project_id, this.doc_id, (error, res, body) => {
if !body.lines? if (error != null) { throw error; }
return console.error "[#{new Date()}] \t[#{@client_id.slice(0,4)}] ERROR: Invalid response from get doc (#{doc_id})", body if ((body.lines == null)) {
content = body.lines.join("\n") return console.error(`[${new Date()}] \t[${this.client_id.slice(0,4)}] ERROR: Invalid response from get doc (${doc_id})`, body);
version = body.version }
if content != @content const content = body.lines.join("\n");
if version == @version const {
console.error "[#{new Date()}] \t[#{@client_id.slice(0,4)}] Error: Client content does not match server." version
console.error "Server: #{content.split('a')}" } = body;
console.error "Client: #{@content.split('a')}" if (content !== this.content) {
else if (version === this.version) {
console.error "[#{new Date()}] \t[#{@client_id.slice(0,4)}] Error: Version mismatch (Server: '#{version}', Client: '#{@version}')" console.error(`[${new Date()}] \t[${this.client_id.slice(0,4)}] Error: Client content does not match server.`);
console.error(`Server: ${content.split('a')}`);
console.error(`Client: ${this.content.split('a')}`);
} else {
console.error(`[${new Date()}] \t[${this.client_id.slice(0,4)}] Error: Version mismatch (Server: '${version}', Client: '${this.version}')`);
}
}
if !@isContentValid(@content) if (!this.isContentValid(this.content)) {
for chunk, i in @content.split("") const iterable = this.content.split("");
if chunk? and chunk != "a" for (let i = 0; i < iterable.length; i++) {
console.log chunk, i const chunk = iterable[i];
throw new Error("bad content") if ((chunk != null) && (chunk !== "a")) {
callback() console.log(chunk, i);
}
}
throw new Error("bad content");
}
return callback();
});
}
isChunkValid: (chunk) -> isChunkValid(chunk) {
char = 0 const char = 0;
for letter, i in chunk for (let i = 0; i < chunk.length; i++) {
if letter.charCodeAt(0) != 65 + i % 26 const letter = chunk[i];
console.error "[#{new Date()}] \t[#{@client_id.slice(0,4)}] Invalid Chunk:", chunk if (letter.charCodeAt(0) !== (65 + (i % 26))) {
return false console.error(`[${new Date()}] \t[${this.client_id.slice(0,4)}] Invalid Chunk:`, chunk);
return true return false;
}
}
return true;
}
isContentValid: (content) -> isContentValid(content) {
for chunk in content.split('a') for (let chunk of Array.from(content.split('a'))) {
if chunk? and chunk != "" if ((chunk != null) && (chunk !== "")) {
if !@isChunkValid(chunk) if (!this.isChunkValid(chunk)) {
console.error "[#{new Date()}] \t[#{@client_id.slice(0,4)}] Invalid content", content console.error(`[${new Date()}] \t[${this.client_id.slice(0,4)}] Invalid content`, content);
return false return false;
return true }
}
}
return true;
}
}
checkDocument = (project_id, doc_id, clients, callback = (error) ->) -> const checkDocument = function(project_id, doc_id, clients, callback) {
jobs = clients.map (client) -> if (callback == null) { callback = function(error) {}; }
(cb) -> client.check cb const jobs = clients.map(client => cb => client.check(cb));
async.parallel jobs, callback return async.parallel(jobs, callback);
};
printSummary = (doc_id, clients) -> const printSummary = function(doc_id, clients) {
slot = require('cluster-key-slot') const slot = require('cluster-key-slot');
now = new Date() const now = new Date();
console.log "[#{now}] [#{doc_id.slice(0,4)} (slot: #{slot(doc_id)})] #{clients.length} clients..." console.log(`[${now}] [${doc_id.slice(0,4)} (slot: ${slot(doc_id)})] ${clients.length} clients...`);
for client in clients return (() => {
console.log "[#{now}] \t[#{client.client_id.slice(0,4)}] { local: #{client.counts.local_updates }, remote: #{client.counts.remote_updates}, conflicts: #{client.counts.conflicts}, max_delay: #{client.counts.max_delay} }" const result = [];
client.counts = { for (let client of Array.from(clients)) {
local_updates: 0 console.log(`[${now}] \t[${client.client_id.slice(0,4)}] { local: ${client.counts.local_updates }, remote: ${client.counts.remote_updates}, conflicts: ${client.counts.conflicts}, max_delay: ${client.counts.max_delay} }`);
remote_updates: 0 result.push(client.counts = {
conflicts: 0 local_updates: 0,
remote_updates: 0,
conflicts: 0,
max_delay: 0 max_delay: 0
});
}
return result;
})();
};
const CLIENT_COUNT = parseInt(process.argv[2], 10);
const UPDATE_DELAY = parseInt(process.argv[3], 10);
const SAMPLE_INTERVAL = parseInt(process.argv[4], 10);
for (let doc_and_project_id of Array.from(process.argv.slice(5))) {
(function(doc_and_project_id) {
const [project_id, doc_id] = Array.from(doc_and_project_id.split(":"));
console.log({project_id, doc_id});
return DocUpdaterClient.setDocLines(project_id, doc_id, [(new Array(CLIENT_COUNT + 2)).join('a')], null, null, function(error) {
if (error != null) { throw error; }
return DocUpdaterClient.getDoc(project_id, doc_id, (error, res, body) => {
let runBatch;
if (error != null) { throw error; }
if ((body.lines == null)) {
return console.error(`[${new Date()}] ERROR: Invalid response from get doc (${doc_id})`, body);
}
const content = body.lines.join("\n");
const {
version
} = body;
const clients = [];
for (let pos = 1, end = CLIENT_COUNT, asc = 1 <= end; asc ? pos <= end : pos >= end; asc ? pos++ : pos--) {
(function(pos) {
const client = new StressTestClient({doc_id, project_id, content, pos, version, updateDelay: UPDATE_DELAY});
return clients.push(client);
})(pos);
} }
CLIENT_COUNT = parseInt(process.argv[2], 10) return (runBatch = function() {
UPDATE_DELAY = parseInt(process.argv[3], 10) const jobs = clients.map(client => cb => client.runForNUpdates(SAMPLE_INTERVAL / UPDATE_DELAY, cb));
SAMPLE_INTERVAL = parseInt(process.argv[4], 10) return async.parallel(jobs, function(error) {
if (error != null) { throw error; }
for doc_and_project_id in process.argv.slice(5) printSummary(doc_id, clients);
do (doc_and_project_id) -> return checkDocument(project_id, doc_id, clients, function(error) {
[project_id, doc_id] = doc_and_project_id.split(":") if (error != null) { throw error; }
console.log {project_id, doc_id} return runBatch();
DocUpdaterClient.setDocLines project_id, doc_id, [(new Array(CLIENT_COUNT + 2)).join('a')], null, null, (error) -> });
throw error if error? });
DocUpdaterClient.getDoc project_id, doc_id, (error, res, body) => })();
throw error if error? });
if !body.lines? });
return console.error "[#{new Date()}] ERROR: Invalid response from get doc (#{doc_id})", body })(doc_and_project_id);
content = body.lines.join("\n") }
version = body.version
clients = []
for pos in [1..CLIENT_COUNT]
do (pos) ->
client = new StressTestClient({doc_id, project_id, content, pos: pos, version: version, updateDelay: UPDATE_DELAY})
clients.push client
do runBatch = () ->
jobs = clients.map (client) ->
(cb) -> client.runForNUpdates(SAMPLE_INTERVAL / UPDATE_DELAY, cb)
async.parallel jobs, (error) ->
throw error if error?
printSummary(doc_id, clients)
checkDocument project_id, doc_id, clients, (error) ->
throw error if error?
runBatch()