fit 1.1 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/CHANGELOG +27 -0
  2. data/{README.txt → README.rdoc} +28 -9
  3. data/Rakefile +44 -4
  4. data/bin/fit +29 -3
  5. data/doc/book/TestChatServer.html +207 -0
  6. data/doc/book/TestDiscount.html +178 -0
  7. data/doc/book/TestDiscountGroup.html +223 -0
  8. data/doc/book/TestDiscountMoney.html +179 -0
  9. data/doc/book/TestLateHours.html +245 -0
  10. data/doc/bugs/ColumnFixtureFollowedByActionFixture.html +316 -0
  11. data/doc/examples/MusicExampleWithErrors.html +1 -1
  12. data/doc/spec/annotation.html +3634 -3833
  13. data/doc/spec/index.html +1043 -947
  14. data/doc/spec/parse-windows-1252.html +3806 -0
  15. data/doc/spec/parse.html +3806 -3094
  16. data/doc/spec/ui.html +1537 -0
  17. data/lib/eg/all_files.rb +1 -1
  18. data/lib/eg/book/calculate_discount.rb +25 -0
  19. data/lib/eg/book/calculate_discount_money.rb +60 -0
  20. data/lib/eg/book/chat_server_actions.rb +85 -0
  21. data/lib/eg/book/discount_group_ordered_list.rb +59 -0
  22. data/lib/eg/book/rent/calculate_late_hours.rb +35 -0
  23. data/lib/eg/column_index.rb +4 -3
  24. data/lib/eg/music/display.rb +4 -2
  25. data/lib/eg/music/music.rb +4 -2
  26. data/lib/fat/command_line_fixture.rb +33 -0
  27. data/lib/fat/html_to_text_fixture.rb +3 -3
  28. data/lib/fit/column_fixture.rb +11 -7
  29. data/lib/fit/file_runner.rb +65 -10
  30. data/lib/fit/fixture.rb +10 -4
  31. data/lib/fit/fixture_loader.rb +29 -19
  32. data/lib/fit/parse.rb +46 -12
  33. data/lib/fit/type_adapter.rb +8 -3
  34. data/lib/fit/version.rb +26 -0
  35. data/lib/fitlibrary/comment_fixture.rb +12 -0
  36. data/lib/fitlibrary/spec/specify_fixture.rb +154 -0
  37. data/lib/fitlibrary/specify/column_fixture_under_test.rb +17 -0
  38. data/lib/fitlibrary/specify/column_fixture_under_test_with_args.rb +17 -0
  39. data/lib/fittask.rb +43 -47
  40. data/test/all_tests.rb +2 -1
  41. data/test/column_fixture_test.rb +26 -0
  42. data/test/file_runner_test.rb +2 -7
  43. data/test/fixture_loader_test.rb +16 -3
  44. data/test/parse_test.rb +18 -3
  45. data/test/type_adapter_test.rb +58 -44
  46. metadata +180 -156
@@ -18,7 +18,7 @@ module Eg
18
18
  cell.add_to_body Fit::Fixture.gray('no match')
19
19
  end
20
20
  end
21
- def expand pattern; Dir[pattern]; end
21
+ def expand pattern; Dir[pattern].sort; end
22
22
  protected
23
23
  def do_row_files row, files; do_files row, files; end
24
24
  def do_files row, files
