pmdtester 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.ci/build.sh +91 -0
  3. data/.ci/files/env.gpg +1 -0
  4. data/.github/workflows/build.yml +39 -0
  5. data/.gitignore +9 -0
  6. data/.hoerc +1 -1
  7. data/.rubocop.yml +6 -2
  8. data/.ruby-version +1 -0
  9. data/History.md +40 -0
  10. data/Manifest.txt +24 -9
  11. data/README.rdoc +45 -30
  12. data/Rakefile +5 -3
  13. data/config/all-java.xml +1 -1
  14. data/config/design.xml +1 -1
  15. data/config/projectlist_1_0_0.xsd +2 -1
  16. data/config/projectlist_1_1_0.xsd +31 -0
  17. data/lib/pmdtester.rb +8 -7
  18. data/lib/pmdtester/builders/liquid_renderer.rb +73 -0
  19. data/lib/pmdtester/builders/pmd_report_builder.rb +102 -78
  20. data/lib/pmdtester/builders/project_builder.rb +100 -0
  21. data/lib/pmdtester/builders/project_hasher.rb +126 -0
  22. data/lib/pmdtester/builders/rule_set_builder.rb +92 -47
  23. data/lib/pmdtester/builders/simple_progress_logger.rb +4 -4
  24. data/lib/pmdtester/builders/summary_report_builder.rb +62 -131
  25. data/lib/pmdtester/collection_by_file.rb +55 -0
  26. data/lib/pmdtester/parsers/options.rb +19 -0
  27. data/lib/pmdtester/parsers/pmd_report_document.rb +74 -29
  28. data/lib/pmdtester/parsers/projects_parser.rb +2 -4
  29. data/lib/pmdtester/pmd_branch_detail.rb +29 -19
  30. data/lib/pmdtester/pmd_configerror.rb +23 -24
  31. data/lib/pmdtester/pmd_error.rb +34 -34
  32. data/lib/pmdtester/pmd_report_detail.rb +9 -12
  33. data/lib/pmdtester/pmd_tester_utils.rb +55 -0
  34. data/lib/pmdtester/pmd_violation.rb +66 -28
  35. data/lib/pmdtester/project.rb +21 -48
  36. data/lib/pmdtester/report_diff.rb +179 -111
  37. data/lib/pmdtester/resource_locator.rb +4 -0
  38. data/lib/pmdtester/runner.rb +66 -64
  39. data/pmdtester.gemspec +27 -36
  40. data/resources/_includes/diff_pill_row.html +6 -0
  41. data/resources/css/bootstrap.min.css +7 -0
  42. data/resources/css/datatables.min.css +36 -0
  43. data/resources/css/pmd-tester.css +131 -0
  44. data/resources/js/bootstrap.min.js +7 -0
  45. data/resources/js/code-snippets.js +66 -0
  46. data/resources/js/datatables.min.js +726 -0
  47. data/resources/js/jquery-3.2.1.slim.min.js +4 -0
  48. data/resources/js/jquery.min.js +2 -0
  49. data/resources/js/popper.min.js +5 -0
  50. data/resources/js/project-report.js +136 -0
  51. data/resources/project_diff_report.html +205 -0
  52. data/resources/project_index.html +102 -0
  53. metadata +64 -20
  54. data/.travis.yml +0 -40
  55. data/lib/pmdtester/builders/diff_builder.rb +0 -31
  56. data/lib/pmdtester/builders/diff_report/configerrors.rb +0 -50
  57. data/lib/pmdtester/builders/diff_report/errors.rb +0 -71
  58. data/lib/pmdtester/builders/diff_report/violations.rb +0 -77
  59. data/lib/pmdtester/builders/diff_report_builder.rb +0 -99
  60. data/lib/pmdtester/builders/html_report_builder.rb +0 -56
  61. data/resources/css/maven-base.css +0 -155
  62. data/resources/css/maven-theme.css +0 -171
@@ -7,12 +7,12 @@ module PmdTester
7
7
  class PmdReportDetail
8
8
  attr_accessor :execution_time
9
9
  attr_accessor :timestamp
