nitrous 1.0.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/.gitignore +1 -0
- data/Rakefile +14 -0
- data/VERSION +1 -0
- data/cmd_test +11 -0
- data/command_line.rb +53 -0
- data/example.rb +4 -0
- data/init.rb +1 -0
- data/lib/core_ext.rb +27 -0
- data/lib/nitrous.rb +5 -0
- data/lib/nitrous/assertions.rb +95 -0
- data/lib/nitrous/daemon.rb +35 -0
- data/lib/nitrous/daemon_controller.rb +11 -0
- data/lib/nitrous/http_io.rb +34 -0
- data/lib/nitrous/integration_test.rb +372 -0
- data/lib/nitrous/progress_bar.rb +82 -0
- data/lib/nitrous/rails_test.rb +77 -0
- data/lib/nitrous/server.rb +4 -0
- data/lib/nitrous/test.rb +121 -0
- data/lib/nitrous/test_block.rb +43 -0
- data/lib/nitrous/test_context.rb +76 -0
- data/lib/nitrous/test_result.rb +17 -0
- data/lib/rails_ext.rb +60 -0
- data/nitrous.gemspec +64 -0
- data/rails_env.rb +68 -0
- data/test/assertion_test.rb +31 -0
- data/test/test_test.rb +30 -0
- data/test_helper.rb +2 -0
- metadata +90 -0
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'curses'
|
2
|
+
|
3
|
+
module Kernel
|
4
|
+
def puts(*args)
|
5
|
+
$stdout.puts(*args)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module Nitrous
|
10
|
+
class ProgressBarAwareStandardOut
|
11
|
+
def initialize(stdout, progress_bar)
|
12
|
+
@stdout, @progress_bar = stdout, progress_bar
|
13
|
+
end
|
14
|
+
|
15
|
+
def write(object)
|
16
|
+
@progress_bar.delete_bar
|
17
|
+
@stdout.write(object)
|
18
|
+
@progress_bar.redraw_bar
|
19
|
+
end
|
20
|
+
|
21
|
+
def print(*args)
|
22
|
+
args.each do |arg|
|
23
|
+
write(arg)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def puts(*args)
|
28
|
+
args.each do |arg|
|
29
|
+
write("#{arg}\n")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def direct_write(object)
|
34
|
+
@stdout.write(object)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class ProgressBar
|
39
|
+
RED = 101
|
40
|
+
GREEN = 102
|
41
|
+
|
42
|
+
attr_accessor :color, :text
|
43
|
+
|
44
|
+
def initialize(steps)
|
45
|
+
@total_steps = steps
|
46
|
+
@step = 0
|
47
|
+
@color = GREEN
|
48
|
+
Curses.init_screen
|
49
|
+
@dimensions = [Curses.stdscr.maxx, Curses.stdscr.maxy]
|
50
|
+
Curses.close_screen
|
51
|
+
$stdout.puts ""
|
52
|
+
$stdout = ProgressBarAwareStandardOut.new($stdout, self)
|
53
|
+
@text = ""
|
54
|
+
end
|
55
|
+
|
56
|
+
def step
|
57
|
+
@step += 1
|
58
|
+
draw
|
59
|
+
end
|
60
|
+
|
61
|
+
def draw_bar(color, width)
|
62
|
+
content = @text[0..[width, @text.length].min] + " " * [width - @text.length, 0].max
|
63
|
+
$stdout.direct_write("\e[#{color}m#{content}\e[0m")
|
64
|
+
$stdout.direct_write(@text[(width + 1)..-1]) if @text.length > width
|
65
|
+
$stdout.direct_write("\n")
|
66
|
+
end
|
67
|
+
|
68
|
+
def delete_bar
|
69
|
+
$stdout.direct_write("\e[1F\e[0K")
|
70
|
+
end
|
71
|
+
|
72
|
+
def draw
|
73
|
+
delete_bar
|
74
|
+
redraw_bar
|
75
|
+
end
|
76
|
+
|
77
|
+
def redraw_bar
|
78
|
+
draw_bar(@color, (@dimensions[0].to_f/@total_steps)*@step)
|
79
|
+
STDOUT.flush
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'rails_ext'
|
2
|
+
module Nitrous
|
3
|
+
class RailsTest < Nitrous::Test
|
4
|
+
def created(type)
|
5
|
+
lookup(type, ActiveRecord::Base.saved_objects)
|
6
|
+
end
|
7
|
+
|
8
|
+
def assert_created!(type)
|
9
|
+
instance = created(type)
|
10
|
+
fail("Should have created a #{type}.#{invalid(type) ? " Errors: #{invalid(type).errors.full_messages.to_sentence}" : ''}") unless instance
|
11
|
+
yield(instance) if block_given?
|
12
|
+
instance
|
13
|
+
end
|
14
|
+
|
15
|
+
def assert_not_created!(type)
|
16
|
+
instance = created(type).reload rescue nil
|
17
|
+
fail("Should not have created a #{type}. Instance: #{instance.inspect}") if instance
|
18
|
+
yield if block_given?
|
19
|
+
end
|
20
|
+
|
21
|
+
def assert_email_sent!(count=1, &block)
|
22
|
+
assert_equal! @emails + count, ActionMailer::Base.deliveries.size
|
23
|
+
block.call(*ActionMailer::Base.deliveries[-count..-1]) if block_given?
|
24
|
+
ensure
|
25
|
+
@emails = ActionMailer::Base.deliveries.size
|
26
|
+
end
|
27
|
+
|
28
|
+
def assert_no_email_sent!(&block)
|
29
|
+
assert_equal! @emails, ActionMailer::Base.deliveries.size
|
30
|
+
block.call if block_given?
|
31
|
+
ensure
|
32
|
+
@emails = ActionMailer::Base.deliveries.size
|
33
|
+
end
|
34
|
+
|
35
|
+
def invalid(type)
|
36
|
+
lookup(type, ActiveRecord::Base.invalid_objects)
|
37
|
+
end
|
38
|
+
|
39
|
+
def destroyed(type)
|
40
|
+
lookup(type, ActiveRecord::Base.destroyed_objects)
|
41
|
+
end
|
42
|
+
|
43
|
+
def assert_destroyed!(type)
|
44
|
+
instance = destroyed(type)
|
45
|
+
fail("Should have destroyed a #{type}.") unless instance
|
46
|
+
yield(instance) if block_given?
|
47
|
+
instance
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def lookup(type, list)
|
52
|
+
(list[type] && list[type].last) || list[type.to_s.singularize.to_sym]
|
53
|
+
end
|
54
|
+
|
55
|
+
def reset_record_tracking
|
56
|
+
if defined?(ActiveRecord)
|
57
|
+
ActiveRecord::Base.saved_objects = {}
|
58
|
+
ActiveRecord::Base.invalid_objects = {}
|
59
|
+
ActiveRecord::Base.destroyed_objects = {}
|
60
|
+
end
|
61
|
+
@emails = ActionMailer::Base.deliveries.size
|
62
|
+
end
|
63
|
+
|
64
|
+
def nitrous_setup
|
65
|
+
super
|
66
|
+
reset_record_tracking
|
67
|
+
end
|
68
|
+
|
69
|
+
def nitrous_teardown
|
70
|
+
if defined?(ActiveRecord)
|
71
|
+
ActiveRecord::Base.send(:subclasses).each do |klass|
|
72
|
+
klass.delete_all rescue nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,4 @@
|
|
1
|
+
PID_FILE = File.expand_path(File.join(RAILS_ROOT, 'tmp/pids/nitrous_server.pid'))
|
2
|
+
if File.exists?(PID_FILE) && !`ps ax | grep "\b#{File.read(PID_FILE)}\b"`.empty?
|
3
|
+
`ruby #{File.join(File.dirname(__FILE__), 'daemon_controller.rb')} start #{File.expand_path(RAILS_ROOT)}`
|
4
|
+
end
|
data/lib/nitrous/test.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../core_ext'
|
2
|
+
require 'nitrous/assertions'
|
3
|
+
require 'nitrous/test_block'
|
4
|
+
require 'nitrous/test_context'
|
5
|
+
require 'nitrous/test_result'
|
6
|
+
module Nitrous
|
7
|
+
class Test
|
8
|
+
include Assertions
|
9
|
+
|
10
|
+
def self.callbacks
|
11
|
+
@@callbacks ||= {:suite_setup => [], :suite_teardown => [], :setup => [], :teardown => []}
|
12
|
+
end
|
13
|
+
|
14
|
+
def callbacks
|
15
|
+
self.class.callbacks
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.register_callback(type, &callback)
|
19
|
+
callbacks[type] << callback
|
20
|
+
end
|
21
|
+
|
22
|
+
def register_callback(type, &callback)
|
23
|
+
self.class.register_callback(type, callback)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.tests
|
27
|
+
@tests ||= []
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.test_classes
|
31
|
+
@test_classes ||= []
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.test(name=nil, &block)
|
35
|
+
self.tests << TestBlock.new(name, block) unless @single_test
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.stest(name=nil, &block)
|
39
|
+
@tests = [TestBlock.new(name, block)]
|
40
|
+
@single_test = true;
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.ztest(name=nil, &block)
|
44
|
+
self.tests << TestBlock.new(name, block, true) unless @single_test
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.exclude(klass)
|
48
|
+
@test_classes.delete(klass)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.inherited(subclass)
|
52
|
+
class << subclass
|
53
|
+
def inherited(subclass)
|
54
|
+
Nitrous::Test.exclude(self)
|
55
|
+
Nitrous::Test.inherited(subclass)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
if !@test_classes
|
59
|
+
@test_classes = []
|
60
|
+
at_exit do
|
61
|
+
callbacks[:suite_setup].each(&:call)
|
62
|
+
context = TestContext.create(@test_classes.sum {|klass| klass.tests.size})
|
63
|
+
@test_classes.each do |klass|
|
64
|
+
klass.run(context)
|
65
|
+
end
|
66
|
+
context.finish
|
67
|
+
callbacks[:suite_teardown].each(&:call)
|
68
|
+
exit(context.exit_status)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
@test_classes << subclass
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.run(context=TestContext.new)
|
75
|
+
self.new(context).run
|
76
|
+
end
|
77
|
+
|
78
|
+
def initialize(context)
|
79
|
+
@context = context
|
80
|
+
@test_results = []
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.setup; end
|
84
|
+
def self.teardown; end
|
85
|
+
def nitrous_setup; end
|
86
|
+
def nitrous_teardown; end
|
87
|
+
def setup; end
|
88
|
+
def teardown; end
|
89
|
+
|
90
|
+
def collect_errors
|
91
|
+
yield
|
92
|
+
rescue Exception
|
93
|
+
@test_results.last.errors << $!
|
94
|
+
@context.failed($!)
|
95
|
+
end
|
96
|
+
|
97
|
+
def running(test)
|
98
|
+
@current_test = test
|
99
|
+
@test_results << TestResult.new(test)
|
100
|
+
end
|
101
|
+
|
102
|
+
def run
|
103
|
+
puts self.class.name
|
104
|
+
self.class.setup
|
105
|
+
self.class.tests.each do |test_block|
|
106
|
+
running(test_block)
|
107
|
+
nitrous_setup
|
108
|
+
collect_errors do
|
109
|
+
callbacks[:setup].each(&:call)
|
110
|
+
setup
|
111
|
+
test_block.run(self)
|
112
|
+
teardown
|
113
|
+
callbacks[:teardown].each(&:call)
|
114
|
+
end
|
115
|
+
nitrous_teardown
|
116
|
+
@context.ran_test(test_block, @test_results.last)
|
117
|
+
end
|
118
|
+
self.class.teardown
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Nitrous
|
2
|
+
class TestBlock
|
3
|
+
def initialize(name, block, skip=false)
|
4
|
+
@name, @block, @skip = name, block, skip
|
5
|
+
end
|
6
|
+
|
7
|
+
def run(test)
|
8
|
+
test.collect_errors do
|
9
|
+
test.instance_eval(&@block) unless self.skip?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def filename
|
14
|
+
parse_location
|
15
|
+
@filename
|
16
|
+
end
|
17
|
+
|
18
|
+
def line
|
19
|
+
parse_location
|
20
|
+
@line
|
21
|
+
end
|
22
|
+
|
23
|
+
def skip?
|
24
|
+
@skip
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
@name || first_line
|
29
|
+
end
|
30
|
+
|
31
|
+
def first_line
|
32
|
+
"[ #{File.readlines(filename)[line].strip} ]"
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def parse_location
|
37
|
+
return if @filename && @line
|
38
|
+
@block.inspect =~ /#<Proc:[^@]+@([^:]+):(\d+)>/
|
39
|
+
@filename = $1
|
40
|
+
@line = $2.to_i
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'nitrous/progress_bar'
|
2
|
+
module Nitrous
|
3
|
+
class TestContext
|
4
|
+
def self.gui?
|
5
|
+
!!ENV["TM_MODE"] || !!ENV["VIMRUNTIME"]
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.create(test_count)
|
9
|
+
if gui?
|
10
|
+
TestContext.new(test_count)
|
11
|
+
else
|
12
|
+
CommandLineTestContext.new(test_count)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(test_count)
|
17
|
+
@start_time = Time.now
|
18
|
+
@total, @failures, @test, @skip = test_count, 0, 0, 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def ran_test(test, result)
|
22
|
+
@test += 1
|
23
|
+
@skip += 1 if test.skip?
|
24
|
+
puts result
|
25
|
+
puts result.errors.map(&:test_output).join("\n\n") + "\n" unless result.errors.empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
def failed(exception)
|
29
|
+
@failures += 1
|
30
|
+
end
|
31
|
+
|
32
|
+
def finish
|
33
|
+
puts summary_with_benchmark
|
34
|
+
end
|
35
|
+
|
36
|
+
def exit_status
|
37
|
+
@failures > 0 ? 1 : 0
|
38
|
+
end
|
39
|
+
|
40
|
+
def summary
|
41
|
+
"Test #{@test} of #{@total} -- #{@failures} failure#{@failures == 1 ? '' : 's'} -- #{@skip} skipped"
|
42
|
+
end
|
43
|
+
|
44
|
+
def summary_with_benchmark
|
45
|
+
summary + " -- #{Time.now - @start_time} seconds"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class CommandLineTestContext < TestContext
|
50
|
+
def initialize(test_count)
|
51
|
+
super
|
52
|
+
@progress_bar = ProgressBar.new(test_count)
|
53
|
+
update_text
|
54
|
+
end
|
55
|
+
|
56
|
+
def ran_test(test, result)
|
57
|
+
super
|
58
|
+
@progress_bar.step
|
59
|
+
update_text
|
60
|
+
end
|
61
|
+
|
62
|
+
def update_text
|
63
|
+
@progress_bar.text = summary
|
64
|
+
end
|
65
|
+
|
66
|
+
def failed(exception)
|
67
|
+
super
|
68
|
+
@progress_bar.color = ProgressBar::RED
|
69
|
+
end
|
70
|
+
|
71
|
+
def finish
|
72
|
+
@progress_bar.text = summary_with_benchmark
|
73
|
+
@progress_bar.draw
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Nitrous
|
2
|
+
class TestResult
|
3
|
+
attr_reader :errors
|
4
|
+
def initialize(test)
|
5
|
+
@test, @errors = test, []
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
%{ #{@test}: #{pass_fail_skip}}
|
10
|
+
end
|
11
|
+
|
12
|
+
def pass_fail_skip
|
13
|
+
return "Skipped" if @test.skip?
|
14
|
+
@errors.empty? ? "Passed" : "Failed"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/rails_ext.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
if defined?(ActiveRecord)
|
2
|
+
unless ActiveRecord::Base.instance_methods.include?('save_with_tracking')
|
3
|
+
class ActiveRecord::Base
|
4
|
+
cattr_accessor :saved_objects
|
5
|
+
cattr_accessor :invalid_objects
|
6
|
+
cattr_accessor :destroyed_objects
|
7
|
+
|
8
|
+
def create_or_update_with_tracking(*args)
|
9
|
+
new_record = new_record?
|
10
|
+
saved = create_or_update_without_tracking(*args)
|
11
|
+
append_instance(ActiveRecord::Base.saved_objects) if new_record && saved
|
12
|
+
saved
|
13
|
+
end
|
14
|
+
alias_method_chain :create_or_update, :tracking
|
15
|
+
|
16
|
+
def destroy_with_tracking(*args)
|
17
|
+
result = destroy_without_tracking(*args)
|
18
|
+
append_instance(ActiveRecord::Base.destroyed_objects) if result
|
19
|
+
result
|
20
|
+
end
|
21
|
+
alias_method_chain :destroy, :tracking
|
22
|
+
|
23
|
+
def validate_with_tracking(*args)
|
24
|
+
valid = validate_without_tracking(*args)
|
25
|
+
append_instance(ActiveRecord::Base.invalid_objects) if !valid
|
26
|
+
valid
|
27
|
+
end
|
28
|
+
alias_method_chain :validate, :tracking
|
29
|
+
|
30
|
+
private
|
31
|
+
def append_instance(list)
|
32
|
+
return if !list
|
33
|
+
key = self.class.name.underscore.to_sym
|
34
|
+
list[key] ||= []
|
35
|
+
list[key] << self
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
module Extensions
|
41
|
+
module Core
|
42
|
+
module Hash
|
43
|
+
def to_fields(fields = {}, namespace = nil)
|
44
|
+
each do |key, value|
|
45
|
+
key = namespace ? "#{namespace}[#{key}]" : key
|
46
|
+
case value
|
47
|
+
when ::Hash
|
48
|
+
value.to_fields(fields, key)
|
49
|
+
when ::Array
|
50
|
+
fields["#{key}[]"] = value
|
51
|
+
else
|
52
|
+
fields[key.to_s] = value
|
53
|
+
end
|
54
|
+
end
|
55
|
+
fields
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
Hash.send :include, Extensions::Core::Hash
|