@@ -0,0 +1,25 @@
1
+ require 'fit/column_fixture'
2
+
3
+ module Eg
4
+ module Book
5
+
6
+ class CalculateDiscount < Fit::ColumnFixture
7
+ attr_writer :amount
8
+ def initialize
9
+ @application = Discount.new
10
+ end
11
+ def discount
12
+ @application.get_discount @amount
13
+ end
14
+ end
15
+
16
+ class Discount
17
+ # A 5 percent discount is provided whenever the
18
+ # total purchase is greater than $1,000
19
+ def get_discount amount
20
+ (amount >= 1000) ? (amount / 100.0) * 5 : 0.0
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,60 @@
1
+ require 'fit/column_fixture'
2
+
3
+ module Eg
4
+ module Book
5
+
6
+ class MoneyDiscount
7
+ # A 5 percent discount is provided whenever the
8
+ # total purchase is greater than $1,000
9
+ def get_discount amount
10
+ (amount >= Money.new(1000)) ? (amount * 0.05) : Money.new
11
+ end
12
+ end
13
+
14
+ class Money
15
+ attr_reader :cents
16
+ def initialize amount = 0
17
+ @cents = (amount * 100 + 0.5).to_i
18
+ end
19
+ def >= money
20
+ @cents >= money.cents
21
+ end
22
+ def * times
23
+ Money.new(@cents / 100.0 * times)
24
+ end
25
+ def == money
26
+ money.class == Money and @cents == money.cents
27
+ end
28
+ def Money.parse string
29
+ raise Exception.new('Invalid money value') unless string =~ /^\$/
30
+ dot = string.index '.'
31
+ raise Exception.new('Invalid money value') if dot.nil? or dot != string.size - 3
32
+ money = string[1..-1].to_f
33
+ Money.new money
34
+ end
35
+ def to_s
36
+ cent_string = "#{@cents % 100}"
37
+ cent_string += '0' if cent_string.size == 1
38
+ "$#{cents / 100}.#{cent_string}"
39
+ end
40
+ end
41
+
42
+ # Fixture
43
+
44
+ class CalculateDiscountMoney < Fit::ColumnFixture
45
+ attr_writer :amount
46
+ def initialize
47
+ @application = MoneyDiscount.new
48
+ end
49
+ def discount
50
+ @application.get_discount @amount
51
+ end
52
+ @@metadata = { 'amount' => Money, 'discount()' => Money }
53
+ def parse string, klass
54
+ return Money.parse(string) if klass == Money
55
+ super
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,85 @@
1
+ require 'fit/fixture'
2
+
3
+ module Eg
4
+ module Book
5
+
6
+ class ChatServerActions < Fit::Fixture
7
+ def initialize
8
+ @chat = ChatRoom.new
9
+ end
10
+ def user user_name
11
+ @user_name = user_name
12
+ end
13
+ def connect
14
+ @chat.connect_user @user_name
15
+ end
16
+ def room room_name
17
+ @room_name = room_name
18
+ end
19
+ def new_room
20
+ @chat.user_creates_room @user_name, @room_name
21
+ end
22
+ def enter_room
23
+ @chat.user_enters_room @user_name, @room_name
24
+ end
25
+ def occupant_count
26
+ @chat.occupants @room_name
27
+ end
28
+ end
29
+
30
+ class ChatRoom
31
+ def initialize
32
+ @users = {}
33
+ @rooms = {}
34
+ end
35
+ def connect_user user_name
36
+ return false unless @users[user_name].nil?
37
+ @users[user_name] = User.new user_name
38
+ return true
39
+ end
40
+ def user_creates_room user_name, room_name
41
+ user = @users[user_name]
42
+ raise Exception.new("Unknown user name: #{user_name}") unless user
43
+ raise Exception.new("Duplicate room name: #{room_name}") if @rooms.include? room_name
44
+ @rooms[room_name] = Room.new room_name, user, self
45
+ end
46
+ def user_enters_room user_name, room_name
47
+ user = @users[user_name]
48
+ room = @rooms[room_name]
49
+ return false if user.nil? or room.nil?
50
+ room.add user
51
+ return true
52
+ end
53
+ def occupants room_name
54
+ room = @rooms[room_name]
55
+ raise Exception.new("Unknown room: #{room_name}") unless room
56
+ room.occupant_count
57
+ end
58
+ end
59
+
60
+ class User
61
+ attr_accessor :name
62
+ def initialize name
63
+ @name = name
64
+ end
65
+ end
66
+
67
+ require 'set'
68
+
69
+ class Room
70
+ def initialize room_name, owner, chat
71
+ @name = room_name
72
+ @owner = owner
73
+ @chat = chat
74
+ @users = Set.new
75
+ end
76
+ def add user
77
+ @users.add user
78
+ end
79
+ def occupant_count
80
+ @users.size
81
+ end
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,59 @@
1
+ require 'fit/row_fixture'
2
+
3
+ module Eg
4
+ module Book
5
+
6
+ class DiscountGroupOrderedList < Fit::RowFixture
7
+ def query
8
+ groups = DiscountGroup.get_elements
9
+ ordered_groups = []
10
+ groups.each_with_index { |e, i|
11
+ group = OrderedDiscountGroup.new(i + 1, e.future_value,
12
+ e.max_owing, e.min_purchase, e.discount_percent)
13
+ ordered_groups << group
14
+ }
15
+ ordered_groups
16
+ end
17
+ def get_target_class
18
+ OrderedDiscountGroup
19
+ end
20
+ end
21
+
22
+ # System under test
23
+
24
+ class OrderedDiscountGroup
25
+ attr_reader :order, :future_value
26
+ attr_reader :max_owing, :min_purchase, :discount_percent
27
+ def initialize order, future_value, max_owing, min_purchase, discount_percent
28
+ @order = order
29
+ @future_value = future_value
30
+ @max_owing = max_owing
31
+ @min_purchase = min_purchase
32
+ @discount_percent = discount_percent
33
+ end
34
+ @@metadata = { 'order' => Fixnum, 'future_value' => String,
35
+ 'max_owing' => Float, 'min_purchase' => Float, 'discount_percent' => Float }
36
+ def OrderedDiscountGroup.metadata; @@metadata; end
37
+ end
38
+
39
+ class DiscountGroup
40
+ attr_reader :future_value
41
+ attr_reader :max_owing, :min_purchase, :discount_percent
42
+ def initialize future_value, max_owing, min_purchase, discount_percent
43
+ @future_value = future_value
44
+ @max_owing = max_owing
45
+ @min_purchase = min_purchase
46
+ @discount_percent = discount_percent
47
+ end
48
+ def DiscountGroup.get_elements
49
+ [ DiscountGroup.new('low', 0, 0, 0),
50
+ DiscountGroup.new('low', 0, 2000, 3),
51
+ DiscountGroup.new('medium', 500, 600, 3),
52
+ DiscountGroup.new('medium', 0, 500, 5),
53
+ DiscountGroup.new('high', 2000, 2000, 10)
54
+ ]
55
+ end
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,35 @@
1
+ require 'fit/column_fixture'
2
+
3
+ module Eg
4
+ module Book
5
+
6
+ module Rent
7
+ class CalculateLateHours < Fit::ColumnFixture
8
+ attr_writer :hours_late, :grace, :high_demand
9
+ attr_writer :count_grace
10
+ def count_grace= string
11
+ @count_grace = { 'yes' => true, 'no' => false }[string]
12
+ end
13
+ def extra_hours
14
+ late_returns = LateReturns.new @count_grace
15
+ late_returns.extra_hours @hours_late, @grace, @high_demand
16
+ end
17
+ end
18
+ end
19
+
20
+ # System under test
21
+
22
+ class LateReturns
23
+ def initialize count_grace
24
+ @count_grace = count_grace
25
+ end
26
+ def extra_hours hours_late, grace, high_demand
27
+ return 0 if hours_late < 1
28
+ hours = @count_grace ? hours_late : (hours_late - grace)
29
+ return 0 if hours.zero?
30
+ high_demand + hours
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -31,8 +31,9 @@ module Eg
31
31
  # ...then find the columns in these classes