10
- attr_reader :working_dir
10
+ attr_accessor :working_dir
11
11
 
12
- def initialize
13
- @execution_time = 0
14
- @timestamp = ''
15
- @working_dir = Dir.getwd
12
+ def initialize(execution_time: 0, timestamp: '', working_dir: Dir.getwd)
13
+ @execution_time = execution_time
14
+ @timestamp = timestamp
15
+ @working_dir = working_dir
16
16
  end
17
17
 
18
18
  def save(report_info_path)
@@ -22,16 +22,13 @@ module PmdTester
22
22
  file.close
23
23
  end
24
24
 
25
- def load(report_info_path)
25
+ def self.load(report_info_path)
26
26
  if File.exist?(report_info_path)
27
- hash = JSON.parse(File.read(report_info_path))
28
- @execution_time = hash['execution_time']
29
- @timestamp = hash['timestamp']
30
- @working_dir = hash['working_dir']
31
- hash
27
+ hash = JSON.parse(File.read(report_info_path), symbolize_names: true)
28
+ PmdReportDetail.new(**hash)
32
29
  else
33
30
  puts "#{report_info_path} doesn't exist"
34
- {}
31
+ PmdReportDetail.new
35
32
  end
36
33
  end
37
34
 
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PmdTester
4
+ # Some functions that that don't belong in a specific class,
5
+ module PmdTesterUtils
6
+ include PmdTester
7
+
8
+ # Parse the base and the patch report, compute their diff
9
+ # Returns a +ReportDiff+
10
+ def build_report_diff(base_report_file, patch_report_file, base_info, patch_info, filter_set = nil)
11
+ base_details = PmdReportDetail.load(base_info)
12
+ patch_details = PmdReportDetail.load(patch_info)
13
+
14
+ base_report = parse_pmd_report(base_report_file, BASE, base_details, filter_set)
15
+ patch_report = parse_pmd_report(patch_report_file, PATCH, patch_details)
16
+
17
+ logger.info 'Calculating diffs'
18
+ ReportDiff.new(base_report: base_report, patch_report: patch_report)
19
+ end
20
+
21
+ # Parse the +report_file+ to produce a +Report+.
22
+ # For the schema of xml reports, refer to http://pmd.sourceforge.net/report_2_0_0.xsd
23
+ def parse_pmd_report(report_file, branch, report_details, filter_set = nil)
24
+ require 'nokogiri'
25
+
26
+ logger.info "Parsing #{report_file}"
27
+ doc = PmdReportDocument.new(branch, report_details.working_dir, filter_set)
28
+ parser = Nokogiri::XML::SAX::Parser.new(doc)
29
+ parser.parse_file(report_file) if File.exist?(report_file)
30
+ Report.new(
31
+ report_document: doc,
32
+
33
+ timestamp: report_details.timestamp,
34
+ exec_time: report_details.execution_time
35
+ )
36
+ end
37
+
38
+ # Fill the report_diff field of every project
39
+ def compute_project_diffs(projects, base_branch, patch_branch, filter_set = nil)
40
+ projects.each do |project|
41
+ logger.info "Preparing report for #{project.name}"
42
+ project.compute_report_diff(base_branch, patch_branch, filter_set)
43
+ end
44
+ end
45
+
46
+ # Build the diff reports and write them all
47
+ def build_html_reports(projects, base_branch_details, patch_branch_details)
48
+ compute_project_diffs(projects, base_branch_details.branch_name, patch_branch_details.branch_name)
49
+
50
+ SummaryReportBuilder.new.write_all_projects(projects,
51
+ base_branch_details,
52
+ patch_branch_details)
53
+ end
54
+ end
55
+ end
@@ -1,25 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PmdTester
4
- # This class is used to store pmd violations and its size.
5
- class PmdViolations
6
- attr_reader :violations
7
- attr_reader :violations_size
8
-
9
- def initialize
10
- # key:filename as String => value:PmdViolation Array
11
- @violations = {}
12
- @violations_size = 0
13
- end
14
-
15
- def add_violations_by_filename(filename, violations)
16
- return if violations.empty?
17
-
18
- @violations.store(filename, violations)
19
- @violations_size += violations.size
20
- end
21
- end
22
-
23
4
  # This class represents a 'violation' element of Pmd xml report
