specjour 0.1.1

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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/MIT_LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Sandro Turriate
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,47 @@
1
+ # Specjour
2
+
3
+ _Distribute your spec suite amongst your LAN via Bonjour._
4
+
5
+ 1. Start a dispatcher in your project directory.
6
+ 2. Spin up a manager on each remote machine.
7
+ 3. Say "goodbye" to your long coffee breaks.
8
+
9
+ ## Requirements
10
+
11
+ * Bonjour or DNSSD (the capability and the gem)
12
+ * Rsync (system command used)
13
+ * Rspec (officially v1.3.0)
14
+
15
+ ## Installation
16
+ gem install specjour
17
+
18
+ ## Start a manager
19
+ Running `specjour` on the command-line will start a manager which advertises that it's ready to run tests. By default, the manager will only use one worker to run the tests. If you had 4 cores however, you could use `specjour --workers 4` to run 4 sets of tests at once.
20
+
21
+ $ specjour
22
+
23
+ ## Setup the dispatcher
24
+ Add the rake task to the `Rakefile` in your project's directory.
25
+
26
+ require 'specjour/tasks/specjour'
27
+
28
+ ## Distribute the tests
29
+ Run the rake task in your project directory to start the test suite.
30
+
31
+ $ rake specjour
32
+
33
+ The worker reports passes/failures in batches of 25 so you won't get immediate feedback, override the batch size via `specjour --batch-size 1`
34
+
35
+ ## Note on Patches/Pull Requests
36
+
37
+ * Fork the project.
38
+ * Make your feature addition or bug fix.
39
+ * Add tests for it. This is important so I don't break it in a
40
+ future version unintentionally.
41
+ * Commit, do not mess with rakefile, version, or history.
42
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
43
+ * Send me a pull request. Bonus points for topic branches.
44
+
45
+ ## Copyright
46
+
47
+ Copyright (c) 2010 Sandro Turriate. See MIT_LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "specjour"
8
+ gem.summary = %Q{Distribute your spec suite amongst your LAN via Bonjour.}
9
+ gem.description = %Q{Distribute your spec suite amongst your LAN via Bonjour.}
10
+ gem.email = "sandro.turriate@gmail.com"
11
+ gem.homepage = "http://github.com/sandro/specjour"
12
+ gem.authors = ["Sandro Turriate"]
13
+ gem.add_dependency "dnssd", "1.3.1"
14
+ gem.add_dependency "rspec"
15
+ gem.add_development_dependency "rspec", "1.3.0"
16
+ gem.add_development_dependency "yard", "0.5.3"
17
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
+ end
19
+ Jeweler::GemcutterTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
22
+ end
23
+
24
+ require 'spec/rake/spectask'
25
+ Spec::Rake::SpecTask.new(:spec) do |spec|
26
+ spec.libs << 'lib' << 'spec'
27
+ spec.spec_files = FileList['spec/**/*_spec.rb']
28
+ end
29
+
30
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
31
+ spec.libs << 'lib' << 'spec'
32
+ spec.pattern = 'spec/**/*_spec.rb'
33
+ spec.rcov = true
34
+ end
35
+
36
+ task :spec => :check_dependencies
37
+
38
+ task :default => :spec
39
+
40
+ begin
41
+ require 'yard'
42
+ YARD::Rake::YardocTask.new
43
+ rescue LoadError
44
+ task :yardoc do
45
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
46
+ end
47
+ end
48
+
49
+ $:.unshift(File.dirname(__FILE__) + "/lib")
50
+ require 'specjour/tasks/specjour'
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
data/bin/specjour ADDED
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'specjour'
4
+
5
+ options = {:worker_size => 1, :batch_size => 25}
6
+
7
+ optparse = OptionParser.new do |opts|
8
+ opts.banner = "Usage: specjour [options]"
9
+
10
+ opts.on('-w', '--workers [WORKERS]', Numeric, "Number of WORKERS to spin up, defaults to #{options[:worker_size]}") do |n|
11
+ options[:worker_size] = n
12
+ end
13
+
14
+ opts.on('-b', '--batch-size [SIZE]', Integer, "Number of specs to run before reporting back to the dispatcher, defaults to #{options[:batch_size]}") do |n|
15
+ options[:batch_size] = n
16
+ end
17
+
18
+ opts.on('--do-work OPTIONS', Array, 'INTERNAL USE ONLY') do |args|
19
+ specs_to_run = args[3..-1]
20
+ options[:worker_args] = args[0], args[1], args[2], specs_to_run
21
+ end
22
+
23
+ opts.on_tail('-v', '--version', 'Show the version of specjour') do
24
+ abort Specjour::VERSION
25
+ end
26
+
27
+ opts.on_tail("-h", "--help", "Show this message") do
28
+ summary = opts.to_a
29
+ summary.first << "\n"
30
+ abort summary.reject {|s| s =~ /INTERNAL/}.join
31
+ end
32
+ end
33
+
34
+ optparse.parse!
35
+
36
+ if options[:worker_args]
37
+ options[:worker_args] << options[:batch_size]
38
+ Specjour::Worker.new(*options[:worker_args]).run
39
+ else
40
+ Specjour::Manager.new(options[:worker_size], options[:batch_size]).start
41
+ end
@@ -0,0 +1,15 @@
1
+ module Specjour
2
+ module Among
3
+ def among(group_size)
4
+ group_size = 1 if group_size.zero?
5
+ groups = Array.new(group_size) { [] }
6
+ offset = 0
7
+ each do |item|
8
+ groups[offset] << item
9
+ offset = (offset == group_size - 1) ? 0 : offset + 1
10
+ end
11
+ groups
12
+ end
13
+ end
14
+ end
15
+ ::Array.send(:include, Specjour::Among)
@@ -0,0 +1,23 @@
1
+ module Specjour
2
+ module DbScrub
3
+ extend self
4
+
5
+ def scrub
6
+ begin
7
+ ActiveRecord::Base.connection
8
+ rescue # assume the database doesn't exist
9
+ create_db_and_schema
10
+ else
11
+ ActiveRecord::Base.connection.tables.each do |table|
12
+ ActiveRecord::Base.connection.delete "delete from #{table}"
13
+ end
14
+ end
15
+ end
16
+
17
+ def create_db_and_schema
18
+ load 'Rakefile'
19
+ Rake::Task['db:create'].invoke
20
+ Rake::Task['db:schema:load'].invoke
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,113 @@
1
+ module Specjour
2
+ class Dispatcher
3
+ require 'dnssd'
4
+ Thread.abort_on_exception = true
5
+
6
+ attr_reader :project_path, :managers, :manager_threads, :hosts
7
+ attr_accessor :worker_size
8
+
9
+ def initialize(project_path)
10
+ @project_path = project_path
11
+ @managers = []
12
+ @worker_size = 0
13
+ reset_manager_threads
14
+ end
15
+
16
+ def start
17
+ rsync_daemon.start
18
+ gather_managers
19
+ sync_managers
20
+ dispatch_work
21
+ printer.join
22
+ end
23
+
24
+ protected
25
+
26
+ def all_specs
27
+ @all_specs ||= Dir.chdir(project_path) do
28
+ Dir["spec/**/**/*_spec.rb"].partition {|f| f =~ /integration/}.flatten
29
+ end
30
+ end
31
+
32
+ def dispatch_work
33
+ distributable_specs = all_specs.among(worker_size)
34
+ last_index = 0
35
+ managers.each_with_index do |manager, index|
36
+ manager.specs_to_run = Array.new(manager.worker_size) do |i|
37
+ distributable_specs[last_index + i]
38
+ end
39
+ last_index += manager.worker_size
40
+ manager_threads << Thread.new(manager) {|m| m.dispatch}
41
+ end
42
+ end
43
+
44
+ def drb_start
45
+ DRb.start_service nil, self
46
+ at_exit { puts 'shutting down DRb client'; DRb.stop_service }
47
+ end
48
+
49
+ def fetch_manager(uri)
50
+ manager = DRbObject.new_with_uri(uri.to_s)
51
+ unless managers.include?(manager)
52
+ set_up_manager(manager, uri)
53
+ managers << manager
54
+ self.worker_size += manager.worker_size
55
+ end
56
+ end
57
+
58
+ def gather_managers
59
+ puts "Waiting for managers"
60
+ Signal.trap('INT') { exit }
61
+ browser = DNSSD::Service.new
62
+ browser.browse '_druby._tcp' do |reply|
63
+ if reply.flags.add?
64
+ resolve_reply(reply)
65
+ end
66
+ browser.stop unless reply.flags.more_coming?
67
+ end
68
+ puts "Managers found: #{managers.size}"
69
+ printer.worker_size = worker_size
70
+ end
71
+
72
+ def printer
73
+ @printer ||= Printer.new.start
74
+ end
75
+
76
+ def project_name
77
+ @project_name ||= File.basename(project_path)
78
+ end
79
+
80
+ def reset_manager_threads
81
+ @manager_threads = []
82
+ end
83
+
84
+ def resolve_reply(reply)
85
+ DNSSD.resolve!(reply) do |resolved|
86
+ uri = URI::Generic.build :scheme => reply.service_name, :host => resolved.target, :port => resolved.port
87
+ fetch_manager(uri)
88
+ resolved.service.stop if resolved.service.started?
89
+ end
90
+ end
91
+
92
+ def rsync_daemon
93
+ @rsync_daemon ||= RsyncDaemon.new(project_path, project_name)
94
+ end
95
+
96
+ def set_up_manager(manager, uri)
97
+ manager.project_name = project_name
98
+ manager.dispatcher_uri = URI::Generic.build :scheme => "specjour", :host => printer.host, :port => printer.port
99
+ end
100
+
101
+ def sync_managers
102
+ managers.each do |manager|
103
+ manager_threads << Thread.new(manager) { |manager| manager.sync }
104
+ end
105
+ wait_on_managers
106
+ end
107
+
108
+ def wait_on_managers
109
+ manager_threads.each {|t| t.join; t.exit}
110
+ reset_manager_threads
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,84 @@
1
+ module Specjour
2
+ class DistributedFormatter < Spec::Runner::Formatter::BaseTextFormatter
3
+ require 'specjour/marshalable_rspec_failure'
4
+
5
+ class << self
6
+ attr_accessor :batch_size
7
+ end
8
+ @batch_size = 1
9
+
10
+ attr_reader :failing_messages, :passing_messages, :pending_messages, :output
11
+ attr_reader :duration, :example_count, :failure_count, :pending_count, :pending_examples, :failing_examples
12
+
13
+ def initialize(options, output)
14
+ @options = options
15
+ @output = output.extend Specjour::Protocol
16
+ @failing_messages = []
17
+ @passing_messages = []
18
+ @pending_messages = []
19
+ @pending_examples = []
20
+ @failing_examples = []
21
+ end
22
+
23
+ def example_failed(example, counter, failure)
24
+ failing_messages << colorize_failure('F', failure)
25
+ batch_print(failing_messages)
26
+ end
27
+
28
+ def example_passed(example)
29
+ passing_messages << green('.')
30
+ batch_print(passing_messages)
31
+ end
32
+
33
+ def example_pending(example, message, deprecated_pending_location=nil)
34
+ super
35
+ pending_messages << yellow('*')
36
+ batch_print(pending_messages)
37
+ end
38
+
39
+ def dump_summary(duration, example_count, failure_count, pending_count)
40
+ @duration = duration
41
+ @example_count = example_count
42
+ @failure_count = failure_count
43
+ @pending_count = pending_count
44
+ output.puts [:worker_summary=, to_hash]
45
+ output.flush
46
+ end
47
+
48
+ def dump_pending
49
+ #noop
50
+ end
51
+
52
+ def dump_failure(counter, failure)
53
+ failing_examples << failure
54
+ end
55
+
56
+ def start_dump
57
+ print_and_flush failing_messages
58
+ print_and_flush passing_messages
59
+ print_and_flush pending_messages
60
+ end
61
+
62
+ def to_hash
63
+ h = {}
64
+ [:duration, :example_count, :failure_count, :pending_count, :pending_examples, :failing_examples].each do |key|
65
+ h[key] = send(key)
66
+ end
67
+ h
68
+ end
69
+
70
+ protected
71
+
72
+ def batch_print(messages)
73
+ if messages.size == self.class.batch_size
74
+ print_and_flush(messages)
75
+ end
76
+ end
77
+
78
+ def print_and_flush(messages)
79
+ output.print messages.to_s
80
+ output.flush
81
+ messages.replace []
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,60 @@
1
+ module Specjour
2
+ class FinalReport
3
+ require 'specjour/marshalable_rspec_failure'
4
+ attr_reader :duration, :example_count, :failure_count, :pending_count, :pending_examples, :failing_examples
5
+
6
+ def initialize
7
+ @duration = 0.0
8
+ @example_count = 0
9
+ @failure_count = 0
10
+ @pending_count = 0
11
+ @pending_examples = []
12
+ @failing_examples = []
13
+ end
14
+
15
+ def add(stats)
16
+ stats.each do |key, value|
17
+ if key == :duration
18
+ @duration = value.to_f if duration < value.to_f
19
+ else
20
+ increment(key, value)
21
+ end
22
+ end
23
+ end
24
+
25
+ def increment(key, value)
26
+ current = instance_variable_get("@#{key}")
27
+ instance_variable_set("@#{key}", current + value)
28
+ end
29
+
30
+ def formatter_options
31
+ @formatter_options ||= OpenStruct.new(
32
+ :colour => true,
33
+ :autospec => false,
34
+ :dry_run => false
35
+ )
36
+ end
37
+
38
+ def formatter
39
+ @formatter ||= begin
40
+ f = MarshalableFailureFormatter.new(formatter_options, $stdout)
41
+ f.instance_variable_set(:@pending_examples, pending_examples)
42
+ f
43
+ end
44
+ end
45
+
46
+ def summarize
47
+ if example_count > 0
48
+ formatter.dump_pending
49
+ dump_failures
50
+ formatter.dump_summary(duration, example_count, failure_count, pending_count)
51
+ end
52
+ end
53
+
54
+ def dump_failures
55
+ failing_examples.each_with_index do |failure, index|
56
+ formatter.dump_failure index + 1, failure
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,67 @@
1
+ module Specjour
2
+ class Manager
3
+ require 'dnssd'
4
+ include DRbUndumped
5
+
6
+ attr_accessor :project_name, :specs_to_run, :dispatcher_uri, :worker_size, :bonjour_service, :batch_size
7
+
8
+ def initialize(worker_size = 1, batch_size = 25)
9
+ @worker_size = worker_size
10
+ @batch_size = 25
11
+ end
12
+
13
+ def project_path=(name)
14
+ @project_path = name
15
+ end
16
+
17
+ def project_path
18
+ @project_path ||= File.join("/tmp", project_name)
19
+ end
20
+
21
+ def dispatch
22
+ bonjour_service.stop
23
+ pids = []
24
+ (1..worker_size).each do |index|
25
+ pids << fork do
26
+ exec("specjour --batch-size #{batch_size} --do-work #{project_path},#{dispatcher_uri},#{index},#{specs_to_run[index - 1].join(',')}")
27
+ Kernel.exit!
28
+ end
29
+ end
30
+ at_exit { Process.kill('KILL', *pids) rescue nil }
31
+ Process.waitall
32
+ bonjour_announce
33
+ end
34
+
35
+ def start
36
+ drb_start
37
+ bonjour_announce
38
+ Signal.trap('INT') { puts; puts "Shutting down manager..."; exit }
39
+ DRb.thread.join
40
+ end
41
+
42
+ def drb_start
43
+ DRb.start_service nil, self
44
+ Kernel.puts "Manager started at #{drb_uri}"
45
+ at_exit { DRb.stop_service }
46
+ end
47
+
48
+ def sync
49
+ cmd "rsync -a --port=8989 #{dispatcher_uri.host}::#{project_name} #{project_path}"
50
+ end
51
+
52
+ protected
53
+
54
+ def cmd(command)
55
+ Kernel.puts command
56
+ system command
57
+ end
58
+
59
+ def drb_uri
60
+ @drb_uri ||= URI.parse(DRb.uri)
61
+ end
62
+
63
+ def bonjour_announce
64
+ @bonjour_service = DNSSD.register! "specjour_manager_#{object_id}", "_#{drb_uri.scheme}._tcp", nil, drb_uri.port
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,11 @@
1
+ module Specjour
2
+ class MarshalableFailureFormatter < Spec::Runner::Formatter::BaseTextFormatter
3
+ def dump_failure(counter, failure)
4
+ @output.puts
5
+ @output.puts "#{counter.to_s})"
6
+ @output.puts colorize_failure("#{failure.header}\n#{failure.message}", failure)
7
+ @output.puts format_backtrace(failure.backtrace)
8
+ @output.flush
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,33 @@
1
+ module Specjour
2
+ class Spec::Runner::Reporter::Failure
3
+ attr_reader :backtrace, :message, :header, :exception_class_name
4
+
5
+ def initialize(group_description, example_description, exception)
6
+ @example_name = "#{group_description} #{example_description}"
7
+ @message = exception.message
8
+ @backtrace = exception.backtrace
9
+ @exception_class_name = exception.class.name
10
+ @pending_fixed = exception.is_a?(Spec::Example::PendingExampleFixedError)
11
+ @exception_not_met = exception.is_a?(Spec::Expectations::ExpectationNotMetError)
12
+ set_header
13
+ end
14
+
15
+ def set_header
16
+ if expectation_not_met?
17
+ @header = "'#{@example_name}' FAILED"
18
+ elsif pending_fixed?
19
+ @header = "'#{@example_name}' FIXED"
20
+ else
21
+ @header = "#{exception_class_name} in '#{@example_name}'"
22
+ end
23
+ end
24
+
25
+ def pending_fixed?
26
+ @pending_fixed
27
+ end
28
+
29
+ def expectation_not_met?
30
+ @exception_not_met
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,65 @@
1
+ module Specjour
2
+ class Printer < GServer
3
+ include Protocol
4
+ RANDOM_PORT = 0
5
+
6
+ attr_reader :completed_workers
7
+ attr_accessor :worker_size
8
+
9
+ def initialize
10
+ super(
11
+ port = RANDOM_PORT,
12
+ host = hostname,
13
+ max_connections = 100,
14
+ stdlog = $stderr,
15
+ audit = true,
16
+ debug = true
17
+ )
18
+ @completed_workers = 0
19
+ end
20
+
21
+ def serve(client)
22
+ client.each(TERMINATOR) do |data|
23
+ process load_object(data)
24
+ end
25
+ end
26
+
27
+ def worker_summary=(summary)
28
+ report.add(summary)
29
+ end
30
+
31
+ protected
32
+
33
+ def disconnecting(client_port)
34
+ @completed_workers += 1
35
+ if completed_workers == worker_size
36
+ stop
37
+ end
38
+ end
39
+
40
+ def hostname
41
+ @hostname ||= Socket.gethostname
42
+ end
43
+
44
+ def log(msg)
45
+ #noop
46
+ end
47
+
48
+ def process(message)
49
+ if message.is_a?(String)
50
+ $stdout.print message
51
+ $stdout.flush
52
+ elsif message.is_a?(Array)
53
+ send(message.first, message[1])
54
+ end
55
+ end
56
+
57
+ def report
58
+ @report ||= FinalReport.new
59
+ end
60
+
61
+ def stopping
62
+ report.summarize
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,21 @@
1
+ module Specjour
2
+ module Protocol
3
+ TERMINATOR = "|ruojceps|"
4
+
5
+ def puts(arg)
6
+ print(arg << "\n")
7
+ end
8
+
9
+ def print(arg)
10
+ super dump_object(arg)
11
+ end
12
+
13
+ def dump_object(data)
14
+ Marshal.dump(data) << TERMINATOR
15
+ end
16
+
17
+ def load_object(data)
18
+ Marshal.load(data.sub(/#{TERMINATOR}$/, ''))
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,60 @@
1
+ module Specjour
2
+ class RsyncDaemon
3
+ require 'fileutils'
4
+
5
+ attr_reader :project_path, :project_name
6
+ def initialize(project_path, project_name)
7
+ @project_path = project_path
8
+ @project_name = project_name
9
+ end
10
+
11
+ def config_file
12
+ File.join("/tmp", "rsyncd.conf")
13
+ end
14
+
15
+ def start
16
+ write_config
17
+ system("rsync", "--daemon", "--config=#{config_file}", "--port=8989")
18
+ at_exit { puts 'shutting down rsync'; stop }
19
+ end
20
+
21
+ def stop
22
+ if pid
23
+ Process.kill("TERM", pid)
24
+ FileUtils.rm(pid_file)
25
+ end
26
+ end
27
+
28
+ protected
29
+
30
+ def write_config
31
+ File.open(config_file, 'w') do |f|
32
+ f.write config
33
+ end
34
+ end
35
+
36
+ def pid
37
+ if File.exists?(pid_file)
38
+ File.read(pid_file).strip.to_i
39
+ end
40
+ end
41
+
42
+ def pid_file
43
+ File.join("/tmp", "#{project_name}_rsync_daemon.pid")
44
+ end
45
+
46
+ def config
47
+ <<-CONFIG
48
+ # global configuration
49
+ use chroot = no
50
+ timeout = 60
51
+ read only = yes
52
+ pid file = #{pid_file}
53
+
54
+ [#{project_name}]
55
+ path = #{project_path}
56
+ exclude = .git* doc tmp/* public log script
57
+ CONFIG
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,13 @@
1
+ require 'specjour'
2
+
3
+ namespace :specjour do
4
+ task :dispatch, [:project_path] do |task, args|
5
+ args.with_defaults :project_path => Rake.original_dir
6
+ Specjour::Dispatcher.new(args.project_path).start
7
+ end
8
+ end
9
+
10
+ desc "Dispatch the [project_path] to listening managers"
11
+ task :specjour, [:project_path] do |task, args|
12
+ Rake::Task['specjour:dispatch'].invoke(args[:project_path])
13
+ end
@@ -0,0 +1 @@
1
+ load File.join(File.dirname(__FILE__), "dispatch.rake")
@@ -0,0 +1,55 @@
1
+ module Specjour
2
+ class Worker
3
+ attr_accessor :dispatcher_uri
4
+ attr_reader :project_path, :specs_to_run, :number, :batch_size
5
+
6
+ def initialize(project_path, dispatcher_uri, number, specs_to_run, batch_size)
7
+ @project_path = project_path
8
+ @specs_to_run = specs_to_run
9
+ @number = number.to_i
10
+ @batch_size = batch_size.to_i
11
+ self.dispatcher_uri = dispatcher_uri
12
+ end
13
+
14
+ def dispatcher_uri=(val)
15
+ @dispatcher_uri = URI.parse(val)
16
+ end
17
+
18
+ def run
19
+ puts "Running #{specs_to_run.size} spec files..."
20
+ GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=)
21
+ DistributedFormatter.batch_size = batch_size
22
+ Dir.chdir(project_path) do
23
+ set_env_variables
24
+ options = Spec::Runner::OptionParser.parse(
25
+ rspec_options,
26
+ STDERR,
27
+ dispatcher
28
+ )
29
+ Spec::Runner.use options
30
+ options.run_examples
31
+ Spec::Runner.options.instance_variable_set(:@examples_run, true)
32
+ end
33
+ end
34
+
35
+ protected
36
+
37
+ def dispatcher
38
+ @dispatcher ||= TCPSocket.open dispatcher_uri.host, dispatcher_uri.port
39
+ end
40
+
41
+ def rspec_options
42
+ %w(--format=Specjour::DistributedFormatter) + specs_to_run
43
+ end
44
+
45
+ def set_env_variables
46
+ ENV['PREPARE_DB'] = 'true'
47
+ ENV['RSPEC_COLOR'] = 'true'
48
+ if number > 1
49
+ ENV['TEST_ENV_NUMBER'] = number.to_s
50
+ else
51
+ ENV['TEST_ENV_NUMBER'] = nil
52
+ end
53
+ end
54
+ end
55
+ end
data/lib/specjour.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'spec'
2
+ require 'spec/runner/formatter/base_text_formatter'
3
+ require 'specjour/protocol'
4
+ require 'specjour/core_ext/array'
5
+
6
+ autoload :URI, 'uri'
7
+ autoload :DRb, 'drb'
8
+ autoload :Forwardable, 'forwardable'
9
+ autoload :GServer, 'gserver'
10
+
11
+ module Specjour
12
+ autoload :Dispatcher, 'specjour/dispatcher'
13
+ autoload :DistributedFormatter, 'specjour/distributed_formatter'
14
+ autoload :FinalReport, 'specjour/final_report'
15
+ autoload :Manager, 'specjour/manager'
16
+ autoload :MarshalableFailureFormatter, 'specjour/marshalable_failure_formatter'
17
+ autoload :Printer, 'specjour/printer'
18
+ autoload :RsyncDaemon, 'specjour/rsync_daemon'
19
+ autoload :Worker, 'specjour/worker'
20
+
21
+ VERSION = "0.1.1".freeze
22
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,4 @@
1
+ if ENV['PREPARE_DB']
2
+ require 'specjour/db_scrub'
3
+ Specjour::DbScrub.scrub
4
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Array splitting among many" do
4
+ describe "#among" do
5
+ let(:array) { [1,2,3,4,5] }
6
+
7
+ it "splits among 0" do
8
+ array.among(0).should == [[1,2,3,4,5]]
9
+ end
10
+
11
+ it "splits among by 1" do
12
+ array.among(1).should == [[1,2,3,4,5]]
13
+ end
14
+
15
+ it "splits among by 2" do
16
+ array.among(2).should == [[1,3,5],[2,4]]
17
+ end
18
+
19
+ it "splits among by 3" do
20
+ array.among(3).should == [[1,4],[2,5],[3]]
21
+ end
22
+
23
+ it "splits among by 4" do
24
+ array.among(4).should == [[1,5],[2],[3],[4]]
25
+ end
26
+
27
+ it "splits among by 5" do
28
+ array.among(5).should == [[1],[2],[3],[4],[5]]
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ class CustomException < RuntimeError
4
+ end
5
+
6
+ def boo
7
+ raise CustomException, 'fails'
8
+ end
9
+
10
+ describe Specjour::Worker do
11
+ it "fails" do
12
+ boo
13
+ end
14
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --backtrace
@@ -0,0 +1,8 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'specjour'
4
+ require 'spec/autorun'
5
+
6
+ Spec::Runner.configure do |config|
7
+
8
+ end
@@ -0,0 +1,5 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Specjour do
4
+ it "lives"
5
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: specjour
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 1
9
+ version: 0.1.1
10
+ platform: ruby
11
+ authors:
12
+ - Sandro Turriate
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-03-20 00:00:00 -04:00
18
+ default_executable: specjour
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: dnssd
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 1
29
+ - 3
30
+ - 1
31
+ version: 1.3.1
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rspec
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :runtime
45
+ version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "="
52
+ - !ruby/object:Gem::Version
53
+ segments:
54
+ - 1
55
+ - 3
56
+ - 0
57
+ version: 1.3.0
58
+ type: :development
59
+ version_requirements: *id003
60
+ - !ruby/object:Gem::Dependency
61
+ name: yard
62
+ prerelease: false
63
+ requirement: &id004 !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "="
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
69
+ - 5
70
+ - 3
71
+ version: 0.5.3
72
+ type: :development
73
+ version_requirements: *id004
74
+ description: Distribute your spec suite amongst your LAN via Bonjour.
75
+ email: sandro.turriate@gmail.com
76
+ executables:
77
+ - specjour
78
+ extensions: []
79
+
80
+ extra_rdoc_files:
81
+ - README.markdown
82
+ files:
83
+ - .document
84
+ - .gitignore
85
+ - MIT_LICENSE
86
+ - README.markdown
87
+ - Rakefile
88
+ - VERSION
89
+ - bin/specjour
90
+ - lib/specjour.rb
91
+ - lib/specjour/core_ext/array.rb
92
+ - lib/specjour/db_scrub.rb
93
+ - lib/specjour/dispatcher.rb
94
+ - lib/specjour/distributed_formatter.rb
95
+ - lib/specjour/final_report.rb
96
+ - lib/specjour/manager.rb
97
+ - lib/specjour/marshalable_failure_formatter.rb
98
+ - lib/specjour/marshalable_rspec_failure.rb
99
+ - lib/specjour/printer.rb
100
+ - lib/specjour/protocol.rb
101
+ - lib/specjour/rsync_daemon.rb
102
+ - lib/specjour/tasks/dispatch.rake
103
+ - lib/specjour/tasks/specjour.rb
104
+ - lib/specjour/worker.rb
105
+ - rails/init.rb
106
+ - spec/lib/specjour/core_ext/array_spec.rb
107
+ - spec/lib/specjour/worker_spec.rb
108
+ - spec/spec.opts
109
+ - spec/spec_helper.rb
110
+ - spec/specjour_spec.rb
111
+ has_rdoc: true
112
+ homepage: http://github.com/sandro/specjour
113
+ licenses: []
114
+
115
+ post_install_message:
116
+ rdoc_options:
117
+ - --charset=UTF-8
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ segments:
125
+ - 0
126
+ version: "0"
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ segments:
132
+ - 0
133
+ version: "0"
134
+ requirements: []
135
+
136
+ rubyforge_project:
137
+ rubygems_version: 1.3.6
138
+ signing_key:
139
+ specification_version: 3
140
+ summary: Distribute your spec suite amongst your LAN via Bonjour.
141
+ test_files:
142
+ - spec/lib/specjour/core_ext/array_spec.rb
143
+ - spec/lib/specjour/worker_spec.rb
144
+ - spec/spec_helper.rb
145
+ - spec/specjour_spec.rb