32
32
  columns = []
33
33
  names.each do |name|
34
- obj = Fit::FixtureLoader.new.find_class(name).new
35
- attributes = obj.methods - Object.new.methods
34
+ obj = @@loader.find_class(name).new
35
+ # Ruby 1.9 returns method names as symbols
36
+ attributes = (obj.methods - Object.new.methods).collect {|a| a.to_s}
36
37
  setters = attributes.dup.delete_if {|a| a[-1..-1] != "="}
37
38
  attributes -= setters
38
39
  setters.each do |s|
@@ -58,7 +59,7 @@ module Eg
58
59
  end
59
60
 
60
61
  def parse_class name
61
- Fit::FixtureLoader.new.find_class(name)
62
+ @@loader.find_class(name)
62
63
  end
63
64
 
64
65
  # Helper class
@@ -3,7 +3,7 @@
3
3
 
4
4
  require 'fit/row_fixture'
5
5
 
6
- require 'parsedate'
6
+ require 'date'
7
7
 
8
8
  module Eg
9
9
  module Music
@@ -13,7 +13,9 @@ module Eg
13
13
  def query; MusicLibrary.display_contents; end
14
14
  def parse string, klass
15
15
  if klass == Time
16
- Time.gm(*ParseDate.parsedate(string).compact)
16
+ d = Date._parse string
17
+ d_array = [d[:year], d[:mon], d[:mday], d[:hour], d[:min], d[:sec], d[:zone], d[:wday]]
18
+ Time.gm(*d_array.compact)
17
19
  else
