Class: Wrong::Chunk

Inherits:
Object
  • Object
show all
Defined in:
lib/wrong/chunk.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file, line_number, block = nil) ⇒ Chunk

line parameter is 1-based



39
40
41
42
43
# File 'lib/wrong/chunk.rb', line 39

def initialize(file, line_number, block = nil)
  @file = file
  @line_number = line_number.to_i
  @block = block
end

Instance Attribute Details

#blockObject (readonly)

Returns the value of attribute block.



36
37
38
# File 'lib/wrong/chunk.rb', line 36

def block
  @block
end

#fileObject (readonly)

Returns the value of attribute file.



36
37
38
# File 'lib/wrong/chunk.rb', line 36

def file
  @file
end

#line_numberObject (readonly)

Returns the value of attribute line_number.



36
37
38
# File 'lib/wrong/chunk.rb', line 36

def line_number
  @line_number
end

Class Method Details

.from_block(block, depth = 0) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/wrong/chunk.rb', line 21

def self.from_block(block, depth = 0)

  as_proc = block.to_proc
  file, line =
          if as_proc.respond_to? :source_location
            # in Ruby 1.9, or with Sourcify, it reads the source location from the block
            as_proc.source_location
          else
            # in Ruby 1.8, it reads the source location from the call stack
            caller[depth].split(":")
          end

  new(file, line, block)
end

.read_here_or_higher(file, dir = ".") ⇒ Object



79
80
81
82
83
84
85
86
87
88
# File 'lib/wrong/chunk.rb', line 79

def self.read_here_or_higher(file, dir = ".")
  File.read "#{dir}/#{file}"
rescue Errno::ENOENT, Errno::EACCES => e
  # we may be in a chdir underneath where the file is, so move up one level and try again
  parent = "#{dir}/..".gsub(/^(\.\/)*/, '')
  if File.expand_path(dir) == File.expand_path(parent)
    raise Errno::ENOENT, "couldn't find #{file}"
  end
  read_here_or_higher(file, parent)
end

Instance Method Details

#build_sexpObject



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/wrong/chunk.rb', line 57

def build_sexp
  sexp = begin
    unless @block.nil? or @block.is_a?(String) or !Object.const_defined?(:Sourcify)
      # first try sourcify
      @block.to_sexp[3] # the [3] is to strip out the "proc {" sourcify adds to everything
    end
  rescue ::Sourcify::MultipleMatchingProcsPerLineError, Racc::ParseError, Errno::ENOENT => e
    # fall through
  end

  # next try glomming
  sexp ||= glom(if @file == "(irb)"
                  IRB.CurrentContext.all_lines
                else
                  read_source_file(@file)
                end)
end

#claimObject

The claim is the part of the assertion inside the curly braces. E.g. for “assert { x == 5 }” the claim is “x == 5”



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/wrong/chunk.rb', line 115

def claim
  sexp()

  if @sexp.nil?
    raise "Could not parse #{location}"
  else
    assertion = @sexp.assertion
    statement = assertion && assertion[3]
    if statement.nil?
      @sexp
#          raise "Could not find assertion in #{location}\n\t#{@chunk.strip}\n\t#{@sexp}"
    else
      statement
    end
  end
end

#codeObject



132
133
134
135
136
137
138
# File 'lib/wrong/chunk.rb', line 132

def code
  self.claim.to_ruby
rescue => e
  # note: this is untested; it's to recover from when we can't locate the code
  message = "Failed assertion at #{file}:#{line_number} [couldn't retrieve source code due to #{e.inspect}]"
  raise failure_class.new(message)
end

#detailsObject



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/wrong/chunk.rb', line 170

def details
  require "wrong/rainbow" if Wrong.config[:color]
  s = ""
  parts = self.parts
  parts.shift # remove the first part, since it's the same as the code

  details = []

  if parts.size > 0
    parts.each do |part|
      begin
        value = eval(part, block.binding)
        unless part == value.inspect # this skips literals or tautologies
          if part =~ /\n/m
            part.gsub!(/\n/, newline(2))
            part += newline(3)
          end
          value = indent_all(3, value.inspect)
          if Wrong.config[:color]
            part = part.color(:blue)
            value = value.color(:magenta)
          end
          details << indent(2, part, " is ", value)
        end
      rescue Exception => e
        raises = "raises #{e.class}"
        if Wrong.config[:color]
          part = part.color(:blue)
          raises = raises.bold.color(:red)
        end
        formatted_exeption = if e.message and e.message != e.class.to_s
                               indent(2, part, " ", raises, ": ", indent_all(3, e.message))
                             else
                               indent(2, part, " ", raises)
                             end
        details << formatted_exeption
      end
    end
  end

  details.uniq!
  if details.empty?
    ""
  else
    "\n" + details.join("\n") + "\n"
  end

end

#glom(source) ⇒ Object

Algorithm:

  • try to parse the starting line

  • if it parses OK, then we’re done!

  • if not, then glom the next line and try again

  • repeat until it parses or we’re out of lines



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/wrong/chunk.rb', line 95

def glom(source)
  lines = source.split("\n")
  @parser ||= RubyParser.new
  @chunk = nil
  c = 0
  sexp = nil
  while sexp.nil? && line_index + c < lines.size
    begin
      @chunk = lines[line_index..line_index+c].join("\n")
      sexp = @parser.parse(@chunk)
    rescue Racc::ParseError => e
      # loop and try again
      c += 1
    end
  end
  sexp
end

#line_indexObject



45
46
47
# File 'lib/wrong/chunk.rb', line 45

def line_index
  @line_number - 1
end

#locationObject



49
50
51
# File 'lib/wrong/chunk.rb', line 49

def location
  "#{@file}:#{@line_number}"
end

#parts(sexp = nil) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/wrong/chunk.rb', line 140

def parts(sexp = nil)
  if sexp.nil?
    parts(self.claim).compact.uniq
  else
    # todo: extract some of this into Sexp
    parts_list = []
    begin
      unless sexp.first == :arglist
        code = sexp.to_ruby.strip
        parts_list << code unless code == "" || parts_list.include?(code)
      end
    rescue => e
      puts "#{e.class}: #{e.message}"
      puts e.backtrace.join("\n")
    end

    if sexp.first == :iter
      sexp.delete_at(1) # remove the method-call-sans-block subnode
    end

    sexp.each do |sub|
      if sub.is_a?(Sexp)
        parts_list += parts(sub)
      end
    end

    parts_list
  end
end

#read_source_file(file) ⇒ Object



75
76
77
# File 'lib/wrong/chunk.rb', line 75

def read_source_file(file)
  Chunk.read_here_or_higher(file)
end

#sexpObject



53
54
55
# File 'lib/wrong/chunk.rb', line 53

def sexp
  @sexp ||= build_sexp
end