specjour 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -19,3 +19,5 @@ rdoc
19
19
  pkg
20
20
 
21
21
  ## PROJECT::SPECIFIC
22
+ .specjour
23
+ spec/spec.opts
data/History.markdown CHANGED
@@ -1,8 +1,17 @@
1
1
  History
2
2
  =======
3
3
 
4
+ 0.2.1
5
+ -----
6
+ *2010-04-21*
7
+
8
+ * [added] The rsync daemon configuration file now lives in
9
+ project_path/.specjour/rsyncd.conf. Edit your rsync exclusions there.
10
+ * [fixed] Don't report connection errors when CTRL-C is sent.
11
+
4
12
  0.2.0
5
13
  -----
6
14
  *2010-04-20*
7
15
 
8
16
  * [added] Cucumber support. `rake specjour:cucumber`
17
+ * [added] CPU Core detection, use -w to override with less or more workers
data/README.markdown CHANGED
@@ -17,7 +17,7 @@ _Distribute your spec suite amongst your LAN via Bonjour._
17
17
  gem install specjour
18
18
 
19
19
  ## Start a manager
20
- Running `specjour` on the command-line will start a manager which advertises that it's ready to run specs. By default, the manager will only use one worker to run yours specs. If you had 4 cores however, you could use `specjour --workers 4` to run 4 sets of specs at once.
20
+ Running `specjour` on the command-line will start a manager which advertises that it's ready to run specs. By default, the manager will use your system cores to determine the number of workers to use. Two cores equals two workers. If you only want to dedicate 1 core to running specs, use `$ specjour --workers 1`.
21
21
 
22
22
  $ specjour
23
23
 
@@ -57,6 +57,22 @@ You could also listen to multiple projects:
57
57
 
58
58
  $ specjour --projects bizconf,workbeast # only run specs for the bizconf and workbeast projects
59
59
 
60
+ ## Customize what gets rsync'd
61
+ The standard rsync configuration file may be too broad for your
62
+ project. If you find you're rsyncing gigs of extraneous data from your public
63
+ directory, add an exclusion to your projects rsyncd.conf file.
64
+
65
+ $ vi workbeast/.specjour/rsyncd.conf
66
+
67
+ ## Use one machine
68
+ Distributed testing doesn't have to happen over multiple machines, just multiple processes. Specjour is an excellent candidiate for running 4 tests at once on one machine with 4 cores. Just run `$ specjour` in one window and `$ rake specjour` in another.
69
+
70
+ ## Thanks
71
+
72
+ * shayarnett - Cucumber support, pairing and other various patches
73
+ * voxdolo - Endless support, alpha testing, various patches
74
+ * leshill - Made rsync daemon configurable
75
+
60
76
  ## Note on Patches/Pull Requests
61
77
 
62
78
  * Fork the project.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.2.1
@@ -29,14 +29,12 @@ module Specjour
29
29
  end
30
30
 
31
31
  def scenarios(status=nil)
32
- require 'ostruct'
33
32
  length = status ? @scenarios[status] : @scenarios.inject(0) {|h,(k,v)| h += v}
34
33
  any = @scenarios[status] > 0 if status
35
34
  OpenStruct.new(:length => length , :any? => any)
36
35
  end
37
36
 
38
37
  def steps(status=nil)
39
- require 'ostruct'
40
38
  length = status ? @steps[status] : @steps.inject(0) {|h,(k,v)| h += v}
41
39
  any = @steps[status] > 0 if status
42
40
  OpenStruct.new(:length => length , :any? => any)
@@ -6,7 +6,7 @@ module Specjour
6
6
  def scrub
7
7
  connect_to_database
8
8
  if pending_migrations?
9
- puts "Migrating schema for database #{ENV['TEST_ENV_NUMBER'] || 1}..."
9
+ puts "Migrating schema for database #{ENV['TEST_ENV_NUMBER']}..."
10
10
  Rake::Task['db:test:load'].invoke
11
11
  else
12
12
  purge_tables
@@ -4,6 +4,11 @@ module Specjour
4
4
  Thread.abort_on_exception = true
5
5
  include SocketHelpers
6
6
 
7
+ class << self
8
+ attr_accessor :interrupted
9
+ alias interrupted? interrupted
10
+ end
11
+
7
12
  attr_reader :project_path, :managers, :manager_threads, :hosts
8
13
  attr_accessor :worker_size
9
14
 
@@ -55,7 +60,7 @@ module Specjour
55
60
 
56
61
  def gather_managers
57
62
  puts "Waiting for managers"
58
- Signal.trap('INT') { exit }
63
+ Signal.trap('INT') { self.class.interrupted = true; exit }
59
64
  browser = DNSSD::Service.new
60
65
  begin
61
66
  Timeout.timeout(10) do
@@ -1,4 +1,6 @@
1
1
  module Specjour