24
5
  # and which pmd branch the 'violation' is from
25
6
  class PmdViolation
@@ -46,23 +27,80 @@ module PmdTester
46
27
  # </xs:simpleContent>
47
28
  # </xs:complexType>
48
29
 
49
- attr_reader :attrs
50
- attr_accessor :text
30
+ attr_reader :fname, :info_url, :line, :old_line, :old_message, :rule_name, :ruleset_name
31
+ attr_accessor :message
51
32
 
52
- def initialize(attrs, branch)
53
- @attrs = attrs
33
+ # rubocop:disable Metrics/ParameterLists
34
+ # Disable it: how is replacing a long parameter list with a single hash helping?
35
+ def initialize(branch:, fname:, info_url:, bline:, rule_name:, ruleset_name:)
54
36
  @branch = branch
55
- @text = ''
37
+ @fname = fname
38
+ @message = ''
39
+
40
+ @info_url = info_url
41
+ @line = bline
42
+ @rule_name = rule_name
43
+
44
+ @ruleset_name = ruleset_name
45
+
46
+ @changed = false
47
+ @old_message = nil
48
+ @old_line = nil
49
+ end
50
+ # rubocop:enable Metrics/ParameterLists
51
+
52
+ def line_move?(other)
53
+ message.eql?(other.message) && (line - other.line).abs <= 5
54
+ end
55
+
56
+ def try_merge?(other)
57
+ if branch != BASE && branch != other.branch && rule_name == other.rule_name &&
58
+ !changed? && # not already changed
59
+ (line == other.line || line_move?(other))
60
+ @changed = true
61
+ @old_message = other.message
62
+ @old_line = other.line
63
+ true
64
+ else
65
+ false
66
+ end
67
+ end
68
+
69
+ # only makes sense if this is a diff
70
+ def added?
71
+ branch != BASE && !changed?
72
+ end
73
+
74
+ # only makes sense if this is a diff
75
+ def changed?
76
+ @changed
77
+ end
78
+
79
+ # only makes sense if this is a diff
80
+ def removed?
81
+ branch == BASE
82
+ end
83
+
84
+ def sort_key
85
+ line
56
86
  end
57
87
 
58
88
  def eql?(other)
59
- @attrs['beginline'].eql?(other.attrs['beginline']) &&
60
- @attrs['rule'].eql?(other.attrs['rule']) &&
61
- @text.eql?(other.text)
89
+ rule_name.eql?(other.rule_name) &&
90
+ line.eql?(other.line) &&
91
+ fname.eql?(other.fname) &&
92
+ message.eql?(other.message)
62
93
  end
63
94
 
64
95
  def hash
65
- [@attrs['beginline'], @attrs['rule'], @text].hash
96
+ [line, rule_name, message].hash
97
+ end
98
+
99
+ def to_liquid
100
+ {
101
+ 'branch' => branch,
102
+ 'changed' => changed?
103
+ }
66
104
  end
67
105
  end
68
106
  end
@@ -3,6 +3,8 @@
3
3
  module PmdTester
4
4
  # This class represents all the information about the project
5
5
  class Project
6
+ include PmdTesterUtils
7
+
6
8
  REPOSITORIES_PATH = 'target/repositories'
7
9
 
8
10
  attr_reader :name
@@ -13,15 +15,17 @@ module PmdTester
13
15
  attr_reader :exclude_pattern
14
16
  attr_accessor :report_diff
15
17
  # key: pmd branch name as String => value: local path of pmd report
18
+ attr_reader :build_command
19
+ attr_reader :auxclasspath_command
20
+ # stores the auxclasspath calculated after cloning/preparing the project
21
+ attr_accessor :auxclasspath
16
22
 
17
23
  def initialize(project)
18
24
  @name = project.at_xpath('name').text
19
25
  @type = project.at_xpath('type').text
20
26
  @connection = project.at_xpath('connection').text
21
27
 
