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.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +12 -0
- data/.github/workflows/build.yml +7 -7
- data/.github/workflows/manual-integration-tests.yml +7 -7
- data/.github/workflows/publish-release.yml +9 -9
- data/.hoerc +1 -1
- data/.rubocop_todo.yml +1 -14
- data/.vscode/launch.json +32 -0
- data/History.md +47 -0
- data/Manifest.txt +13 -0
- data/README.rdoc +76 -30
- data/Rakefile +11 -11
- data/config/custom.jfc +1126 -0
- data/config/project-list-with-cpd.xml +268 -0
- data/config/projectlist_1_2_0.xsd +1 -1
- data/config/projectlist_1_3_0.xsd +53 -0
- data/lib/pmdtester/builders/cpd_project_hasher.rb +70 -0
- data/lib/pmdtester/builders/liquid_renderer.rb +111 -16
- data/lib/pmdtester/builders/pmd_report_builder.rb +133 -37
- data/lib/pmdtester/builders/project_hasher.rb +24 -25
- data/lib/pmdtester/builders/rule_set_builder.rb +2 -0
- data/lib/pmdtester/builders/summary_report_builder.rb +6 -1
- data/lib/pmdtester/cmd.rb +16 -7
- data/lib/pmdtester/cpd_report_diff.rb +99 -0
- data/lib/pmdtester/jfr_summary.rb +119 -0
- data/lib/pmdtester/location.rb +38 -0
- data/lib/pmdtester/parsers/cpd_report_document.rb +241 -0
- data/lib/pmdtester/parsers/options.rb +19 -0
- data/lib/pmdtester/parsers/pmd_report_document.rb +14 -1
- data/lib/pmdtester/parsers/projects_parser.rb +1 -1
- data/lib/pmdtester/pmd_branch_detail.rb +29 -9
- data/lib/pmdtester/pmd_report_detail.rb +54 -13
- data/lib/pmdtester/pmd_tester_utils.rb +45 -17
- data/lib/pmdtester/pmd_violation.rb +15 -6
- data/lib/pmdtester/project.rb +63 -3
- data/lib/pmdtester/report_diff.rb +5 -13
- data/lib/pmdtester/runner.rb +185 -37
- data/lib/pmdtester/system_info.rb +58 -0
- data/lib/pmdtester/word_differ.rb +132 -0
- data/lib/pmdtester.rb +8 -1
- data/pmdtester.gemspec +17 -17
- data/resources/css/pmd-tester.css +15 -0
- data/resources/js/project-report.js +293 -112
- data/resources/project_cpd_report.html +144 -0
- data/resources/project_diff_report.html +151 -18
- data/resources/project_index.html +12 -3
- data/resources/project_pmd_report.html +17 -2
- 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
|
-
(
|
|
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
|
-
|
|
88
|
+
location.beginline
|
|
85
89
|
end
|
|
86
90
|
|
|
87
91
|
def eql?(other)
|
|
88
92
|
rule_name.eql?(other.rule_name) &&
|
|
89
|
-
|
|
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
|
-
[
|
|
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
|
data/lib/pmdtester/project.rb
CHANGED
|
@@ -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)}/
|
|
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
|
-
:
|
|
59
|
-
:
|
|
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:
|
|
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
|
-
@
|
|
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
|
data/lib/pmdtester/runner.rb
CHANGED
|
@@ -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
|
-
|
|
43
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
157
|
-
|
|
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
|