2
+ require 'specjour/rspec'
3
+
2
4
  class Printer < GServer
3
5
  include Protocol
4
6
  RANDOM_PORT = 0
@@ -48,7 +50,8 @@ module Specjour
48
50
  def disconnecting(client_port)
49
51
  self.disconnections += 1
50
52
  if disconnections == worker_size
51
- stop
53
+ shutdown
54
+ stop unless stopped?
52
55
  end
53
56
  end
54
57
 
@@ -56,6 +59,10 @@ module Specjour
56
59
  # noop
57
60
  end
58
61
 
62
+ def error(exception)
63
+ Specjour.logger.debug exception.inspect
64
+ end
65
+
59
66
  def process(message, client)
60
67
  if message.is_a?(String)
61
68
  $stdout.print message
@@ -71,11 +78,15 @@ module Specjour
71
78
 
72
79
  def stopping
73
80
  report.summarize
74
- if disconnections != completed_workers
75
- puts "*" * 63
76
- puts "* ERROR: NOT ALL WORKERS COMPLETED PROPERLY, RE-RUN THE SUITE *"
77
- puts "*" * 63
81
+ if disconnections != completed_workers && !Specjour::Dispatcher.interrupted?
82
+ puts abandoned_worker_message
78
83
  end
79
84
  end
85
+
86
+ def abandoned_worker_message
87
+ data = "* ERROR: NOT ALL WORKERS COMPLETED PROPERLY *"
88
+ filler = "*" * data.size
89
+ [filler, data, filler].join "\n"
90
+ end
80
91
  end
81
92
  end
@@ -1,7 +1,6 @@
1
1
  module Specjour
2
2
  module Rspec
3
3
  class FinalReport
4
- require 'specjour/rspec/marshalable_rspec_failure'
5
4
  attr_reader :duration, :example_count, :failure_count, :pending_count, :pending_examples, :failing_examples
6
5
 
7
6
  def initialize
@@ -38,7 +37,7 @@ module Specjour
38
37
 
39
38
  def formatter
40
39
  @formatter ||= begin
41
- f = MarshalableFailureFormatter.new(formatter_options, $stdout)
40
+ f = Spec::Runner::Formatter::BaseTextFormatter.new(formatter_options, $stdout)
42
41
  f.instance_variable_set(:@pending_examples, pending_examples)
43
42
  f
44
43
  end
@@ -1,33 +1,35 @@
1
- module Specjour::Rspec
2
- class Spec::Runner::Reporter::Failure
3
- attr_reader :backtrace, :message, :header, :exception_class_name
1
+ module Specjour
2
+ module Rspec
3
+ class ::Spec::Runner::Reporter::Failure
4
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
5
+ def initialize(group_description, example_description, exception)
6
+ @example_name = "#{group_description} #{example_description}"
7
+ @exception = MarshalableException.new(exception)
8
+ @pending_fixed = exception.is_a?(Spec::Example::PendingExampleFixedError)
9
+ @exception_not_met = exception.is_a?(Spec::Expectations::ExpectationNotMetError)
10
+ end
11
+
12
+ def pending_fixed?
13
+ @pending_fixed
14
+ end
14
15
 
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}'"
16
+ def expectation_not_met?
17
+ @exception_not_met
22
18
  end
23
19
  end
20
+ end
21
+
22
+ class MarshalableException
23
+ attr_accessor :message, :backtrace, :class_name
24
24
 
25
- def pending_fixed?
26
- @pending_fixed
25
+ def initialize(exception)
26
+ self.class_name = exception.class.name
27
+ self.message = exception.message
28
+ self.backtrace = exception.backtrace
27
29
  end
28
30
 
29
- def expectation_not_met?
30
- @exception_not_met
31
+ def class
32
+ @class ||= OpenStruct.new :name => class_name
31
33
  end
32
34
  end
33
35
  end
@@ -3,8 +3,7 @@ module Specjour
3
3
  require 'spec'
4
4
  require 'spec/runner/formatter/base_text_formatter'
5
5
 
6
- autoload :DistributedFormatter, 'specjour/rspec/distributed_formatter'
7
- autoload :FinalReport, 'specjour/rspec/final_report'
8
- autoload :MarshalableFailureFormatter, 'specjour/rspec/marshalable_failure_formatter'
6
+ require 'specjour/rspec/distributed_formatter'
7
+ require 'specjour/rspec/final_report'
9
8
  end
10
9
  end
@@ -1,20 +1,36 @@
1
1
  module Specjour
2
2
  class RsyncDaemon
3
3
  require 'fileutils'
4
+ include SocketHelpers
4
5
 
5
6
  attr_reader :project_path, :project_name
7
+
6
8
  def initialize(project_path, project_name)
7
9
  @project_path = project_path
