cutest 0.1.5 → 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -8,10 +8,10 @@ Description
8
8
 
9
9
  Run tests in separate processes to avoid shared state.
10
10
 
11
- Each test file is run in a forked process and, if the second parameter to
12
- `Cutest.run` is true, it is also loaded inside an anonymous module. Once a
13
- failure is found in a file, the rest of the file is skipped and the error is
14
- reported. This way, running your test suite feels faster.
11
+ Each test file is run in a forked process and. Once a failure is found in a
12
+ file, you get a debugger and the ability to fix the error in place. If you
13
+ choose to quit the debugger, the rest of the file is skipped. This way, doing
14
+ TDD is easier and running your test suite feels faster.
15
15
 
16
16
  You can use the `scope` command around tests: it guarantees that no instance
17
17
  variables are shared between tests.
@@ -50,11 +50,15 @@ In your Rakefile:
50
50
  require "cutest"
51
51
 
52
52
  task :test do
53
- Cutest.run(Dir["test/*"])
53
+ Cutest.run(Dir["test/*.rb"])
54
54
  end
55
55
 
56
56
  task :default => :test
57
57
 
58
+ Or from the command line:
59
+
60
+ $ cutest test/*.rb
61
+
58
62
  In your tests:
59
63
 
60
64
  setup do
@@ -73,22 +77,6 @@ In your tests:
73
77
  assert 23 == params[:a]
74
78
  end
75
79
 
76
-
77
- To run the tests:
78
-
79
- $ rake
80
-
81
- If you get an error, the report will look like this:
82
-
83
- >> should preserve the original values from the setup
84
- => assert 24 == params[:a]
85
- test/a_test.rb +14
86
-
87
-
88
- Instead of a description of the error, you get to see the assertion
89
- that failed along with the file and line number. Adding a debugger and
90
- fixing the bug is left as an exercise for the reader.
91
-
92
80
  An example working with a prepare block:
93
81
 
94
82
  prepare do
@@ -122,9 +110,29 @@ And working with scopes:
122
110
  The tests in these two examples will pass.
123
111
 
124
112
  Unlike other testing frameworks, Cutest does not compile all the tests before
125
- running them. Another shift in design is that one dot is shown after a file is
126
- examined, and not the usual one-dot-per-assertion. And finally, the execution
127
- of a file stops one the first failure is found.
113
+ running them.
114
+
115
+ Handling errors
116
+ ---------------
117
+
118
+ If you get an error when running the tests, you will see a debugger prompt:
119
+
120
+ Exception: Cutest::AssertionFailed
121
+ Type continue to retry or edit to modify the source
122
+ .Breakpoint 1 at test/setup.rb:14
123
+ [12, 16] in test/setup.rb
124
+ 12
125
+ 13 test "should preserve the original values from the setup" do |params|
126
+ => 14 assert 24 == params[:a]
127
+ 15 end
128
+ 16
129
+ test/setup.rb:14
130
+ assert 24 == params[:a]
131
+ (rdb:1)
132
+
133
+ Instead of a getting a report of the error, you get to interact with it live
134
+ and even fix it: if you type `edit`, the file is opened in your editor. Once
135
+ you fix the code and save it, the debugger will reload it and retry.
128
136
 
129
137
  Installation
130
138
  ------------
data/bin/cutest CHANGED
@@ -3,7 +3,56 @@
3
3
  require File.expand_path("../lib/cutest", File.dirname(__FILE__))
4
4
 
5
5
  if ARGV.empty?
6
- puts "usage: cutest file ..."
7
- else
8
- Cutest.run(Dir[*ARGV])
6
+ puts "usage: cutest [-r lib] [-v] file ..."
7
+ exit
9
8
  end
9
+
10
+ class Cutest
11
+
12
+ # Clap: Command line argument parsing.
13
+ # http://github.com/soveran/clap
14
+ # http://rubygems.org/gems/clap
15
+ class Clap
16
+ VERSION = "0.0.1"
17
+
18
+ attr :argv
19
+ attr :opts
20
+
21
+ def self.run(args, opts)
22
+ new(args, opts).run
23
+ end
24
+
25
+ def initialize(argv, opts)
26
+ @argv = argv.reverse.dup
27
+ @opts = opts
28
+ end
29
+
30
+ def run
31
+ args = []
32
+
33
+ while argv.any?
34
+ item = argv.pop
35
+
36
+ if opts[item]
37
+
38
+ # Call the lambda with N items from argv,
39
+ # where N is the lambda's arity.
40
+ opts[item].call(*argv.pop(opts[item].arity))
41
+ else
42
+
43
+ # Collect the items that don't correspond to
44
+ # flags.
45
+ args << item
46
+ end
47
+ end
48
+
49
+ args
50
+ end
51
+ end
52
+ end
53
+
54
+ files = Cutest::Clap.run ARGV,
55
+ "-r" => lambda { |file| require file },
56
+ "-v" => lambda { puts Cutest::VERSION }
57
+
58
+ Cutest.run(Dir[*files]) if files.any?
data/cutest.gemspec CHANGED
@@ -1,12 +1,11 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "cutest"
3
- s.version = "0.1.5"
3
+ s.version = "1.0.0.beta"
4
4
  s.summary = "Forking tests."
5
5
  s.description = "Run tests in separate processes to avoid shared state."
6
6
  s.authors = ["Damian Janowski", "Michel Martens"]
7
7
  s.email = ["djanowski@dimaion.com", "michel@soveran.com"]
8
8
  s.homepage = "http://github.com/djanowski/cutest"
9
9
  s.files = ["LICENSE", "README.markdown", "Rakefile", "lib/cutest.rb", "cutest.gemspec", "test/assert.rb", "test/assert_raise.rb", "test/prepare.rb", "test/run.rb", "test/scopes.rb", "test/setup.rb"]
10
- s.add_dependency "batch", "~> 0.0.3"
11
10
  s.executables.push "cutest"
12
11
  end
data/lib/cutest.rb CHANGED
@@ -1,48 +1,75 @@
1
- require "batch"
1
+ begin
2
+ require "ruby-debug"
3
+ rescue LoadError
4
+ puts "Cutest needs ruby-debug, but it couldn't be required."
5
+ puts "Please install ruby-debug or ruby-debug19 and try again."
6
+ exit
7
+ end
2
8
 
3
- class Cutest < Batch
4
- VERSION = "0.1.5"
9
+ Debugger.settings[:autoeval] = 1
10
+ Debugger.settings[:autolist] = 1
11
+ Debugger.settings[:listsize] = 5
12
+ Debugger.settings[:reload_source_on_change] = 1
5
13
 
6
- def report_error(_, error)
7
- $stderr.puts "#{error}\n"
8
- end
14
+ class Cutest
15
+ VERSION = "1.0.0.beta"
16
+
17
+ def self.run(files)
18
+ trap("INT") { exit }
19
+ trap("TERM") { exit }
20
+
21
+ files.each do |file|
22
+ Debugger.start do
23
+
24
+ loop do
25
+ stdin, stdout = IO.pipe
26
+
27
+ fork do
28
+ stdin.close
9
29
 
10
- def self.run(files, anonymous = false)
11
- each(files) do |file|
12
- read, write = IO.pipe
30
+ begin
31
+ load(file)
13
32
 
14
- fork do
15
- read.close
33
+ rescue LoadError, SyntaxError
34
+ puts ["\n", error([file, $!.message])]
35
+ exit
16
36
 
17
- begin
18
- load(file, anonymous)
37
+ rescue Exception
38
+ fn, ln = $!.backtrace.first.split(":")
39
+ stdout.write("#{fn}\n#{ln}\n#{error($!)}\n#{hint}")
40
+ end
41
+ end
19
42
 
20
- rescue Cutest::AssertionFailed => e
21
- write.puts e.message
43
+ stdout.close
22
44
 
23
- rescue Exception => e
24
- error = [e.message]
25
- error += e.backtrace.take_while { |line| !line.index(__FILE__) }
45
+ Process.wait
26
46
 
27
- write.puts ">> #{cutest[:test]}"
28
- write.puts error.join("\n")
47
+ output = stdin.read
48
+
49
+ unless output.empty?
50
+ fn, ln, error, hint = output.split("\n")
51
+ Debugger.add_breakpoint(fn, ln.to_i)
52
+ puts ["\n", error, hint]
53
+ else
54
+ break
55
+ end
29
56
  end
30
57
  end
58
+ end
31
59
 
32
- write.close
60
+ puts
61
+ end
33
62
 
34
- Process.wait
63
+ def self.error(e)
64
+ "\033[01;36mException: \033[01;33m#{e}\033[00m"
65
+ end
35
66
 
36
- output = read.read
37
- raise Cutest::AssertionFailed.new(output) unless output.empty?
38
- read.close
39
- end
67
+ def self.hint
68
+ "\033[01;36mType \033[0;33mcontinue\033[0;36m to retry " +
69
+ "or \033[0;33medit\033[0;36m to modify the source\033[00m"
40
70
  end
41
71
 
42
72
  class AssertionFailed < StandardError
43
- def backtrace
44
- []
45
- end
46
73
  end
47
74
 
48
75
  class Scope
@@ -121,6 +148,7 @@ private
121
148
  # Assert that value is not nil or false.
122
149
  def assert(value)
123
150
  flunk unless value
151
+ print "."
124
152
  end
125
153
 
126
154
  # Assert that the block doesn't raise the expected exception.
@@ -130,16 +158,16 @@ private
130
158
  rescue => exception
131
159
  ensure
132
160
  flunk unless exception.kind_of?(expected)
161
+ print "."
133
162
  end
134
163
  end
135
164
 
136
165
  # Stop the tests and raise an error where the message is the last line
137
166
  # executed before flunking.
138
167
  def flunk
139
- file, line = caller[1].split(":")
140
- code = File.readlines(file)[line.to_i - 1]
141
- msg = ">> #{cutest[:test]}\n=> #{code.strip}\n #{file} +#{line}"
168
+ exception = Cutest::AssertionFailed.new
169
+ exception.set_backtrace([caller[1]])
142
170
 
143
- raise Cutest::AssertionFailed.new(msg)
171
+ raise exception
144
172
  end
145
173
  end
data/test/assert_raise.rb CHANGED
@@ -4,7 +4,6 @@ test "catches the right exception" do
4
4
  end
5
5
  end
6
6
 
7
-
8
7
  test "raises if the expectation is not met" do
9
8
  assert_raise(Cutest::AssertionFailed) do
10
9
  assert_raise(RuntimeError) do
data/test/run.rb CHANGED
@@ -11,7 +11,7 @@ ensure
11
11
  end
12
12
 
13
13
  test "output of successful run" do
14
- expected = " 0% .\n100% \n"
14
+ expected = "\n"
15
15
 
16
16
  stdout, stderr = capture do
17
17
  Cutest.run(Dir["test/fixtures/success.rb"])
@@ -19,35 +19,3 @@ test "output of successful run" do
19
19
 
20
20
  assert stdout == expected
21
21
  end
22
-
23
- test "output of failed run" do
24
- stdout, stderr = capture do
25
- Cutest.run(Dir["test/fixtures/failure.rb"])
26
- end
27
-
28
- assert stdout == " 0% E\n100% \n"
29
- assert stderr["\nSome errors occured:\n\n>> failed assertion\n=> assert false\n"]
30
- assert stderr["test/fixtures/failure.rb +2\n\n"]
31
- end
32
-
33
- test "output of failed run on a custom assertion" do
34
-
35
- stdout, stderr = capture do
36
- Cutest.run(Dir["test/fixtures/fail_custom_assertion.rb"])
37
- end
38
-
39
- assert stdout == " 0% E\n100% \n"
40
- assert stderr["\nSome errors occured:\n\n>> failed custom assertion\n=> assert_empty \"foo\"\n"]
41
- assert stderr["test/fixtures/fail_custom_assertion.rb +6\n\n"]
42
- end
43
-
44
- test "output of failed run on an exception" do
45
- stdout, stderr = capture do
46
- Cutest.run(Dir["test/fixtures/exception.rb"])
47
- end
48
-
49
- assert stdout == " 0% E\n100% \n"
50
- assert stderr["\nSome errors occured:\n\n>> some unhandled exception\nOops\n"]
51
- assert stderr["test/fixtures/exception.rb:2:in `foo'\n"]
52
- assert stderr["test/fixtures/exception.rb:6"]
53
- end
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cutest
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ prerelease: true
5
5
  segments:
6
- - 0
7
6
  - 1
8
- - 5
9
- version: 0.1.5
7
+ - 0
8
+ - 0
9
+ - beta
10
+ version: 1.0.0.beta
10
11
  platform: ruby
11
12
  authors:
12
13
  - Damian Janowski
@@ -15,24 +16,10 @@ autorequire:
15
16
  bindir: bin
16
17
  cert_chain: []
17
18
 
18
- date: 2010-09-24 00:00:00 -03:00
19
+ date: 2010-10-10 00:00:00 -03:00
19
20
  default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
22
- name: batch
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ~>
28
- - !ruby/object:Gem::Version
29
- segments:
30
- - 0
31
- - 0
32
- - 3
33
- version: 0.0.3
34
- type: :runtime
35
- version_requirements: *id001
21
+ dependencies: []
22
+
36
23
  description: Run tests in separate processes to avoid shared state.
37
24
  email:
38
25
  - djanowski@dimaion.com
@@ -76,11 +63,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
76
63
  required_rubygems_version: !ruby/object:Gem::Requirement
77
64
  none: false
78
65
  requirements:
79
- - - ">="
66
+ - - ">"
80
67
  - !ruby/object:Gem::Version
81
68
  segments:
82
- - 0
83
- version: "0"
69
+ - 1
70
+ - 3
71
+ - 1
72
+ version: 1.3.1
84
73
  requirements: []
85
74
 
86
75
  rubyforge_project: