pmdtester 1.0.1 → 1.1.0

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