New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

weaver

Package Overview
Dependencies
Maintainers
1
Versions
19
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

weaver - npm Package Compare versions

Comparing version
0.2.3
to
0.3.0
+353
lib/task.coffee
assert = require('assert')
resolve = require('path').resolve
fork = require('child_process').spawn
EventEmitter = require('events').EventEmitter
Watcher = require('./watcher')
###*
#* Status codes
#* R - restart
#* E - error
#* D - done (clean exit)
#* W - work in progress
#* S - stopped
###
# Which subtask parameters can be changed without restart
mutable =
count : yes
source : no
cwd : no
env : no
persistent : yes
executable : no
timeout : yes
runtime : yes
watch : yes
arguments : no
class Task extends EventEmitter
@tasks: Object.create(null)
@create: (name) ->
return @tasks[name] ?= new Task(name)
@destroy: (name) ->
if name of @tasks
Watcher.stop(@tasks[name].watchHandler)
delete @tasks[name]
return
@status: ->
now = Date.now()
status = {}
for name, task of @tasks
status[name] =
count : task.count
source : task.source
restart : task.restart
subtasks : task.subtasks.map (subtask) ->
pid : subtask.pid
args : subtask.args
status : subtask.status
uptime : now - subtask.start
return status
log: ->
active: yes
constructor: (@name) ->
@subtasks = []
@watchHandler = (error) =>
if error
@emit('error', error)
else
@restartSubtasks()
return
@on('exit', @exitHandler)
return @
# Upgrade task
upgrade: (options = {}) ->
restartRequired = no
for own key of mutable
try
assert.deepEqual(@[key], options[key])
catch change
@upgradeParameter(key, options[key])
# Force restart when one of non-mutable keys gets modified
restartRequired = restartRequired or not mutable[key]
# Restart existing
if restartRequired and @subtasks.length
@log("Restart required for #{@name} task group")
@restartSubtasks()
# Spawn required
for index in [0...(@count or 0)]
subtask = @subtasks[index]
if not subtask or (subtask.status is 'R' and not subtask.pid)
@spawn(index)
# Kill redundant
while @subtasks.length > (@count or 0)
@stopSubtask(@subtasks.pop())
return
# Upgrade task parameter with given value
upgradeParameter: (key, value) ->
if value?
@[key] = value
else
delete @[key]
switch key
when 'watch'
Watcher.stop(@watchHandler)
Watcher.start(@cwd, @watch or [], @watchHandler)
return
# Spawn subtask
spawn: (id) ->
args = @arguments or []
binary = process.execPath
subtask =
id : id
status : 'W'
name : @name
start : Date.now()
env : @expandEnv()
subtask.args = for argument in args
if Array.isArray(argument) then argument[id] else argument
eargs = subtask.args.slice()
if @executable
binary = @source
else
eargs.unshift(@source)
subtask.process = fork(binary, eargs, {
stdio : 'pipe'
cwd : resolve(@cwd)
env : subtask.env
})
subtask.pid = subtask.process.pid or 0
if subtask.pid
# Setup logger
subtask.process.stdout.on('data', @logHandler.bind(@, subtask.pid))
subtask.process.stderr.on('data', @logHandler.bind(@, subtask.pid))
# Setup exit handler
subtask.process.once('exit', @emit.bind(@, 'exit', subtask))
@log("Task #{subtask.pid} (#{@name}) spawned")
subtask.process.once('error', (error) =>
subtask.status = 'E'
subtask.code = 255
subtask.pid = 0
@emit('error', error)
@log("Failed to start task (#{@name})")
)
@subtasks[id] = subtask
return
# Call fn for each subtask
foreach: (fn, argument) ->
for subtask in @subtasks
fn.call(@, subtask, argument)
return
# Kill subtask with signal
killSubtask: (subtask, signal) ->
if subtask and subtask.pid
try
subtask.process.kill(signal)
catch error
@log("Failed to kill #{subtask.pid} (#{subtask.name}) with #{signal}")
return
# Stop subtask
stopSubtask: (subtask) ->
if subtask and subtask.pid
subtask.process.kill('SIGINT')
setTimeout((->
if subtask.pid
subtask.process.kill('SIGTERM')
), @timeout or 1000)
# Restart subtask
restartSubtask: (subtask) ->
if subtask
subtask.status = 'R'
@stopSubtask(subtask)
return
# Get subtask by pid
getPID: (pid) ->
for subtask in @subtasks when subtask and subtask.pid is pid
return subtask
return
# Kill subtask by pid with signal
killPID: (pid, signal) ->
if pid?
@killSubtask(@getPID(pid), signal)
else
@killSubtasks(signal)
return
# Restart subtask by pid
restartPID: (pid) ->
if pid?
@restartSubtask(@getPID(pid))
else
@restartSubtasks()
return
# Stop subtask by pid
stopPID: (pid) ->
if pid?
@stopSubtask(@getPID(pid))
else
@stopSubtasks()
return
# Kill all subtasks
killSubtasks: ->
@foreach(@killSubtask)
return
# Restart all subtasks
restartSubtasks: ->
@foreach(@restartSubtask)
return
# Stop all subtasks
stopSubtasks: ->
@foreach(@stopSubtask)
return
# Drop task
dropSubtasks: ->
if @active
@active = no
unless @activeSubtasks().length
Task.destroy(@name)
else
@stopSubtasks()
return
activeSubtasks: ->
return @subtasks.filter((subtask) -> subtask.pid)
exitHandler: (subtask, code, signal) ->
restartRequired = @persistent
if code is null
@log("Task #{subtask.pid} (#{@name}) was killed by #{signal}")
else
@log("Task #{subtask.pid} (#{@name}) exited with code #{code}")
subtask.pid = 0
subtask.code = code
subtask.signal = signal
delete subtask.process
if subtask.status isnt 'R'
if code
subtask.status = 'E'
else if signal
subtask.status = 'S'
else
subtask.status = 'D'
if restartRequired and code
elapsed = Date.now() - subtask.start
if elapsed < (@runtime or 1000)
@log("Restart skipped after #{elapsed}ms (#{@name})")
restartRequired = no
# Restart requested
if subtask.status is 'R'
restartRequired = yes
# Task dropped
unless @active
restartRequired = no
unless @activeSubtasks().length
Task.destroy(@name)
if restartRequired
@spawn(subtask.id)
return
logHandler: (pid, data) ->
@log("#{pid} (#{@name}) #{data}")
return
expandEnv: ->
expanded = {}
expanded.HOME = process.env.HOME
expanded.PATH = process.env.PATH
unless @executable
expanded.NODE_PATH = process.env.NODE_PATH
for own key, value of @env
switch value
when true
if process.env.hasOwnProperty(key)
expanded[key] = process.env[key]
when false
delete expanded[key]
else
expanded[key] = @env[key]
return expanded
module.exports = Task
glob = require('glob')
fs = require('fs')
util = require('util')
watches = Object.create(null)
watcher = Object.create(null)
class Watcher
log: ->
start: (cwd, patterns, callback) ->
fn = @watch.bind(@, cwd, callback)
options =
cwd : cwd
nomount : yes
for pattern in patterns
glob(pattern, options, fn)
return
stop: (callback) ->
for file of watches
watches[file] = watches[file]
.filter (item) -> item isnt callback
unless watches[file]
# Stop watching file completely
watcher[file].close()
delete watches[file]
delete watcher[file]
return
watch: (cwd, callback, error, files) ->
if error
callback(error)
return
for file in files
if file[0] isnt '/'
# Expand to full path
file = cwd + '/' + file
callbacks = watches[file]
if callbacks
# Add callback to watches
callbacks.push(callback)
else
# Start watching file
watches[file] = [callback]
watcher[file] = fs.watch(file, persistent: no, @watchHandler.bind(@, file))
return
watchHandler: (file, event) ->
if file of watches
@log("File #{file} changed")
for callback in watches[file]
callback(null)
return
module.exports = new Watcher()
assert = require('assert')
resolve = require('path').resolve
zs = require('z-schema')
schema = require('./schema')
EventEmitter = require('events').EventEmitter
Task = require('./task')
Watcher = require('./watcher')
validator = new zs(strictMode: true)
class Weaver extends EventEmitter
@logger: (fn) ->
for item in [Weaver, Task, Watcher.constructor]
item::log = fn
return
version : require('../package').version
log : ->
constructor: ->
@on('error', @errorHandler)
@on('upgrade', @upgradeHandler)
@start = Date.now()
@config = Object.create(null)
return @
# Validate configuration object
validate: (configuration) ->
# Validate schema
assert.ok(validator.validate(configuration, schema), 'Invalid configuration')
# Perform additional validation
for own name, task of configuration.tasks
# Validate nested arrays for arguments
for own key of task
if key is 'arguments'
for argument in task[key]
if Array.isArray(argument)
assert.equal(
task.count, argument.length,
"Nested array in arguments should contain #{task.count} values"
)
task.name = name
return configuration
# Upgrade current state
upgrade: (data, path) ->
parts = [path]
params = null
try
# Try to parse JSON
data = JSON.parse(data)
# Validate new state
params = @validate(data)
catch error
error.message = "Config error: #{error.message}"
@emit('error', error)
if params
if params.path
parts.push(params.path)
for own name, task of params.tasks
task.cwd = resolve.apply(undefined, parts.concat(task.cwd or '.'))
@config[name] = task
@emit('upgrade')
return
# Get status report
status: ->
return Task.status()
# Execute command with given arguments
command: (action, name, args) ->
fn = Task::[action + 'PID']
unless Array.isArray(args)
args = []
unless typeof fn is 'function'
throw new Error('Unknown action ' + action)
unless name?
# Execute command for all tasks
for own name, task of Task.tasks
fn.apply(task, args)
else
task = Task.tasks[name]
if task
if action is 'kill'
args.unshift(null)
fn.apply(task, args)
else if `Number(name) == name`
args.unshift(Number(name))
@command(action, null, args)
else
@log('Task ' + name + ' was not found')
return
# Stop all subtasks and exit
die: (code) ->
code = if code? then code else 1
tryExit = =>
for own name, task of Task.tasks
for subtask in task.subtasks
return if subtask.pid
@emit('exit', code)
return
for own name, task of Task.tasks
if task.timeout > timeout
timeout = task.timeout
task.dropSubtasks()
task.on('exit', tryExit)
setImmediate(tryExit)
return
errorHandler: (error) ->
@log(error.message)
return
upgradeHandler: ->
# Spot dropped tasks
for own name, task of Task.tasks
unless name of @config
task.dropSubtasks()
# Create or update tasks
for own name, options of @config
task = Task.create(name)
# Setup error handler
unless task.listeners('error').length
task.on('error', @emit.bind(@, 'error'))
task.upgrade(options)
return
module.exports = new Weaver()
assert = require('assert')
exec = require('child_process').exec
spawn = require('child_process').spawn
write = require('fs').writeFileSync
unlink = require('fs').unlinkSync
daemon = '../bin/weaver'
port = 58015
config = "#{__dirname}/weaver_#{port}.json"
random1 = String(Math.random())
random2 = String(Math.random())
options =
cwd: __dirname
env:
PATH: process.env.PATH
RND1: random1
RND2: random2
WEAVER_TEST: 1
WEAVER_PORT: port
log = ''
monitor = spawn daemon, ['monitor'], options
monitor.stdout.on 'data', (data) -> log += String(data)
(require 'vows')
.describe('env')
.addBatch
start:
topic: ->
# Write config
write config, JSON.stringify
tasks:
dump:
count: 1
executable: yes
source: 'bin/env'
env:
RND2 : yes
RND3 : yes
HOME : no
PORT : String(port)
# Start daemon
exec "#{daemon} --config #{config}", options, @callback
return
code: (error, stdout, stderr) -> assert not error
stdout: (error, stdout, stderr) -> assert not stdout
stderr: (error, stdout, stderr) -> assert not stderr
status:
topic: ->
# Check status
exec "#{daemon} status", options, (args...) =>
args[1] = args[1]
.replace(/\n$/, '')
.split(/\n/)
.map(($_) -> +/^\s*(\d+)/.exec($_)[1])
setTimeout((=>
@callback(args...)
), 250)
return
code: (error, stdout, stderr) -> assert not error
stderr: (error, stdout, stderr) -> assert not stderr
stdout: (error, stdout, stderr) -> assert.equal stdout.length, 2
env: (error, stdout, stderr) ->
if /^(\d+) \(dump\) ({.+})/m.exec(log)
pid = RegExp.$1
env = JSON.parse(RegExp.$2)
else
assert 0, 'UDP log failed'
assert.equal env.PORT, port
assert.equal env.PATH, process.env.PATH
assert.equal env.$PID, pid
assert.equal env.RND2, random2
assert not env.hasOwnProperty 'HOME'
assert not env.hasOwnProperty 'RND1'
assert not env.hasOwnProperty 'RND3'
exit:
topic: ->
# Remove config file
unlink config
# Stop monitor
monitor.kill('SIGTERM')
# Stop daemon
exec "#{daemon} exit", options, @callback
return
code: (error, stdout, stderr) -> assert not error
stdout: (error, stdout, stderr) -> assert not stdout
stderr: (error, stdout, stderr) -> assert not stderr
.export(module)

