bugwatch 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,57 @@
1
+ # Copyright (c) 2013, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions
6
+ # are met:
7
+ #
8
+ # Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # Redistributions in binary form must reproduce the above copyright
12
+ # notice, this list of conditions and the following disclaimer in the
13
+ # documentation and/or other materials provided with the distribution.
14
+ #
15
+ # Neither the name of GROUPON nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20
+ # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25
+ # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ module Bugwatch
32
+
33
+ class Import
34
+
35
+ def initialize(repo, caching_strategy)
36
+ @repo = repo
37
+ @caching_strategy = caching_strategy
38
+ end
39
+
40
+ def import(begin_sha=nil)
41
+ unimported_commits(begin_sha).each do |commit_sha|
42
+ commit = @repo.commit(commit_sha)
43
+ @caching_strategy.store(commit) if commit
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def unimported_commits(begin_sha)
50
+ repo_shas = @repo.commit_shas(begin_sha)
51
+ imported_shas = @caching_strategy.imported
52
+ repo_shas - imported_shas
53
+ end
54
+
55
+ end
56
+
57
+ end
@@ -0,0 +1,105 @@
1
+ # Copyright (c) 2013, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions
6
+ # are met:
7
+ #
8
+ # Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # Redistributions in binary form must reproduce the above copyright
12
+ # notice, this list of conditions and the following disclaimer in the
13
+ # documentation and/or other materials provided with the distribution.
14
+ #
15
+ # Neither the name of GROUPON nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20
+ # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25
+ # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ require 'ruby_parser'
32
+ require 'sexp_processor'
33
+
34
+ module Bugwatch
35
+
36
+ class MethodParser < SexpProcessor
37
+
38
+ attr_reader :parser, :klasses
39
+
40
+ def initialize(line_range)
41
+ super()
42
+ @line_range = line_range
43
+ @klasses = Hash.new {|h, k| h[k] = []}
44
+ @current_class = []
45
+ self.auto_shift_type = true
46
+ end
47
+
48
+ def self.find(code, line_range)
49
+ method_parser = new(line_range)
50
+ ast = RubyParser.new.process(code)
51
+ method_parser.process ast
52
+ method_parser.klasses
53
+ end
54
+
55
+ def process_class(exp)
56
+ begin_line = exp.line
57
+ line_range = begin_line..exp.last.line # TODO figure out last line
58
+ class_name = get_class_name(exp.shift)
59
+ within_class(class_name, line_range) do
60
+ process(exp.shift) until exp.empty?
61
+ end
62
+ s()
63
+ end
64
+
65
+ alias_method :process_module, :process_class
66
+
67
+ def process_defn(exp)
68
+ first_line_of_method = exp.line
69
+ name = exp.shift
70
+ @klasses[current_class].push name.to_s if within_target?(first_line_of_method..exp.last.line)
71
+ process(exp.shift) until exp.empty?
72
+ s()
73
+ end
74
+
75
+ def within_class(class_name, line_range, &block)
76
+ @current_class.push class_name
77
+ methods_before_process = @klasses[current_class].count
78
+ block.call
79
+ if (methods_before_process == @klasses[current_class].count) && within_target?(line_range)
80
+ @klasses[current_class].push nil
81
+ end
82
+ @current_class.shift
83
+ end
84
+
85
+ def within_target?(range)
86
+ @line_range.any? {|num| range.cover? num}
87
+ end
88
+
89
+ def current_class
90
+ @current_class.join('::')
91
+ end
92
+
93
+ def get_class_name(exp, namespace=[])
94
+ if exp.is_a?(Sexp) && exp.first.is_a?(Sexp)
95
+ get_class_name(exp.first, namespace + [exp.last])
96
+ elsif exp.is_a?(Sexp) && exp[1].is_a?(Sexp)
97
+ get_class_name(exp[1], namespace + [exp.last])
98
+ else
99
+ ([Array(exp).last] + namespace.reverse).join('::')
100
+ end
101
+ end
102
+
103
+ end
104
+
105
+ end
@@ -0,0 +1,134 @@
1
+ # Copyright (c) 2013, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions
6
+ # are met:
7
+ #
8
+ # Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # Redistributions in binary form must reproduce the above copyright
12
+ # notice, this list of conditions and the following disclaimer in the
13
+ # documentation and/or other materials provided with the distribution.
14
+ #
15
+ # Neither the name of GROUPON nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20
+ # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25
+ # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ module Bugwatch
32
+
33
+ class Repo
34
+
35
+ REPO_PATH = 'repos'
36
+
37
+ attr_reader :name, :url
38
+
39
+ def self.discover(name, url)
40
+ new(name, url).tap {|repo| repo.discover! }
41
+ end
42
+
43
+ def initialize(name, url)
44
+ @name = name
45
+ @url = url
46
+ end
47
+
48
+ def discover!
49
+ if exists?
50
+ update!
51
+ else
52
+ clone!
53
+ end
54
+ end
55
+
56
+ def clone!
57
+ Kernel.system("cd #{REPO_PATH}; git clone #{url} #{name}")
58
+ end
59
+
60
+ def update!
61
+ Kernel.system("cd #{path_to_repo}; git fetch origin master; git reset --hard origin/master; git fetch --tags")
62
+ end
63
+
64
+ def exists?
65
+ File.exists?(path_to_repo)
66
+ end
67
+
68
+ def analyze!(caching_strategy, analyzers, begin_sha=nil)
69
+ import(caching_strategy, begin_sha)
70
+ analyze(caching_strategy, *analyzers)
71
+ end
72
+
73
+ def import(caching_strategy, begin_sha=nil)
74
+ Import.new(self, caching_strategy).import(sha_or_head(begin_sha))
75
+ end
76
+
77
+ def analyze(caching_strategy, *analyzers)
78
+ Analysis.new(self, caching_strategy).analyze(*analyzers)
79
+ end
80
+
81
+ def head
82
+ rugged_repo.head.target
83
+ end
84
+
85
+ def commits(begin_sha=nil)
86
+ rugged_repo.walk(sha_or_head(begin_sha)).map do |rugged_commit|
87
+ self.commit(rugged_commit.oid)
88
+ end
89
+ end
90
+
91
+ def commit(sha)
92
+ grit_commit = grit.commit(sha)
93
+ Commit.from_grit(grit_commit) if grit_commit
94
+ end
95
+
96
+ def commit_shas(begin_sha=nil)
97
+ rugged_repo.walk(sha_or_head(begin_sha)).map(&:oid)
98
+ end
99
+
100
+ def tree
101
+ Tree.new(grit.tree)
102
+ end
103
+
104
+ def grit
105
+ @grit ||= Grit::Repo.new(path_to_repo)
106
+ end
107
+
108
+ def tags
109
+ grit_tags.map do |tag|
110
+ Tag.new(tag)
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ def sha_or_head(sha)
117
+ sha || head
118
+ end
119
+
120
+ def grit_tags
121
+ @grit_tags ||= grit.tags
122
+ end
123
+
124
+ def path_to_repo
125
+ "#{REPO_PATH}/#{name}"
126
+ end
127
+
128
+ def rugged_repo
129
+ @rugged_repo ||= Rugged::Repository.new(path_to_repo)
130
+ end
131
+
132
+ end
133
+
134
+ end
@@ -0,0 +1,85 @@
1
+ # Copyright (c) 2013, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions
6
+ # are met:
7
+ #
8
+ # Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # Redistributions in binary form must reproduce the above copyright
12
+ # notice, this list of conditions and the following disclaimer in the
13
+ # documentation and/or other materials provided with the distribution.
14
+ #
15
+ # Neither the name of GROUPON nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20
+ # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25
+ # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ module Bugwatch
32
+
33
+ class RubyComplexity
34
+
35
+ include RubyFileAdapter
36
+
37
+ def initialize(diffs)
38
+ @diffs = diffs
39
+ end
40
+
41
+ def score
42
+ ruby_flog.total_score
43
+ end
44
+
45
+ def test_score
46
+ test_flog.total_score
47
+ end
48
+
49
+ def scores
50
+ complexity_scores(ruby_flog.scores)
51
+ end
52
+
53
+ def test_scores
54
+ complexity_scores(test_flog.scores)
55
+ end
56
+
57
+ def cyclomatic
58
+ []
59
+ end
60
+
61
+ private
62
+
63
+ def ruby_flog
64
+ FlogScore.new(ruby_diffs)
65
+ end
66
+
67
+ def test_flog
68
+ FlogScore.new(test_diffs)
69
+ end
70
+
71
+ def ruby_diffs
72
+ @diffs.select {|diff| analyzable_file? diff.path }
73
+ end
74
+
75
+ def test_diffs
76
+ @diffs.select {|diff| test_file? diff.path }
77
+ end
78
+
79
+ def complexity_scores(scores)
80
+ scores.map {|(file, before_score, after_score)| ComplexityScore.new(file, before_score, after_score) }
81
+ end
82
+
83
+ end
84
+
85
+ end
@@ -0,0 +1,59 @@
1
+ # Copyright (c) 2013, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions
6
+ # are met:
7
+ #
8
+ # Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # Redistributions in binary form must reproduce the above copyright
12
+ # notice, this list of conditions and the following disclaimer in the
13
+ # documentation and/or other materials provided with the distribution.
14
+ #
15
+ # Neither the name of GROUPON nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20
+ # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25
+ # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ module Bugwatch
32
+
33
+ class Tag
34
+
35
+ attr_reader :grit
36
+
37
+ def initialize(grit_tag)
38
+ @grit = grit_tag
39
+ end
40
+
41
+ def name
42
+ grit.name
43
+ end
44
+
45
+ def committed_date
46
+ grit.commit.committed_date
47
+ end
48
+
49
+ def authored_date
50
+ grit.commit.authored_date
51
+ end
52
+
53
+ def tag_date
54
+ grit.tag_date
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -0,0 +1,65 @@
1
+ # Copyright (c) 2013, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions
6
+ # are met:
7
+ #
8
+ # Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # Redistributions in binary form must reproduce the above copyright
12
+ # notice, this list of conditions and the following disclaimer in the
13
+ # documentation and/or other materials provided with the distribution.
14
+ #
15
+ # Neither the name of GROUPON nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20
+ # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25
+ # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ module Bugwatch
32
+
33
+ class Tree
34
+
35
+ Content = Struct.new(:file, :size)
36
+
37
+ def initialize(grit=nil)
38
+ @grit = grit
39
+ end
40
+
41
+ def /(filepath)
42
+ @grit / filepath if @grit
43
+ end
44
+
45
+ def ruby_files
46
+ @files ||= get_ruby_files(@grit.contents)
47
+ end
48
+
49
+ private
50
+
51
+ def get_ruby_files(tree_contents)
52
+ tree_contents.reduce([]) do |count, type|
53
+ if type.is_a?(Grit::Blob) && RubyFileAdapter.ruby_file?(type.name)
54
+ count + [Content.new(type.name, type.size)]
55
+ elsif type.is_a?(Grit::Tree)
56
+ count + get_ruby_files(type.contents)
57
+ else
58
+ count
59
+ end
60
+ end
61
+ end
62
+
63
+ end
64
+
65
+ end