22
- @tag = 'master'
23
- tag_element = project.at_xpath('tag')
24
- @tag = tag_element.text unless tag_element.nil?
28
+ @tag = project.at_xpath('tag')&.text || 'master'
25
29
 
26
30
  webview_url_element = project.at_xpath('webview-url')
27
31
  @webview_url = default_webview_url
@@ -32,7 +36,10 @@ module PmdTester
32
36
  @exclude_pattern.push(ep.text)
33
37
  end
34
38
 
35
- @report_diff = ReportDiff.new
39
+ @build_command = project.at_xpath('build-command')&.text
40
+ @auxclasspath_command = project.at_xpath('auxclasspath-command')&.text
41
+
42
+ @report_diff = nil
36
43
  end
37
44
 
38
45
  # Generate the default webview url for the projects
@@ -58,6 +65,10 @@ module PmdTester
58
65
  file_path.gsub(%r{/#{local_source_path}}, @name)
59
66
  end
60
67
 
68
+ def get_local_path(file_path)
69
+ file_path.sub(%r{/#{local_source_path}/}, '')
70
+ end
71
+
61
72
  def get_pmd_report_path(branch_name)
62
73
  if branch_name.nil?
63
74
  nil
@@ -93,50 +104,12 @@ module PmdTester
93
104
  "#{REPOSITORIES_PATH}/#{@name}"
94
105
  end
95
106
 
96
- def target_diff_report_path
97
- dir = "target/reports/diff/#{@name}"
98
- FileUtils.mkdir_p(dir) unless File.directory?(dir)
99
- dir
100
- end
101
-
102
- def diff_report_index_path
103
- "#{target_diff_report_path}/index.html"
104
- end
105
-
106
- def diff_report_index_ref_path
107
- "./#{name}/index.html"
108
- end
109
-
110
- def diffs_exist?
111
- @report_diff.nil? ? false : @report_diff.diffs_exist?
112
- end
113
-
114
- def introduce_new_errors?
115
- @report_diff.nil? ? false : @report_diff.introduce_new_errors?
116
- end
117
-
118
- def removed_errors_size
119
- @report_diff.removed_errors_size
120
- end
121
-
122
- def new_errors_size
123
- @report_diff.new_errors_size
124
- end
125
-
126
- def removed_violations_size
127
- @report_diff.removed_violations_size
128
- end
129
-
130
- def new_violations_size
131
- @report_diff.new_violations_size
132
- end
133
-
134
- def removed_configerrors_size
135
- @report_diff.removed_configerrors_size
136
- end
137
-
138
- def new_configerrors_size
139
- @report_diff.new_configerrors_size
107
+ def compute_report_diff(base_branch, patch_branch, filter_set)
108
+ self.report_diff = build_report_diff(get_pmd_report_path(base_branch),
109
+ get_pmd_report_path(patch_branch),
110
+ get_report_info_path(base_branch),
111
+ get_report_info_path(patch_branch),
112
+ filter_set)
140
113
  end
141
114
  end
142
115
  end
@@ -1,157 +1,225 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PmdTester
4
- # This class represents all the diff report information,
5
- # including the summary information of the original pmd reports,
6
- # as well as the specific information of the diff report.
7
- class ReportDiff
8
- include PmdTester
9
-
10
- attr_accessor :base_violations_size
11
- attr_accessor :patch_violations_size
12
- attr_accessor :new_violations_size
13
- attr_accessor :removed_violations_size
14
- attr_accessor :violation_diffs_size
4
+ # A bunch of counters to summarize differences
5
+ class RunningDiffCounters
6
+ attr_accessor :changed, :new, :removed, :patch_total, :base_total
15
7
 
16
- attr_accessor :base_errors_size
17
- attr_accessor :patch_errors_size
18
- attr_accessor :new_errors_size
19
- attr_accessor :removed_errors_size
20
- attr_accessor :error_diffs_size
8
+ def initialize(base_total)
9
+ @base_total = base_total
10
+ @patch_total = 0
21
11
 
22
- attr_accessor :base_configerrors_size
23
- attr_accessor :patch_configerrors_size
24
- attr_accessor :new_configerrors_size
25
- attr_accessor :removed_configerrors_size
26
- attr_accessor :configerrors_diffs_size
27
-
28
- attr_accessor :base_execution_time
29
- attr_accessor :patch_execution_time
30
- attr_accessor :diff_execution_time
31
-
32
- attr_accessor :base_timestamp
33
- attr_accessor :patch_timestamp
12
+ @new = @removed = @changed = 0
13
+ end
34
14
 
35
- attr_accessor :violation_diffs
36
- attr_accessor :error_diffs
37
- attr_accessor :configerrors_diffs
15
+ def changed_total
16
+ new + removed + changed
17
+ end
38
18
 
39
- def initialize
40
- init_violations
41
- init_errors
42
- init_configerrors
19
+ def merge!(other)
20
+ self.changed += other.changed
21
+ self.new += other.new
22
+ self.removed += other.removed
23
+ self.base_total += other.base_total
24
+ self.patch_total += other.patch_total
25
+ end
43
26
 
44
- @base_execution_time = 0
45
- @patch_execution_time = 0
46
- @diff_execution_time = 0
27
+ def to_h
28
+ {
29
+ 'changed' => changed,
30
+ 'new' => new,
31
+ 'removed' => removed,
32
+ 'base_total' => base_total,
33
+ 'patch_total' => patch_total
34
+ }
35
+ end
36
+ end
47
37
 
48
- @base_timestamp = ''
49
- @patch_timestamp = ''
38
+ # Simple info about a rule, collected by the report xml parser
39
+ class RuleInfo
40
+ attr_reader :name, :info_url
50
41
 
51
- @violation_diffs = {}
52
- @error_diffs = {}
53
- @configerrors_diffs = {}
42
+ def initialize(name, info_url)
43
+ @name = name
44
+ @info_url = info_url
54
45
  end
46
+ end
55
47
 
56
- def init_violations
57
- @base_violations_size = 0
58
- @patch_violations_size = 0
59
- @new_violations_size = 0
60
- @removed_violations_size = 0
61
- @violation_diffs_size = 0
48
+ # A full report, created by the report XML parser,
49
+ # can be diffed with another report into a ReportDiff
50
+ class Report
51
+ attr_reader :violations_by_file,
52
+ :errors_by_file,
53
+ :configerrors_by_rule,
54
+ :exec_time,
55
+ :timestamp,
56
+ :infos_by_rule
57
+
58
+ def initialize(report_document: nil,
59
+ exec_time: 0,
60
+ timestamp: '0')
61
+ initialize_empty
62
+ initialize_with_report_document report_document unless report_document.nil?
63
+ @exec_time = exec_time
64
+ @timestamp = timestamp
62
65
  end
63
66
 
64
- def init_errors
65
- @base_errors_size = 0
66
- @patch_errors_size = 0
67
- @new_errors_size = 0
68
- @removed_errors_size = 0
69
- @error_diffs_size = 0
67
+ def self.empty
68
+ new
70
69
  end
71
70
 
72
- def init_configerrors
73
- @base_configerrors_size = 0
74
- @patch_configerrors_size = 0
75
- @new_configerrors_size = 0
76
- @removed_configerrors_size = 0
77
- @configerrors_diffs_size = 0
78
- end
71
+ private
79
72
 
80
- def self.comparable?(errors)
81
- errors.size == 2 && errors[0].branch != errors[1].branch
73
+ def initialize_with_report_document(report_document)
74
+ @violations_by_file = report_document.violations
75
+ @errors_by_file = report_document.errors
76
+ @configerrors_by_rule = report_document.configerrors
77
+ @infos_by_rule = report_document.infos_by_rules
82
78
  end
83
79
 
84
- def diffs_exist?
85
- !error_diffs_size.zero? || !violation_diffs_size.zero? || !configerrors_diffs_size.zero?
80
+ def initialize_empty
81
+ @violations_by_file = CollectionByFile.new
82
+ @errors_by_file = CollectionByFile.new
83
+ @configerrors_by_rule = {}
84
+ @infos_by_rule = {}
86
85
  end
86
+ end
87
+
88
+ # This class represents all the diff report information,
89
+ # including the summary information of the original pmd reports,
90
+ # as well as the specific information of the diff report.
91
+ class ReportDiff
92
+ include PmdTester
87
93
 
88
- def calculate_violations(base_violations, patch_violations)
89
- @base_violations_size = base_violations.violations_size
90
- @patch_violations_size = patch_violations.violations_size
91
- violation_diffs = build_diffs(base_violations.violations, patch_violations.violations)
92
- @violation_diffs = violation_diffs
93
- @new_violations_size, @removed_violations_size = get_diffs_size(violation_diffs)
94
- @violation_diffs_size = @new_violations_size + @removed_violations_size
94
+ attr_reader :error_counts
95
+ attr_reader :violation_counts
96
+ attr_reader :configerror_counts
97
+
98
+ attr_accessor :violation_diffs_by_file
99
+ attr_accessor :error_diffs_by_file
100
+ attr_accessor :configerror_diffs_by_rule
101
+
102
+ attr_accessor :rule_infos_union
103
+ attr_accessor :base_report
104
+ attr_accessor :patch_report
105
+
106
+ def initialize(base_report:, patch_report:)
107
+ @violation_counts = RunningDiffCounters.new(base_report.violations_by_file.total_size)
108
+ @error_counts = RunningDiffCounters.new(base_report.errors_by_file.total_size)
109
+ @configerror_counts = RunningDiffCounters.new(base_report.configerrors_by_rule.values.flatten.length)
110
+
111
+ @violation_diffs_by_file = {}
112
+ @error_diffs_by_file = {}
113
+ @configerror_diffs_by_rule = {}
114
+
115
+ @rule_infos_union = base_report.infos_by_rule.dup
116
+ @base_report = base_report
117
+ @patch_report = patch_report
118
+
119
+ @violation_diffs_by_rule = {}
120
+ diff_with(patch_report)
95
121
  end
96
122
 
97
- def calculate_errors(base_errors, patch_errors)
98
- @base_errors_size = base_errors.errors_size
99
- @patch_errors_size = patch_errors.errors_size
100
- error_diffs = build_diffs(base_errors.errors, patch_errors.errors)
101
- @error_diffs = error_diffs
102
- @new_errors_size, @removed_errors_size = get_diffs_size(error_diffs)
103
- @error_diffs_size = @new_errors_size + @removed_errors_size
123
+ def rule_summaries
124
+ @violation_diffs_by_rule.map do |(rule, counters)|
125
+ {
126
+ 'name' => rule,
127
+ 'info_url' => @rule_infos_union[rule].info_url,
128
+ **counters.to_h
129
+ }
130
+ end
104
131
  end
105
132
 
106
- def calculate_configerrors(base_configerrors, patch_configerrors)
107
- @base_configerrors_size = base_configerrors.size
108
- @patch_configerrors_size = patch_configerrors.size
109
- configerrors_diffs = build_diffs(base_configerrors.errors, patch_configerrors.errors)
110
- @configerrors_diffs = configerrors_diffs
111
- @new_configerrors_size, @removed_configerrors_size = get_diffs_size(configerrors_diffs)
112
- @configerrors_diffs_size = @new_configerrors_size + @removed_configerrors_size
133
+ private
134
+
135
+ def diff_with(patch_report)
136
+ @violation_counts.patch_total = patch_report.violations_by_file.total_size
137
+ @error_counts.patch_total = patch_report.errors_by_file.total_size
138
+ @configerror_counts.patch_total = patch_report.configerrors_by_rule.values.flatten.length
139
+
140
+ @violation_diffs_by_file = build_diffs(@violation_counts, &:violations_by_file)
141
+ count(@violation_diffs_by_file) { |v| getvdiff(v.rule_name) } # record the diffs in the rule counter
142
+
143
+ @error_diffs_by_file = build_diffs(@error_counts, &:errors_by_file)
144
+ @configerror_diffs_by_rule = build_diffs(@configerror_counts, &:configerrors_by_rule)
145
+
146
+ count_by_rule(@base_report.violations_by_file, base: true)
147
+ count_by_rule(@patch_report.violations_by_file, base: false)
148
+ self
113
149
  end
114
150
 
115
- def calculate_details(base_info, patch_info)
116
- base_details = PmdReportDetail.new
117
- base_details.load(base_info) unless base_info.nil?
118
- patch_details = PmdReportDetail.new
119
- patch_details.load(patch_info) unless patch_info.nil?
151
+ def record_rule_info(violation)
152
+ return if @rule_infos_union.key?(violation.rule_name)
120
153
 
121
- @base_execution_time = base_details.format_execution_time
122
- @patch_execution_time = patch_details.format_execution_time
123
- @diff_execution_time =
124
- PmdReportDetail.convert_seconds(base_details.execution_time -
125
- patch_details.execution_time)
154
+ @rule_infos_union[violation.rule_name] = RuleInfo.new(violation.rule_name, violation.info_url)
155
+ end
156
+
157
+ def getvdiff(rule_name)
158
+ @violation_diffs_by_rule.fetch(rule_name) do |_|
159
+ @violation_diffs_by_rule[rule_name] = RunningDiffCounters.new(0)
160
+ end
161
+ end
126
162
 
127
- @base_timestamp = base_details.timestamp
128
- @patch_timestamp = patch_details.timestamp
129
- [base_details, patch_details]
163
+ def count_by_rule(violations_h, base:)
164
+ violations_h.each_value do |v|
165
+ record_rule_info(v)
166
+ rule_diff = getvdiff(v.rule_name)
167
+ if base
168
+ rule_diff.base_total += 1
169
+ else
170
+ rule_diff.patch_total += 1
171
+ end
172
+ end
130
173
  end
131
174
 
132
- def build_diffs(base_hash, patch_hash)
133
- diffs = base_hash.merge(patch_hash) do |_key, base_value, patch_value|
175
+ def build_diffs(counters, &getter)
176
+ base_hash = getter.yield(@base_report)
177
+ patch_hash = getter.yield(@patch_report)
178
+ # Keys are filenames
179
+ # Values are lists of violations/errors
180
+ diffs = base_hash.to_h.merge(patch_hash.to_h) do |_key, base_value, patch_value|
181
+ # make the difference of values
134
182
  (base_value | patch_value) - (base_value & patch_value)
135
183
  end
136
184
 
137
185
  diffs.delete_if do |_key, value|
138
186
  value.empty?
139
187
  end
188
+
189
+ merge_changed_items(diffs)
190
+ count(diffs) { |_| counters }
191
+ diffs
140
192
  end
141
193
 
142
- def get_diffs_size(diffs_hash)
143
- new_size = 0
144
- removed_size = 0
145
- diffs_hash.each_value do |value|
146
- value.each do |item|
147
- item.branch.eql?(BASE) ? removed_size += 1 : new_size += 1
194
+ # @param diff_h a hash { filename => list[violation]}, containing those that changed
195
+ # in case of config errors it's a hash { rulename => list[configerror] }
196
+ def merge_changed_items(diff_h)
197
+ diff_h.each do |fname, different|
198
+ different.sort_by!(&:sort_key)
199
+ diff_h[fname] = different.delete_if do |v|
200
+ v.branch == BASE &&
201
+ # try_merge will set v2.changed = true if it succeeds
202
+ different.any? { |v2| v2.try_merge?(v) }
148
203
  end
149
204
  end
150
- [new_size, removed_size]
151
205
  end
152
206
 
153
- def introduce_new_errors?
154
- !@new_errors_size.zero? || !@new_configerrors_size.zero?
207
+ def count(item_h)
208
+ item_h = { '' => item_h } if item_h.is_a?(Array)
209
+
210
+ item_h.each do |_k, items|
211
+ items.each do |item|
212
+ counter = yield item
213
+
214
+ if item.changed?
215
+ counter.changed += 1
216
+ elsif item.branch.eql?(BASE)
217
+ counter.removed += 1
218
+ else
219
+ counter.new += 1
220
+ end
221
+ end
222
+ end
155
223
  end
156
224
  end
157
225
  end