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,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 ComplexityScore
34
+
35
+ DEFAULT_COMPLEXITY = 0.0
36
+
37
+ attr_reader :file, :before_score, :after_score
38
+
39
+ def initialize(file, before_score, after_score)
40
+ @file = file
41
+ @before_score = before_score
42
+ @after_score = after_score
43
+ end
44
+
45
+ def accumulated
46
+ if before_score && after_score
47
+ after_score - before_score
48
+ elsif after_score
49
+ after_score
50
+ elsif before_score
51
+ -before_score
52
+ else
53
+ DEFAULT_COMPLEXITY
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -0,0 +1,73 @@
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 Diff
34
+
35
+ extend Attrs
36
+
37
+ Content = Struct.new(:path, :a_blob, :b_blob)
38
+
39
+ attrs :path
40
+ lazy_attrs :diff, :a_blob, :b_blob
41
+
42
+ def self.from_grit(grit_diff)
43
+ new path: String(grit_diff.b_path || grit_diff.a_path),
44
+ diff: -> { grit_diff.diff },
45
+ a_blob: -> { blob_data(grit_diff.a_blob) },
46
+ b_blob: -> { blob_data(grit_diff.b_blob) }
47
+ end
48
+
49
+ def initialize(attributes={})
50
+ @attributes = attributes
51
+ end
52
+
53
+ def identify(line_number)
54
+ DiffParser.new(diff, b_blob).modifications(line_number..line_number)
55
+ end
56
+
57
+ def modifications
58
+ DiffParser.new(diff, b_blob).modifications
59
+ end
60
+
61
+ def content
62
+ Content.new(path, a_blob, b_blob)
63
+ end
64
+
65
+ private
66
+
67
+ def self.blob_data(blob)
68
+ blob.data if blob
69
+ end
70
+
71
+ end
72
+
73
+ end
@@ -0,0 +1,68 @@
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 DiffParser
34
+
35
+ def initialize(diff_string, code)
36
+ @diff_string = diff_string
37
+ @code = code
38
+ end
39
+
40
+ def modifications(line_numbers=nil)
41
+ parse_class_and_functions(line_numbers_of_changes || line_numbers)
42
+ end
43
+
44
+ private
45
+
46
+ def parse_class_and_functions(line_range=nil)
47
+ line_range.inject({}) do |hsh, lines|
48
+ hsh.merge(MethodParser.find(@code, lines)) do |_, a, b|
49
+ a + b
50
+ end
51
+ end
52
+ end
53
+
54
+ def line_numbers_of_changes
55
+ @diff_string.scan(/@@ -\d+,\d+\s\+(\d+),(\d+)/).map do |(start_line, length)|
56
+ begin_length = start_line.to_i + 3
57
+ end_length = length.to_i - 3
58
+ if begin_length > end_length
59
+ (begin_length)..(length.to_i + 3)
60
+ else
61
+ (begin_length)..(end_length)
62
+ end
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ end
@@ -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 ExceptionData
34
+
35
+ attr_reader :type, :backtrace
36
+
37
+ def initialize(opts={})
38
+ @type = opts[:type]
39
+ @backtrace = opts[:backtrace] || []
40
+ end
41
+
42
+ def file
43
+ files.first
44
+ end
45
+
46
+ def line
47
+ _, line = backtrace.first
48
+ line.to_i if line
49
+ end
50
+
51
+ def files
52
+ backtrace.map(&:first)
53
+ end
54
+
55
+ end
56
+
57
+ end
@@ -0,0 +1,72 @@
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 ExceptionTracker
34
+
35
+ def initialize(repo)
36
+ @repo = repo
37
+ end
38
+
39
+ def discover(begin_sha, exception_data, end_sha=nil)
40
+ commits = @repo.commits(begin_sha)
41
+ exception_commit = commits.first
42
+ exception_sources = exception_data.backtrace.map {|(file, line)| exception_commit.identify(file, line.to_i) }
43
+ commits_in_range(commits, end_sha).select do |commit|
44
+ diffs(commit, exception_data).any? do |diff|
45
+ exception_sources.any? {|exception_source| touched_exception?(diff.modifications, exception_source) }
46
+ end
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def commits_in_range(commits, end_sha)
53
+ commits.take_while do |commit|
54
+ commit.sha != end_sha
55
+ end
56
+ end
57
+
58
+ def diffs(commit, exception_data)
59
+ files = exception_data.backtrace.map(&:first)
60
+ commit.diffs.select do |diff|
61
+ files.include? diff.path
62
+ end
63
+ end
64
+
65
+ def touched_exception?(modifications, exception_source)
66
+ exception_source.any? { |klass, _methods|
67
+ modifications[klass] && _methods.any? { |func| modifications[klass].include? func } }
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,51 @@
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
+ module RubyFileAdapter
34
+
35
+ def analyzable_file?(file)
36
+ ruby_file?(file) && !test_file?(file)
37
+ end
38
+
39
+ def test_file?(file)
40
+ file.match(/(_spec|_steps|_test)\.rb$/)
41
+ end
42
+
43
+ def ruby_file?(file)
44
+ file.match(/\.rb$/)
45
+ end
46
+
47
+ extend self
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,88 @@
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 File.expand_path('./../../../vendor/flog/lib/flog', __FILE__)
32
+
33
+ module Bugwatch
34
+
35
+ class FlogScore
36
+
37
+ include RubyFileAdapter
38
+
39
+ def initialize(diff_contents)
40
+ @diff_contents = diff_contents
41
+ end
42
+
43
+ def scores
44
+ filtered_diff_contents.map do |diff_content|
45
+ [diff_content.path, score_blob(diff_content.a_blob), score_blob(diff_content.b_blob)]
46
+ end
47
+ end
48
+
49
+ def average
50
+ average_score = scores.reduce(0.0) do |memo, score|
51
+ _, before_score, after_score = score
52
+ (after_score - before_score) + memo
53
+ end / scores.size.to_f
54
+ average_score.nan? ? 0.0 : average_score
55
+ end
56
+
57
+ def total_score
58
+ scores.reduce(0) do |calculated_score, (_, before_score, after_score)|
59
+ calculated_score + (after_score - before_score)
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def filtered_diff_contents
66
+ @diff_contents.select(&:path)
67
+ end
68
+
69
+ def score_blob(blob)
70
+ blob ? score(blob) : 0.0
71
+ end
72
+
73
+ def score(code)
74
+ flog.flog_code(code)
75
+ flog.total
76
+ rescue
77
+ 0.0
78
+ ensure
79
+ flog.reset
80
+ end
81
+
82
+ def flog
83
+ @flog ||= Flog.new parser: RubyParser
84
+ end
85
+
86
+ end
87
+
88
+ end