butterfly_net 0.0.3 → 0.0.4

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/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