8
10
  @project_name = project_name
9
11
  end
10
12
 
13
+ def config_directory
14
+ @config_directory ||= File.join(project_path, ".specjour")
15
+ end
16
+
11
17
  def config_file
12
- File.join("/tmp", "rsyncd.conf")
18
+ @config_file ||= File.join(config_directory, "rsyncd.conf")
19
+ end
20
+
21
+ def pid
22
+ if File.exists?(pid_file)
23
+ File.read(pid_file).strip.to_i
24
+ end
25
+ end
26
+
27
+ def pid_file
28
+ File.join(config_directory, "rsync_daemon.pid")
13
29
  end
14
30
 
15
31
  def start
16
32
  write_config
17
- system("rsync", "--daemon", "--config=#{config_file}", "--port=8989")
33
+ system *command
18
34
  at_exit { stop }
19
35
  end
20
36
 
@@ -28,32 +44,38 @@ module Specjour
28
44
  protected
29
45
 
30
46
  def write_config
31
- File.open(config_file, 'w') do |f|
32
- f.write config
33
- end
34
- end
47
+ unless File.exists? config_file
48
+ FileUtils.mkdir_p config_directory
35
49
 
36
- def pid
37
- if File.exists?(pid_file)
38
- File.read(pid_file).strip.to_i
50
+ File.open(config_file, 'w') do |f|
51
+ f.write config
52
+ end
39
53
  end
40
54
  end
41
55
 
42
- def pid_file
43
- File.join("/tmp", "#{project_name}_rsync_daemon.pid")
56
+ def command
57
+ ["rsync", "--daemon", "--config=#{config_file}", "--port=8989"]
44
58
  end
45
59
 
46
60
  def config
47
61
  <<-CONFIG
48
- # global configuration
62
+ # #{Specjour::VERSION}
63
+ # Anonymous rsync daemon config for #{project_name}
64
+ #
65
+ # Serve this project with the following command:
66
+ # $ #{command.join(' ')}
67
+ #
68
+ # Rsync with the following command:
69
+ # $ rsync -a --port=8989 #{hostname}::#{project_name} ~/#{project_name}
70
+ #
49
71
  use chroot = no
50
- timeout = 60
72
+ timeout = 20
51
73
  read only = yes
52
74
  pid file = #{pid_file}
53
75
 
54
76
  [#{project_name}]
55
77
  path = #{project_path}
56
- exclude = .git* doc tmp/* log script
78
+ exclude = .git* .specjour doc tmp/* log script
57
79
  CONFIG
58
80
  end
59
81
  end
@@ -22,28 +22,28 @@ module Specjour
22
22
  end
23
23
 
24
24
  def run
25
- printer.send_message(:ready)
25
+ connection.send_message(:ready)
26
26
  run_time = 0
27
27
  Dir.chdir(project_path)
28
- while !printer.closed? && data = printer.gets(TERMINATOR)
28
+ while !connection.closed? && data = connection.gets(TERMINATOR)
29
29
  test = load_object(data)
30
30
  if test
31
31
  run_time += Benchmark.realtime do
32
32
  run_test test
33
33
  end
34
- printer.send_message(:ready)
34
+ connection.send_message(:ready)
35
35
  else
36
- printer.send_message(:worker_summary=, {:duration => sprintf("%6f", run_time)})
37
- printer.send_message(:done)
38
- printer.disconnect
36
+ connection.send_message(:worker_summary=, {:duration => sprintf("%6f", run_time)})
37
+ connection.send_message(:done)
38
+ connection.disconnect
39
39
  end
40
40
  end
41
41
  end
42
42
 
43
43
  protected
44
44
 
45
- def printer
46
- @printer ||= printer_connection
45
+ def connection
46
+ @connection ||= printer_connection
47
47
  end
48
48
 
49
49
  def printer_connection
@@ -51,7 +51,7 @@ module Specjour
51
51
  end
52
52
 
53
53
  def run_test(test)
54
- puts "Running #{test}"
54
+ puts "[#{ENV['TEST_ENV_NUMBER']}] Running #{test}"
55
55
  if test =~ /\.feature$/
56
56
  run_feature test
57
57
  else
@@ -61,7 +61,7 @@ module Specjour
61
61
 
62
62
  def run_feature(feature)
63
63
  set_up_cucumber
64
- cli = ::Cucumber::Cli::Main.new(['--format', 'Specjour::Cucumber::DistributedFormatter', feature], printer)
64
+ cli = ::Cucumber::Cli::Main.new(['--format', 'Specjour::Cucumber::DistributedFormatter', feature], connection)
65
65
  cli.execute!(::Cucumber::Cli::Main.step_mother)
66
66
  end
67
67
 
@@ -69,7 +69,7 @@ module Specjour
69
69
  options = Spec::Runner::OptionParser.parse(
70
70
  ['--format=Specjour::Rspec::DistributedFormatter', spec],
71
71
  $stderr,
72
- printer
72
+ connection
73
73
  )
74
74
  Spec::Runner.use options
75
75
  options.run_examples
data/lib/specjour.rb CHANGED
@@ -13,6 +13,7 @@ module Specjour
13
13
  autoload :Connection, 'specjour/connection'
14
14
  autoload :Dispatcher, 'specjour/dispatcher'
15
15
  autoload :Manager, 'specjour/manager'
16
+ autoload :OpenStruct, 'ostruct'
16
17
  autoload :Printer, 'specjour/printer'
17
18
  autoload :Protocol, 'specjour/protocol'
18
19
  autoload :RsyncDaemon, 'specjour/rsync_daemon'
@@ -22,7 +23,7 @@ module Specjour
22
23
  autoload :Cucumber, 'specjour/cucumber'
23
24
  autoload :Rspec, 'specjour/rspec'
24
25
 
25
- VERSION = "0.2.0".freeze
26
+ VERSION = "0.2.1".freeze
26
27
 
27
28
  class Error < StandardError; end
28
29
 
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe Specjour::RsyncDaemon do
4
+ subject do
5
+ Specjour::RsyncDaemon.new('/tmp/seasonal', 'seasonal')
6
+ end
7
+
8
+ before do
9
+ stub(:system)
10
+ stub(:at_exit)
11
+ subject.stub(:write_config)
12
+ end
13
+
14
+ describe "#config_directory" do
15
+ specify { subject.config_directory.should == '/tmp/seasonal/.specjour' }
16
+ end
17
+
18
+ describe "#config_file" do
19
+ specify { subject.config_file.should == '/tmp/seasonal/.specjour/rsyncd.conf' }
20
+ end
21
+
22
+ describe "#start" do
23
+ it "writes the config" do
24
+ subject.should_receive(:write_config)
25
+ subject.start
26
+ end
27
+
28
+ it "executes the system command" do
29
+ subject.should_receive(:system).with(*subject.send(:command))
30
+ subject.start
31
+ end
32
+
33
+ it "stops at_exit" do
34
+ subject.should_receive(:at_exit)
35
+ subject.start
36
+ end
37
+ end
38
+
39
+ describe "#stop" do
40
+ context "with pid" do
41
+ before do
42
+ subject.stub(:pid => 100_000_000)
43
+ Process.stub(:kill)
44
+ FileUtils.stub(:rm)
45
+ end
46
+
47
+ it "kills the pid with TERM" do
48
+ Process.should_receive(:kill).with('TERM', subject.pid)
49
+ subject.stop
50
+ end
51
+
52
+ it "removes the pid file" do
53
+ FileUtils.should_receive(:rm).with(subject.pid_file)
54
+ subject.stop
55
+ end
56
+ end
57
+
58
+ context "without pid" do
59
+ it "does nothing" do
60
+ subject.stub(:pid => nil)
61
+ subject.stop.should be_nil
62
+ end
63
+ end
64
+ end
65
+ end
data/spec/spec.opts CHANGED
@@ -1,2 +1,2 @@
1
1
  --color
2
- --backtrace
2
+ --format specdoc
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 2
8
- - 0
9
- version: 0.2.0
8
+ - 1
9
+ version: 0.2.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Sandro Turriate
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-04-20 00:00:00 -04:00
17
+ date: 2010-04-21 00:00:00 -04:00
18
18
  default_executable: specjour
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -104,7 +104,6 @@ files:
104
104
  - lib/specjour/rspec.rb
105
105
  - lib/specjour/rspec/distributed_formatter.rb
106
106
  - lib/specjour/rspec/final_report.rb
107
- - lib/specjour/rspec/marshalable_failure_formatter.rb
108
107
  - lib/specjour/rspec/marshalable_rspec_failure.rb
109
108
  - lib/specjour/rsync_daemon.rb
110
109
  - lib/specjour/socket_helpers.rb
@@ -113,6 +112,7 @@ files:
113
112
  - lib/specjour/worker.rb
114
113
  - rails/init.rb
115
114
  - spec/lib/specjour/worker_spec.rb
115
+ - spec/rsync_daemon_spec.rb
116
116
  - spec/spec.opts
117
117
  - spec/spec_helper.rb
118
118
  - spec/specjour_spec.rb
@@ -148,5 +148,6 @@ specification_version: 3
148
148
  summary: Distribute your spec suite amongst your LAN via Bonjour.
149
149
  test_files:
150
150
  - spec/lib/specjour/worker_spec.rb
151
+ - spec/rsync_daemon_spec.rb
151
152
  - spec/spec_helper.rb
152
153
  - spec/specjour_spec.rb
@@ -1,11 +0,0 @@
1
- module Specjour::Rspec
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