pmdtester 1.6.2 → 1.7.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +12 -0
  3. data/.github/workflows/build.yml +7 -7
  4. data/.github/workflows/manual-integration-tests.yml +7 -7
  5. data/.github/workflows/publish-release.yml +9 -9
  6. data/.hoerc +1 -1
  7. data/.rubocop_todo.yml +1 -14
  8. data/.vscode/launch.json +32 -0
  9. data/History.md +47 -0
  10. data/Manifest.txt +13 -0
  11. data/README.rdoc +76 -30
  12. data/Rakefile +11 -11
  13. data/config/custom.jfc +1126 -0
  14. data/config/project-list-with-cpd.xml +268 -0
  15. data/config/projectlist_1_2_0.xsd +1 -1
  16. data/config/projectlist_1_3_0.xsd +53 -0
  17. data/lib/pmdtester/builders/cpd_project_hasher.rb +70 -0
  18. data/lib/pmdtester/builders/liquid_renderer.rb +111 -16
  19. data/lib/pmdtester/builders/pmd_report_builder.rb +133 -37
  20. data/lib/pmdtester/builders/project_hasher.rb +24 -25
  21. data/lib/pmdtester/builders/rule_set_builder.rb +2 -0
  22. data/lib/pmdtester/builders/summary_report_builder.rb +6 -1
  23. data/lib/pmdtester/cmd.rb +16 -7
  24. data/lib/pmdtester/cpd_report_diff.rb +99 -0
  25. data/lib/pmdtester/jfr_summary.rb +119 -0
  26. data/lib/pmdtester/location.rb +38 -0
  27. data/lib/pmdtester/parsers/cpd_report_document.rb +241 -0
  28. data/lib/pmdtester/parsers/options.rb +19 -0
  29. data/lib/pmdtester/parsers/pmd_report_document.rb +14 -1
  30. data/lib/pmdtester/parsers/projects_parser.rb +1 -1
  31. data/lib/pmdtester/pmd_branch_detail.rb +29 -9
  32. data/lib/pmdtester/pmd_report_detail.rb +54 -13
  33. data/lib/pmdtester/pmd_tester_utils.rb +45 -17
  34. data/lib/pmdtester/pmd_violation.rb +15 -6
  35. data/lib/pmdtester/project.rb +63 -3
  36. data/lib/pmdtester/report_diff.rb +5 -13
  37. data/lib/pmdtester/runner.rb +185 -37
  38. data/lib/pmdtester/system_info.rb +58 -0
  39. data/lib/pmdtester/word_differ.rb +132 -0
  40. data/lib/pmdtester.rb +8 -1
  41. data/pmdtester.gemspec +17 -17
  42. data/resources/css/pmd-tester.css +15 -0
  43. data/resources/js/project-report.js +293 -112
  44. data/resources/project_cpd_report.html +144 -0
  45. data/resources/project_diff_report.html +151 -18
  46. data/resources/project_index.html +12 -3
  47. data/resources/project_pmd_report.html +17 -2
  48. metadata +63 -43
@@ -27,16 +27,18 @@ module PmdTester
27
27
  # </xs:simpleContent>
28
28
  # </xs:complexType>
29
29
 
30
- attr_reader :fname, :info_url, :line, :old_line, :old_message, :rule_name, :ruleset_name, :language
30
+ attr_reader :fname, :info_url, :line, :old_line, :old_message, :rule_name, :ruleset_name, :language, :location,
31
+ :old_location
31
32
  attr_accessor :message
32
33
 
33
- def initialize(branch:, fname:, info_url:, bline:, rule_name:, ruleset_name:)
34
+ def initialize(branch:, fname:, info_url:, bline:, rule_name:, ruleset_name:, location:)
34
35
  @branch = branch
35
36
  @fname = fname
36
37
  @message = ''
37
38
 
38
39
  @info_url = info_url
39
40
  @line = bline
41
+ @location = location
40
42
  @rule_name = rule_name
41
43
 
42
44
  @ruleset_name = ruleset_name
@@ -46,6 +48,7 @@ module PmdTester
46
48
  @changed = false
47
49
  @old_message = nil
48
50
  @old_line = nil
51
+ @old_location = nil
49
52
  end
50
53
 
51
54
  def line_move?(other)
@@ -55,10 +58,11 @@ module PmdTester
55
58
  def try_merge?(other)
56
59
  if branch != BASE && branch != other.branch && rule_name == other.rule_name &&
57
60
  !changed? && # not already changed
58
- (line == other.line || line_move?(other))
61
+ (location.eql?(other.location) || location_move?(other))
59
62
  @changed = true
60
63
  @old_message = other.message
61
64
  @old_line = other.line
65
+ @old_location = other.location
62
66
  true
63
67
  else
64
68
  false
@@ -81,18 +85,18 @@ module PmdTester
81
85
  end
82
86
 
83
87
  def sort_key
84
- line
88
+ location.beginline
85
89
  end
86
90
 
87
91
  def eql?(other)
88
92
  rule_name.eql?(other.rule_name) &&
89
- line.eql?(other.line) &&
93
+ location.eql?(other.location) &&
90
94
  fname.eql?(other.fname) &&
91
95
  message.eql?(other.message)
92
96
  end
93
97
 
94
98
  def hash
95
- [line, rule_name, message].hash
99
+ [location, rule_name, message].hash
96
100
  end
97
101
 
98
102
  def to_liquid
@@ -109,5 +113,10 @@ module PmdTester
109
113
  m = @info_url.match(/pmd_rules_(\w+)_/)
110
114
  m[1]
111
115
  end
116
+
117
+ def location_move?(other)
118
+ message.eql?(other.message) &&
119
+ (location.beginline - other.location.beginline).abs <= 5
120
+ end
112
121
  end
113
122
  end
@@ -17,11 +17,13 @@ module PmdTester
17
17
  attr_reader :exclude_patterns
18
18
  attr_reader :src_subpath
19
19
  attr_accessor :report_diff
20
+ attr_accessor :cpd_report_diff
20
21
  # key: pmd branch name as String => value: local path of pmd report
21
22
  attr_reader :build_command
22
23
  attr_reader :auxclasspath_command
23
24
  # stores the auxclasspath calculated after cloning/preparing the project
24
25
  attr_accessor :auxclasspath
26
+ attr_reader :cpd_options
25
27
 
26
28
  def initialize(project) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
27
29
  @name = project.at_xpath('name').text
@@ -44,6 +46,7 @@ module PmdTester
44
46
  @auxclasspath_command = project.at_xpath('auxclasspath-command')&.text
45
47
 
46
48
  @report_diff = nil
49
+ @cpd_options = CpdOptions.new(project.at_xpath('cpd-options'))
47
50
  end
48
51
 
49
52
  # Generate the default webview url for the projects
@@ -83,11 +86,27 @@ module PmdTester
83
86
  end
84
87
  end
85
88
 
89
+ def get_cpd_report_path(branch_name)
90
+ if branch_name.nil?
91
+ nil
92
+ else
93
+ "#{get_project_target_dir(branch_name)}/cpd_report.xml"
94
+ end
95
+ end
96
+
86
97
  def get_report_info_path(branch_name)
87
98
  if branch_name.nil?
88
99
  nil
89
100
  else
90
- "#{get_project_target_dir(branch_name)}/report_info.json"
101
+ "#{get_project_target_dir(branch_name)}/pmd_report_info.json"
102
+ end
103
+ end
104
+
105
+ def get_cpd_report_info_path(branch_name)
106
+ if branch_name.nil?
107
+ nil
108
+ else
109
+ "#{get_project_target_dir(branch_name)}/cpd_report_info.json"
91
110
  end
92
111
  end
93
112
 
@@ -119,15 +138,56 @@ module PmdTester
119
138
  dir
120
139
  end
121
140
 
122
- def compute_report_diff(base_branch, patch_branch, filter_set)
141
+ def compute_report_diff(base_branch, patch_branch, filter_set, rules_changed:, impl_changed:)
123
142
  self.report_diff = build_report_diff(get_pmd_report_path(base_branch),
124
143
  get_pmd_report_path(patch_branch),
125
144
  get_report_info_path(base_branch),
126
145
  get_report_info_path(patch_branch),
127
- filter_set)
146
+ filter_set, rules_changed: rules_changed)
128
147
 
129
148
  report_diff.base_report.report_folder = get_project_target_dir(base_branch)
130
149
  report_diff.patch_report.report_folder = get_project_target_dir(patch_branch)
150
+
151
+ self.cpd_report_diff = build_cpd_report_diff(get_cpd_report_path(base_branch),
152
+ get_cpd_report_path(patch_branch),
153
+ get_cpd_report_info_path(base_branch),
154
+ get_cpd_report_info_path(patch_branch),
155
+ impl_changed: impl_changed)
156
+ end
157
+
158
+ # Containts Cpd specific options from project-list.xml
159
+ class CpdOptions
160
+ attr_reader :language
161
+ attr_reader :minimum_tokens
162
+ attr_reader :max_memory
163
+ attr_reader :directories
164
+
165
+ def initialize(cpd_options_element)
166
+ # default values
167
+ @language = 'java'
168
+ @minimum_tokens = 150
169
+ @max_memory = '5g'
170
+ @directories = ['.']
171
+
172
+ return if cpd_options_element.nil?
173
+
174
+ @language = parse_text(cpd_options_element, 'language', @language)
175
+ @minimum_tokens = parse_text(cpd_options_element, 'minimum-tokens', @minimum_tokens).to_i
176
+ @max_memory = parse_text(cpd_options_element, 'max-memory', @max_memory)
177
+ @directories = parse_directories(cpd_options_element, @directories)
178
+ end
179
+
180
+ private
181
+
182
+ def parse_text(cpd_options_element, element_name, default_value)
183
+ element = cpd_options_element.at_xpath(element_name)
184
+ element.nil? ? default_value : element.text
185
+ end
186
+
187
+ def parse_directories(cpd_options_element, default_value)
188
+ directories = cpd_options_element.xpath('directories/directory').map(&:text)
189
+ directories.empty? ? default_value : directories
190
+ end
131
191
  end
132
192
  end
133
193
  end
@@ -55,28 +55,20 @@ module PmdTester
55
55
  attr_reader :violations_by_file,
56
56
  :errors_by_file,
57
57
  :configerrors_by_rule,
58
- :exec_time,
59
- :timestamp,
60
- :exit_code,
61
- :file
58
+ :file,
59
+ :report_details
62
60
 
63
61
  attr_accessor :report_folder
64
62
 
65
- def initialize(report_document: nil,
66
- file: '',
67
- exec_time: 0,
68
- timestamp: '0',
69
- exit_code: '?')
63
+ def initialize(report_details:, report_document:, file:)
70
64
  initialize_empty
71
65
  initialize_with_report_document report_document unless report_document.nil?
72
- @exec_time = exec_time
73
- @timestamp = timestamp
66
+ @report_details = report_details
74
67
  @file = file
75
- @exit_code = exit_code
76
68
  end
77
69
 
78
70
  def self.empty
79
- new
71
+ new(report_details: PmdTester::PmdReportDetail.empty, report_document: nil, file: '')
80
72
  end
81
73
 
82
74
  def rule_summaries
@@ -22,7 +22,14 @@ module PmdTester
22
22
  run_single_mode
23
23
  end
24
24
 
25
- summarize_diffs
25
+ summary = summarize_diffs
26
+ unless @options.html_flag
27
+ FileUtils.mkdir_p('target/reports/diff') unless File.directory?('target/reports/diff')
28
+ File.write('target/reports/diff/summary.txt', Runner.create_message(@options.base_branch, summary))
29
+ File.write('target/reports/diff/conclusion.txt', Runner.determine_conclusion(summary))
30
+ end
31
+
32
+ summary
26
33
  end
27
34
 
28
35
  def clean
@@ -33,16 +40,27 @@ module PmdTester
33
40
  def run_local_mode
34
41
  logger.info "Mode: #{@options.mode}"
35
42
  get_projects(@options.project_list) unless @options.nil?
36
- if @options.auto_config_flag
37
- run_required = RuleSetBuilder.new(@options).build?
38
- logger.debug "Run required: #{run_required}"
39
- return unless run_required
40
- end
41
43
 