18
20
  super
19
21
  end
@@ -1,7 +1,7 @@
1
1
  # Copyright (c) 2002 Cunningham & Cunningham, Inc.
2
2
  # Released under the terms of the GNU General Public License version 2 or later.
3
3
 
4
- require 'parsedate'
4
+ require 'date'
5
5
 
6
6
  module Eg
7
7
  module Music
@@ -48,7 +48,9 @@ module Eg
48
48
  m.track_number = data[6].to_i
49
49
  m.track_count = data[7].to_i
50
50
  m.year = data[8].to_i
51
- m.date = Time.gm(*ParseDate.parsedate(data[9]).compact)
51
+ d = Date._parse data[9]
52
+ d_array = [d[:year], d[:mon], d[:mday], d[:hour], d[:min], d[:sec], d[:zone], d[:wday]]
53
+ m.date = Time.gm(*d_array.compact)
52
54
  m
53
55
  end
54
56
 
@@ -0,0 +1,33 @@
1
+ # Copyright (c) 2002 Cunningham & Cunningham, Inc.
2
+ # Released under the terms of the GNU General Public License version 2 or later.
3
+
4
+ require 'fit/column_fixture'
5
+
6
+ require 'optparse'
7
+
8
+ module Fat
9
+
10
+ # This is really testing OptionParser rather than some
11
+ # new RubyFIT code, but it's nice to have anyway just
12
+ # in case the Ruby standard library behaviour should
13
+ # change between subsequent versions.
14
+ class CommandLineFixture < Fit::ColumnFixture
15
+ attr_accessor :command_line
16
+ attr_reader :encoding
17
+ def initialize
18
+ @options = OptionParser.new
19
+ @options.on('--encoding=ENC') { |enc| @encoding = enc }
20
+ end
21
+ def input_file
22
+ args()[0]
23
+ end
24
+ def output_file
25
+ args()[1]
26
+ end
27
+ def args
28
+ @encoding = 'Implementation-specific'
29
+ @options.parse(command_line.split)
30
+ end
31
+ end
32
+
33
+ end
@@ -9,12 +9,12 @@ module Fat
9
9
  @@metadata = {'html' => String}
10
10
  attr_writer :html
11
11
  def text
12
- html = @html.gsub(/\\u00a0/, "\240")
12
+ html = @html.gsub(/\\u00a0/, [0x00a0].pack('U'))
13
13
  escape_ascii(Fit::Parse.html_to_text(html))
14
14
  end
15
15
  def escape_ascii text
16
- text.gsub("\x0a", "\\n").gsub("\x0d", "\\r").gsub("\xa0", " ")
16
+ text.gsub([0x0a].pack('U'), "\\n").gsub([0x0d].pack('U'), "\\r").gsub([0xa0].pack('U'), " ")
17
17
  end
18
18
  end
19
19
 
20
- end
20
+ end
@@ -79,13 +79,7 @@ module Fit
79
79
  if name.empty?
80
80
  @column_bindings << nil
81
81
  else
82
- suffix = ''
83
- if name =~ /\(\)$/
84
- suffix = '()'
85
- name = name[0...-2]
86
- end
87
- name = name.split(/([a-zA-Z][^A-Z]+)/).delete_if {|e| e.empty?}.collect {|e| e.downcase}.join('_')
88
- name += suffix
82
+ name = camel name
89
83
  adapter = TypeAdapter.for(self, name)
90
84
  adapter.type = get_target_class.metadata[name]
91
85
  @column_bindings << adapter
@@ -97,6 +91,16 @@ module Fit
97
91
  end
98
92
  end
99
93
 
94
+ def camel name
95
+ suffix = ''
96
+ if name =~ /\(\)$/
97
+ suffix = '()'
98
+ name = name[0...-2]
99
+ end
100
+ name = name.split(/([a-zA-Z][^A-Z]+)/).delete_if {|e| e.empty?}.collect {|e| e.strip.downcase}.join('_')
101
+ name += suffix
102
+ end
103
+
100
104
  def get_target_class; self.class; end
101
105
 
102
106
  end
@@ -5,17 +5,23 @@ require 'fit/fixture'
5
5
  require 'fit/parse'
