bugwatch 0.1

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,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