42
- base_branch_details = create_pmd_report(config: @options.base_config, branch: @options.base_branch)
43
- patch_branch_details = create_pmd_report(config: @options.patch_config, branch: @options.patch_branch)
44
+ rules_changed = true
45
+ rules_changed = RuleSetBuilder.new(@options).build? if @options.auto_config_flag
46
+ impl_changed = determine_impl_changed?
47
+
48
+ base_branch_details = create_pmd_report(config: @options.base_config, branch: @options.base_branch,
49
+ rules_changed: rules_changed, impl_changed: impl_changed)
50
+ # copy list of projects file to the base baseline
51
+ base_branch_dir = File.dirname(base_branch_details.target_branch_project_list_path)
52
+ FileUtils.mkdir_p(base_branch_dir) unless File.directory?(base_branch_dir)
53
+ FileUtils.cp(@options.project_list, base_branch_details.target_branch_project_list_path)
54
+
55
+ patch_branch_details = create_pmd_report(config: @options.patch_config, branch: @options.patch_branch,
56
+ rules_changed: rules_changed, impl_changed: impl_changed)
57
+ # copy list of projects file to the patch baseline
58
+ patch_branch_dir = File.dirname(patch_branch_details.target_branch_project_list_path)
59
+ FileUtils.mkdir_p(patch_branch_dir) unless File.directory?(patch_branch_dir)
60
+ FileUtils.cp(@options.project_list, patch_branch_details.target_branch_project_list_path)
44
61
 
45
- build_html_reports(@projects, base_branch_details, patch_branch_details)
62
+ build_html_reports(@projects, base_branch_details, patch_branch_details, nil,
63
+ rules_changed: rules_changed, impl_changed: impl_changed)
46
64
  end
47
65
 
48
66
  def run_online_mode
@@ -53,23 +71,27 @@ module PmdTester
53
71
  project_list = determine_project_list_for_online_mode(baseline_path)
54
72
  get_projects(project_list)
55
73
 
56
- if @options.auto_config_flag
57
- logger.info 'Autogenerating a dynamic ruleset based on source changes'
58
- return unless RuleSetBuilder.new(@options).build?
59
- elsif @options.patch_config == Options::DEFAULT_CONFIG_PATH
60
- # patch branch build pmd reports with same configuration as base branch
61
- # if not specified otherwise. This allows to use a different config (e.g. less rules)
62
- # than used for creating the baseline. Use with care, though
63
- @options.patch_config = "#{baseline_path}/config.xml"
64
- else
65
- logger.info "Using config #{@options.patch_config} which might differ from baseline"
66
- RuleSetBuilder.new(@options).calculate_filter_set if @options.filter_with_patch_config
74
+ rules_changed = determine_rule_changed_in_online_mode?(baseline_path)
75
+ impl_changed = determine_impl_changed?
76
+
77
+ # When neither PMD nor CPD is executed, we can abort directly
78
+ # No report will be generated then
79
+ if !rules_changed && !impl_changed
80
+ @skipped = true
81
+ logger.info 'No relevant source code has been changed, pmdtester skipped.'
82
+ return
67
83
  end
68
84
 
69
- patch_branch_details = create_pmd_report(config: @options.patch_config, branch: @options.patch_branch)
85
+ patch_branch_details = create_pmd_report(config: @options.patch_config, branch: @options.patch_branch,
86
+ rules_changed: rules_changed, impl_changed: impl_changed)
87
+ # copy list of projects file to the patch baseline
88
+ patch_branch_dir = File.dirname(patch_branch_details.target_branch_project_list_path)
89
+ FileUtils.mkdir_p(patch_branch_dir) unless File.directory?(patch_branch_dir)
90
+ FileUtils.cp(project_list, patch_branch_details.target_branch_project_list_path)
70
91
 
71
92
  base_branch_details = PmdBranchDetail.load(@options.base_branch, logger)
72
- build_html_reports(@projects, base_branch_details, patch_branch_details, @options.filter_set)
93
+ build_html_reports(@projects, base_branch_details, patch_branch_details, @options.filter_set,
94
+ rules_changed: rules_changed, impl_changed: impl_changed)
73
95
  end
74
96
 
75
97
  def determine_project_list_for_online_mode(baseline_path)
@@ -101,6 +123,8 @@ module PmdTester
101
123
  Cmd.execute_successfully(unzip_cmd)