Sorry, the diff of this file is not supported yet

# 0.3.0
# 0.3.1
* Command for runtime exception
* Test for daemon killed with signal
* Test for runtime exception
* Test for watchHandler on dropped task
* Precompile coffee
# 0.3.2
* Verbose logging
* Pass config over TCP (always)
+10
-0
# Changelog
## 0.3.0
Released 2016-09-05
* Starting script now monitors for available TCP connection
* Configuration is passed only over established TCP connection
* Daemon finishes when tasks exit, not after longest timeout
* Refactored core classes (CoffeeScript now)
* Improved uncaughtException and error handling
## 0.2.3

@@ -4,0 +14,0 @@

+1
-1

@@ -9,2 +9,2 @@ test: compile

clean:
rm t/*.js
rm -f t/*.js
{
"name" : "weaver",
"version" : "0.2.3",
"version" : "0.3.0",
"license" : "LGPL-3.0",

@@ -19,11 +19,11 @@ "author" : "Alexander Nazarov <n4kz@n4kz.com>",

"dependencies": {
"glob" : "*",
"optimist" : "*",
"sprintf" : "*",
"z-schema" : "*"
"coffee-script" : ">=1.7.1",
"glob" : "*",
"optimist" : "*",
"sprintf" : "*",
"z-schema" : "*"
},
"devDependencies": {
"coffee-script" : ">=1.6.0",
"vows" : "*"
"vows": "*"
},

@@ -30,0 +30,0 @@

@@ -1,7 +0,9 @@

assert = require('assert')
weaver = require('../lib/weaver.js')
emitter = require('events').EventEmitter
assert = require('assert')
Weaver = require('../lib/weaver.coffee')
Task = require('../lib/task.coffee')
Watcher = require('../lib/watcher.coffee')
EventEmitter = require('events').EventEmitter
methods = [
'define', 'task', 'log', 'validate', 'die', 'upgrade',
'log', 'validate', 'die', 'upgrade',
'status', 'command'

@@ -16,44 +18,47 @@ ]

constructor: ->
assert.instanceOf weaver, weaver.constructor
assert.instanceOf weaver, emitter
assert.instanceOf Weaver, Weaver.constructor
assert.instanceOf Weaver, EventEmitter
properties: ->
# version
assert.equal weaver.version, require('../package').version
assert.equal Weaver.version, require('../package').version
# start
assert.isNumber weaver.start
assert weaver.start <= Date.now()
assert weaver.start > 0
assert.isNumber Weaver.start
assert Weaver.start <= Date.now()
assert Weaver.start > 0
# tasks
assert.deepEqual weaver.tasks, {}
assert.typeOf weaver.tasks, 'object'
# config
assert.deepEqual Weaver.config, {}
assert.typeOf Weaver.config, 'object'
assert.isUndefined weaver.tasks.__proto__
assert not Weaver.config.__proto__?
# config
assert.deepEqual weaver.config, {}
assert.typeOf weaver.config, 'object'
logger: ->
assert.isFunction Weaver.constructor.logger
assert.isUndefined weaver.config.__proto__
methods: ->
for method in methods
assert.isFunction weaver[method]
assert not weaver.propertyIsEnumerable method
assert.isFunction Weaver[method]
define: ->
noop = ->
events: ->
for event in events
assert.equal EventEmitter.listenerCount(Weaver, event), 1
weaver.define 'method', 'noop', noop
watcher: ->
assert.instanceOf Watcher, Watcher.constructor
# New method defined
assert.equal weaver.noop, noop
assert not weaver.propertyIsEnumerable 'noop'
assert.isFunction Watcher.stop
assert.isFunction Watcher.start
events: ->
for event in events
assert.equal emitter.listenerCount(weaver, event), 1
task: ->
assert.isFunction Task
assert.isFunction Task.create
assert.isFunction Task.status
assert.deepEqual Task.tasks, {}
assert.typeOf Task.tasks, 'object'
assert not Task.tasks.__proto__?
.export(module)
assert = require('assert')
weaver = require('../lib/weaver.js')
emitter = require('events').EventEmitter
Task = require('../lib/task.coffee')
methods = [
'upgrade', 'upgradeParameter', 'get', 'spawn', 'foreach',
'upgrade', 'upgradeParameter', 'spawn', 'foreach',
'killSubtask', 'stopSubtask', 'restartSubtask',
'killPID', 'stopPID', 'restartPID',
'getPID', 'killPID', 'stopPID', 'restartPID',
'stopSubtasks', 'killSubtasks', 'restartSubtasks',
'log', 'exitHandler'
'exitHandler', 'expandEnv'
]
defaultName = 'test' + Date.now()
weaver.task defaultName, {}
Task
.create defaultName
.upgrade {}

@@ -22,26 +23,26 @@ (require 'vows')

name = defaultName
task = weaver.tasks[name]
task = Task.tasks[name]
assert.equal name, task.name
assert.isArray task.subtasks
assert.isArray task.watch
assert.isArray task.arguments
assert.isObject task.env
assert.isArray task.subtasks
assert.isFunction task.watchHandler
assert.equal 1000, task.timeout
assert.equal 1000, task.runtime
assert.equal 0, task.count
assert.equal '', task.source
assert.equal false, task.executable
assert.equal false, task.persistent
assert.equal process.cwd(), task.cwd
assert.isUndefined task.timeout
assert.isUndefined task.runtime
assert.isUndefined task.count
assert.isUndefined task.source
assert.isUndefined task.executable
assert.isUndefined task.persistent
assert.isUndefined task.cwd
assert.isUndefined task.env
assert.isUndefined task.arguments
assert.isUndefined task.watch
methods: ->
name = defaultName
task = weaver.tasks[name]
task = Task.tasks[name]
for method in methods
assert.isFunction task[method]
assert not task.propertyIsEnumerable method

@@ -51,15 +52,23 @@ constructor: ->

task = weaver.task name, {}
task = Task.create name
assert.equal task.constructor, weaver.task
assert.equal task, weaver.tasks[name]
assert.equal 1000, task.runtime
assert.equal 1000, task.timeout
assert.equal task, Task.tasks[name]
assert.equal task, weaver.task name, runtime: 2000
assert.isUndefined task.runtime
assert.isUndefined task.timeout
Task
.create name
.upgrade runtime: 2000
assert.equal 2000, task.runtime
assert.equal task, weaver.task name, timeout: 5000
assert.equal task, Task.tasks[name]
Task
.create name
.upgrade timeout: 5000
assert.equal 5000, task.timeout
assert.equal task, weaver.tasks[name]
assert.equal task, Task.tasks[name]
.export(module)

@@ -1,4 +0,3 @@

assert = require('assert')
weaver = require('../lib/weaver.js')
emitter = require('events').EventEmitter
assert = require('assert')
Weaver = require('../lib/weaver.coffee')

@@ -10,16 +9,16 @@ (require 'vows')

basic: ->
assert.throws -> weaver.validate undefined
assert.throws -> weaver.validate null
assert.throws -> weaver.validate []
assert.throws -> weaver.validate {}
assert.throws -> weaver.validate tasks: null
assert.throws -> weaver.validate tasks: []
assert.throws -> weaver.validate tasks: {}
assert.throws -> weaver.validate tasks: test: null
assert.throws -> weaver.validate tasks: test: []
assert.throws -> weaver.validate tasks: test: {}
assert.throws -> Weaver.validate undefined
assert.throws -> Weaver.validate null
assert.throws -> Weaver.validate []
assert.throws -> Weaver.validate {}
assert.throws -> Weaver.validate tasks: null
assert.throws -> Weaver.validate tasks: []
assert.throws -> Weaver.validate tasks: {}
assert.throws -> Weaver.validate tasks: test: null
assert.throws -> Weaver.validate tasks: test: []
assert.throws -> Weaver.validate tasks: test: {}
# Okay
assert.doesNotThrow ->
weaver.validate
Weaver.validate
tasks:

@@ -33,3 +32,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
path: null

@@ -43,3 +42,3 @@ tasks:

assert.throws ->
weaver.validate
Weaver.validate
path: ''

@@ -53,3 +52,3 @@ tasks:

assert.doesNotThrow ->
weaver.validate
Weaver.validate
path: '.'

@@ -64,3 +63,3 @@ tasks:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -72,3 +71,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -80,3 +79,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -88,3 +87,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -97,3 +96,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -106,3 +105,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -116,3 +115,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -124,3 +123,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -132,3 +131,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -140,3 +139,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -149,3 +148,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -159,3 +158,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -168,3 +167,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -178,3 +177,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -188,3 +187,3 @@ test:

assert.doesNotThrow ->
weaver.validate
Weaver.validate
tasks:

@@ -199,3 +198,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -208,3 +207,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -218,3 +217,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -228,3 +227,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -239,3 +238,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -248,3 +247,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -258,3 +257,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -268,3 +267,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -279,3 +278,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -289,3 +288,3 @@ test:

assert.doesNotThrow ->
weaver.validate
Weaver.validate
tasks:

@@ -300,3 +299,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -310,3 +309,3 @@ test:

assert.doesNotThrow ->
weaver.validate
Weaver.validate
tasks:

@@ -321,3 +320,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -330,3 +329,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -340,3 +339,3 @@ test:

assert.doesNotThrow ->
weaver.validate
Weaver.validate
tasks:

@@ -350,3 +349,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -360,3 +359,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -370,3 +369,3 @@ test:

assert.doesNotThrow ->
weaver.validate
Weaver.validate
tasks:

@@ -382,3 +381,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -391,3 +390,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -401,3 +400,3 @@ test:

assert.doesNotThrow ->
weaver.validate
Weaver.validate
tasks:

@@ -411,3 +410,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -421,3 +420,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -431,3 +430,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -441,3 +440,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -451,3 +450,3 @@ test:

assert.doesNotThrow ->
weaver.validate
Weaver.validate
tasks:

@@ -461,3 +460,3 @@ test:

assert.doesNotThrow ->
weaver.validate
Weaver.validate
tasks:

@@ -471,3 +470,3 @@ test:

assert.doesNotThrow ->
weaver.validate
Weaver.validate
tasks:

@@ -482,3 +481,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -491,3 +490,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -501,3 +500,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -512,3 +511,3 @@ test:

assert.doesNotThrow ->
weaver.validate
Weaver.validate
tasks:

@@ -522,3 +521,3 @@ test:

assert.doesNotThrow ->
weaver.validate
Weaver.validate
tasks:

@@ -536,3 +535,3 @@ test:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -545,3 +544,3 @@ '':

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -555,3 +554,3 @@ '❄':

assert.throws ->
weaver.validate
Weaver.validate
whoa: 'so unexpected'

@@ -565,3 +564,3 @@ tasks:

assert.throws ->
weaver.validate
Weaver.validate
tasks:

@@ -568,0 +567,0 @@ test:

assert = require('assert')
weaver = require('../lib/weaver.js')
Weaver = require('../lib/weaver.coffee')
exec = require('child_process').exec

@@ -24,3 +24,3 @@ daemon = '../bin/weaver'

stdout: (error, stdout, stderr) ->
assert.include stdout, weaver.version
assert.include stdout, Weaver.version

@@ -61,3 +61,3 @@ help:

topic: ->
exec "#{daemon} status --nocolor", options, @callback
exec "#{daemon} status", options, @callback
return

@@ -67,3 +67,3 @@

stderr: (error, stdout, stderr) -> assert not stderr
version: (error, stdout, stderr) -> assert.include stdout, weaver.version
version: (error, stdout, stderr) -> assert.include stdout, Weaver.version
name: (error, stdout, stderr) -> assert.include stdout, 'weaver'

@@ -73,22 +73,22 @@ pid: (error, stdout, stderr) -> assert.match stdout, /^\s*\d+\s/

exit:
topic: ->
exec "#{daemon} exit", options, @callback
return
code: (error, stdout, stderr) -> assert not error
stdout: (error, stdout, stderr) -> assert not stdout
stderr: (error, stdout, stderr) -> assert not stderr
status:
exit:
topic: ->
exec "#{daemon} status", options, @callback
exec "#{daemon} exit", options, @callback
return
code: (error, stdout, stderr) -> assert error?.code
code: (error, stdout, stderr) -> assert not error
stdout: (error, stdout, stderr) -> assert not stdout
stderr: (error, stdout, stderr) ->
assert.include stderr, 'Could not connect'
assert.include stderr, port
stderr: (error, stdout, stderr) -> assert not stderr
status:
topic: ->
exec "#{daemon} status", options, @callback
return
code: (error, stdout, stderr) -> assert error?.code
stdout: (error, stdout, stderr) -> assert not stdout
stderr: (error, stdout, stderr) ->
assert.include stderr, 'Could not connect'
assert.include stderr, port
.export(module)
assert = require('assert')
weaver = require('../lib/weaver.js')
exec = require('child_process').exec

@@ -24,6 +23,3 @@ spawn = require('child_process').spawn

topic: ->
exec daemon, options, (args...) =>
setTimeout((=>
@callback(args...)
), 50)
exec daemon, options, @callback

@@ -40,10 +36,6 @@ return

exec "#{daemon} exit", options, (args...) =>
# Wait for message to arrive
setTimeout((=>
# Remove zombie
monitor.kill('SIGTERM')
monitor.kill('SIGTERM')
# Run tests
@callback(args...)
), 50)
@callback(args...)
return

@@ -50,0 +42,0 @@

assert = require('assert')
weaver = require('../lib/weaver.js')
exec = require('child_process').exec

@@ -16,2 +15,5 @@ write = require('fs').writeFileSync

bpid1 = 0
bpid2 = 0
(require 'vows')

@@ -22,7 +24,4 @@ .describe('upgrade')

topic: ->
# Write empty config
write config, JSON.stringify tasks: {}
# Start daemon
exec "#{daemon} --config #{config}", options, @callback
exec daemon, options, @callback
return

@@ -52,3 +51,5 @@

# Check status
exec "#{daemon} status --nocolor", options, @callback
exec "#{daemon} status", options, (args...) =>
setTimeout((=> @callback(args...)), 250)
return

@@ -89,3 +90,5 @@

# Check status
exec "#{daemon} status --nocolor", options, @callback
exec "#{daemon} status", options, (args...) =>
setTimeout((=> @callback(args...)), 250)
return

@@ -102,6 +105,6 @@

assert.match status[1], /^ *(\d+) +W +\d+s +base +sleep 2006/
pid1 = +RegExp.$1
bpid1 = pid1 = +RegExp.$1
assert.match status[2], /^ *(\d+) +W +\d+s +base +sleep 2006/
pid2 = +RegExp.$1
bpid2 = pid2 = +RegExp.$1

@@ -134,3 +137,5 @@ assert.match status[3], /^ *(\d+) +D +\d+s +bonus +uname -a/

# Check status
exec "#{daemon} status --nocolor", options, @callback
exec "#{daemon} status", options, (args...) =>
setTimeout((=> @callback(args...)), 250)
return

@@ -158,2 +163,6 @@

# Base subtasks should retain pids
assert.equal bpid1, pid1
assert.equal bpid2, pid2
assert.notEqual pid1, pid2

@@ -160,0 +169,0 @@ assert.notEqual pid1, pid3

assert = require('assert')
weaver = require('../lib/weaver.js')
exec = require('child_process').exec

@@ -4,0 +3,0 @@ write = require('fs').writeFileSync

assert = require('assert')
weaver = require('../lib/weaver.js')
exec = require('child_process').exec

@@ -4,0 +3,0 @@ write = require('fs').writeFileSync

assert = require('assert')
weaver = require('../lib/weaver.js')
exec = require('child_process').exec

@@ -4,0 +3,0 @@ write = require('fs').writeFileSync

assert = require('assert')
weaver = require('../lib/weaver.js')
exec = require('child_process').exec

@@ -46,3 +45,3 @@ write = require('fs').writeFileSync

# Check status
exec "#{daemon} status --nocolor", options, (args...) =>
exec "#{daemon} status", options, (args...) =>
args[1] = args[1]

@@ -64,3 +63,3 @@ .replace(/\n$/, '')

write config, configData
exec "#{daemon} status --nocolor", options, (args...) => @callback(args..., pid)
exec "#{daemon} status", options, (args...) => @callback(args..., pid)
return

@@ -86,3 +85,3 @@

# Check status
exec "#{daemon} status --nocolor", options, (args...) =>
exec "#{daemon} status", options, (args...) =>
args[1] = args[1]

@@ -104,3 +103,3 @@ .replace(/\n$/, '')

write config, configData
exec "#{daemon} status --nocolor", options, (args...) => @callback(args..., pid)
exec "#{daemon} status", options, (args...) => @callback(args..., pid)
return

@@ -107,0 +106,0 @@

assert = require('assert')
weaver = require('../lib/weaver.js')
exec = require('child_process').exec

@@ -4,0 +3,0 @@ write = require('fs').writeFileSync

assert = require('assert')
weaver = require('../lib/weaver.js')
exec = require('child_process').exec

@@ -4,0 +3,0 @@ write = require('fs').writeFileSync

assert = require('assert')
weaver = require('../lib/weaver.js')
exec = require('child_process').exec

@@ -4,0 +3,0 @@ write = require('fs').writeFileSync

assert = require('assert')
weaver = require('../lib/weaver.js')
exec = require('child_process').exec

@@ -36,2 +35,7 @@ write = require('fs').writeFileSync

arguments: [2014]
s3:
count: 1
executable: yes
source: 'uname'
arguments: ['-a']

@@ -49,3 +53,3 @@ # Start daemon

# Check status
exec "#{daemon} status --nocolor", options, (args...) =>
exec "#{daemon} status", options, (args...) =>
args[1] = args[1]

@@ -61,3 +65,3 @@ .replace(/\n$/, '')

stderr: (error, stdout, stderr) -> assert not stderr
stdout: (error, stdout, stderr) -> assert.equal stdout.length, 7
stdout: (error, stdout, stderr) -> assert.equal stdout.length, 8

@@ -69,7 +73,7 @@ drop:

# Get configuration dump
exec "#{daemon} dump --nocolor", options, (args...) =>
exec "#{daemon} dump", options, (args...) =>
dump = JSON.parse args[1]
# Check status
exec "#{daemon} status --nocolor", options, (args...) =>
exec "#{daemon} status", options, (args...) =>
@callback(args..., pid, dump)

@@ -87,3 +91,3 @@ return

assert.equal status.length, 5
assert.equal status.length, 6

@@ -96,16 +100,43 @@ assert.match stdout, /^(?:[\s\S](?!s2))+$/

assert.equal pid[4], status[4]
assert.equal 0, status[5]
exit:
topic: ->
# Remove config file
unlink config
drop:
topic: (stdout, stderr, pid) ->
# Drop finished task
exec "#{daemon} drop s3", options, =>
# Check status
exec "#{daemon} status", options, (args...) =>
@callback(args..., pid)
# Stop daemon
exec "#{daemon} exit", options, @callback
return
code: (error, stdout, stderr) -> assert not error
stdout: (error, stdout, stderr) -> assert not stdout
stderr: (error, stdout, stderr) -> assert not stderr
code: (error, stdout, stderr, pid) -> assert not error
stderr: (error, stdout, stderr, pid) -> assert not stderr
stdout: (error, stdout, stderr, pid) ->
status = stdout
.replace(/\n$/, '')
.split(/\n/)
.map(($_) -> +/^\s*(\d+)/.exec($_)[1])
assert.equal status.length, 5
assert.match stdout, /^(?:[\s\S](?!s[23]))+$/
assert.equal pid[1], status[1]
assert.equal pid[2], status[2]
assert.equal pid[3], status[3]
assert.equal pid[4], status[4]
exit:
topic: ->
# Remove config file
unlink config
# Stop daemon
exec "#{daemon} exit", options, @callback
return
code: (error, stdout, stderr) -> assert not error
stdout: (error, stdout, stderr) -> assert not stdout
stderr: (error, stdout, stderr) -> assert not stderr
.export(module)
'use strict';
var glob = require('glob'),
fs = require('fs'),
util = require('util'),
events = require('events'),
watches = {},
watcher = {};
function handler (weaver, file, event) {
weaver.log('File ' + file + ' changed');
(watches[file] || []).forEach(function (callback) {
callback();
});
}
function watch (weaver, cwd, callback, error, files) {
var cbs, file, i, l;
if (error) {
weaver.emit('error', error);
return;
}
for (i = 0, l = files.length; i < l; i++) {
file = files[i];
if (file[0] !== '/') {
/* Expand to full path */
file = cwd + '/' + file;
}
cbs = watches[file];
if (cbs) {
/* Add callback to watches */
cbs.push(callback);
} else {
/* Start watching file */
watches[file] = [callback];
watcher[file] = fs.watch(file, handler.bind(undefined, weaver, file));
}
}
}
function start (weaver, cwd, patterns, callback) {
var fn = watch.bind(undefined, weaver, cwd, callback),
i, l;
for (i = 0, l = patterns.length; i < l; i++) {
glob(patterns[i], {
cwd : cwd,
nomount : true
}, fn);
}
}
function stop (callback) {
var cbs, i, file;
for (file in watches) {
if (watches.hasOwnProperty(file)) {
cbs = watches[file];
i = cbs.length;
while (i--) {
if (cbs[i] === callback) {
cbs.splice(i, 1);
}
}
if (!cbs.length) {
/* Stop watching file completely */
watcher[file].close();
delete watches[file];
delete watcher[file];
}
}
}
}
function Watcher () {
this.start = start;
this.stop = stop;
return this;
}
util.inherits(Watcher, events.EventEmitter);
module.exports = new Watcher();
'use strict';
var fs = require('fs'),
util = require('util'),
events = require('events'),
assert = require('assert'),
resolve = require('path').resolve,
fork = require('child_process').spawn,
zs = require('z-schema'),
schema = require('./schema'),
Watcher = require('./watcher'),
sprintf = require('sprintf').sprintf,
/* Which subtask parameters can be changed without restart */
mutable = {
count : true,
source : false,
cwd : false,
env : false,
persistent : true,
executable : false,
timeout : true,
runtime : true,
watch : true,
arguments : false
};
var validator = new zs({ strictMode : true });
/**
* Weaver
* @class Weaver
* @constructor
*/
function Weaver () {
return this;
}
util.inherits(Weaver, events.EventEmitter);
var weaver
= module.exports
= new Weaver();
function define (type, name, value, descriptor) {
descriptor = descriptor || {};
descriptor.value = value;
descriptor.enumerable = true;
var target = descriptor.target || weaver;
if (!('writable' in descriptor)) {
descriptor.writable = true;
}
switch (type) {
case 'handler':
target.on(name, value);
return;
case 'method':
if (typeof value !== 'function') {
throw new Error('Method must be a function');
}
descriptor.enumerable = false;
break;
case 'property':
break;
default:
throw new Error('Unsupported definition type ' + type);
}
Object.defineProperty(target, name, descriptor);
}
function expandEnv (env, executable) {
var expanded = Object.create(null);
expanded.HOME = process.env.HOME;
expanded.PATH = process.env.PATH;
if (!executable) {
expanded.NODE_PATH = process.env.NODE_PATH;
}
Object.keys(env)
.forEach(function (key) {
switch (env[key]) {
case true:
if (process.env.hasOwnProperty(key)) {
expanded[key] = process.env[key];
}
break;
case false:
delete expanded[key];
break;
default:
expanded[key] = env[key];
}
});
return expanded;
}
/**
* Weaver version
* @property version
* @type String
*/
define('property', 'version', require('../package').version, { writable: false });
/**
* Start timestamp
* @property start
* @type Number
*/
define('property', 'start', Date.now(), { writable: false });
/**
* Tasks by name
* @property tasks
* @type Object
*/
define('property', 'tasks', Object.create(null), { writable: false });
/**
* Parsed configuration file
* @property config
* @type Object
*/
define('property', 'config', Object.create(null), { writable: false });
/**
* Extend Weaver with property or method
* @method define
*/
define('method', 'define', define);
/**
* Update or create new task group with given options
* @method task
* @param {String} name
* @param {Object} options
*/
define('method', 'task', Task);
/**
* Write something to log
* @method log
*/
define('method', 'log', function () {});
/**
* Validate configuration object
* @method validate
* @param {Object} configuration
* @return {Object} Valid configuration
*/
define('method', 'validate', function (configuration) {
var tasks = configuration.tasks;
/* Validate schema */
assert.ok(validator.validate(configuration, schema), 'Invalid configuration');
/* Perform additional validation */
Object.keys(tasks).forEach(function (name) {
var task = tasks[name];
/* Validate nested arrays for arguments */
Object.keys(task).forEach(function (key) {
if (key === 'arguments') {
task[key].forEach(function (argument) {
if (Array.isArray(argument)) {
assert.equal(
task.count, argument.length,
'Nested array in arguments should contain ' + task.count + ' values'
);
}
});
}
});
task.name = name;
});
return configuration;
});
/**
* Send SIGTERM to all processes and exit
* @method die
* @param {Number} code Exit code
*/
define('method', 'die', function (code) {
var that = this,
tasks = this.tasks,
timeout = 100,
name;
for (name in tasks) {
if (tasks[name].timeout > timeout) {
timeout = tasks[name].timeout;
}
tasks[name].persistent = false;
tasks[name].stopSubtasks();
}
setTimeout(function () {
code = null == code? 1 : code;
that.log(sprintf('Terminated with code %u', code));
setTimeout(function () {
process.exit(code);
}, 100);
}, timeout);
});
/**
* Upgrade current state
* @method upgrade
* @param {String} data Configuration data
* @param {String} path Configuration path
*/
define('method', 'upgrade', function (data, path) {
var parts = [path],
params;
try {
/* Try to parse JSON */
data = JSON.parse(data);
/* Validate new state */
params = this.validate(data);
} catch (error) {
error.message = 'Config error: ' + error.message;
this.emit('error', error);
}
if (params) {
if (params.path) {
parts.push(params.path);
}
Object.keys(params.tasks)
.map(function (name) {
return params.tasks[name];
}).forEach(function (task) {
task.cwd = resolve.apply(undefined, parts.concat(task.cwd || '.'));
});
Object.keys(params.tasks)
.forEach(function (name) {
weaver.config[name] = params.tasks[name];
});
this.emit('upgrade');
}
});
/**
* Get status report
* @method status
* @return {Object} Task groups with subtasks data
*/
define('method', 'status', function () {
var tasks = this.tasks,
now = Date.now(),
result = {},
i, l, name, task, subtask, subtasks;
for (name in tasks) {
result[name] = task = {
count : tasks[name].count,
source : tasks[name].source,
restart : tasks[name].restart,
subtasks : []
};
subtasks = tasks[name].subtasks;
for (i = 0, l = subtasks.length; i < l; i++) {
subtask = subtasks[i];
task.subtasks.push(subtask? {
pid : subtask.pid,
args : subtask.args,
status : subtask.status,
uptime : now - subtask.start
} : null);
}
}
return result;
});
/**
* Execute command with given arguments
* @method command
* @param {String} action Action name
* @param {String|Number} name Task group name or subtask pid
* @param {Array} args Arguments
*/
define('method', 'command', function (action, name, args) {
var tasks = this.tasks,
fn = Task.prototype[action + 'PID'],
task;
if (!Array.isArray(args)) {
args = [];
}
if (typeof fn !== 'function') {
throw new Error('Unknown action ' + action);
}
/* Execute command for all tasks */
if (null == name) {
for (name in tasks) {
fn.apply(tasks[name], args);
}
} else {
task = tasks[name];
if (task) {
if (action === 'kill') {
args.unshift(null);
}
fn.apply(task, args);
} else if (Number(name) == name) {
args.unshift(Number(name));
this.command(action, null, args);
} else {
this.log('Task ' + name + ' was not found');
}
}
});
/**
* Fired when error occured
* @event error
* @param {Error} error Object with error details
*/
define('handler', 'error', function (error) {
this.log(error.message);
});
/**
* Fired when tasks should be checked and upgraded
* @event upgrade
*/
define('handler', 'upgrade', function () {
var name;
for (name in this.config) {
/* Create or update task */
this.task(name, this.config[name]);
}
});
/*
* Task status codes
* R - restart
* E - error
* D - done (clean exit)
* W - work in progress
* S - stopped
*/
/**
* Task singleton (by name)
* @class Task
* @constructor
* @param {String} name Task group name
* @param {Object} options Task group configuration
*/
function Task (name, options) {
var task;
if (!(this instanceof Task)) {
return new Task(name, options);
}
task = weaver.tasks[name];
if (task) {
task.upgrade(options);
return task;
}
task = weaver.tasks[name] = this;
/**
* Task name
* @property name
* @type String
*/
define('property', 'name', name, {
writable : false,
target : this
});
/**
* Array with sub-process related data
* @property subtasks
* @type Array
*/
define('property', 'subtasks', [], {
writable : false,
target : this
});
/**
* Watched patterns
* @property watch
* @type Array
*/
define('property', 'watch', [], { target: this });
/**
* Watch callback
* @method watchHandler
*/
define('method', 'watchHandler', this.restartSubtasks.bind(this), { target: this });
this.upgrade(options);
return this;
}
/**
* Timeout between SIGINT and SIGTERM for stop and restart
* @property timeout
* @type Number
* @default 1000
*/
define('property', 'timeout', 1000, { target: Task.prototype });
/**
* Minimal runtime required for persistent task
* to be restarted after unclean exit
* @property runtime
* @type Number
* @default 1000
*/
define('property', 'runtime', 1000, { target: Task.prototype });
/**
* Subtasks count
* @property count
* @type Number
* @default 0
*/
define('property', 'count', 0, { target: Task.prototype });
/**
* Source to execute in subtask
* @property source
* @type String
*/
define('property', 'source', '', { target: Task.prototype });
/**
* Source file is executable
* @property executable
* @type Boolean
* @default false
*/
define('property', 'executable', false, { target: Task.prototype });
/**
* Working directory for subtasks
* @property cwd
* @type String
*/
define('property', 'cwd', process.cwd(), { target: Task.prototype });
/**
* Arguments for subtasks
* @property arguments
* @type Array
*/
define('property', 'arguments', [], { target: Task.prototype });
/**
* Restart subtask on dirty exit
* @property persistent
* @type Boolean
* @default false
*/
define('property', 'persistent', false, { target: Task.prototype });
/**
* Environment variables for subtasks
* @property env
* @type Object
*/
define('property', 'env', {}, { target: Task.prototype });
/**
* Upgrade task group
* @method upgrade
* @param {Object} options Task group configuration
*/
define('method', 'upgrade', function (options) {
var that = this,
restart = false,
i, l, pid, subtask, key;
/* Upgrade parameters */
Object.keys(mutable)
.forEach(function (key) {
try {
assert.deepEqual(that[key], options[key]);
} catch (change) {
that.upgradeParameter(key, options[key]);
restart = restart || !mutable[key];
}
});
/* Restart on demand */
if (restart && this.subtasks.length) {
weaver.log(sprintf('Restart required for %s task group', this.name));
this.restartSubtasks();
}
/* Check count */
for (i = 0, l = this.count; i < l; i++) {
subtask = this.subtasks[i];
if (!subtask || (subtask.status === 'R' && !subtask.pid)) {
this.spawn(i);
}
}
/* Kill redundant */
while (this.subtasks.length > this.count) {
this.stopSubtask(this.subtasks.pop());
}
}, { target: Task.prototype });
/**
* Upgrade task parameter with value
* @method upgradeParameter
* @param {String} key Parameter name
* @param {Object} value Parameter value
*/
define('method', 'upgradeParameter', function (key, value) {
switch (key) {
case 'watch':
this.watch = value || [];
Watcher.stop(this.watchHandler);
Watcher.start(weaver, this.cwd, this.watch, this.watchHandler);
break;
default:
if (null == value) {
delete this[key];
} else {
this[key] = value;
}
}
}, { target: Task.prototype });
/**
* Get subtask by pid
* @method get
* @param {Number} pid
* @return {Object} Subtask
*/
define('method', 'get', function (pid) {
var subtasks = this.subtasks,
subtask, i, l;
for (i = 0, l = subtasks.length; i < l; i++) {
subtask = subtasks[i];
if (subtask && subtask.pid === pid) {
return subtask;
}
}
}, { target: Task.prototype });
/**
* Spawn subtask with given id
* @method spawn
* @param {Number} id
*/
define('method', 'spawn', function (id) {
var args = this.arguments || [],
binary = process.execPath,
subtask = {
id : id,
args : [],
status : 'W',
name : this.name,
start : Date.now(),
env : expandEnv(this.env, this.executable),
}, i, l, p1, p2, eargs;
/* Prepare arguments */
for (i = 0, l = args.length; i < l; i++) {
if (Array.isArray(args[i])) {
subtask.args.push(args[i][id]);
} else {
subtask.args.push(args[i]);
}
}
eargs = subtask.args.slice();
/* Prepare binary/source */
if (this.executable) {
binary = this.source;
} else {
eargs.unshift(this.source);
}
/* Create new process */
subtask.process = fork(binary, eargs, {
stdio : 'pipe',
cwd : resolve(this.cwd),
env : subtask.env
});
/* Get subtask pid */
subtask.pid = subtask.process.pid || 0;
if (subtask.pid) {
p1 = sprintf('%u (%s) ', subtask.pid, subtask.name);
p2 = sprintf('%u [%s] ', subtask.pid, subtask.name);
/* Setup logger */
subtask.process.stdout.on('data', this.log.bind(this, p1));
subtask.process.stderr.on('data', this.log.bind(this, p2));
/* Setup exit handler */
subtask.process.once('exit', this.exitHandler.bind(this, subtask));
weaver.log(sprintf('Task %u (%s) spawned', subtask.pid, subtask.name));
} else {
subtask.status = 'E';
subtask.code = 255;
subtask.process.once('error', function (error) {
weaver.emit('error', error);
});
weaver.log(sprintf('Failed to start task (%s)', subtask.name));
}
this.subtasks[id] = subtask;
}, { target: Task.prototype });
/**
* Do something for each subtask
* @method foreach
* @param {Function} fn
* @param {Number|String} argument
*/
define('method', 'foreach', function (fn, argument) {
var subtasks = this.subtasks,
i, l;
for (i = 0, l = subtasks.length; i < l; i++) {
fn.call(this, subtasks[i], argument);
}
}, { target: Task.prototype });
/**
* Kill given subtask with signal
* @method killSubtask
* @param {Object} subtask
* @param {String} signal
*/
define('method', 'killSubtask', function (subtask, signal) {
if (subtask && subtask.pid) {
try {
subtask.process.kill(signal);
} catch (error) {
weaver.log(sprintf(
'Failed to kill %u (%s) with %s',
subtask.pid, subtask.name, signal
));
}
}
}, { target: Task.prototype });
/**
* Stop given subtask
* @method stopSubtask
* @param {Object} subtask
*/
define('method', 'stopSubtask', function (subtask) {
if (subtask && subtask.pid) {
subtask.process.kill('SIGINT');
setTimeout(function () {
if (subtask.pid) {
subtask.process.kill('SIGTERM');
}
}, this.timeout);
}
}, { target: Task.prototype });
/**
* Restart given subtask
* @method restartSubtask
* @param {Object} subtask
*/
define('method', 'restartSubtask', function (subtask) {
if (subtask) {
subtask.status = 'R';
this.stopSubtask(subtask);
}
}, { target: Task.prototype });
/**
* Kill subtask with signal by PID
* @method killPID
* @param {Number} pid
* @param {String} signal
*/
define('method', 'killPID', function (pid, signal) {
if (null == pid) {
this.killSubtasks(signal);
} else {
this.killSubtask(this.get(pid), signal);
}
}, { target: Task.prototype });
/**
* Restart subtask by pid
* @method restartPID
* @param {Number} pid
*/
define('method', 'restartPID', function (pid) {
if (null == pid) {
this.restartSubtasks();
} else {
this.restartSubtask(this.get(pid));
}
}, { target: Task.prototype });
/**
* Stop subtask by pid
* @method restartPID
* @param {Number} pid
*/
define('method', 'stopPID', function (pid) {
if (null == pid) {
this.stopSubtasks();
} else {
this.stopSubtask(this.get(pid));
}
}, { target: Task.prototype });
/**
* Stop all subtasks
* @method stopSubtasks
*/
define('method', 'stopSubtasks', function () {
this.foreach(this.stopSubtask);
}, { target: Task.prototype });
/**
* Restart all subtasks
* @method restartSubtasks
*/
define('method', 'restartSubtasks', function () {
this.foreach(this.restartSubtask);
}, { target: Task.prototype });
/**
* Kill all subtasks with signal
* @method killSubtasks
* @param {String} signal
*/
define('method', 'killSubtasks', function (signal) {
this.foreach(this.killSubtask, signal);
}, { target: Task.prototype });
/**
* Output data from buffer to weaver.log
* @method log
* @param {String} prefix
* @param {Buffer} data
*/
define('method', 'log', function (prefix, data) {
var messages = data.toString().split('\n'),
i, l;
for (i = 0, l = messages.length; i < l; i++) {
if (messages[i]) {
weaver.log.call(null, prefix + messages[i]);
}
}
}, { target: Task.prototype });
/**
* @method exitHandler
* @param {Object} subtask
* @param {Number} code
* @param {String} signal
*/
define('method', 'exitHandler', function (subtask, code, signal) {
var restart = this.persistent,
elapsed;
if (code === null) {
weaver.log(sprintf(
'Task %u (%s) was killed by %s',
subtask.pid, subtask.name, signal
));
} else {
weaver.log(sprintf(
'Task %u (%s) exited with code %u',
subtask.pid, subtask.name, code
));
}
subtask.pid = 0;
subtask.code = code;
subtask.signal = signal;
delete subtask.process;
if (subtask.status !== 'R') {
if (code) {
subtask.status = 'E';
} else if (signal) {
subtask.status = 'S';
} else {
subtask.status = 'D';
}
}
if (restart && code) {
elapsed = Date.now() - subtask.start;
if (elapsed < this.runtime) {
weaver.log(sprintf(
'Restart skipped after %ums (%s)',
elapsed, subtask.name
));
restart = false;
}
}
if (subtask.status === 'R') {
/* Restart was requested */
restart = true;
}
if (!(this.name in weaver.config)) {
/* Task was dropped */
restart = false;
if (!this.subtasks.filter(function (subtask) { return !!subtask.pid }).length) {
/* All subtasks were stopped */
delete weaver.tasks[this.name];
}
}
if (restart) {
this.spawn(subtask.id);
}
}, { target: Task.prototype });
assert = require('assert')
weaver = require('../lib/weaver.js')
(require 'vows')
.describe('define')
.addBatch
'property#writable': ->
name = "test_#{Math.random()}"
value = Math.random()
weaver.define 'property', name, value
assert name of weaver
assert.equal weaver[name], value
'property#protected': ->
name = "test_#{Math.random()}"
value = Math.random()
weaver.define 'property', name, value, writable: false
assert name of weaver
assert.equal weaver[name], value
assert.throws ->
weaver.define 'property', name, 1 + value
'method#parameters': ->
name = "test_#{Math.random()}"
assert.throws ->
weaver.define 'method', name, null
assert.throws ->
weaver.define 'method', name, {}
weaver.define 'method', name, ->
assert not weaver.propertyIsEnumerable name
assert.equal typeof weaver[name], 'function'
'method#protected': ->
name = "test_#{Math.random()}"
weaver.define 'method', name, (->), writable: false
assert.throws ->
weaver.define 'method', name, ->
handler: ->
listeners = weaver.listeners('error').length
for i in [1 .. 5]
weaver.define 'handler', 'error', ->
assert.equal ++listeners, weaver.listeners('error').length
parameters: ->
assert.throws ->
weaver.define()
assert.throws ->
weaver.define 'event'
target: ->
name = "test_#{Math.random()}"
value = Math.random()
target = Object.create null
weaver.define 'property', name, value, target: target
assert not (name of weaver)
assert name of target
assert.equal target[name], value
.export(module)

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet