testowl 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -0
- data/README.markdown +100 -0
- data/Rakefile +2 -0
- data/bin/testowl +19 -0
- data/images/error.png +0 -0
- data/images/failed.png +0 -0
- data/images/success.png +0 -0
- data/images/wait.png +0 -0
- data/lib/testowl/growl.rb +34 -0
- data/lib/testowl/monitor.rb +74 -0
- data/lib/testowl/rspec_runner.rb +28 -0
- data/lib/testowl/test_unit_runner.rb +56 -0
- data/lib/testowl/version.rb +3 -0
- data/lib/testowl.rb +8 -0
- data/testowl.gemspec +37 -0
- metadata +109 -0
data/Gemfile
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
TestOwl
|
2
|
+
=
|
3
|
+
|
4
|
+
![TestOwl](https://github.com/billhorsman/testowl/raw/master/images/testowl.png)
|
5
|
+
|
6
|
+
Narrow Minded TestUnit/RSpec, Watchr and Growl Integration for Continuous Testing. TestOwl assumes you are running Rails and makes some guesses about what tests depend on what files. For instance, if you change model Foo then it looks for foo_test.rb and foos_controller_test.rb.
|
7
|
+
|
8
|
+
At the very least, it will run each test every time you save it.
|
9
|
+
|
10
|
+
Usage
|
11
|
+
==
|
12
|
+
|
13
|
+
From Rails root:
|
14
|
+
|
15
|
+
testowl
|
16
|
+
|
17
|
+
If you're using [bundler](http://gembundler.com/) then you should probably run:
|
18
|
+
|
19
|
+
bundle exec testowl
|
20
|
+
|
21
|
+
Dependencies
|
22
|
+
==
|
23
|
+
|
24
|
+
It uses [growlnotifiy](http://growl.info/extras.php) to send messages to Growl. If you don't have it installed then it will only write a message to your terminal. To get the full benefit of TestOwl you should definitely install growlnotify.
|
25
|
+
|
26
|
+
Bundler
|
27
|
+
==
|
28
|
+
|
29
|
+
To install using Bundler:
|
30
|
+
|
31
|
+
group :test do
|
32
|
+
gem 'testowl', :git => "git@github.com:billhorsman/testowl.git"
|
33
|
+
end
|
34
|
+
|
35
|
+
RSpec
|
36
|
+
==
|
37
|
+
|
38
|
+
TestOwl looks for <code>spec/spec_helper.rb</code> and if it finds it then it uses [RSpec](http://relishapp.com/rspec).
|
39
|
+
|
40
|
+
Test Unit
|
41
|
+
==
|
42
|
+
|
43
|
+
TestOwl looks for <code>test/test_helper.rb</code> and if it finds it then it uses Test Unit. Actually, it looks for RSpec first and only checks for Test Unit if it can't find RSpec.
|
44
|
+
|
45
|
+
Spork
|
46
|
+
==
|
47
|
+
|
48
|
+
[Spork](https://github.com/timcharper/spork) is a testing framework for RSpec and Cucumber (see below for use with Test Unit) that forks before each run to ensure a clean testing state. By preloading the Rails environment it speeds up the launch time to run tests. This is especially significant when running single, small tests.
|
49
|
+
|
50
|
+
If you are running Spork then it will use it (assuming it is on port 8988) but if it gets no response on that port then it will just run the tests directly.
|
51
|
+
|
52
|
+
To get it running you just:
|
53
|
+
|
54
|
+
$ bundle exec spork
|
55
|
+
Using TestUnit
|
56
|
+
Preloading Rails environment
|
57
|
+
Loading Spork.prefork block...
|
58
|
+
Spork is ready and listening on 8988!
|
59
|
+
|
60
|
+
Spork with RSpec
|
61
|
+
==
|
62
|
+
|
63
|
+
You need to tell RSpec to use Spork by running it with the <code>--drb</code> option. If you want to do that every time then you can add a file called <code>.rspec</code> to your project:
|
64
|
+
|
65
|
+
--drb
|
66
|
+
|
67
|
+
If Spork isn't running then RSpec will just run the specs directly.
|
68
|
+
|
69
|
+
Tip: if you prefer full output instead of just dots then you can use <code>-f d</code> and I always like some color too. My .rspec looks like this:
|
70
|
+
|
71
|
+
-f d --color --drb
|
72
|
+
|
73
|
+
Spork with Test Unit
|
74
|
+
==
|
75
|
+
|
76
|
+
Spork doesn't support Test Unit out of the box, but it does if you include the [spork-testunit](https://github.com/timcharper/spork-testunit) gem. This is what your Gemfile might look like
|
77
|
+
|
78
|
+
group :test do
|
79
|
+
gem 'spork', "~> 0.9.0.rc"
|
80
|
+
gem 'spork-testunit', :git => 'git://github.com/timcharper/spork-testunit.git'
|
81
|
+
gem 'testowl', :git => "git@github.com:billhorsman/testowl.git"
|
82
|
+
end
|
83
|
+
|
84
|
+
To use Spork you then have to run your tests like this:
|
85
|
+
|
86
|
+
testdrb test/unit/foo_test.rb
|
87
|
+
|
88
|
+
TestOwl does that for you.
|
89
|
+
|
90
|
+
Todo
|
91
|
+
==
|
92
|
+
|
93
|
+
* Make Drb port configurable
|
94
|
+
* DSL to define relationship between changed files and tests to run.
|
95
|
+
* Add some more rules for relationships (with or without DSL)
|
96
|
+
|
97
|
+
Credits
|
98
|
+
==
|
99
|
+
|
100
|
+
Copyright (c) 2011 [Bill Horsman](http://bill.logicalcobwebs.com), released under the MIT license.
|
data/Rakefile
ADDED
data/bin/testowl
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'testowl'
|
4
|
+
require 'active_support/inflector'
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib') unless $LOAD_PATH.include?(File.dirname(__FILE__) + '/../lib')
|
7
|
+
CONFIG = File.dirname(__FILE__) + '/../lib/testowl/config.rb'
|
8
|
+
|
9
|
+
|
10
|
+
begin
|
11
|
+
success = Testowl::Monitor.new.run
|
12
|
+
Kernel.exit 1 unless success
|
13
|
+
rescue SystemExit => e
|
14
|
+
Kernel.exit(e.status)
|
15
|
+
rescue Exception => e
|
16
|
+
STDERR.puts("#{e.message} (#{e.class})")
|
17
|
+
STDERR.puts(e.backtrace.join("\n"))
|
18
|
+
Kernel.exit 1
|
19
|
+
end
|
data/images/error.png
ADDED
Binary file
|
data/images/failed.png
ADDED
Binary file
|
data/images/success.png
ADDED
Binary file
|
data/images/wait.png
ADDED
Binary file
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Testowl
|
2
|
+
module Growl
|
3
|
+
|
4
|
+
Growlnotify = "growlnotify"
|
5
|
+
|
6
|
+
def self.grr(title, message, status, files, suffix)
|
7
|
+
project = File.expand_path(".").split("/").last
|
8
|
+
growlnotify = `which #{Growlnotify}`.chomp
|
9
|
+
if growlnotify == ''
|
10
|
+
if @warning_done
|
11
|
+
puts "Skipping growl"
|
12
|
+
else
|
13
|
+
puts "If you install #{Growlnotify} you'll get growl notifications. See the README."
|
14
|
+
@warning_done = true
|
15
|
+
end
|
16
|
+
else
|
17
|
+
options = []
|
18
|
+
options << "-n Watchr"
|
19
|
+
options << "--message '#{message.gsub("'", "`")}\n\n#{files.map{|file| file.sub(/^spec\/[^\/]*\//, '').sub(/_test.rb$/, '')}.join("\n")}\n#{suffix}'"
|
20
|
+
options << "--sticky" if status == :error
|
21
|
+
options << "--image '#{image_path(status)}'"
|
22
|
+
options << "--identifier #{Digest::MD5.hexdigest files.join}" # (used for coalescing)
|
23
|
+
title = "RSpec #{title} (#{project})"
|
24
|
+
system %(#{growlnotify} #{options.join(' ')} '#{title}' &)
|
25
|
+
puts message
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.image_path(name)
|
30
|
+
File.dirname(__FILE__) + "/../../images/#{name}.png"
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Testowl
|
2
|
+
class Monitor
|
3
|
+
|
4
|
+
attr_reader :test_dir, :test_suffix
|
5
|
+
|
6
|
+
def initialize()
|
7
|
+
if File.exist?("spec/spec_helper.rb")
|
8
|
+
@runner = RspecRunner.new
|
9
|
+
@test_dir = "spec"
|
10
|
+
@test_suffix = "spec"
|
11
|
+
elsif File.exist?("test/test_helper.rb")
|
12
|
+
@runner = TestUnitRunner.new
|
13
|
+
@test_dir = "test"
|
14
|
+
@test_suffix = "test"
|
15
|
+
else
|
16
|
+
raise "Can't find either spec_helper.rb or test_helper.rb and this owl is confused."
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
script = Watchr::Script.new
|
22
|
+
# Watch the scripts themselves
|
23
|
+
script.watch("#{test_dir}/.*/*_#{test_suffix}\.rb") do |match|
|
24
|
+
puts "Detected change in #{match[0]}"
|
25
|
+
fire [match[0]], "has been updated"
|
26
|
+
end
|
27
|
+
# Watch models
|
28
|
+
script.watch("app/models/(.*)\.rb") do |match|
|
29
|
+
puts "Detected change in #{match[0]}"
|
30
|
+
tests = []
|
31
|
+
model = match[1]
|
32
|
+
tests += tests_for_model(model)
|
33
|
+
fire tests, "triggered by #{match[0]}"
|
34
|
+
end
|
35
|
+
puts "Monitoring files..."
|
36
|
+
Watchr::Controller.new(script, Watchr.handler.new).run
|
37
|
+
end
|
38
|
+
|
39
|
+
def fire(files, reason)
|
40
|
+
return if files.nil? || files.size == 0
|
41
|
+
# We don't want to do no performance testing
|
42
|
+
files = files.map{|file| file =~ /^test\/performance\// ? nil : file }.compact
|
43
|
+
puts "Running #{files.join(", ")}"
|
44
|
+
begin
|
45
|
+
test_count, fail_count, timing = @runner.run(files)
|
46
|
+
if test_count == 0
|
47
|
+
Growl.grr "Empty Test", "No tests run", :error, files, reason
|
48
|
+
elsif fail_count > 0
|
49
|
+
Growl.grr "Fail", "#{fail_count} out of #{test_count} test#{'s' if test_count > 1} failed in #{timing} :(", :failed, files, reason
|
50
|
+
else
|
51
|
+
Growl.grr "Pass", "All #{test_count} example#{'s' if test_count > 1} passed in #{timing} :)", :success, files, reason
|
52
|
+
end
|
53
|
+
rescue => exc
|
54
|
+
Growl.grr "Exception", exc.message, :error, files, reason
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def tests_for_model(model)
|
59
|
+
tests = []
|
60
|
+
tests += Dir["#{test_dir}/**/#{model.pluralize}_controller_#{test_suffix}.rb"]
|
61
|
+
tests += Dir["#{test_dir}/**/#{model}_#{test_suffix}.rb"]
|
62
|
+
class_name = model.classify
|
63
|
+
count = 0
|
64
|
+
`grep #{class_name} -R app/controllers/* | grep '# Dependencies'`.lines.each do |line|
|
65
|
+
tests += Dir[line.split(':').first.sub(/^app/, test_suffix).sub(/\.rb$/, "_#{test_suffix}.rb")]
|
66
|
+
count += 1
|
67
|
+
end
|
68
|
+
puts "Found 1 dependency" if count == 1
|
69
|
+
puts "Found #{count} dependencies" if count > 1
|
70
|
+
tests
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Testowl
|
2
|
+
class RspecRunner
|
3
|
+
|
4
|
+
def run(files)
|
5
|
+
results = `rspec -c #{files.join(" ")}`
|
6
|
+
lines = results.split("\n")
|
7
|
+
exception_message = lines.detect{|line| line =~ /^Exception encountered/ }
|
8
|
+
counts = lines.detect{|line| line =~ /(\d+)\sexamples?,\s(\d+)\sfailures?/ }
|
9
|
+
if counts
|
10
|
+
test_count, fail_count = counts.split(',').map(&:to_i)
|
11
|
+
timing = lines.detect{|line| line =~ /Finished\sin/}
|
12
|
+
timing = timing.sub(/Finished\sin/, '').strip if timing
|
13
|
+
if fail_count > 0
|
14
|
+
puts results
|
15
|
+
end
|
16
|
+
return test_count, fail_count, timing
|
17
|
+
else
|
18
|
+
$stderr.print results
|
19
|
+
if exception_message
|
20
|
+
raise exception_message
|
21
|
+
else
|
22
|
+
raise "Problem interpreting output"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# These fix 1.8.7 test/unit results where a DRbUnknown is returned because the testdrb client doesn't have the classes
|
2
|
+
# in its local namespace. (See issue #2)
|
3
|
+
unless RUBY_VERSION =~ /^1\.9/
|
4
|
+
# MiniTest is Ruby 1.9, and the classes below only exist in the pre 1.9 test/unit
|
5
|
+
require 'test/unit/testresult'
|
6
|
+
require 'test/unit/failure'
|
7
|
+
end
|
8
|
+
require 'drb'
|
9
|
+
|
10
|
+
module Testowl
|
11
|
+
class TestUnitRunner
|
12
|
+
|
13
|
+
def run(files)
|
14
|
+
results = nil
|
15
|
+
begin
|
16
|
+
results = runDrb(files)
|
17
|
+
rescue
|
18
|
+
puts "Drb not available. Running tests directly instead."
|
19
|
+
results = runDirectly(files)
|
20
|
+
end
|
21
|
+
puts "Done"
|
22
|
+
lines = results.split("\n")
|
23
|
+
exception_message = lines.detect{|line| line =~ /^Exception encountered/ }
|
24
|
+
counts = lines.detect{|line| line =~ /(\d+)\sassertions?,\s(\d+)\sfailures?/ }
|
25
|
+
if counts
|
26
|
+
file_count, test_count, fail_count = counts.split(',').map(&:to_i)
|
27
|
+
timing = lines.detect{|line| line =~ /Finished\sin/}
|
28
|
+
timing = timing.sub(/Finished\sin/, '').strip if timing
|
29
|
+
if fail_count > 0
|
30
|
+
puts results
|
31
|
+
end
|
32
|
+
return test_count, fail_count, timing
|
33
|
+
else
|
34
|
+
if exception_message
|
35
|
+
raise exception_message
|
36
|
+
else
|
37
|
+
$stderr.print results
|
38
|
+
raise "Problem interpreting output"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def runDrb(files)
|
44
|
+
DRb.start_service("druby://127.0.0.1:0") # this allows Ruby to do some magical stuff so you can pass an output stream over DRb.
|
45
|
+
test_server = DRbObject.new_with_uri("druby://127.0.0.1:8988")
|
46
|
+
results = StringIO.new
|
47
|
+
test_server.run(files, $stderr, results)
|
48
|
+
results.string
|
49
|
+
end
|
50
|
+
|
51
|
+
def runDirectly(files)
|
52
|
+
`ruby #{files.join(' ')}`
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
data/lib/testowl.rb
ADDED
data/testowl.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "testowl/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "testowl"
|
7
|
+
s.version = Testowl::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Bill Horsman"]
|
10
|
+
s.email = ["bill@logicalcobwebs.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{TestUnit, Watchr and Growl Integration for Continuous Testing}
|
13
|
+
s.description = %q{TestUnit, Watchr and Growl Integration for Continuous Testing}
|
14
|
+
|
15
|
+
s.add_runtime_dependency "watchr"
|
16
|
+
s.add_runtime_dependency "rails"
|
17
|
+
|
18
|
+
s.files = [
|
19
|
+
"Gemfile",
|
20
|
+
"README.markdown",
|
21
|
+
"Rakefile",
|
22
|
+
"bin/testowl",
|
23
|
+
"images/error.png",
|
24
|
+
"images/failed.png",
|
25
|
+
"images/success.png",
|
26
|
+
"images/wait.png",
|
27
|
+
"lib/testowl.rb",
|
28
|
+
"lib/testowl/growl.rb",
|
29
|
+
"lib/testowl/monitor.rb",
|
30
|
+
"lib/testowl/rspec_runner.rb",
|
31
|
+
"lib/testowl/test_unit_runner.rb",
|
32
|
+
"lib/testowl/version.rb",
|
33
|
+
"testowl.gemspec"
|
34
|
+
]
|
35
|
+
s.executables = ["testowl"]
|
36
|
+
s.require_paths = ["lib"]
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: testowl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Bill Horsman
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-06-14 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
hash: 3
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
prerelease: false
|
32
|
+
type: :runtime
|
33
|
+
requirement: *id001
|
34
|
+
name: watchr
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
hash: 3
|
42
|
+
segments:
|
43
|
+
- 0
|
44
|
+
version: "0"
|
45
|
+
prerelease: false
|
46
|
+
type: :runtime
|
47
|
+
requirement: *id002
|
48
|
+
name: rails
|
49
|
+
description: TestUnit, Watchr and Growl Integration for Continuous Testing
|
50
|
+
email:
|
51
|
+
- bill@logicalcobwebs.com
|
52
|
+
executables:
|
53
|
+
- testowl
|
54
|
+
extensions: []
|
55
|
+
|
56
|
+
extra_rdoc_files: []
|
57
|
+
|
58
|
+
files:
|
59
|
+
- Gemfile
|
60
|
+
- README.markdown
|
61
|
+
- Rakefile
|
62
|
+
- bin/testowl
|
63
|
+
- images/error.png
|
64
|
+
- images/failed.png
|
65
|
+
- images/success.png
|
66
|
+
- images/wait.png
|
67
|
+
- lib/testowl.rb
|
68
|
+
- lib/testowl/growl.rb
|
69
|
+
- lib/testowl/monitor.rb
|
70
|
+
- lib/testowl/rspec_runner.rb
|
71
|
+
- lib/testowl/test_unit_runner.rb
|
72
|
+
- lib/testowl/version.rb
|
73
|
+
- testowl.gemspec
|
74
|
+
has_rdoc: true
|
75
|
+
homepage: ""
|
76
|
+
licenses: []
|
77
|
+
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
hash: 3
|
89
|
+
segments:
|
90
|
+
- 0
|
91
|
+
version: "0"
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
hash: 3
|
98
|
+
segments:
|
99
|
+
- 0
|
100
|
+
version: "0"
|
101
|
+
requirements: []
|
102
|
+
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 1.5.0
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: TestUnit, Watchr and Growl Integration for Continuous Testing
|
108
|
+
test_files: []
|
109
|
+
|