102
124
  end
103
125
 
126
+ make_baseline_compatible("#{target_path}/#{branch_filename}")
127
+
104
128
  "#{target_path}/#{branch_filename}"
105
129
  end
106
130
 
@@ -112,8 +136,11 @@ module PmdTester
112
136
  logger.info "Mode: #{@options.mode}"
113
137
 
114
138
  get_projects(@options.project_list) unless @options.nil?
115
- patch_branch_details = create_pmd_report(config: @options.patch_config, branch: @options.patch_branch)
139
+ patch_branch_details = create_pmd_report(config: @options.patch_config, branch: @options.patch_branch,
140
+ rules_changed: true, impl_changed: true)
116
141
  # copy list of projects file to the patch baseline
142
+ patch_branch_dir = File.dirname(patch_branch_details.target_branch_project_list_path)
143
+ FileUtils.mkdir_p(patch_branch_dir) unless File.directory?(patch_branch_dir)
117
144
  FileUtils.cp(@options.project_list, patch_branch_details.target_branch_project_list_path)
118
145
 
119
146
  # for creating a baseline, no html report is needed
@@ -121,7 +148,8 @@ module PmdTester
121
148
 
122
149
  # in single mode, we don't have a base branch, only a patch branch...
123
150
  empty_base_branch_details = PmdBranchDetail.load('single-mode', logger)
124
- build_html_reports(@projects, empty_base_branch_details, patch_branch_details)
151
+ build_html_reports(@projects, empty_base_branch_details, patch_branch_details, nil,
152
+ rules_changed: true, impl_changed: true)
125
153
  end
126
154
 
127
155
  def get_projects(file_path)
@@ -129,32 +157,152 @@ module PmdTester
129
157
  end
130
158
 
131
159
  def summarize_diffs
132
- error_total = RunningDiffCounters.new(0)
133
- violations_total = RunningDiffCounters.new(0)
134
- configerrors_total = RunningDiffCounters.new(0)
160
+ pmd_error_total = RunningDiffCounters.new(0)
161
+ pmd_violations_total = RunningDiffCounters.new(0)
162
+ pmd_configerrors_total = RunningDiffCounters.new(0)
163
+ cpd_error_total = RunningDiffCounters.new(0)
164
+ cpd_duplications_total = RunningDiffCounters.new(0)
135
165
 
136
166
  @projects.each do |project|
137
167
  diff = project.report_diff
168
+ cpd_diff = project.cpd_report_diff
138
169
 
139
170
  # in case we are in single mode, there might be no diffs (only the patch branch is available)
140
- next if diff.nil?
141
-
142
- error_total.merge!(diff.error_counts)
143
- violations_total.merge!(diff.violation_counts)
144
- configerrors_total.merge!(diff.configerror_counts)
171
+ # or if only pmd or only cpd is has been run
172
+ sum_pmd_counters(diff, pmd_error_total, pmd_violations_total, pmd_configerrors_total) unless diff.nil?
173
+ sum_cpd_counters(cpd_diff, cpd_error_total, cpd_duplications_total) unless cpd_diff.nil?
145
174
  end
146
175
 
147
176
  {
148
- errors: error_total.to_h,
149
- violations: violations_total.to_h,
150
- configerrors: configerrors_total.to_h
177
+ pmd_errors: pmd_error_total.to_h,
178
+ pmd_violations: pmd_violations_total.to_h,
179
+ pmd_configerrors: pmd_configerrors_total.to_h,
180
+ cpd_errors: cpd_error_total.to_h,
181
+ cpd_duplications: cpd_duplications_total.to_h,
182
+ skipped: @skipped
151
183
  }
152
184
  end
153
185
 
