mutagem 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitattributes +1 -0
- data/.gitignore +11 -0
- data/.yardopts +5 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +44 -0
- data/HISTORY.markdown +11 -0
- data/LICENSE +20 -0
- data/README.markdown +119 -0
- data/Rakefile +55 -0
- data/TODO.markdown +6 -0
- data/config/cucumber.yml +7 -0
- data/features/mutex.feature +22 -0
- data/features/step_definitions/.gitignore +0 -0
- data/features/step_definitions/mutagem_steps.rb +51 -0
- data/features/support/env.rb +4 -0
- data/features/task.feature +15 -0
- data/lib/mutagem/lockfile.rb +58 -0
- data/lib/mutagem/mutex.rb +47 -0
- data/lib/mutagem/task.rb +66 -0
- data/lib/mutagem/version.rb +3 -0
- data/lib/mutagem.rb +9 -0
- data/mutagem.gemspec +40 -0
- data/spec/basic_gem/basic_gem_spec.rb +13 -0
- data/spec/mutagem/lockfile_spec.rb +19 -0
- data/spec/mutagem/mutex_spec.rb +49 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/watchr.rb +142 -0
- metadata +207 -0
data/.gitattributes
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.rb diff=ruby
|
data/.gitignore
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
mutagem (0.1.3)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
aruba (0.2.3)
|
10
|
+
background_process
|
11
|
+
cucumber (~> 0.9.0)
|
12
|
+
background_process (1.2)
|
13
|
+
builder (2.1.2)
|
14
|
+
cucumber (0.9.0)
|
15
|
+
builder (~> 2.1.2)
|
16
|
+
diff-lcs (~> 1.1.2)
|
17
|
+
gherkin (~> 2.2.2)
|
18
|
+
json (~> 1.4.6)
|
19
|
+
term-ansicolor (~> 1.0.5)
|
20
|
+
diff-lcs (1.1.2)
|
21
|
+
gherkin (2.2.4)
|
22
|
+
json (~> 1.4.6)
|
23
|
+
term-ansicolor (~> 1.0.5)
|
24
|
+
trollop (~> 1.16.2)
|
25
|
+
json (1.4.6)
|
26
|
+
rake (0.8.7)
|
27
|
+
rdiscount (1.6.5)
|
28
|
+
rspec (1.3.0)
|
29
|
+
term-ansicolor (1.0.5)
|
30
|
+
trollop (1.16.2)
|
31
|
+
yard (0.6.1)
|
32
|
+
|
33
|
+
PLATFORMS
|
34
|
+
ruby
|
35
|
+
|
36
|
+
DEPENDENCIES
|
37
|
+
aruba (>= 0.2.0)
|
38
|
+
bundler (>= 1.0.0)
|
39
|
+
cucumber (>= 0.6)
|
40
|
+
mutagem!
|
41
|
+
rake (>= 0.8.7)
|
42
|
+
rdiscount (>= 1.6.5)
|
43
|
+
rspec (>= 1.2.9)
|
44
|
+
yard (>= 0.6.1)
|
data/HISTORY.markdown
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 GearheadForHire, LLC
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
Mutagem
|
2
|
+
========
|
3
|
+
|
4
|
+
A Ruby gem for file based mutexes with a simple external process management wrapper.
|
5
|
+
|
6
|
+
|
7
|
+
Overview
|
8
|
+
--------
|
9
|
+
The Mutagem library provides file based mutexes for recursion protection and wrapper classes for
|
10
|
+
threading of external processes with support for output and exit status capturing.
|
11
|
+
|
12
|
+
A test suite is provided for both unit testing with [RSpec](http://github.com/dchelimsky/rspec)
|
13
|
+
and functional testing with [Cucumber](http://github.com/aslakhellesoy/cucumber). The code is
|
14
|
+
documented using [YARD](http://github.com/lsegal/yard).
|
15
|
+
|
16
|
+
|
17
|
+
Example Usage
|
18
|
+
-------------
|
19
|
+
The following Ruby code is used to run a word by word diff on two folders of comma delimited data.
|
20
|
+
Each folder contains before and after CSV dumps from a SQL database. The CSV files have the same name
|
21
|
+
in each of the two folders.
|
22
|
+
|
23
|
+
Mutagem is providing recursion protection. The recursion protection is very useful if the script is
|
24
|
+
run in an automated environment (ex. cron). Mutagem is also capturing output and exit status for the
|
25
|
+
diff processes allowing customized output.
|
26
|
+
|
27
|
+
Standard diff is quick but it can't give you a word by word diff of CSV data. Word by word
|
28
|
+
colorized diffing with support for custom delimiters is provided
|
29
|
+
by [dwdiff](http://os.ghalkes.nl/dwdiff.html).
|
30
|
+
|
31
|
+
|
32
|
+
#!/usr/bin/env ruby
|
33
|
+
|
34
|
+
require 'rubygems'
|
35
|
+
require 'mutagem'
|
36
|
+
require 'pathname'
|
37
|
+
require 'term/ansicolor'
|
38
|
+
|
39
|
+
class String
|
40
|
+
include Term::ANSIColor
|
41
|
+
end
|
42
|
+
|
43
|
+
lock_was_sucessful = Mutagem::Mutex.new('diff.lck').execute do
|
44
|
+
|
45
|
+
$stdout.sync = true # force screen updates
|
46
|
+
before_files = Dir.glob('test/dump/before/*.csv')
|
47
|
+
|
48
|
+
before_files.each do |bf|
|
49
|
+
|
50
|
+
af = 'test/dump/after/' + Pathname.new(bf).basename.to_s
|
51
|
+
|
52
|
+
# quick diff
|
53
|
+
cmd = "diff #{bf} #{af}"
|
54
|
+
task = Mutagem::Task.new(cmd)
|
55
|
+
task.join
|
56
|
+
|
57
|
+
if (task.exitstatus == 0)
|
58
|
+
# no changes, show a "still working" indicator
|
59
|
+
print ".".green
|
60
|
+
else
|
61
|
+
# we have changes, slow diff, word based and CSV (comma) sensitive
|
62
|
+
print "D".red
|
63
|
+
puts "\n#{af}"
|
64
|
+
|
65
|
+
cmd = "dwdiff --color --context=0 --delimiters=, #{bf} #{af}"
|
66
|
+
task = Mutagem::Task.new(cmd)
|
67
|
+
task.join
|
68
|
+
|
69
|
+
puts task.output
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
puts "\n"
|
74
|
+
|
75
|
+
end
|
76
|
+
puts "process locked" unless lock_was_sucessful
|
77
|
+
|
78
|
+
|
79
|
+
System Requirements
|
80
|
+
-------------------
|
81
|
+
* POSIX system (tested on Linux and Windows/Cygwin environments)
|
82
|
+
|
83
|
+
|
84
|
+
Installation
|
85
|
+
------------
|
86
|
+
Mutagem is avaliable on [RubyGems.org](http://rubygems.org/gems/mutagem)
|
87
|
+
|
88
|
+
gem install mutagem
|
89
|
+
|
90
|
+
|
91
|
+
Development
|
92
|
+
-----------
|
93
|
+
|
94
|
+
Mutagem uses [Bundler](http://github.com/carlhuda/bundler) to manage dependencies, the gemspec
|
95
|
+
file is maintained by hand.
|
96
|
+
|
97
|
+
git clone http://github.com/robertwahler/mutagem
|
98
|
+
cd mutagem
|
99
|
+
|
100
|
+
Use bundle to install development dependencies
|
101
|
+
|
102
|
+
bundle install
|
103
|
+
|
104
|
+
rake -T
|
105
|
+
|
106
|
+
rake build # Build mutagem-0.1.2.gem into the pkg directory
|
107
|
+
rake doc:clean # Remove generated documenation
|
108
|
+
rake doc:generate # Generate YARD Documentation
|
109
|
+
rake features # Run Cucumber features
|
110
|
+
rake install # Build and install mutagem-0.1.2.gem into system gems
|
111
|
+
rake release # Create tag v0.1.2 and build and push mutagem-0.1.2.gem to Rubygems
|
112
|
+
rake spec # Run specs
|
113
|
+
rake test # Run specs and features
|
114
|
+
|
115
|
+
|
116
|
+
Copyright
|
117
|
+
---------
|
118
|
+
|
119
|
+
Copyright (c) 2010 GearheadForHire, LLC. See [LICENSE](LICENSE) for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# bundler/setup is managing $LOAD_PATH, any gem needed by this Rakefile must
|
4
|
+
# be listed as a development dependency in the gemspec
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'bundler/setup'
|
8
|
+
|
9
|
+
Bundler::GemHelper.install_tasks
|
10
|
+
|
11
|
+
def gemspec
|
12
|
+
@gemspec ||= begin
|
13
|
+
file = File.expand_path('../mutagem.gemspec', __FILE__)
|
14
|
+
eval(File.read(file), binding, file)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'spec'
|
19
|
+
require 'spec/rake/spectask'
|
20
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
21
|
+
spec.libs << 'lib' << 'spec'
|
22
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'cucumber'
|
26
|
+
require 'cucumber/rake/task'
|
27
|
+
Cucumber::Rake::Task.new(:features) do |task|
|
28
|
+
task.cucumber_opts = ["features"]
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "Run specs and features"
|
32
|
+
task :test => [:spec, :features]
|
33
|
+
|
34
|
+
task :default => :test
|
35
|
+
|
36
|
+
namespace :doc do
|
37
|
+
project_root = File.expand_path(File.dirname(__FILE__))
|
38
|
+
doc_destination = File.join(project_root, 'rdoc')
|
39
|
+
|
40
|
+
require 'yard'
|
41
|
+
require 'yard/rake/yardoc_task'
|
42
|
+
|
43
|
+
YARD::Rake::YardocTask.new(:generate) do |yt|
|
44
|
+
yt.options = ['--markup-provider', 'rdiscount',
|
45
|
+
'--output-dir', doc_destination
|
46
|
+
] +
|
47
|
+
gemspec.rdoc_options - ['--line-numbers', '--inline-source']
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "Remove generated documenation"
|
51
|
+
task :clean do
|
52
|
+
rm_r doc_destination if File.exists?(doc_destination)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
data/TODO.markdown
ADDED
data/config/cucumber.yml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
<%
|
2
|
+
rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : ""
|
3
|
+
rerun_opts = rerun.to_s.strip.empty? ? "--format pretty features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}"
|
4
|
+
std_opts = "#{rerun_opts} --format rerun --out rerun.txt --strict --tags ~@wip"
|
5
|
+
%>
|
6
|
+
default: <%= std_opts %>
|
7
|
+
wip: --tags @wip:3 --wip features
|
@@ -0,0 +1,22 @@
|
|
1
|
+
@announce
|
2
|
+
Feature: File based mutexes
|
3
|
+
|
4
|
+
Automated processes need locking to prevent recursion on long running commands
|
5
|
+
|
6
|
+
Scenario: Process runs with no lock file present
|
7
|
+
Given a Ruby source file that uses Mutagem::Mutex named "test.rb"
|
8
|
+
When I run "ruby test.rb"
|
9
|
+
Then the exit status should be 0
|
10
|
+
And the output should contain:
|
11
|
+
"""
|
12
|
+
hello world
|
13
|
+
"""
|
14
|
+
|
15
|
+
Scenario: Process runs with a lock file present
|
16
|
+
Given a Ruby source file that uses Mutagem::Mutex named "test.rb"
|
17
|
+
When I run with a lock file present "ruby test.rb"
|
18
|
+
Then the exit status should be 1
|
19
|
+
And the output should not contain:
|
20
|
+
"""
|
21
|
+
hello world
|
22
|
+
"""
|
File without changes
|
@@ -0,0 +1,51 @@
|
|
1
|
+
|
2
|
+
Given /^a Ruby source file that uses Mutagem::Mutex named "([^\"]*)"$/ do |filename|
|
3
|
+
steps %Q{
|
4
|
+
Given a file named "#{filename}" with:
|
5
|
+
"""
|
6
|
+
$LOAD_PATH.unshift File.expand_path('../../../lib', __FILE__) unless
|
7
|
+
$LOAD_PATH.include? File.expand_path('../../../lib', __FILE__)
|
8
|
+
|
9
|
+
require 'mutagem'
|
10
|
+
|
11
|
+
# make sure we are working with the expected gem
|
12
|
+
raise "unexpected mutagem version" unless Mutagem::VERSION == '#{Mutagem::VERSION}'
|
13
|
+
|
14
|
+
mutext = Mutagem::Mutex.new
|
15
|
+
mutext.execute do
|
16
|
+
puts "hello world"
|
17
|
+
exit 0
|
18
|
+
end
|
19
|
+
exit 1
|
20
|
+
"""
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
Given /^a Ruby source file that uses Mutagem::Task named "([^\"]*)"$/ do |filename|
|
25
|
+
steps %Q{
|
26
|
+
Given a file named "#{filename}" with:
|
27
|
+
"""
|
28
|
+
$LOAD_PATH.unshift File.expand_path('../../../lib', __FILE__) unless
|
29
|
+
$LOAD_PATH.include? File.expand_path('../../../lib', __FILE__)
|
30
|
+
|
31
|
+
require 'mutagem'
|
32
|
+
|
33
|
+
# make sure we are working with the expected gem
|
34
|
+
raise "unexpected mutagem version" unless Mutagem::VERSION == '#{Mutagem::VERSION}'
|
35
|
+
|
36
|
+
cmd = %q[ruby -e 'puts "hello world"; exit 2']
|
37
|
+
task = Mutagem::Task.new(cmd)
|
38
|
+
task.join
|
39
|
+
|
40
|
+
puts task.output
|
41
|
+
exit task.exitstatus
|
42
|
+
"""
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
When /^I run with a lock file present "(.*)"$/ do |cmd|
|
47
|
+
lockfile = File.join(current_dir, 'mutagem.lck')
|
48
|
+
Mutagem::Mutex.new(lockfile).execute do
|
49
|
+
run(unescape(cmd), false)
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
@announce
|
2
|
+
Feature: Simple process management wrapper
|
3
|
+
|
4
|
+
Manage subprocess tasks in a separate thread
|
5
|
+
in order to capture the output and manage additional tasks
|
6
|
+
based on the subprocess exit status
|
7
|
+
|
8
|
+
Scenario: Sucessfully run a subprocess
|
9
|
+
Given a Ruby source file that uses Mutagem::Task named "tasker.rb"
|
10
|
+
When I run "ruby tasker.rb"
|
11
|
+
Then the exit status should be 2
|
12
|
+
And the output should contain:
|
13
|
+
"""
|
14
|
+
hello world
|
15
|
+
"""
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Mutagem
|
2
|
+
|
3
|
+
# File locking wrapper for Flock
|
4
|
+
class Lockfile
|
5
|
+
# filename for the lockfile, can include path
|
6
|
+
attr_accessor :lockfile
|
7
|
+
|
8
|
+
# Create a new LockFile
|
9
|
+
#
|
10
|
+
# @param [String] lockfile filename
|
11
|
+
def initialize(lockfile=nil)
|
12
|
+
raise ArgumentError, "lockfile not specified" unless lockfile
|
13
|
+
@lockfile = lockfile
|
14
|
+
end
|
15
|
+
|
16
|
+
# Does another process have a lock?
|
17
|
+
# True if we can't get an exclusive lock
|
18
|
+
def locked?
|
19
|
+
return false unless File.exists?(lockfile)
|
20
|
+
result = false
|
21
|
+
open(lockfile, 'w') do |f|
|
22
|
+
# exclusive non-blocking lock
|
23
|
+
result = !lock(f, File::LOCK_EX | File::LOCK_NB)
|
24
|
+
end
|
25
|
+
result
|
26
|
+
end
|
27
|
+
|
28
|
+
# Get a file lock with flock and ensure the
|
29
|
+
# lock is removed (but not the lockfile). Accepts an optional
|
30
|
+
# code block.
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
#
|
34
|
+
# open('output', 'w') do |f|
|
35
|
+
# # exclusive blocking lock
|
36
|
+
# lock(f, File::LOCK_EX) do |f|
|
37
|
+
# f << "write to file while blocking other processes"
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# @param [Object] file the file IO object returned from a call to "open(filename, mode_string)"
|
42
|
+
# @param [Constant] mode locking_constant, see File
|
43
|
+
# @return [Boolean] 0 or false if unable to sucessfully lock
|
44
|
+
def lock(file, mode)
|
45
|
+
result = file.flock(mode)
|
46
|
+
if result
|
47
|
+
begin
|
48
|
+
yield file if block_given? # may not have block if called by locked?
|
49
|
+
ensure
|
50
|
+
# unlock but leave the file on the filesystem
|
51
|
+
file.flock(File::LOCK_UN)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
return result
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Mutagem
|
2
|
+
|
3
|
+
# File based mutex
|
4
|
+
class Mutex < Lockfile
|
5
|
+
|
6
|
+
# Creates a new Mutex
|
7
|
+
#
|
8
|
+
# @param [String] lockfile filename
|
9
|
+
def initialize(lockfile='mutagem.lck')
|
10
|
+
super lockfile
|
11
|
+
end
|
12
|
+
|
13
|
+
# Protect a block
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
#
|
17
|
+
# require 'rubygems'
|
18
|
+
# require 'mutagem'
|
19
|
+
#
|
20
|
+
# mutex = Mutagem::Mutex.new("my_process_name.lck")
|
21
|
+
# mutex.execute do
|
22
|
+
# puts "this block is protected from recursion"
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# @param block the block of code to protect with the mutex
|
26
|
+
# @return [Boolean] 0 if lock sucessful, otherwise false
|
27
|
+
def execute(&block)
|
28
|
+
result = false
|
29
|
+
raise ArgumentError, "missing block" unless block_given?
|
30
|
+
|
31
|
+
begin
|
32
|
+
open(lockfile, 'w') do |f|
|
33
|
+
# exclusive non-blocking lock
|
34
|
+
result = lock(f, File::LOCK_EX | File::LOCK_NB) do |f|
|
35
|
+
yield
|
36
|
+
end
|
37
|
+
end
|
38
|
+
ensure
|
39
|
+
# clean up but only if we have a positive result meaning we wrote the lockfile
|
40
|
+
FileUtils.rm(lockfile) if (result && File.exists?(lockfile))
|
41
|
+
end
|
42
|
+
|
43
|
+
result
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/lib/mutagem/task.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Mutagem
|
4
|
+
|
5
|
+
# A simple external process management wrapper
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
#
|
9
|
+
# cmd = "diff file1.txt file2.txt"
|
10
|
+
# task = Mutagem::Task.new(cmd)
|
11
|
+
# task.join
|
12
|
+
#
|
13
|
+
# if (task.exitstatus == 0)
|
14
|
+
# puts "files match"
|
15
|
+
# end
|
16
|
+
class Task
|
17
|
+
# @return [String] command to run
|
18
|
+
attr_reader :cmd
|
19
|
+
attr_accessor :thread
|
20
|
+
|
21
|
+
# Create a new Task
|
22
|
+
#
|
23
|
+
# @param [String] cmd the cmd to execute
|
24
|
+
def initialize(cmd)
|
25
|
+
@cmd = cmd
|
26
|
+
@thread = Thread.new do
|
27
|
+
pipe = IO.popen(cmd + " 2>&1")
|
28
|
+
@pid = pipe.pid
|
29
|
+
begin
|
30
|
+
@output = pipe.readlines
|
31
|
+
pipe.close
|
32
|
+
@exitstatus = $?.exitstatus
|
33
|
+
rescue => e
|
34
|
+
@exception = e
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Array] array of strings from the subprocess output
|
40
|
+
def output
|
41
|
+
@output
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return subprocess exit status
|
45
|
+
def exitstatus
|
46
|
+
@exitstatus
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return subprocess pid
|
50
|
+
def pid
|
51
|
+
@pid
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Exception] exception returned if one is thrown by Task
|
55
|
+
def exception
|
56
|
+
@exception
|
57
|
+
end
|
58
|
+
|
59
|
+
# join the task's thead and wait for it to finish
|
60
|
+
def join
|
61
|
+
thread.join
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
data/lib/mutagem.rb
ADDED
data/mutagem.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path("../lib/mutagem/version", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "mutagem"
|
6
|
+
s.version = Mutagem::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = ["Robert Wahler"]
|
9
|
+
s.email = ["robert@gearheadforhire.com"]
|
10
|
+
s.homepage = "http://rubygems.org/gems/mutagem"
|
11
|
+
s.summary = "File based mutexes with a simple external process management wrapper"
|
12
|
+
s.description = "The Mutagem library provides file based mutexes for recursion protection and
|
13
|
+
classes for threading of external processes with support for output and
|
14
|
+
exit status capturing. A test suite is provided for both unit and functional testing.
|
15
|
+
The code is documented using YARD."
|
16
|
+
|
17
|
+
s.required_rubygems_version = ">= 1.3.6"
|
18
|
+
s.rubyforge_project = "mutagem"
|
19
|
+
|
20
|
+
s.add_development_dependency "bundler", ">= 1.0.0"
|
21
|
+
s.add_development_dependency "rspec", ">= 1.2.9"
|
22
|
+
s.add_development_dependency "cucumber", ">= 0.6"
|
23
|
+
s.add_development_dependency "aruba", ">= 0.2.0"
|
24
|
+
s.add_development_dependency "rake", ">= 0.8.7"
|
25
|
+
s.add_development_dependency "yard", ">= 0.6.1"
|
26
|
+
s.add_development_dependency "rdiscount", ">= 1.6.5"
|
27
|
+
|
28
|
+
s.files = `git ls-files`.split("\n")
|
29
|
+
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
|
30
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
31
|
+
s.require_path = 'lib'
|
32
|
+
|
33
|
+
s.has_rdoc = 'yard'
|
34
|
+
s.rdoc_options = [
|
35
|
+
'--title', 'Mutagem Documentation',
|
36
|
+
'--main', 'README.markdown',
|
37
|
+
'--line-numbers',
|
38
|
+
'--inline-source'
|
39
|
+
]
|
40
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Mutagem do
|
4
|
+
|
5
|
+
describe 'VERSION' do
|
6
|
+
|
7
|
+
it "should return a string formatted '#.#.#'" do
|
8
|
+
Mutagem::VERSION.should match(/(^[\d]+\.[\d]+\.[\d]+$)/)
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Mutagem::Lockfile do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
# remove tmp/aruba
|
7
|
+
FileUtils.rm_rf(current_dir)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should raise ArgumentError if initialized without a lock filename" do
|
11
|
+
in_current_dir do
|
12
|
+
lambda {Mutagem::Lockfile.new}.should raise_error(ArgumentError, 'lockfile not specified')
|
13
|
+
lambda {Mutagem::Lockfile.new('mylock.lock')}.should_not raise_error
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Mutagem::Mutex do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
# remove tmp/aruba
|
7
|
+
FileUtils.rm_rf(current_dir)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should create a mutex, yield, and clean up" do
|
11
|
+
in_current_dir do
|
12
|
+
mutex = Mutagem::Mutex.new
|
13
|
+
result = mutex.execute do
|
14
|
+
File.should be_file('mutagem.lck')
|
15
|
+
mutex.should be_locked
|
16
|
+
end
|
17
|
+
result.should be_true
|
18
|
+
mutex.should_not be_locked
|
19
|
+
File.should_not be_file('mutagem.lck')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should prevent recursion but not block" do
|
24
|
+
in_current_dir do
|
25
|
+
Mutagem::Mutex.new.execute do
|
26
|
+
File.should be_file('mutagem.lck')
|
27
|
+
|
28
|
+
mutex = Mutagem::Mutex.new
|
29
|
+
result = mutex.execute do
|
30
|
+
# This block is protected, should not be here
|
31
|
+
true.should be(false)
|
32
|
+
end
|
33
|
+
result.should be_false
|
34
|
+
mutex.should be_locked
|
35
|
+
end
|
36
|
+
File.should_not be_file('mutagem.lck')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should raise ArgumentError unless a block is given" do
|
41
|
+
in_current_dir do
|
42
|
+
mutex = Mutagem::Mutex.new
|
43
|
+
lambda {result = mutex.execute}.should raise_error(ArgumentError, 'missing block')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('..', __FILE__) unless
|
2
|
+
$LOAD_PATH.include? File.expand_path('..', __FILE__)
|
3
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) unless
|
4
|
+
$LOAD_PATH.include? File.expand_path('../../lib', __FILE__)
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'mutagem'
|
8
|
+
require 'spec'
|
9
|
+
require 'spec/autorun'
|
10
|
+
require 'aruba/api'
|
11
|
+
|
12
|
+
# aruba helper, returns full path to files in the aruba tmp folder
|
13
|
+
def fullpath(filename)
|
14
|
+
File.expand_path(File.join(current_dir, filename))
|
15
|
+
end
|
16
|
+
|
17
|
+
Spec::Runner.configure do |config|
|
18
|
+
config.include Aruba::Api
|
19
|
+
end
|
data/spec/watchr.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
# Watchr: Autotest like functionality
|
2
|
+
#
|
3
|
+
# Run me with:
|
4
|
+
#
|
5
|
+
# $ watchr spec/watchr.rb
|
6
|
+
|
7
|
+
require 'term/ansicolor'
|
8
|
+
|
9
|
+
$c = Term::ANSIColor
|
10
|
+
|
11
|
+
def getch
|
12
|
+
state = `stty -g`
|
13
|
+
begin
|
14
|
+
`stty raw -echo cbreak`
|
15
|
+
$stdin.getc
|
16
|
+
ensure
|
17
|
+
`stty #{state}`
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# --------------------------------------------------
|
22
|
+
# Convenience Methods
|
23
|
+
# --------------------------------------------------
|
24
|
+
def all_feature_files
|
25
|
+
Dir['features/*.feature']
|
26
|
+
end
|
27
|
+
|
28
|
+
def all_spec_files
|
29
|
+
files = Dir['spec/revenc/*.rb']
|
30
|
+
end
|
31
|
+
|
32
|
+
def run(cmd)
|
33
|
+
|
34
|
+
pid = fork do
|
35
|
+
puts "\n"
|
36
|
+
print $c.cyan, cmd, $c.clear, "\n"
|
37
|
+
exec(cmd)
|
38
|
+
end
|
39
|
+
Signal.trap('INT') do
|
40
|
+
puts "sending KILL to pid: #{pid}"
|
41
|
+
Process.kill("KILL", pid)
|
42
|
+
end
|
43
|
+
Process.waitpid(pid)
|
44
|
+
|
45
|
+
prompt
|
46
|
+
end
|
47
|
+
|
48
|
+
def run_all
|
49
|
+
run_all_specs
|
50
|
+
run_default_cucumber
|
51
|
+
end
|
52
|
+
|
53
|
+
# allow cucumber rerun.txt smarts
|
54
|
+
def run_default_cucumber
|
55
|
+
cmd = "cucumber"
|
56
|
+
run(cmd)
|
57
|
+
end
|
58
|
+
|
59
|
+
def run_all_features
|
60
|
+
cmd = "cucumber #{all_feature_files.join(' ')}"
|
61
|
+
run(cmd)
|
62
|
+
end
|
63
|
+
|
64
|
+
def run_feature(feature)
|
65
|
+
cmd = "cucumber #{feature}"
|
66
|
+
$last_feature = feature
|
67
|
+
run(cmd)
|
68
|
+
end
|
69
|
+
|
70
|
+
def run_last_feature
|
71
|
+
run_feature($last_feature) if $last_feature
|
72
|
+
end
|
73
|
+
|
74
|
+
def run_default_spec
|
75
|
+
cmd = "spec --color --format s ./spec"
|
76
|
+
run(cmd)
|
77
|
+
end
|
78
|
+
|
79
|
+
def run_all_specs
|
80
|
+
cmd = "spec --color --format s #{all_spec_files.join(' ')}"
|
81
|
+
p cmd
|
82
|
+
run(cmd)
|
83
|
+
end
|
84
|
+
|
85
|
+
def run_spec(spec)
|
86
|
+
cmd = "spec --color --format s #{spec}"
|
87
|
+
$last_spec = spec
|
88
|
+
run(cmd)
|
89
|
+
end
|
90
|
+
|
91
|
+
def run_last_spec
|
92
|
+
run_spec($last_spec) if $last_spec
|
93
|
+
end
|
94
|
+
|
95
|
+
def prompt
|
96
|
+
puts "Ctrl-\\ for menu, Ctrl-C to quit"
|
97
|
+
end
|
98
|
+
|
99
|
+
# init
|
100
|
+
$last_feature = nil
|
101
|
+
prompt
|
102
|
+
|
103
|
+
# --------------------------------------------------
|
104
|
+
# Watchr Rules
|
105
|
+
# --------------------------------------------------
|
106
|
+
#watch( '^features/(.*)\.feature' ) { |m| run_feature(m[0]) }
|
107
|
+
watch( '^features/(.*)\.feature' ) { run_default_cucumber }
|
108
|
+
|
109
|
+
watch( '^bin/(.*)' ) { run_default_cucumber }
|
110
|
+
watch( '^lib/(.*)' ) { run_default_cucumber }
|
111
|
+
|
112
|
+
watch( '^features/step_definitions/(.*)\.rb' ) { run_default_cucumber }
|
113
|
+
watch( '^features/support/(.*)\.rb' ) { run_default_cucumber }
|
114
|
+
|
115
|
+
watch( '^spec/(.*)_spec\.rb' ) { |m| run_spec(m[0]) }
|
116
|
+
# watch( '^lib/revenc/io.rb' ) { run_default_spec }
|
117
|
+
watch( '^lib/revenc/errors.rb' ) { run_default_spec }
|
118
|
+
watch( '^lib/revenc/lockfile.rb' ) { run_default_spec }
|
119
|
+
|
120
|
+
# --------------------------------------------------
|
121
|
+
# Signal Handling
|
122
|
+
# --------------------------------------------------
|
123
|
+
|
124
|
+
# Ctrl-\
|
125
|
+
Signal.trap('QUIT') do
|
126
|
+
|
127
|
+
puts "\n\nMENU: a = all , f = features s = specs, l = last feature (#{$last_feature ? $last_feature : 'none'}), q = quit\n\n"
|
128
|
+
c = getch
|
129
|
+
puts c.chr
|
130
|
+
if c.chr == "a"
|
131
|
+
run_all
|
132
|
+
elsif c.chr == "f"
|
133
|
+
run_default_cucumber
|
134
|
+
elsif c.chr == "s"
|
135
|
+
run_all_specs
|
136
|
+
elsif c.chr == "q"
|
137
|
+
abort("exiting\n")
|
138
|
+
elsif c.chr == "l"
|
139
|
+
run_last_feature
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
metadata
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mutagem
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 3
|
9
|
+
version: 0.1.3
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Robert Wahler
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-10-01 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: bundler
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 0
|
30
|
+
- 0
|
31
|
+
version: 1.0.0
|
32
|
+
type: :development
|
33
|
+
prerelease: false
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rspec
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
segments:
|
43
|
+
- 1
|
44
|
+
- 2
|
45
|
+
- 9
|
46
|
+
version: 1.2.9
|
47
|
+
type: :development
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: cucumber
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
- 6
|
60
|
+
version: "0.6"
|
61
|
+
type: :development
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: *id003
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: aruba
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
- 2
|
74
|
+
- 0
|
75
|
+
version: 0.2.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: *id004
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
name: rake
|
81
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
- 8
|
89
|
+
- 7
|
90
|
+
version: 0.8.7
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: *id005
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: yard
|
96
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
segments:
|
102
|
+
- 0
|
103
|
+
- 6
|
104
|
+
- 1
|
105
|
+
version: 0.6.1
|
106
|
+
type: :development
|
107
|
+
prerelease: false
|
108
|
+
version_requirements: *id006
|
109
|
+
- !ruby/object:Gem::Dependency
|
110
|
+
name: rdiscount
|
111
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
segments:
|
117
|
+
- 1
|
118
|
+
- 6
|
119
|
+
- 5
|
120
|
+
version: 1.6.5
|
121
|
+
type: :development
|
122
|
+
prerelease: false
|
123
|
+
version_requirements: *id007
|
124
|
+
description: |-
|
125
|
+
The Mutagem library provides file based mutexes for recursion protection and
|
126
|
+
classes for threading of external processes with support for output and
|
127
|
+
exit status capturing. A test suite is provided for both unit and functional testing.
|
128
|
+
The code is documented using YARD.
|
129
|
+
email:
|
130
|
+
- robert@gearheadforhire.com
|
131
|
+
executables: []
|
132
|
+
|
133
|
+
extensions: []
|
134
|
+
|
135
|
+
extra_rdoc_files: []
|
136
|
+
|
137
|
+
files:
|
138
|
+
- .gitattributes
|
139
|
+
- .gitignore
|
140
|
+
- .yardopts
|
141
|
+
- Gemfile
|
142
|
+
- Gemfile.lock
|
143
|
+
- HISTORY.markdown
|
144
|
+
- LICENSE
|
145
|
+
- README.markdown
|
146
|
+
- Rakefile
|
147
|
+
- TODO.markdown
|
148
|
+
- config/cucumber.yml
|
149
|
+
- features/mutex.feature
|
150
|
+
- features/step_definitions/.gitignore
|
151
|
+
- features/step_definitions/mutagem_steps.rb
|
152
|
+
- features/support/env.rb
|
153
|
+
- features/task.feature
|
154
|
+
- lib/mutagem.rb
|
155
|
+
- lib/mutagem/lockfile.rb
|
156
|
+
- lib/mutagem/mutex.rb
|
157
|
+
- lib/mutagem/task.rb
|
158
|
+
- lib/mutagem/version.rb
|
159
|
+
- mutagem.gemspec
|
160
|
+
- spec/basic_gem/basic_gem_spec.rb
|
161
|
+
- spec/mutagem/lockfile_spec.rb
|
162
|
+
- spec/mutagem/mutex_spec.rb
|
163
|
+
- spec/spec.opts
|
164
|
+
- spec/spec_helper.rb
|
165
|
+
- spec/watchr.rb
|
166
|
+
has_rdoc: yard
|
167
|
+
homepage: http://rubygems.org/gems/mutagem
|
168
|
+
licenses: []
|
169
|
+
|
170
|
+
post_install_message:
|
171
|
+
rdoc_options:
|
172
|
+
- --title
|
173
|
+
- Mutagem Documentation
|
174
|
+
- --main
|
175
|
+
- README.markdown
|
176
|
+
- --line-numbers
|
177
|
+
- --inline-source
|
178
|
+
require_paths:
|
179
|
+
- lib
|
180
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
181
|
+
none: false
|
182
|
+
requirements:
|
183
|
+
- - ">="
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
hash: 723877775
|
186
|
+
segments:
|
187
|
+
- 0
|
188
|
+
version: "0"
|
189
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
190
|
+
none: false
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
segments:
|
195
|
+
- 1
|
196
|
+
- 3
|
197
|
+
- 6
|
198
|
+
version: 1.3.6
|
199
|
+
requirements: []
|
200
|
+
|
201
|
+
rubyforge_project: mutagem
|
202
|
+
rubygems_version: 1.3.7
|
203
|
+
signing_key:
|
204
|
+
specification_version: 3
|
205
|
+
summary: File based mutexes with a simple external process management wrapper
|
206
|
+
test_files: []
|
207
|
+
|