-
-
Save seancribbs/769841 to your computer and use it in GitHub Desktop.
$ node ts.js | |
10 Jan 15:01:23 - prepared | |
10 Jan 15:01:24 - started | |
PUT /riak/airlines/KLM | |
10 Jan 15:01:24 - saved | |
10 Jan 15:01:24 - cleared | |
GET /riak/airlines/KLM | |
10 Jan 15:01:24 - not found! it works |
sys = require 'sys' | |
{spawn} = require 'child_process' | |
fs = require 'fs' | |
path = require 'path' | |
EventEmitter = require('events').EventEmitter | |
Utils = require './utils' | |
erlangPath = path.normalize("#{__dirname}/../erl_src") | |
tempPath = path.normalize("#{process.cwd()}/.riaktest") | |
# Ported from the Ruby riak-client | |
class TestServer extends EventEmitter | |
@defaults = | |
appConfig: | |
riak_core: | |
web_ip: "127.0.0.1" | |
web_port: 9000 | |
handoff_port: 9001 | |
ring_creation_size: 64 | |
riak_kv: | |
storage_backend: {atom: "riak_kv_test_backend"} | |
pb_ip: "127.0.0.1" | |
pb_port: 9002 | |
js_vm_count: 8 | |
js_max_vm_mem: 8 | |
js_thread_stack: 16 | |
riak_kv_stat: true | |
luwak: | |
enabled: false | |
sasl: | |
errlog_type: {atom: "error"} | |
vmArgs: | |
"-name": "riaktest#{Math.floor(Math.random()*100000000000)}@127.0.0.1" | |
"-setcookie": "riak-js-test" | |
"+K": true | |
"+A": 64 | |
"-smp": "enable" | |
"-env ERL_MAX_PORTS": 4096 | |
"-env ERL_FULLSWEEP_AFTER": 0 | |
"-pa": erlangPath | |
tempDir: tempPath | |
constructor: (options) -> | |
@options = Utils.mixin true, {}, TestServer.defaults, options | |
@options.appConfig.riak_core.ring_state_dir = "#{@options.tempDir}/data/ring" | |
@options.binDir = path.normalize(@options.binDir) | |
@erlangPrompt = new RegExp("^.#{@options.vmArgs['-name']}.\\d+>", "m") | |
prepare: (callback) -> | |
unless @prepared? | |
@createTempDirectories => | |
@riakScript = "#{@temp_bin}/riak" | |
@writeRiakScript => | |
@writeVmArgs => | |
@writeAppConfig => | |
@prepared = true | |
callback() if callback | |
start: (callback) -> | |
if @prepared and not @started and @listeners('erlangPrompt').length is 0 | |
setStarted = => | |
@started = true | |
callback() if callback | |
@once 'erlangPrompt', setStarted | |
@console = spawn(@riakScript, ["console"]) | |
@console.on 'exit', @registerStop | |
@console.stdout.setEncoding("ascii") | |
@console.stderr.setEncoding("ascii") | |
# do the work of what we get from expect() in Ruby | |
@console.stdout.on 'data', (data) => | |
unless data.search(@erlangPrompt) is -1 | |
@emit('erlangPrompt') | |
if @options.debug | |
@console.stderr.on 'data', sys.debug | |
@console.stdout.on 'data', sys.debug | |
process.on 'exit', => | |
@console.kill('SIGKILL') | |
@registerStop() | |
stop: -> | |
if @started and @listeners('erlangPrompt').length is 0 | |
@console.stdin.write("init:stop().\n", "ascii") | |
clear: (callback) -> | |
if @started and @listeners('erlangPrompt').length is 0 | |
setStarted = => | |
@started = true | |
callback() if callback | |
sendReset = => | |
@once 'erlangPrompt', setStarted | |
@started = false | |
@console.stdin.write("riak_kv_test_backend:reset().\n", "ascii") | |
@once 'erlangPrompt', sendReset | |
@console.stdin.write("ok.\n", "ascii") | |
registerStop: -> | |
@removeAllListeners('erlangPrompt') | |
delete @console | |
@started = false | |
createTempDirectories: (callback) -> | |
subdirs = for dir in ['bin', 'etc', 'log', 'data', 'data/ring', 'pipe'] | |
this["temp_#{dir}"] = path.normalize("#{@options.tempDir}/#{dir}") | |
subdirs.unshift @options.tempDir | |
createDir = => | |
if subdirs.length is 0 | |
callback() | |
else | |
currDir = subdirs.shift() | |
fs.mkdir currDir, 0700, createDir | |
rmrf = spawn("rm", ["-rf", @options.tempDir]) | |
rmrf.on 'exit', createDir | |
writeRiakScript: (callback) -> | |
outScript = fs.createWriteStream @riakScript, {encoding: 'utf8', mode: 0700} | |
inScript = fs.createReadStream "#{@options.binDir}/riak", encoding: 'utf8' | |
inScript.on 'error', (err) -> | |
sys.debug "error reading from #{inScript.path}:\n#{sys.inspect(err, true, null)}" | |
throw err | |
outScript.on 'error', (err) -> | |
sys.debug "error writing to #{outScript.path} script:\n#{sys.inspect(err, true, null)}" | |
throw err | |
outScript.on 'drain', -> inScript.resume() | |
inScript.on 'data', (data) => | |
data = data.toString('utf8') if Buffer.isBuffer(data) | |
data = data.replace(/(RUNNER_SCRIPT_DIR=)(.*)$/m, "$1#{@temp_bin}") | |
data = data.replace(/(RUNNER_ETC_DIR=)(.*)$/m, "$1#{@temp_etc}") | |
data = data.replace(/(RUNNER_USER=)(.*)$/m, "$1") | |
data = data.replace(/(RUNNER_LOG_DIR=)(.*)$/m, "$1#{@temp_log}") | |
data = data.replace(/(PIPE_DIR=)(.*)$/m, "$1#{@temp_pipe}") | |
data = data.replace("RUNNER_BASE_DIR=${RUNNER_SCRIPT_DIR%/*}", "RUNNER_BASE_DIR=#{path.normalize(@options.binDir + '/..')}") | |
outScript.write data | |
inScript.pause() | |
inScript.on 'end', -> | |
outScript.end() | |
callback() if callback | |
writeVmArgs: (callback) -> | |
vmArgs = for own option, value of @options.vmArgs | |
"#{option} #{value}" | |
vmArgs = vmArgs.join("\n") | |
fs.writeFile("#{@temp_etc}/vm.args", vmArgs, callback) | |
writeAppConfig: (callback) -> | |
appConfig = @toErlangConfig(@options.appConfig) + "." | |
fs.writeFile("#{@temp_etc}/app.config", appConfig, callback) | |
# Converts an object into app.config-compatible Erlang terms | |
toErlangConfig: (object, depth = 1) -> | |
padding = (' ' for num in [1..depth]).join "" | |
parentPadding = if depth <= 1 | |
'' | |
else | |
(' ' for num in [1..(depth-1)]).join "" | |
values = for own key, value of object | |
if value.atom? | |
printable = value.atom | |
else if typeof value is 'string' | |
printable = "\"#{value}\"" | |
else if value instanceof Object | |
printable = @toErlangConfig(value, depth+1) | |
else | |
printable = value.toString() | |
"{#{key}, #{printable}}" | |
values = values.join(",\n#{padding}") | |
"[\n#{padding}#{values}\n#{parentPadding}]" | |
# Node v0.2.6 doesn't have EventEmitter.once | |
once: (type, listener) -> | |
callback = => | |
@removeListener(type, callback) | |
listener.apply(this, arguments) | |
@on type, callback | |
this | |
module.exports = TestServer |
var sys = require('sys') | |
var TestServer = require("./lib/test_server") | |
var ts = new TestServer({binDir: "/Users/sean/Development/riak/rel/riak/bin", debug: false}) | |
process.on("exit", function(){ | |
ts.stop() | |
}) | |
ts.prepare(function(){ | |
sys.log("prepared") | |
ts.start(function(){ | |
sys.log("started") | |
var db = require('riak-js').getClient({port: 9000}) | |
db.save('airlines', 'KLM', | |
{fleet: 111, country: 'NL'}, | |
{ links: | |
[{ bucket: 'flights', key: 'KLM-8098', tag: 'cargo' }, | |
{ bucket: 'flights', key: 'KLM-1196', tag: 'passenger' }] | |
}, function(err){ | |
if(err) throw err; | |
sys.log("saved") | |
ts.clear(function(){ | |
sys.log("cleared") | |
db.get("airlines", "KLM", function(err){ | |
if(err && err.notFound){ | |
sys.log("not found! it works") | |
} | |
process.exit(0) | |
}) | |
}) | |
} | |
) | |
}) | |
}) |
Any sample usage I could help to wrap my head around this with? Never really used coffeescript much except for reading / understanding other people's libraries >.<
Seems like a lot of your event bookkeeping could be simplified by using EventEmitter.once instead of using EventEmitter.on and removing the listeners yourself. Interesting approach, I'll let you know if I see any other optimizations.
@indexzero Thanks for the tip about once, as you can see I'm a node newb.
The idea of the code is to run Riak as a subprocess with an in-memory backend that can be easily cleared at the end of a test-suite, either after each example or after a section of examples. It's a port of https://github.com/seancribbs/ripple/blob/master/riak-client/lib/riak/test_server.rb
@frank06 I meant that I'm simulating blocking operations by adding/removing listeners and avoiding critical sections by checking the listener list. Seems hackish to me...
Still, thanks for your comments. It's almost working, will try to finish Monday.