186
+ def self.create_message(base_branch, summary)
187
+ return 'No relevant source code has been changed, pmdtester skipped.' if summary[:skipped]
188
+
189
+ "Compared to #{base_branch}:\n" \
190
+ 'This changeset ' \
191
+ "changes #{summary[:pmd_violations][:changed]} violations,\n" \
192
+ "introduces #{summary[:pmd_violations][:new]} new violations, " \
193
+ "#{summary[:pmd_errors][:new]} new errors and " \
194
+ "#{summary[:pmd_configerrors][:new]} new configuration errors,\n" \
195
+ "removes #{summary[:pmd_violations][:removed]} violations, " \
196
+ "#{summary[:pmd_errors][:removed]} errors and " \
197
+ "#{summary[:pmd_configerrors][:removed]} configuration errors.\n" \
198
+ "There are #{summary[:cpd_duplications][:changed]} changed duplications, " \
199
+ "#{summary[:cpd_duplications][:new]} new duplications and " \
200
+ "#{summary[:cpd_duplications][:removed]} removed duplications.\n" \
201
+ "There are #{summary[:cpd_errors][:changed]} changed CPD errors, " \
202
+ "#{summary[:cpd_errors][:new]} new CPD errors and " \
203
+ "#{summary[:cpd_errors][:removed]} removed CPD errors.\n"
204
+ end
205
+
206
+ def self.determine_conclusion(summary)
207
+ return 'skipped' if summary[:skipped]
208
+
209
+ total_changes = summary[:pmd_violations][:changed] \
210
+ + summary[:pmd_violations][:new] \
211
+ + summary[:pmd_violations][:removed] \
212
+ + summary[:pmd_errors][:new] \
213
+ + summary[:pmd_configerrors][:new] \
214
+ + summary[:cpd_duplications][:changed] \
215
+ + summary[:cpd_duplications][:new] \
216
+ + summary[:cpd_duplications][:removed] \
217
+ + summary[:cpd_errors][:new]
218
+ if total_changes.positive?
219
+ 'neutral'
220
+ else
221
+ 'success'
222
+ end
223
+ end
224
+
154
225
  private
155
226
 
156
- def create_pmd_report(config:, branch:)
157
- PmdReportBuilder.new(@projects, @options, config, branch).build
227
+ def sum_pmd_counters(diff, pmd_error_total, pmd_violations_total, pmd_configerrors_total)
228
+ pmd_error_total.merge!(diff.error_counts)
229
+ pmd_violations_total.merge!(diff.violation_counts)
230
+ pmd_configerrors_total.merge!(diff.configerror_counts)
231
+ end
232
+
233
+ def sum_cpd_counters(diff, cpd_error_total, cpd_duplications_total)
234
+ cpd_error_total.merge!(diff.error_counts)
235
+ cpd_duplications_total.merge!(diff.duplication_counts)
236
+ end
237
+
238
+ def create_pmd_report(config:, branch:, rules_changed:, impl_changed:)
239
+ PmdReportBuilder.new(@projects, @options, config, branch)
240
+ .with_changes(rules_changed, impl_changed)
241
+ .build
242
+ end
243
+
244
+ # for compatibility with old baselines, create empty CPD reports if they are missing in the baseline
245
+ # also create empty JFR recording files.
246
+ # this allows to run the regression tester with baselines created with an old regression tester version
247
+ def make_baseline_compatible(branch_path)
248
+ Dir.each_child(branch_path) do |project_path|
249
+ next unless File.directory?("#{branch_path}/#{project_path}")
250
+
251
+ unless File.exist?("#{branch_path}/#{project_path}/cpd_report_info.json")
252
+ File.write("#{branch_path}/#{project_path}/cpd_report_info.json", '{}')
253
+ end
254
+ unless File.exist?("#{branch_path}/#{project_path}/cpd_report.xml")
255
+ FileUtils.touch("#{branch_path}/#{project_path}/cpd_report.xml")
256
+ end
257
+ unless File.exist?("#{branch_path}/#{project_path}/pmd_report_info.json")
258
+ FileUtils.cp("#{branch_path}/#{project_path}/report_info.json",
259
+ "#{branch_path}/#{project_path}/pmd_report_info.json")
260
+ end
261
+ add_jfr_recording_for_old_baseline("#{branch_path}/#{project_path}", true)
262
+ add_jfr_recording_for_old_baseline("#{branch_path}/#{project_path}", false)
263
+ end
264
+ end
265
+
266
+ def add_jfr_recording_for_old_baseline(path, pmd_or_cpd)
267
+ prefix = pmd_or_cpd ? 'pmd' : 'cpd'
268
+ recording_path = "#{path}/#{prefix}_recording.jfr"
269
+ return if File.exist?(recording_path)
270
+
271
+ FileUtils.touch(recording_path)
272
+ report_info = JSON.parse(File.read("#{path}/#{prefix}_report_info.json"))
273
+ report_info['jfr_summary'] = { 'recording_path' => recording_path }
274
+ File.write("#{path}/#{prefix}_report_info.json", JSON.pretty_generate(report_info))
275
+ end
276
+
277
+ def determine_impl_changed?
278
+ filenames = nil
279
+ Dir.chdir(@options.local_git_repo) do
280
+ base = @options.base_branch
281
+ patch = @options.patch_branch
282
+
283
+ # We only need to support git here, since PMD's repo is using git.
284
+ diff_cmd = "git diff --name-only #{base}..#{patch}"
285
+ filenames = Cmd.execute_successfully(diff_cmd)
286
+ end
287
+ result = filenames.split("\n").any? { |filename| filename.include?('/src/main/') }
288
+ logger.debug "PMD impl changed: #{result}"
289
+ result
290
+ end
291
+
292
+ def determine_rule_changed_in_online_mode?(baseline_path)
293
+ rules_changed = true
294
+ if @options.auto_config_flag
295
+ rules_changed = RuleSetBuilder.new(@options).build?
296
+ elsif @options.patch_config == Options::DEFAULT_CONFIG_PATH
297
+ # patch branch build pmd reports with same configuration as base branch
298
+ # if not specified otherwise. This allows to use a different config (e.g. less rules)
299
+ # than used for creating the baseline. Use with care, though
300
+ @options.patch_config = "#{baseline_path}/config.xml"
301
+ else
302
+ logger.info "Using config #{@options.patch_config} which might differ from baseline"
303
+ RuleSetBuilder.new(@options).calculate_filter_set if @options.filter_with_patch_config
304
+ end
305
+ rules_changed
158
306
  end
