mmullis-flay 1.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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