mmullis-flay 1.4.2

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.
@@ -0,0 +1,80 @@
1
+ === 1.4.2 / 2011-02-18
2
+
3
+ * 2 bug fixes:
4
+
5
+ * Added flay require in flay_task
6
+ * Switched to minitest. (doh)
7
+
8
+ === 1.4.1 / 2010-09-01
9
+
10
+ * 2 minor enhancements:
11
+
12
+ * Added extra error handling for ERB flay to deal with tons of bad ERB
13
+ * Skip plugin if another version already loaded (eg local vs gem).
14
+
15
+ * 1 bug fix:
16
+
17
+ * Fixed all tests that were having problems on 1.9 due to unstable hashes
18
+
19
+ === 1.4.0 / 2009-08-14
20
+
21
+ * 4 minor enhancements:
22
+
23
+ * Pushed Sexp#mass up to sexp_processor.
24
+ * Removed #similarity #compare_to, #intersection, #triangle, and other cruft.
25
+ * Renamed all_subhashes to all_structural_subhashes.
26
+ * Renamed fuzzy_hash to structural_hash.
27
+
28
+ === 1.3.0 / 2009-06-23
29
+
30
+ * 5 minor enhancements:
31
+
32
+ * Added --summary to display flay scores per file.
33
+ * Added --verbose to display processing progress.
34
+ * Protect against syntax errors in bad code and continue flaying.
35
+ * Removed fuzzy matching. Never got it to feel right. Slow. Broken on 1.9
36
+ * Renamed --verbose to --diff.
37
+
38
+ === 1.2.1 / 2009-03-16
39
+
40
+ * 3 minor enhancements:
41
+
42
+ * Added gauntlet_flay.rb
43
+ * Cached value of plugins loaded.
44
+ * Refactored and separated analysis phase from process phase
45
+
46
+ * 1 bug fix:
47
+
48
+ * Added bin dir to default dirs list in FlayTask
49
+
50
+ === 1.2.0 / 2009-03-09
51
+
52
+ * 2 major enhancements:
53
+
54
+ * Added flay_task.rb
55
+ * Added plugin system (any flay_(c,java,js,etc).rb files).
56
+
57
+ * 4 minor enhancements:
58
+
59
+ * Added expand_dirs_to_files and made dirs valid arguments.
60
+ * Added flay_erb.rb plugin.
61
+ * Added optparse option processing.
62
+ * Refactored to make using w/in rake and other CI systems clean and easy.
63
+
64
+ === 1.1.0 / 2009-01-20
65
+
66
+ * 7 minor enhancement:
67
+
68
+ * Added -v verbose mode to print out N-way diff of the detected code.
69
+ * Added identical node scoring and reporting.
70
+ * Added the start of copy/paste+edit detection, not even close yet.
71
+ * Added more tests.
72
+ * Added rcov tasks
73
+ * Clarified output a bit
74
+ * Refactored process_sexps to make doing other languages/systems easier.
75
+
76
+ === 1.0.0 / 2008-11-06
77
+
78
+ * 1 major enhancement
79
+
80
+ * Birthday!
@@ -0,0 +1,10 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ bin/flay
6
+ lib/flay.rb
7
+ lib/flay_erb.rb
8
+ lib/flay_task.rb
9
+ lib/gauntlet_flay.rb
10
+ test/test_flay.rb
@@ -0,0 +1,102 @@
1
+ = flay
2
+
3
+ * http://ruby.sadi.st/
4
+ * http://rubyforge.org/projects/seattlerb
5
+
6
+ == DESCRIPTION:
7
+
8
+ Flay analyzes code for structural similarities. Differences in literal
9
+ values, variable, class, method names, whitespace, programming style,
10
+ braces vs do/end, etc are all ignored. Making this totally rad.
11
+
12
+ == FEATURES/PROBLEMS:
13
+
14
+ * Plugin system allows other languages to be flayed.
15
+ * Ships with .rb and .erb. javascript and others will be available separately.
16
+ * Includes FlayTask for Rakefiles.
17
+ * Differences in literal values, variable, class, and method names are ignored.
18
+ * Differences in whitespace, programming style, braces vs do/end, etc are ignored.
19
+ * Works across files.
20
+ * Reports differences at any level of code.
21
+ * Totally rad.
22
+ * Adds a score multiplier to identical nodes.
23
+ * Run verbose to see an N-way diff of the code.
24
+
25
+ == TODO:
26
+
27
+ * Editor integration (emacs, textmate, other contributions welcome).
28
+ * Score sequence fragments (a;b;c;d;e) vs (b;c;d) etc.
29
+
30
+ == SYNOPSIS:
31
+
32
+ % flay -v ~/Work/svn/ruby/ruby_1_8/lib/cgi.rb
33
+ Processing /Users/ryan/Work/svn/ruby/ruby_1_8/lib/cgi.rb...
34
+
35
+ Matches found in :defn (mass = 184)
36
+ A: /Users/ryan/Work/svn/ruby/ruby_1_8/lib/cgi.rb:1470
37
+ B: /Users/ryan/Work/svn/ruby/ruby_1_8/lib/cgi.rb:1925
38
+
39
+ A: def checkbox_group(name = "", *values)
40
+ B: def radio_group(name = "", *values)
41
+ if name.kind_of?(Hash) then
42
+ values = name["VALUES"]
43
+ name = name["NAME"]
44
+ end
45
+ values.collect do |value|
46
+ if value.kind_of?(String) then
47
+ A: (checkbox(name, value) + value)
48
+ B: (radio_button(name, value) + value)
49
+ else
50
+ if (value[(value.size - 1)] == true) then
51
+ A: (checkbox(name, value[0], true) + value[(value.size - 2)])
52
+ B: (radio_button(name, value[0], true) + value[(value.size - 2)])
53
+ else
54
+ A: (checkbox(name, value[0]) + value[(value.size - 1)])
55
+ B: (radio_button(name, value[0]) + value[(value.size - 1)])
56
+ end
57
+ end
58
+ end.to_s
59
+ end
60
+
61
+ IDENTICAL Matches found in :for (mass*2 = 144)
62
+ A: /Users/ryan/Work/svn/ruby/ruby_1_8/lib/cgi.rb:2160
63
+ B: /Users/ryan/Work/svn/ruby/ruby_1_8/lib/cgi.rb:2217
64
+
65
+ for element in ["HTML", "BODY", "P", "DT", "DD", "LI", "OPTION", "THEAD", "TFOOT", "TBODY", "COLGROUP", "TR", "TH", "TD", "HEAD"] do
66
+ methods = (methods + ((" def #{element.downcase}(attributes = {})\n" + nO_element_def(element)) + " end\n"))
67
+ end
68
+ ...
69
+
70
+ == REQUIREMENTS:
71
+
72
+ * ruby_parser
73
+ * sexp_processor
74
+
75
+ == INSTALL:
76
+
77
+ * sudo gem install flay
78
+
79
+ == LICENSE:
80
+
81
+ (The MIT License)
82
+
83
+ Copyright (c) 2008-2009 Ryan Davis, Seattle.rb
84
+
85
+ Permission is hereby granted, free of charge, to any person obtaining
86
+ a copy of this software and associated documentation files (the
87
+ 'Software'), to deal in the Software without restriction, including
88
+ without limitation the rights to use, copy, modify, merge, publish,
89
+ distribute, sublicense, and/or sell copies of the Software, and to
90
+ permit persons to whom the Software is furnished to do so, subject to
91
+ the following conditions:
92
+
93
+ The above copyright notice and this permission notice shall be
94
+ included in all copies or substantial portions of the Software.
95
+
96
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
97
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
98
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
99
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
100
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
101
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
102
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,22 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe::add_include_dirs(".",
7
+ "../../sexp_processor/dev/lib",
8
+ "../../ruby_parser/dev/lib")
9
+
10
+ Hoe.plugin :seattlerb
11
+
12
+ Hoe.spec 'flay' do
13
+ developer 'Ryan Davis', 'ryand-ruby@zenspider.com'
14
+
15
+ self.rubyforge_name = 'seattlerb'
16
+ self.flay_threshold = 250
17
+
18
+ extra_deps << ['sexp_processor', '~> 3.0']
19
+ extra_deps << ['ruby_parser', '~> 2.0']
20
+ end
21
+
22
+ # vim: syntax=ruby
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.4.2
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'flay'
4
+
5
+ flay = Flay.new Flay.parse_options
6
+
7
+ ARGV << '.' if ARGV.empty?
8
+ files = Flay.expand_dirs_to_files(*ARGV)
9
+
10
+ flay.process(*files)
11
+ flay.report
@@ -0,0 +1,326 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ $: << "../../ruby_parser/dev/lib"
4
+ $: << "../../ruby2ruby/dev/lib"
5
+
6
+ require 'optparse'
7
+ require 'rubygems'
8
+ require 'sexp_processor'
9
+ require 'ruby_parser'
10
+
11
+ class Flay
12
+ VERSION = '1.4.2'
13
+
14
+ def self.default_options
15
+ {
16
+ :diff => false,
17
+ :mass => 16,
18
+ :summary => false,
19
+ :verbose => false,
20
+ }
21
+ end
22
+
23
+ def self.parse_options
24
+ options = self.default_options
25
+
26
+ OptionParser.new do |opts|
27
+ opts.banner = 'flay [options] files_or_dirs'
28
+ opts.version = Flay::VERSION
29
+
30
+ opts.separator ""
31
+ opts.separator "Specific options:"
32
+ opts.separator ""
33
+
34
+ opts.on('-h', '--help', 'Display this help.') do
35
+ puts opts
36
+ exit
37
+ end
38
+
39
+ opts.on('-f', '--fuzzy', "DEAD: fuzzy similarities.") do
40
+ abort "--fuzzy is no longer supported. Sorry. It sucked."
41
+ end
42
+
43
+ opts.on('-m', '--mass MASS', Integer, "Sets mass threshold") do |m|
44
+ options[:mass] = m.to_i
45
+ end
46
+
47
+ opts.on('-v', '--verbose', "Verbose. Show progress processing files.") do
48
+ options[:verbose] = true
49
+ end
50
+
51
+ opts.on('-d', '--diff', "Diff Mode. Display N-Way diff for ruby.") do
52
+ options[:diff] = true
53
+ end
54
+
55
+ opts.on('-s', '--summary', "Summarize. Show flay score per file only.") do
56
+ options[:summary] = true
57
+ end
58
+
59
+ extensions = ['rb'] + Flay.load_plugins
60
+
61
+ opts.separator ""
62
+ opts.separator "Known extensions: #{extensions.join(', ')}"
63
+
64
+ begin
65
+ opts.parse!
66
+ rescue => e
67
+ abort "#{e}\n\n#{opts}"
68
+ end
69
+ end
70
+
71
+ options
72
+ end
73
+
74
+ def self.expand_dirs_to_files *dirs
75
+ extensions = ['rb'] + Flay.load_plugins
76
+
77
+ dirs.flatten.map { |p|
78
+ if File.directory? p then
79
+ Dir[File.join(p, '**', "*.{#{extensions.join(',')}}")]
80
+ else
81
+ p
82
+ end
83
+ }.flatten
84
+ end
85
+
86
+ def self.load_plugins
87
+ unless defined? @@plugins then
88
+ @@plugins = []
89
+
90
+ plugins = Gem.find_files("flay_*.rb").reject { |p| p =~ /flay_task/ }
91
+
92
+ plugins.each do |plugin|
93
+ plugin_name = File.basename(plugin, '.rb').sub(/^flay_/, '')
94
+ next if @@plugins.include? plugin_name
95
+ begin
96
+ load plugin
97
+ @@plugins << plugin_name
98
+ rescue LoadError => e
99
+ warn "error loading #{plugin.inspect}: #{e.message}. skipping..."
100
+ end
101
+ end
102
+ end
103
+ @@plugins
104
+ rescue
105
+ # ignore
106
+ end
107
+
108
+ attr_accessor :mass_threshold, :total, :identical, :masses
109
+ attr_reader :hashes, :option
110
+
111
+ def initialize option = nil
112
+ @option = option || Flay.default_options
113
+ @hashes = Hash.new { |h,k| h[k] = [] }
114
+
115
+ self.identical = {}
116
+ self.masses = {}
117
+ self.total = 0
118
+ self.mass_threshold = @option[:mass]
119
+
120
+ require 'ruby2ruby' if @option[:diff]
121
+ end
122
+
123
+ def process(*files) # TODO: rename from process - should act as SexpProcessor
124
+ files.each do |file|
125
+ warn "Processing #{file}" if option[:verbose]
126
+
127
+ ext = File.extname(file).sub(/^\./, '')
128
+ ext = "rb" if ext.nil? || ext.empty?
129
+ msg = "process_#{ext}"
130
+
131
+ unless respond_to? msg then
132
+ warn " Unknown file type: #{ext}, defaulting to ruby"
133
+ msg = "process_rb"
134
+ end
135
+
136
+ begin
137
+ sexp = begin
138
+ send msg, file
139
+ rescue => e
140
+ warn " #{e.message.strip}"
141
+ warn " skipping #{file}"
142
+ nil
143
+ end
144
+
145
+ next unless sexp
146
+
147
+ process_sexp sexp
148
+ rescue SyntaxError => e
149
+ warn " skipping #{file}: #{e.message}"
150
+ end
151
+ end
152
+
153
+ analyze
154
+ end
155
+
156
+ def analyze
157
+ self.prune
158
+
159
+ self.hashes.each do |hash,nodes|
160
+ identical[hash] = nodes[1..-1].all? { |n| n == nodes.first }
161
+ masses[hash] = nodes.first.mass * nodes.size
162
+ masses[hash] *= (nodes.size) if identical[hash]
163
+ self.total += masses[hash]
164
+ end
165
+ end
166
+
167
+ def process_rb file
168
+ RubyParser.new.process(File.read(file), file)
169
+ end
170
+
171
+ def process_sexp pt
172
+ pt.deep_each do |node|
173
+ next unless node.any? { |sub| Sexp === sub }
174
+ next if node.mass < self.mass_threshold
175
+
176
+ self.hashes[node.structural_hash] << node
177
+ end
178
+ end
179
+
180
+ def prune
181
+ # prune trees that aren't duped at all, or are too small
182
+ self.hashes.delete_if { |_,nodes| nodes.size == 1 }
183
+
184
+ # extract all subtree hashes from all nodes
185
+ all_hashes = {}
186
+ self.hashes.values.each do |nodes|
187
+ nodes.each do |node|
188
+ node.all_structural_subhashes.each do |h|
189
+ all_hashes[h] = true
190
+ end
191
+ end
192
+ end
193
+
194
+ # nuke subtrees so we show the biggest matching tree possible
195
+ self.hashes.delete_if { |h,_| all_hashes[h] }
196
+ end
197
+
198
+ def n_way_diff *data
199
+ data.each_with_index do |s, i|
200
+ c = (?A.ord + i).chr
201
+ s.group = c
202
+ end
203
+
204
+ max = data.map { |s| s.scan(/^.*/).size }.max
205
+
206
+ data.map! { |s| # FIX: this is tarded, but I'm out of brain
207
+ c = s.group
208
+ s = s.scan(/^.*/)
209
+ s.push(*([""] * (max - s.size))) # pad
210
+ s.each do |o|
211
+ o.group = c
212
+ end
213
+ s
214
+ }
215
+
216
+ groups = data[0].zip(*data[1..-1])
217
+ groups.map! { |lines|
218
+ collapsed = lines.uniq
219
+ if collapsed.size == 1 then
220
+ " #{lines.first}"
221
+ else
222
+ # TODO: make r2r have a canonical mode (doesn't make 1-liners)
223
+ lines.reject { |l| l.empty? }.map { |l| "#{l.group}: #{l}" }
224
+ end
225
+ }
226
+ groups.flatten.join("\n")
227
+ end
228
+
229
+ def summary
230
+ score = Hash.new 0
231
+
232
+ masses.each do |hash, mass|
233
+ sexps = hashes[hash]
234
+ mass_per_file = mass.to_f / sexps.size
235
+ sexps.each do |sexp|
236
+ score[sexp.file] += mass_per_file
237
+ end
238
+ end
239
+
240
+ score
241
+ end
242
+
243
+ def report prune = nil
244
+ puts "Total score (lower is better) = #{self.total}"
245
+ puts
246
+
247
+ if option[:summary] then
248
+
249
+ self.summary.sort_by { |_,v| -v }.each do |file, score|
250
+ puts "%8.2f: %s" % [score, file]
251
+ end
252
+
253
+ return
254
+ end
255
+
256
+ count = 0
257
+ masses.sort_by { |h,m| [-m, hashes[h].first.file] }.each do |hash, mass|
258
+ nodes = hashes[hash]
259
+ next unless nodes.first.first == prune if prune
260
+ puts
261
+
262
+ same = identical[hash]
263
+ node = nodes.first
264
+ n = nodes.size
265
+ match, bonus = if same then
266
+ ["IDENTICAL", "*#{n}"]
267
+ else
268
+ ["Similar", ""]
269
+ end
270
+
271
+ count += 1
272
+ puts "%d) %s code found in %p (mass%s = %d)" %
273
+ [count, match, node.first, bonus, mass]
274
+
275
+ nodes.each_with_index do |x, i|
276
+ if option[:diff] then
277
+ c = (?A.ord + i).chr
278
+ puts " #{c}: #{x.file}:#{x.line}"
279
+ else
280
+ puts " #{x.file}:#{x.line}"
281
+ end
282
+ end
283
+
284
+ if option[:diff] then
285
+ puts
286
+ r2r = Ruby2Ruby.new
287
+ puts n_way_diff(*nodes.map { |s| r2r.process(s.deep_clone) })
288
+ end
289
+ end
290
+ end
291
+ end
292
+
293
+ class String
294
+ attr_accessor :group
295
+ end
296
+
297
+ class Sexp
298
+ def structural_hash
299
+ @structural_hash ||= self.structure.hash
300
+ end
301
+
302
+ def all_structural_subhashes
303
+ hashes = []
304
+ self.deep_each do |node|
305
+ hashes << node.structural_hash
306
+ end
307
+ hashes
308
+ end
309
+
310
+ # REFACTOR: move to sexp.rb
311
+ def deep_each(&block)
312
+ self.each_sexp do |sexp|
313
+ block[sexp]
314
+ sexp.deep_each(&block)
315
+ end
316
+ end
317
+
318
+ # REFACTOR: move to sexp.rb
319
+ def each_sexp
320
+ self.each do |sexp|
321
+ next unless Sexp === sexp
322
+
323
+ yield sexp
324
+ end
325
+ end
326
+ end
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'rubygems'
4
+ require 'flay'
5
+ require 'erb'
6
+
7
+ class Flay
8
+ def process_erb file
9
+ erb = File.read file
10
+
11
+ ruby = ERB.new(erb).src
12
+ begin
13
+ RubyParser.new.process(ruby, file)
14
+ rescue => e
15
+ warn ruby if option[:verbose]
16
+ raise e
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,33 @@
1
+ class FlayTask < Rake::TaskLib
2
+ attr_accessor :name
3
+ attr_accessor :dirs
4
+ attr_accessor :threshold
5
+ attr_accessor :verbose
6
+
7
+ def initialize name = :flay, threshold = 200, dirs = nil
8
+ @name = name
9
+ @dirs = dirs || %w(app bin lib spec test)
10
+ @threshold = threshold
11
+ @verbose = Rake.application.options.trace
12
+
13
+ yield self if block_given?
14
+
15
+ @dirs.reject! { |f| ! File.directory? f }
16
+
17
+ define
18
+ end
19
+
20
+ def define
21
+ desc "Analyze for code duplication in: #{dirs.join(', ')}"
22
+ task name do
23
+ require "flay"
24
+ flay = Flay.new
25
+ flay.process(*Flay.expand_dirs_to_files(dirs))
26
+ flay.report if verbose
27
+
28
+ raise "Flay total too high! #{flay.total} > #{threshold}" if
29
+ flay.total > threshold
30
+ end
31
+ self
32
+ end
33
+ end
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/ruby -ws
2
+
3
+ $: << 'lib' << '../../ParseTree/dev/lib' << '../../flay/dev/lib'
4
+
5
+ $v ||= false # HACK
6
+
7
+ require 'rubygems'
8
+ require 'flay'
9
+
10
+ require 'gauntlet'
11
+ require 'pp'
12
+
13
+ class FlayGauntlet < Gauntlet
14
+ $owners = {}
15
+ $score_file = 'flay-scores.yml'
16
+ $misc_error = {:total => -1, :average => -1, :methods => {}}
17
+ $syntax_error = {:total => -2, :average => -2, :methods => {}}
18
+ $no_gem = {:total => -4, :average => -4, :methods => {}}
19
+
20
+ # copied straight from hoedown.rb
21
+ my_projects = %w[InlineFortran ParseTree RubyInline RubyToC
22
+ ZenHacks ZenTest bfts box_layout
23
+ change_class flay flog gauntlet heckle
24
+ hoe image_science miniunit minitest
25
+ minitest_tu_shim png ruby2ruby ruby_parser
26
+ rubyforge test-unit un vlad zenprofile
27
+ zentest]
28
+
29
+ MY_PROJECTS = Regexp.union(*my_projects)
30
+
31
+ def run name
32
+ warn name
33
+ self.data[name] = score_for '.'
34
+ self.dirty = true
35
+ end
36
+
37
+ def display_report max
38
+ good_data = {}
39
+ bad_count = 0
40
+ zero_count = 0
41
+
42
+ @data.each do |name, flay|
43
+ case
44
+ when flay < 0 then
45
+ bad_count += 1
46
+ when flay == 0 then
47
+ zero_count += 1
48
+ else
49
+ good_data[name] = flay
50
+ end
51
+ end
52
+
53
+ scores = good_data.values
54
+
55
+ # SWEET JESUS:
56
+ #
57
+ # without zeros:
58
+ # average flay: 1487.23 +/- 7800.16
59
+ # with zeros:
60
+ # average flay: 988.69 +/- 6398.45
61
+
62
+ puts "broken projects : %d" % bad_count
63
+ puts "great projects : %d" % zero_count
64
+ puts "bad projects : %d" % good_data.size
65
+ puts "average flay : %.2f +/- %.2f" % [scores.average, scores.stddev]
66
+
67
+ top = good_data.sort_by { |name,flay| -flay }.first max
68
+
69
+ puts
70
+ top.each_with_index do |(name, flay), i|
71
+ puts "%3d: %10.2f: %s" % [ i, flay, name ]
72
+ end
73
+ end
74
+
75
+ ############################################################
76
+ # OTHER
77
+ ############################################################
78
+
79
+ def score_for dir
80
+ # files = `find #{dir} -name \\*.rb | grep -v gen.*templ`.split(/\n/)
81
+ flayer = Flay.new
82
+
83
+ dirs = %w(app lib test spec).reject { |f| ! File.directory? f }
84
+
85
+ flay = Flay.new
86
+ flay.process(*Flay.expand_dirs_to_files(dirs))
87
+ flay.total
88
+ rescue Interrupt
89
+ # let us break out
90
+ rescue Exception
91
+ -1
92
+ end
93
+ end
94
+
95
+ max = (ARGV.shift || 10).to_i
96
+ filter = ARGV.shift
97
+ filter = Regexp.new filter if filter
98
+ flayer = FlayGauntlet.new
99
+ flayer.run_the_gauntlet filter
100
+ flayer.display_report max
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ require 'minitest/autorun'
4
+ require 'flay'
5
+
6
+ $: << "../../sexp_processor/dev/lib"
7
+
8
+ class TestSexp < MiniTest::Unit::TestCase
9
+ def setup
10
+ # a(1) { |c| d }
11
+ @s = s(:iter,
12
+ s(:call, nil, :a, s(:arglist, s(:lit, 1))),
13
+ s(:lasgn, :c),
14
+ s(:call, nil, :d, s(:arglist)))
15
+ end
16
+
17
+ def test_structural_hash
18
+ hash = s(:iter,
19
+ s(:call, s(:arglist, s(:lit))),
20
+ s(:lasgn),
21
+ s(:call, s(:arglist))).hash
22
+
23
+ assert_equal hash, @s.structural_hash
24
+ assert_equal hash, @s.deep_clone.structural_hash
25
+ end
26
+
27
+ def test_all_structural_subhashes
28
+ s = s(:iter,
29
+ s(:call, s(:arglist, s(:lit))),
30
+ s(:lasgn),
31
+ s(:call, s(:arglist)))
32
+
33
+ expected = [
34
+ s[1] .hash,
35
+ s[1][1] .hash,
36
+ s[1][1][1].hash,
37
+ s[2] .hash,
38
+ s[3] .hash,
39
+ s[3][1] .hash,
40
+ ].sort
41
+
42
+ assert_equal expected, @s.all_structural_subhashes.sort.uniq
43
+
44
+ x = []
45
+
46
+ @s.deep_each do |o|
47
+ x << o.structural_hash
48
+ end
49
+
50
+ assert_equal expected, x.sort.uniq
51
+ end
52
+
53
+ def test_process_sexp
54
+ flay = Flay.new
55
+
56
+ s = RubyParser.new.process <<-RUBY
57
+ def x(n)
58
+ if n % 2 == 0
59
+ return n
60
+ else
61
+ return n + 1
62
+ end
63
+ end
64
+ RUBY
65
+
66
+ expected = [[:block],
67
+ # HACK [:defn],
68
+ [:scope]] # only ones big enough
69
+
70
+ flay.process_sexp s
71
+
72
+ actual = flay.hashes.values.map { |sexps| sexps.map { |sexp| sexp.first } }
73
+
74
+ assert_equal expected, actual.sort_by { |a| a.first.to_s }
75
+ end
76
+
77
+ def test_process_sexp_full
78
+ flay = Flay.new(:mass => 1)
79
+
80
+ s = RubyParser.new.process <<-RUBY
81
+ def x(n)
82
+ if n % 2 == 0
83
+ return n
84
+ else
85
+ return n + 1
86
+ end
87
+ end
88
+ RUBY
89
+
90
+ expected = [[:arglist, :arglist, :arglist],
91
+ [:block],
92
+ [:call, :call],
93
+ [:call],
94
+ [:if],
95
+ [:return],
96
+ [:return],
97
+ [:scope]]
98
+
99
+ flay.process_sexp s
100
+
101
+ actual = flay.hashes.values.map { |sexps| sexps.map { |sexp| sexp.first } }
102
+
103
+ assert_equal expected, actual.sort_by { |a| a.inspect }
104
+ end
105
+
106
+ def test_process_sexp_no_structure
107
+ flay = Flay.new(:mass => 1)
108
+ flay.process_sexp s(:lit, 1)
109
+
110
+ assert flay.hashes.empty?
111
+ end
112
+
113
+ def test_report
114
+ # make sure we run through options parser
115
+ $*.clear
116
+ $* << "-d"
117
+ $* << "--mass=1"
118
+ $* << "-v"
119
+
120
+ flay = Flay.new Flay.parse_options
121
+
122
+ s = RubyParser.new.process <<-RUBY
123
+ class Dog
124
+ def x
125
+ return "Hello"
126
+ end
127
+ end
128
+ class Cat
129
+ def y
130
+ return "Hello"
131
+ end
132
+ end
133
+ RUBY
134
+
135
+ flay.process_sexp s
136
+ flay.analyze
137
+ flay.report
138
+ end
139
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mmullis-flay
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 1.4.2
6
+ platform: ruby
7
+ authors:
8
+ - Ryan Davis
9
+ - Seattle.rb
10
+ - Michael Mullis
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+
15
+ date: 2011-03-04 00:00:00 -05:00
16
+ default_executable: flay
17
+ dependencies:
18
+ - !ruby/object:Gem::Dependency
19
+ name: sexp_processor
20
+ prerelease: false
21
+ requirement: &id001 !ruby/object:Gem::Requirement
22
+ none: false
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: "3.0"
27
+ type: :runtime
28
+ version_requirements: *id001
29
+ - !ruby/object:Gem::Dependency
30
+ name: ruby_parser
31
+ prerelease: false
32
+ requirement: &id002 !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: "2.0"
38
+ type: :runtime
39
+ version_requirements: *id002
40
+ description: Flay fork with modifications to run on 1.9.2
41
+ email: michael@mullistechnologies.com
42
+ executables:
43
+ - flay
44
+ extensions: []
45
+
46
+ extra_rdoc_files:
47
+ - README.txt
48
+ files:
49
+ - History.txt
50
+ - Manifest.txt
51
+ - README.txt
52
+ - Rakefile
53
+ - VERSION
54
+ - bin/flay
55
+ - lib/flay.rb
56
+ - lib/flay_erb.rb
57
+ - lib/flay_task.rb
58
+ - lib/gauntlet_flay.rb
59
+ - test/test_flay.rb
60
+ has_rdoc: true
61
+ homepage: http://github.com/mmullis/flay
62
+ licenses: []
63
+
64
+ post_install_message:
65
+ rdoc_options: []
66
+
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: "0"
81
+ requirements: []
82
+
83
+ rubyforge_project:
84
+ rubygems_version: 1.5.3
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: Flay fork with 1.9.2 Repairs
88
+ test_files:
89
+ - test/test_flay.rb