159
307
  end
160
308
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PmdTester
4
+ # Utility to deal with semantic versions
5
+ class SystemInfo
6
+ def physical_memory
7
+ meminfo = File.read('/proc/meminfo')
8
+ mem_total_line = meminfo.lines.find { |line| line.start_with?('MemTotal:') }
9
+ mem_total_kb = mem_total_line.split[1].to_i # Get the value in kB
10
+ mem_total_gb = mem_total_kb / 1024.0 / 1024.0 # Convert kB to GB
11
+ mem_total_gb = mem_total_gb.round(1)
12
+ "#{mem_total_gb} GB"
13
+ end
14
+
15
+ def uname
16
+ info = Etc.uname
17
+ "#{info[:sysname]} #{info[:release]}"
18
+ end
19
+
20
+ def cpu_info
21
+ cpuinfo = File.read('/proc/cpuinfo')
22
+ cpus = parse_cpuinfo(cpuinfo)
23
+ model_name = cpus.map { |cpu| cpu[:model_name] }.uniq.join(', ')
24
+ sockets = cpus.map { |cpu| cpu[:physical_id] }.uniq.count
25
+ cores = cpus.map { |cpu| cpu[:core_id] }.uniq.count
26
+ threads = cpus.count
27
+
28
+ "#{model_name} (sockets: #{sockets}, cores: #{cores}, hardware threads: #{threads})"
29
+ end
30
+
31
+ private
32
+
33
+ def parse_cpuinfo(cpuinfo)
34
+ cpus = []
35
+ cpu_index = -1
36
+
37
+ cpuinfo.lines.each do |line|
38
+ if line.start_with?('processor')
39
+ cpu_index = line.split(':')[1].strip.to_i
40
+ cpus[cpu_index] = {}
41
+ end
42
+ if line.start_with?('model name')
43
+ model_name = line.split(':')[1].strip
44
+ cpus[cpu_index][:model_name] = model_name
45
+ end
46
+ if line.start_with?('physical id')
47
+ physical_id = line.split(':')[1].strip.to_i
48
+ cpus[cpu_index][:physical_id] = physical_id
49
+ end
50
+ if line.start_with?('core id')
51
+ core_id = line.split(':')[1].strip.to_i
52
+ cpus[cpu_index][:core_id] = core_id
53
+ end
54
+ end
55
+ cpus
56
+ end
57
+ end
58
+ end