6
6
 
7
7
  require 'fileutils' # for report generation
8
+ require 'iconv' # for I/O encoding
9
+ require 'stringio' # to help fix any possibly invalid byte sequence
8
10
 
9
11
  module Fit
10
12
 
11
13
  class FileRunner
12
14
  attr_accessor :input, :tables, :fixture, :output
13
15
 
16
+ DEFAULT_ENCODING = 'UTF-8'
17
+
14
18
  def initialize
15
19
  @fixture = Fixture.new
20
+ @encoding = DEFAULT_ENCODING
16
21
  end
17
22
 
18
- def run args
23
+ def run args, opts=nil
24
+ @encoding = opts[:encoding].upcase unless opts.nil?
19
25
  process_args args
20
26
  process
21
27
  $stderr.puts @fixture.totals
@@ -23,20 +29,58 @@ module Fit
23
29
  end
24
30
 
25
31
  def process_args args
26
- error "Usage: #{File.basename($0)} input_file output_file" unless args.size == 2
27
-
32
+ error "no input file" if args[0].nil?
28
33
  input_name = File.expand_path args[0]
29
- input_file = File.open input_name
30
- output_name = File.expand_path args[1]
31
- FileUtils.mkpath File.dirname(output_name)
32
- @output = File.open output_name, 'w'
34
+ begin
35
+ if input_name.respond_to? :encoding
36
+ input_file = File.open input_name, "r:#@encoding"
37
+ else
38
+ input_file = File.open input_name
39
+ end
40
+ rescue Errno::ENOENT
41
+ error "#{input_name}: file not found"
42
+ end
43
+ output_name = check_output_file args[1]
44
+ create_output_file output_name
33
45
  @fixture.summary['input file'] = input_name
34
46
  @fixture.summary['input update'] = input_file.mtime.to_s
35
47
  @fixture.summary['output file'] = output_name
36
- @input = input_file.read
48
+ unless input_name.respond_to? :encoding
49
+ read_file_with_encoding input_file
50
+ else
51
+ read_file input_file
52
+ end
37
53
  input_file.close
38
54
  end
39
55
 
56
+ def check_output_file arg
57
+ error "no output file" if arg.nil?
58
+ output_name = File.expand_path arg
59
+ end
60
+
61
+ def create_output_file output_name
62
+ FileUtils.mkpath File.dirname(output_name)
63
+ if output_name.respond_to? :encoding
64
+ @output = File.open output_name, "w:#@encoding"
65
+ else
66
+ @output = File.open output_name, 'w'
67
+ end
68
+ end
69
+
70
+ def read_file_with_encoding input_file
71
+ if @encoding == DEFAULT_ENCODING
72
+ @input = input_file.read
73
+ else
74
+ conv = Iconv.new DEFAULT_ENCODING, @encoding
75
+ @input = conv.iconv(input_file.read)
76
+ @input << conv.iconv(nil)
77
+ end
78
+ end
79
+
80
+ def read_file input_file
81
+ @input = (@encoding == DEFAULT_ENCODING) ? input_file.read : input_file.read.encode(DEFAULT_ENCODING)
82
+ end
83
+
40
84
  def process
41
85
  begin
42
86
  unless @input.index('<wiki>').nil?
@@ -49,7 +93,18 @@ module Fit
49
93
  rescue Exception => e
50
94
  exception e
51
95
  end
52
- @tables.print @output
96
+ if @encoding == DEFAULT_ENCODING
97
+ @tables.print @output
98
+ else
99
+ buffer = StringIO.new
100
+ conv = Iconv.new "#{DEFAULT_ENCODING}//IGNORE", DEFAULT_ENCODING
101
+ @tables.print buffer, conv
102
+ buffer.print conv.iconv(nil)
103
+ conv = Iconv.new "#@encoding//IGNORE", DEFAULT_ENCODING
104
+ @output.print conv.iconv(buffer.string)
105
+ @output << conv.iconv(nil)
106
+ buffer.close
107
+ end
53
108
  @output.close
54
109
  end
55
110
 
@@ -59,7 +114,7 @@ module Fit
59
114
  end
60
115
 
61
116
  def error msg
62
- $stderr.puts msg
117
+ $stderr.puts "#{File.basename($0)}: #{msg}"
63
118
  exit -1
64
119
  end
65
120