butterfly_net 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,21 +1,35 @@
1
1
  Butterfly Net
2
2
  =============
3
3
 
4
- [Home page and source]: http://butterflynet.org
5
- [Rubygems page]: http://rubygems.org/gems/butterfly_net
6
4
 
7
5
  Author: Chris Smith (quartzmo -at- gmail.com)
8
6
 
7
+ [Home page and source download](http://github.com/quartzmo/butterfly_net)
9
8
 
10
- ## DESCRIPTION
9
+ [RubyGems page](http://rubygems.org/gems/butterfly_net)
10
+
11
+
12
+ ## Summary
11
13
 
12
14
  IRB and Rails console history captured as Test::Unit tests. (RSpec and others hopefully soon to come.)
13
15
 
16
+ ## Description
17
+
18
+ Butterfly Net is intended to help you capture, as executable tests, surprising or unexpected behavior that you come across while
19
+ interacting with your project code in IRB.
20
+
21
+ ** Warning **
22
+
23
+ Butterfly Net is not a tool for [test-first](http://www.extremeprogramming.org/rules/testfirst.html) development, and is
24
+ not intended as a primary tool for [Test-Driven Development](http://en.wikipedia.org/wiki/Test-driven_development) (TDD).
25
+ Please use it in addition to these valuable methods.
14
26
 
15
- ## INSTALL
27
+ ## Install
16
28
 
17
- Butterfly Net is available as a gem from rubygems.org (http://rubygems.org/gems/butterfly_net), or as source from
18
- butterflynet.org.
29
+ Butterfly Net is available as a gem from [rubygems.org](http://rubygems.org/gems/butterfly_net), or as source from
30
+ [GitHub](http://github.com/quartzmo/butterfly_net).
31
+
32
+ To install the gem:
19
33
 
20
34
  sudo gem install butterfly_net
21
35
 
@@ -25,36 +39,40 @@ To automatically require Butterfly Net on every IRB session, add the following t
25
39
  require 'butterfly_net'
26
40
 
27
41
 
28
- ## USAGE
42
+ ## Usage
29
43
 
30
44
  ### Command methods
31
45
 
32
- The following commands can be used in any IRB-based console.
46
+ The following commands can be used in any IRB-based console. The shortcuts should be preferred, the longer aliases exist in case of naming conflicts.
33
47
 
34
- * bn, bn_open- Open a new test case, closing the current test case if one exists. Args: file_name:string (optional; '.rb' will appended if needed)
35
- * bnc, bn_close - Close the active test case, and write the output to a file.
36
- * m, bn_method - Close the current test method (or block), naming it with the arg method_name:string (optional)
48
+ * `bn(file_name=nil)`, `bn_open(file_name=nil)` Open a new test case, closing the current test case if one exists. Args: file_name:string (optional; '.rb' will appended if needed)
49
+ * `bnc`, `bn_close` Close the active test case, and write the output to a file.
50
+ * `m(method_name=nil)`, `bn_method(method_name=nil)` Close the current test method (or block), naming it with the arg method_name:string (optional)
37
51
 
38
52
  ### Example Usage in IRB
39
53
 
40
54
  $ irb
41
- irb(main):001:0> bn 'irb_tests'
55
+ irb(main):001:0> require 'rubygems'
56
+ => true
57
+ irb(main):002:0> require 'butterfly_net'
42
58
  => true
43
- irb(main):002:0> a = 1
59
+ irb(main):003:0> bn 'irb_tests'
60
+ => true
61
+ irb(main):004:0> a = 1
44
62
  => 1
45
- irb(main):003:0> a += 2
63
+ irb(main):005:0> a += 2
46
64
  => 3
47
- irb(main):004:0> m 'plusequals'
65
+ irb(main):006:0> m 'plusequals'
48
66
  => true
49
- irb(main):005:0> require 'bigdecimal'
67
+ irb(main):007:0> require 'bigdecimal'
50
68
  => true
51
- irb(main):006:0> infinity = BigDecimal('Infinity')
69
+ irb(main):008:0> infinity = BigDecimal('Infinity')
52
70
  => #<BigDecimal:114ed34,'Infinity',4(4)>
53
- irb(main):007:0> BigDecimal.new("1.0") / BigDecimal.new("0.0") == infinity
71
+ irb(main):009:0> BigDecimal.new("1.0") / BigDecimal.new("0.0") == infinity
54
72
  => true
55
- irb(main):008:0> m 'bigdecimal_infinity'
73
+ irb(main):010:0> m 'bigdecimal_infinity'
56
74
  => true
57
- irb(main):009:0> exit
75
+ irb(main):011:0> exit
58
76
  butterfly_net: irb_tests.rb closed
59
77
  true
60
78
  $ cat irb_tests.rb
@@ -86,11 +104,13 @@ The following commands can be used in any IRB-based console.
86
104
 
87
105
  ### Ruby on Rails console
88
106
 
89
- For repeatable tests, be sure to load the Rails test environment with "./script/console test".
90
- In a Rails project, you can run all tests with the standard rake command 'rake test',
91
- or an individual test by adding the test directory to the path with the option -I when you invoke Ruby.
107
+ For repeatable tests, be sure to load the Rails test environment with `./script/console test`. Invoke the `bn` command
108
+ with the relative path to the appropriate test sub-directory, and file name. (Hint: In a Rails project, you can run an
109
+ individual test by adding the test directory to the path with the option -I when you invoke Ruby.)
110
+
111
+ For example:
92
112
 
93
- chris$ ./script/console test
113
+ $ ./script/console test
94
114
  Loading test environment (Rails 2.3.4)
95
115
  >> bn "test/unit/person_console_test"
96
116
  . . .
@@ -98,33 +118,41 @@ or an individual test by adding the test directory to the path with the option -
98
118
  => 2
99
119
  >> exit
100
120
  . . .
101
- chris$ ruby -Itest test/unit/person_console_test.rb
121
+ $ ruby -Itest test/unit/person_console_test.rb
102
122
  . . .
103
123
  1 tests, 1 assertions, 0 failures, 0 errors
104
124
 
105
125
 
106
- ## KNOWN ISSUES
126
+ ## Known Issues
107
127
 
108
128
  This section covers issues which may not be resolved quickly. Feel free to lend a hand!
109
129
 
110
- ### Object inspect output
130
+ ### The return value of Object#inspect is often not valid Ruby
111
131
 
112
- Butterfly Net relies on an expectation value responding with valid Ruby code to the inspect method, which is the case for core
113
- classes such as Hash and Array. However, it's not the case with most classes, many of which respond with the familiar
114
- #<...> notation, which can't be interpreted.
132
+ Currently, the expectation that Butterfly Net places into an assertion is the output of Object#inspect. Simple types and value-oriented
133
+ types such as Hash and Array usually do return valid code, which works great. Unfortunately, most other types respond with the familiar
134
+ `#<...>` notation, which can't be interpreted.
115
135
 
116
136
  For example:
117
137
 
118
138
  assert_equal(#<BigDecimal:11511d8,'Infinity',4(24)>, BigDecimal.new("1.0") / BigDecimal.new("0.0")) # doesn't work
119
139
 
120
- The workaround is to assign expected values to a variable. Of course, you have to know what to expect in order to do this,
121
- which may take a few tries. Sorry.
140
+ The best workaround is to use IRB in a way that gets you to simple types, the same way you write unit tests that
141
+ compare values by calling `to_s`, `to_i`, `to_f`, etc on more complex objects.
142
+ Another solution, more appropriate for cases like the BigDecimal infinity example above, is to assign the expected
143
+ result to a variable.
144
+
145
+ infinity = BigDecimal('Infinity')
146
+ assert(BigDecimal.new("1.0") / BigDecimal.new("0.0") == infinity) # works great, IF you know what to expect
147
+
148
+ Of course, you have to know what to expect in order to do this, which may take a few tries. Sorry.
149
+ I'll be searching for a better solution to this one.
122
150
 
123
151
 
124
- ### Inline variable assignment
152
+ ### Assigning a variable, even within a string, results in no assertion for that line
125
153
 
126
- Butterfly Net tries to detect simple assignments, such as "a = 1", in order to write them out clearly,
127
- without enclosing assertions. In some cases this causes it to miss statements that should be tested.
154
+ To keep tests readable, Butterfly Net writes simple assignment expressions such as "a = 1" without enclosing assertions.
155
+ However, the regular expression it uses to accomplish this can cause Butterfly Net to miss some tests.
128
156
 
129
157
  For example:
130
158
 
@@ -136,14 +164,41 @@ For example:
136
164
  results in
137
165
 
138
166
  def test_1
139
- a = 1; a + 1 # should have been a = 1; assert_equal(2, a + 1)
140
- "a=1".split('=') # should have been assert_equal(["a", "1"],"a=1".split('='))
167
+ a = 1; a + 1 # should have been a = 1; assert_equal(2, a + 1)
168
+ "a=1".split('=') # should have been assert_equal(["a", "1"],"a=1".split('='))
141
169
  end
142
170
 
143
- In order to avoid this issue, just put assignments (and anything that looks like an assignment) on separate lines.
171
+ Maybe someone can suggest how Butterfly Net can become just enough of an actual Ruby interpreter to get past this issue?
172
+ In the meantime, just put assignments (and anything that looks like an assignment) on separate lines
173
+ from the expressions you want to be tested.
174
+
175
+
176
+ ## Story
177
+
178
+ Coming to Ruby from Java, I felt "The Console" was like a magical English butler, always there discreetly
179
+ at my side to instantly and precisely answer all my questions. How incredible!
180
+
181
+ In Java, JUnit had been my preferred tool for exploring code, both my own project code and
182
+ third-party libraries and frameworks. As anyone who has worked on a legacy (i.e., test-deprived) codebase or experienced
183
+ java.util.Calendar knows, the tighter the feedback loop, the sooner you get your answers. Experimenting with code behavior through an
184
+ application UI is only for the foolish, or those racking up billable hours on a big waterfall project.
185
+ JUnit tests were usually the fastest way I could experience what code did in real life, and with IntelliJ IDEA generating the
186
+ boilerplate at a keystroke, exploratory testing was quick and very acceptable. When I was done learning and
187
+ experimenting, I deleted most of the tests, in accordance with the idea that less is better, and you shouldn't
188
+ run automated regression tests for stable, third-party code.
189
+
190
+ Switching to Ruby, I found IRB to be a much better tool, but as with all great conveniences, it had a downside.
191
+ Although I generally program in a test-first style, I also explore existing code after it is written,
192
+ kicking the tires and banging on the doors. With JUnit, any interesting behavior I discovered during this process was captured in a
193
+ test, and was duly added to the suite. With IRB? Gone, flown off into the deep blue yonder. The idea for Butterfly Net
194
+ occurred to me pretty quickly. That's the story, so far. I'm not sure how others will use Butterfly Net, but I don't
195
+ want to limit the possibilities. Should it become more generalized and flexible, something of a view framework like
196
+ [Hirb](http://github.com/cldwalker/hirb)? Or did I re-invent something that already existed?
197
+
198
+ Please send comments and suggestions to quartzmo -at- gmail.com
144
199
 
145
200
 
146
- ## LICENSE
201
+ ## License
147
202
 
148
203
  The MIT License
149
204
 
@@ -3,6 +3,7 @@
3
3
  #
4
4
  # bn - open a new Butterfly Net test case, with optional name
5
5
  # bnc - close Butterfly Net test case, writing output file
6
+ # m - close current test method, and open a new one
6
7
  # bns - add preceding line to setup method, instead of current assertion set # todo
7
8
  # bnt - add preceding line to teardown method, instead of current assertion set # todo
8
9
  #
@@ -10,27 +11,20 @@ module ButterflyNet
10
11
  module Commands
11
12
 
12
13
  def bn_open(file_name=nil)
13
-
14
-
15
- @file.close if @file
16
- check_for_readline
17
- # todo: if Rails, WARNING if env is not test
18
-
19
- start_index = Readline::HISTORY.empty? ? 0 : (Readline::HISTORY.size - 1)
20
- file_name ||= "butterfly_net_#{Time.now.strftime("%Y%m%d%H%M%S")}.rb"
21
- file_name += ".rb" unless file_name =~ /.rb$/
22
- @file = FileWriter.new(file_name, start_index)
23
- Kernel.at_exit { puts @file.close if @file; @file = nil }
14
+ @file_writer.close if @file_writer
15
+ @file_writer = ReadlineReader.new(file_name)
16
+ Kernel.at_exit { puts @file_writer.close if @file_writer; @file_writer = nil }
24
17
  true
25
- rescue
18
+ rescue Exception => e
19
+ puts e
20
+ puts e.backtrace
26
21
  false
27
22
  end
28
23
 
29
24
  def bn_close
30
- check_for_readline
31
- if @file
32
- status = @file.close
33
- @file = nil
25
+ if @file_writer
26
+ status = @file_writer.close
27
+ @file_writer = nil
34
28
  status
35
29
  else
36
30
  puts "butterfly_net: First invoke 'bn' or 'bn_open' to begin a session"
@@ -39,25 +33,17 @@ module ButterflyNet
39
33
  end
40
34
 
41
35
  def bn_method(method_name=nil)
42
- check_for_readline
43
- if @file
44
- @file.new_assertion_set(method_name)
36
+ if @file_writer
37
+ @file_writer.new_assertion_set(method_name)
45
38
  else
46
39
  puts "butterfly_net: First invoke 'bn' or 'bn_open' to begin a session"
47
40
  false
48
41
  end
49
42
  end
50
43
 
51
-
52
44
  alias :bn :bn_open
53
45
  alias :bnc :bn_close
54
46
  alias :m :bn_method
55
47
 
56
- private
57
-
58
- def check_for_readline
59
- raise "butterfly_net: Readline::HISTORY required!" unless defined? Readline::HISTORY
60
- end
61
-
62
48
  end
63
49
  end
@@ -1,69 +1,19 @@
1
1
  module ButterflyNet
2
2
  class FileWriter
3
3
 
4
- attr_accessor :end_index
5
- attr_reader :start_index
6
-
7
- def initialize(file_name, start_index)
8
- @file_name = file_name
9
- @start_index = start_index
10
- @adapter = TestUnitAdapter.new
4
+ def initialize(filename)
5
+ @file = File.open(filename, 'a+')
11
6
  end
12
7
 
13
- def new_assertion_set(method_name)
14
- lines = Readline::HISTORY.to_a
15
- @end_index ||= Readline::HISTORY.size - 3 # in case of no call to 'bn_close'
16
- if @end_index < 0 || lines[@start_index..@end_index].empty?
17
- false
18
- else
19
-
20
-
21
- lines[@start_index..@end_index].each do |line|
22
- @adapter.add_command(line)
23
- end
24
8
 
25
- @adapter.close_assertion_set(method_name)
26
- @start_index = Readline::HISTORY.size - 1
27
- @end_index = nil
28
-
29
- true
9
+ def create_file(data)
10
+ @file.puts data
11
+ true
12
+ ensure
13
+ if @file # todo: closing is always good, but prevent writing partial data in case of exception
14
+ @file.close
30
15
  end
31
- rescue Exception => e
32
- puts "butterfly_net: Error generating tests: #{e}"
33
- puts e.backtrace
34
- false
35
- end
36
-
37
-
38
- def close
39
- lines = Readline::HISTORY.to_a
40
- @end_index ||= Readline::HISTORY.size - 3 # in case of no call to 'bn_close'
41
- # if @adapter.empty?
42
- # puts "butterfly_net: #{@file_name} closed, no file written to disk."
43
- #
44
- # false
45
- #
46
- # else
47
- if @end_index < 0 || lines[@start_index..@end_index].empty?
48
- puts "butterfly_net: #{@file_name} closed"
49
- else
50
- lines[@start_index..@end_index].each do |line|
51
- @adapter.add_command(line)
52
- end
53
- puts "butterfly_net: #{@file_name} closed, with #{@end_index - @start_index + 1} lines, from '#{lines[@start_index]}' to '#{lines[@end_index]}'"
54
-
55
- end
56
-
57
- @adapter.create_file(@file_name)
58
-
59
- true
60
- # end
61
- rescue Exception => e
62
- puts "butterfly_net: Error generating tests: #{e}"
63
- puts e.backtrace
64
- false
65
16
  end
66
17
 
67
18
  end
68
-
69
- end
19
+ end
@@ -0,0 +1,9 @@
1
+ module ButterflyNet
2
+ class RailsTestUnitAdapter < TestUnitAdapter
3
+
4
+ def header_text
5
+ "require \"test_helper\"\n\n# script/console test capture courtesy of butterfly_net (butterflynet.org)\nclass MyTest < ActiveSupport::TestCase\n\n"
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,74 @@
1
+ module ButterflyNet
2
+ class ReadlineReader
3
+
4
+ attr_accessor :end_index
5
+ attr_reader :start_index
6
+
7
+ def initialize(file_name)
8
+
9
+ check_for_readline
10
+ # todo: if Rails, WARNING if env is not test
11
+
12
+ @start_index = Readline::HISTORY.empty? ? 0 : (Readline::HISTORY.size - 1)
13
+ if file_name
14
+ @file_name = file_name.to_s + ".rb" unless file_name.to_s =~ /.rb$/
15
+ else
16
+ @file_name = "butterfly_net_#{Time.now.strftime("%Y%m%d%H%M%S")}.rb" # todo: something like heroku's generated names
17
+ end
18
+ @test_case = TestCase.new
19
+ end
20
+
21
+ def new_assertion_set(method_name)
22
+ check_for_readline
23
+
24
+ lines = Readline::HISTORY.to_a
25
+ @end_index ||= Readline::HISTORY.size - 3 # in case of no call to 'bn_close'
26
+ if @end_index < 0 || lines[@start_index..@end_index].empty?
27
+ false
28
+ else
29
+ lines[@start_index..@end_index].each do |line|
30
+ @test_case.add_command(line)
31
+ end
32
+
33
+ @test_case.close_assertion_set(method_name)
34
+ @start_index = Readline::HISTORY.size - 1
35
+ @end_index = nil
36
+
37
+ true
38
+ end
39
+ rescue Exception => e
40
+ puts "butterfly_net: Error generating tests: #{e}"
41
+ puts e.backtrace
42
+ false
43
+ end
44
+
45
+
46
+ def close
47
+ check_for_readline
48
+ lines = Readline::HISTORY.to_a
49
+ @end_index ||= Readline::HISTORY.size - 3 # in case of no call to 'bn_close'
50
+ if @end_index < 0 || lines[@start_index..@end_index].empty?
51
+ else
52
+ lines[@start_index..@end_index].each do |line|
53
+ @test_case.add_command(line)
54
+ end
55
+
56
+ end
57
+
58
+ result = @test_case.create_file(@file_name)
59
+ puts result ? "butterfly_net: #{@file_name} created." : "butterfly_net: #{@file_name} was not created. No tests were generated from this session."
60
+ result
61
+
62
+ rescue Exception => e
63
+ puts "butterfly_net: Error generating tests: #{e}"
64
+ puts e.backtrace
65
+ false
66
+ end
67
+
68
+
69
+ def check_for_readline
70
+ raise "butterfly_net: Readline::HISTORY required!" unless defined? Readline::HISTORY
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,43 @@
1
+ module ButterflyNet
2
+ class TestCase
3
+
4
+ attr_reader :assertion_sets
5
+
6
+ def initialize
7
+ rails = defined? ActiveSupport::TestCase # todo support earlier versions of rails?
8
+ @adapter = rails ? RailsTestUnitAdapter.new : TestUnitAdapter.new
9
+ @assertion_sets = [@adapter.test_method]
10
+ end
11
+
12
+ def add_command(line)
13
+ @assertion_sets.last << line
14
+ end
15
+
16
+ # user provides name at closing
17
+ def close_assertion_set(method_name = nil)
18
+ @assertion_sets.last.name = (method_name ? method_name : @assertion_sets.size)
19
+ @assertion_sets << @adapter.test_method
20
+ end
21
+
22
+ # true if ALL are empty
23
+ def empty?
24
+ @assertion_sets.inject(true) {|result,e| result = (result and e.empty?); result}
25
+ end
26
+
27
+ def create_file(filename)
28
+ bodytext = generate_bodytext
29
+ return false if empty? || bodytext.empty?
30
+ FileWriter.new(filename).create_file(@adapter.header_text + bodytext + @adapter.footer_text)
31
+ end
32
+
33
+ def test_methods
34
+ @assertion_sets.last.name = @assertion_sets.size unless @assertion_sets.last.name # assign the default, numbered name; done here for testing
35
+ @assertion_sets.collect {|i| i.text }.compact
36
+ end
37
+
38
+ def generate_bodytext
39
+ test_methods.inject("") { |result,e|result += e; result }
40
+ end
41
+
42
+ end
43
+ end
@@ -1,54 +1,16 @@
1
1
  module ButterflyNet
2
2
  class TestUnitAdapter
3
3
 
4
- attr_reader :assertion_sets
5
-
6
- def initialize
7
- @assertion_sets = []
8
- end
9
-
10
- def add_command(line)
11
- @assertion_sets << TestUnitMethod.new unless @assertion_sets.last
12
- @assertion_sets.last << line
13
- end
14
-
15
- def close_assertion_set(method_name = nil)
16
- @assertion_sets.last.name = (method_name ? method_name : @assertion_sets.size)
17
- @assertion_sets << TestUnitMethod.new
18
- end
19
-
20
- def empty?
21
- @assertion_sets.empty?
4
+ def header_text
5
+ "require \"test/unit\"\n\n# IRB test capture courtesy of butterfly_net (butterflynet.org)\nclass MyTest < Test::Unit::TestCase\n\n"
22
6
  end
23
7
 
24
- def create_file(filename)
25
- return nil if empty?
26
-
27
- #todo detect existing file, and delete last 'end' line
28
- file = File.open(filename, 'a+') # starts at end of file if file exists
29
-
30
- if defined? ActiveSupport::TestCase # rails # todo support earlier versions of rails
31
- file.puts "require \"test_helper\"\n\n# script/console test capture courtesy of butterfly_net (butterflynet.org)\nclass MyTest < ActiveSupport::TestCase"
32
- else
33
- file.puts "require \"test/unit\"\n\n# IRB test capture courtesy of butterfly_net (butterflynet.org)\nclass MyTest < Test::Unit::TestCase"
34
- end
35
-
36
-
37
- test_methods.each do |test_method|
38
- file.puts "\n #{test_method}"
39
- end
40
-
41
- ensure
42
- if file # todo: closing is always good, but prevent writing partial data in case of exception
43
- file.puts "\nend"
44
- file.close
45
- end
46
-
8
+ def footer_text
9
+ 'end'
47
10
  end
48
11
 
49
- def test_methods
50
- @assertion_sets.last.name = @assertion_sets.size unless @assertion_sets.last.name # done here for testing
51
- @assertion_sets.collect {|i| i.text }.compact
12
+ def test_method
13
+ TestUnitMethod.new
52
14
  end
53
15
 
54
16
  end
@@ -5,29 +5,57 @@ module ButterflyNet
5
5
 
6
6
  def initialize
7
7
  @commands = []
8
+ @definitions = []
8
9
  end
9
10
 
10
11
  def <<(line)
11
12
  @commands << line
12
13
  end
13
14
 
15
+ def empty?
16
+ @commands.empty?
17
+ end
18
+
14
19
  def name=(name)
15
20
  name = underscore(name)
16
- @name = name =~ /^test_/ ? name : "test_#{name}"
21
+ @name = name =~ /^\s*test_/ ? name : "test_#{name}"
17
22
  end
18
23
 
19
24
  def self.assignment_or_require?(line) # todo: extract to line class
20
25
  line =~ /require\s*['|"]\w+['|"]|[^=<>!*%\/+-\\|&]=[^=~]/
21
26
  end
22
27
 
28
+ def self.start_def?(line) # todo: extract to line class
29
+ line =~ /^\s*def\s+|^\s*class\s+|^\s*module\s+/
30
+ end
31
+
32
+ def self.end_def?(line) # todo: extract to line class
33
+ line =~ /end\s*$/
34
+ end
35
+
23
36
  def text
24
37
  purge_bad_commands
38
+ before_test_method = ""
25
39
  lines_string = ""
26
- @commands.each_index do |i|
27
- text = assertion(i)
28
- lines_string += " #{text}\n" if text
40
+ nesting_level = 0
41
+ @commands.each_with_index do |current_line, i|
42
+ if TestUnitMethod.start_def?(current_line) and TestUnitMethod.end_def?(current_line)
43
+ before_test_method += " #{(" " * nesting_level)}#{current_line}\n\n"
44
+ elsif TestUnitMethod.start_def?(current_line)
45
+ nesting_level += 1
46
+ before_test_method += "#{(" " * nesting_level)}#{current_line}\n"
47
+ elsif TestUnitMethod.end_def?(current_line)
48
+ nesting_level -= 1
49
+ before_test_method += " #{(" " * nesting_level)}#{current_line}\n#{ nesting_level == 0 ? "\n" : ""}"
50
+ elsif nesting_level == 0
51
+ text = assertion(i)
52
+ lines_string += " #{text}\n" if text
53
+ else
54
+ before_test_method += " #{(" " * nesting_level)}#{current_line}\n"
55
+ end
29
56
  end
30
- lines_string.empty? ? nil : "def #{@name}\n#{lines_string} end"
57
+ before_test_method += " "
58
+ lines_string.empty? ? nil : "#{before_test_method}def #{@name}\n#{lines_string} end\n\n"
31
59
  end
32
60
 
33
61
  def assertion(current_i)
@@ -63,33 +91,49 @@ module ButterflyNet
63
91
  else
64
92
 
65
93
  # any other sort of object is handled as a not equal assertion
66
- "assert_not_equal((#{current_line}), #{current_line})"
94
+ "assert_not_equal((#{current_line}), #{current_line})" # todo assert_not_nil in some cases?
67
95
  end
68
96
 
69
97
  end
70
98
 
71
99
  def instances_equal_by_value?(instance)
72
- instance == instance.dup rescue true # can't dup Fixnum, et al...
100
+ instance == instance.dup rescue true # assume anything like Fixnum that can't be dup'd is a value type...
73
101
  end
74
102
 
75
103
  private
76
104
 
105
+ # def extract_definitions # work in progress, todo: handle definitions separately, place at top of file
106
+ # @commands.each_with_index do |current_line, i|
107
+ # if TestUnitMethod.start_def?(current_line) or TestUnitMethod.end_def?(current_line)
108
+ # @definitions << current_line
109
+ # end
110
+ # end
111
+ # end
77
112
 
78
113
  def purge_bad_commands
79
114
  begin
80
115
  commands = ""
81
116
  index = 0
117
+ nesting_level = 0
82
118
  @commands.each_with_index do |current_line, i|
83
119
  index = i
84
120
  commands += current_line + "\n"
85
- eval commands
121
+ if TestUnitMethod.start_def?(current_line) and TestUnitMethod.end_def?(current_line)
122
+ #let it ride
123
+ elsif TestUnitMethod.start_def?(current_line)
124
+ nesting_level += 1
125
+ elsif TestUnitMethod.end_def?(current_line)
126
+ nesting_level -= 1
127
+ elsif nesting_level == 0
128
+ eval commands #todo write tests breaking assumption that definitions are valid code, and impl separate eval's for definitions
129
+ end
86
130
  end
87
-
131
+ nil
88
132
  rescue Exception
89
133
  # delete offender and start again from the beginning
90
- @commands.delete_at index
134
+ @commands.delete_at index # todo: test if string equality is safe for delete()
135
+ retry
91
136
  end
92
- nil
93
137
  end
94
138
 
95
139
  # Adapted from ActiveSupport Inflector
data/lib/butterfly_net.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  require 'butterfly_net/file_writer'
2
2
  require 'butterfly_net/test_unit_method'
3
3
  require 'butterfly_net/test_unit_adapter'
4
+ require 'butterfly_net/rails_test_unit_adapter'
5
+ require 'butterfly_net/test_case'
6
+ require 'butterfly_net/readline_reader'
4
7
  require 'butterfly_net/commands'
5
8
 
6
9
  include ButterflyNet::Commands
@@ -2,4 +2,4 @@ require "test/unit"
2
2
  $: << File.expand_path(File.dirname(__FILE__))
3
3
  $: << File.join(File.expand_path(File.dirname(__FILE__)), File.join("..", "lib"))
4
4
  require "test_unit_method_test"
5
- require "test_unit_adapter_test"
5
+ require "test_case_test"
@@ -1,14 +1,17 @@
1
1
  require "test/unit"
2
2
  $: << File.expand_path(File.dirname(__FILE__))
3
3
  $: << File.join(File.expand_path(File.dirname(__FILE__)), File.join("..", "lib"))
4
+ require "butterfly_net/file_writer"
4
5
  require "butterfly_net/test_unit_method"
5
6
  require "butterfly_net/test_unit_adapter"
7
+ require "butterfly_net/rails_test_unit_adapter"
8
+ require "butterfly_net/test_case"
6
9
 
7
- class TestUnitAdapterTest < Test::Unit::TestCase
10
+ class TestCaseTest < Test::Unit::TestCase
8
11
  include ButterflyNet
9
12
 
10
13
  def setup
11
- @adapter = TestUnitAdapter.new
14
+ @adapter = TestCase.new
12
15
  end
13
16
 
14
17
  def teardown
@@ -21,43 +24,82 @@ class TestUnitAdapterTest < Test::Unit::TestCase
21
24
 
22
25
  def test_test_methods_single
23
26
  @adapter.add_command("1 + 1")
24
- expected = "def test_1\n assert_equal(2, 1 + 1)\n end"
27
+ expected = " def test_1\n assert_equal(2, 1 + 1)\n end\n\n"
25
28
  assert_equal expected, @adapter.test_methods.first
26
29
  end
27
30
 
28
31
  def test_test_methods_2_assertions_1_method
29
32
  @adapter.add_command("1 + 1")
30
33
  @adapter.add_command("1 + 2")
31
- expected = "def test_1\n assert_equal(2, 1 + 1)\n assert_equal(3, 1 + 2)\n end"
34
+ expected = " def test_1\n assert_equal(2, 1 + 1)\n assert_equal(3, 1 + 2)\n end\n\n"
32
35
  assert_equal expected, @adapter.test_methods.last
33
36
  end
34
37
 
35
38
  def test_test_methods_variable_in_assertion
36
39
  @adapter.add_command("a = 1")
37
40
  @adapter.add_command("a + 2")
38
- expected = "def test_1\n a = 1\n assert_equal(3, a + 2)\n end"
41
+ expected = " def test_1\n a = 1\n assert_equal(3, a + 2)\n end\n\n"
39
42
  assert_equal expected, @adapter.test_methods.last
40
43
  end
41
44
 
42
45
  def test_test_methods_require
43
46
  @adapter.add_command("require 'bigdecimal'")
44
47
  @adapter.add_command("BigDecimal(\"1.0\") - 0.5")
45
- expected = "def test_1\n require 'bigdecimal'\n assert_equal(0.5, BigDecimal(\"1.0\") - 0.5)\n end"
48
+ expected = " def test_1\n require 'bigdecimal'\n assert_equal(0.5, BigDecimal(\"1.0\") - 0.5)\n end\n\n"
46
49
  assert_equal expected, @adapter.test_methods.last
47
50
  end
48
51
 
49
- def test_test_methods_numbering
52
+ def test_test_methods_def_method_single_inline
53
+ @adapter.add_command("def timestwo(i); i * 2; end")
54
+ @adapter.add_command("timestwo(4)")
55
+ expected = <<-EOF
56
+ def timestwo(i); i * 2; end
57
+
58
+ def test_1
59
+ assert_equal(8, timestwo(4))
60
+ end
61
+
62
+ EOF
63
+ assert_equal expected, @adapter.generate_bodytext
64
+ end
65
+
66
+ def test_test_methods_def_methods_two_inline
67
+ @adapter.add_command("def timestwo(i); i * 2; end")
68
+ @adapter.add_command("def timesfour(i); timestwo(i) * timestwo(i); end")
69
+ @adapter.add_command("timestwo(1)")
70
+ @adapter.add_command("timesfour(1)")
71
+ expected = <<-EOF
72
+ def timestwo(i); i * 2; end
73
+
74
+ def timesfour(i); timestwo(i) * timestwo(i); end
75
+
76
+ def test_1
77
+ assert_equal(2, timestwo(1))
78
+ assert_equal(4, timesfour(1))
79
+ end
80
+
81
+ EOF
82
+ assert_equal expected, @adapter.generate_bodytext
83
+ end
84
+
85
+ def test_test_methods_numbering_first_method
86
+ @adapter.add_command("1 + 1")
87
+ @adapter.close_assertion_set
88
+ @adapter.add_command("1 + 2")
89
+ assert_equal " def test_1\n assert_equal(2, 1 + 1)\n end\n\n", @adapter.test_methods.first
90
+ end
91
+
92
+ def test_test_methods_numbering_second_method
50
93
  @adapter.add_command("1 + 1")
51
94
  @adapter.close_assertion_set
52
95
  @adapter.add_command("1 + 2")
53
- assert_equal "def test_1\n assert_equal(2, 1 + 1)\n end", @adapter.test_methods.first
54
- assert_equal "def test_2\n assert_equal(3, 1 + 2)\n end", @adapter.test_methods.last
96
+ assert_equal " def test_2\n assert_equal(3, 1 + 2)\n end\n\n", @adapter.test_methods.last
55
97
  end
56
98
 
57
99
  def test_test_methods_naming
58
100
  @adapter.add_command("1 + 1")
59
101
  @adapter.close_assertion_set 'test_one_plus_one'
60
- assert_equal "def test_one_plus_one\n assert_equal(2, 1 + 1)\n end", @adapter.test_methods.first
102
+ assert_equal " def test_one_plus_one\n assert_equal(2, 1 + 1)\n end\n\n", @adapter.test_methods.first
61
103
  end
62
104
 
63
105
  def test_test_methods_bad_input
@@ -111,6 +111,35 @@ class TestUnitMethodTest < Test::Unit::TestCase
111
111
  assert !TestUnitMethod.assignment_or_require?("a *= 1")
112
112
  end
113
113
 
114
+ def test_method_def_true_whitespace
115
+ assert TestUnitMethod.start_def?(" def timestwo(i); i * 2; end ")
116
+ end
117
+
118
+ def test_class_def_true_whitespace
119
+ assert TestUnitMethod.start_def?(" class MyClass; end ")
120
+ end
121
+
122
+ def test_class_def_true_whitespace
123
+ assert TestUnitMethod.start_def?(" module MyModule; end ")
124
+ end
125
+
126
+ def test_method_def_false_variable
127
+ assert !TestUnitMethod.start_def?("definite == true ")
128
+ end
129
+
130
+
131
+ def test_start_def_true_inline
132
+ assert TestUnitMethod.start_def?(" def timestwo(i); i * 2; end ")
133
+ end
134
+
135
+ def test_end_def_true_inline
136
+ assert TestUnitMethod.end_def?(" def timestwo(i); i * 2; end ")
137
+ end
138
+
139
+ def test_end_def_true_whitespace
140
+ assert TestUnitMethod.end_def?(" end ")
141
+ end
142
+
114
143
  def test_name_uppercase
115
144
  @method.name = "MYMETHOD"
116
145
  assert_equal("test_mymethod", @method.name)
@@ -152,21 +181,21 @@ class TestUnitMethodTest < Test::Unit::TestCase
152
181
  @method << "a = 1"
153
182
  @method << "2b" # syntax error, unexpected tIDENTIFIER, expecting $end
154
183
  @method << "a += 1"
155
- assert_equal "def test_1\n a = 1\n assert_equal(2, a += 1)\n end", @method.text
184
+ assert_equal " def test_1\n a = 1\n assert_equal(2, a += 1)\n end\n\n", @method.text
156
185
  end
157
186
 
158
187
  def test_purge_unexpected_identifiers
159
188
  @method << "a += 1" # undefined method `+' for nil:NilClass
160
189
  @method << "a = 1"
161
190
  @method << "a += 1"
162
- assert_equal "def test_1\n a = 1\n assert_equal(2, a += 1)\n end", @method.text
191
+ assert_equal " def test_1\n a = 1\n assert_equal(2, a += 1)\n end\n\n", @method.text
163
192
  end
164
193
 
165
194
  def test_text_require
166
195
  @method << "require 'rubygems'"
167
196
  @method << "require'active_support'"
168
197
  @method << "'CamelCase'.underscore"
169
- assert_equal "def test_1\n require 'rubygems'\n require'active_support'\n assert_equal(\"camel_case\", 'CamelCase'.underscore)\n end", @method.text
198
+ assert_equal " def test_1\n require 'rubygems'\n require'active_support'\n assert_equal(\"camel_case\", 'CamelCase'.underscore)\n end\n\n", @method.text
170
199
  end
171
200
 
172
201
  def test_expected_boolean
@@ -236,6 +265,13 @@ class TestUnitMethodTest < Test::Unit::TestCase
236
265
  assert_equal("assert_equal([1], #{line})", @method.assertion(0))
237
266
  end
238
267
 
268
+ def test_text_def_method_single_line
269
+ @method << "def timestwo(i); i * 2; end"
270
+ line = "timestwo(2)"
271
+ @method << line
272
+ assert_equal(" def timestwo(i); i * 2; end\n\n def test_1\n assert_equal(4, #{line})\n end\n\n", @method.text)
273
+ end
274
+
239
275
  def test_assertion_illegal_input
240
276
  line = "badtext"
241
277
  @method << line
@@ -244,29 +280,39 @@ class TestUnitMethodTest < Test::Unit::TestCase
244
280
 
245
281
  def test_text
246
282
  @method << "1 + 1"
247
- assert_equal "def test_1\n assert_equal(2, 1 + 1)\n end", @method.text
283
+ assert_equal " def test_1\n assert_equal(2, 1 + 1)\n end\n\n", @method.text
248
284
  end
249
285
 
286
+ def test_text_variable_assignment_only
287
+ line = "a = 1"
288
+ @method << line
289
+ assert_equal " def test_1\n #{line}\n end\n\n", @method.text
290
+ end
250
291
 
292
+ def test_extract_definitions_class_multiline_empty
293
+ line = "class MyClass"
294
+ line2 = "end"
295
+ line3 = "MyClass.new"
296
+ @method << line
297
+ @method << line2
298
+ @method << line3
299
+ assert_equal " #{line}\n #{line2}\n\n def test_1\n assert_not_equal((#{line3}), #{line3})\n end\n\n", @method.text
300
+ end
301
+
302
+ def test_extract_definitions_class_with_method
303
+ @method << "class MyClass"
304
+ @method << "def name"
305
+ @method << "\"classy\""
306
+ @method << "end"
307
+ @method << "end"
308
+ @method << "MyClass.new.name"
309
+ assert_equal " class MyClass\n def name\n \"classy\"\n end\n end\n\n def test_1\n assert_equal(\"classy\", MyClass.new.name)\n end\n\n", @method.text
310
+ end
251
311
 
252
312
  def test_text_bad_input_constant
253
313
  @method << "BADCONSTANT"
254
314
  assert_nil @method.text
255
315
  end
256
-
257
-
258
-
259
-
260
-
261
-
262
-
263
-
264
-
265
-
266
-
267
-
268
-
269
-
270
316
  # non-project tests (scratchpad)
271
317
 
272
318
  def test_eval_scope
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: butterfly_net
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Smith
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-02-21 00:00:00 -07:00
12
+ date: 2010-02-24 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -25,11 +25,14 @@ files:
25
25
  - README.md
26
26
  - lib/butterfly_net/commands.rb
27
27
  - lib/butterfly_net/file_writer.rb
28
+ - lib/butterfly_net/rails_test_unit_adapter.rb
29
+ - lib/butterfly_net/readline_reader.rb
30
+ - lib/butterfly_net/test_case.rb
28
31
  - lib/butterfly_net/test_unit_adapter.rb
29
32
  - lib/butterfly_net/test_unit_method.rb
30
33
  - lib/butterfly_net.rb
31
34
  - test/butterfly_net_tests.rb
32
- - test/test_unit_adapter_test.rb
35
+ - test/test_case_test.rb
33
36
  - test/test_unit_method_test.rb
34
37
  has_rdoc: true
35
38
  homepage: http://www.butterflynet.org