pmdtester 1.1.2 → 1.4.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/.ci/build.sh +87 -55
- data/.ci/inc/fetch_ci_scripts.bash +19 -0
- data/.ci/manual-integration-tests.sh +31 -14
- data/.github/workflows/build.yml +26 -9
- data/.github/workflows/manual-integration-tests.yml +24 -8
- data/.hoerc +1 -1
- data/.rubocop.yml +3 -0
- data/History.md +31 -0
- data/Manifest.txt +4 -2
- data/README.rdoc +26 -25
- data/Rakefile +11 -6
- data/config/project-list.xml +8 -7
- data/config/projectlist_1_2_0.xsd +39 -0
- data/lib/pmdtester/builders/liquid_renderer.rb +60 -3
- data/lib/pmdtester/builders/pmd_report_builder.rb +9 -3
- data/lib/pmdtester/builders/project_builder.rb +14 -9
- data/lib/pmdtester/builders/project_hasher.rb +41 -39
- data/lib/pmdtester/builders/rule_set_builder.rb +116 -75
- data/lib/pmdtester/builders/summary_report_builder.rb +1 -0
- data/lib/pmdtester/parsers/options.rb +5 -0
- data/lib/pmdtester/parsers/pmd_report_document.rb +1 -3
- data/lib/pmdtester/parsers/projects_parser.rb +1 -1
- data/lib/pmdtester/pmd_branch_detail.rb +6 -0
- data/lib/pmdtester/pmd_tester_utils.rb +1 -0
- data/lib/pmdtester/pmd_violation.rb +11 -1
- data/lib/pmdtester/project.rb +24 -11
- data/lib/pmdtester/report_diff.rb +20 -4
- data/lib/pmdtester/runner.rb +8 -3
- data/lib/pmdtester/semver.rb +36 -0
- data/lib/pmdtester.rb +2 -1
- data/pmdtester.gemspec +18 -18
- data/resources/css/pmd-tester.css +17 -1
- data/resources/js/code-snippets.js +48 -15
- data/resources/js/project-report.js +5 -3
- data/resources/project_diff_report.html +9 -0
- data/resources/project_index.html +11 -0
- data/resources/project_pmd_report.html +186 -0
- metadata +26 -30
- data/.ci/files/env.gpg +0 -1
- data/.ci/inc/install-openjdk.inc +0 -26
@@ -57,17 +57,74 @@ module PmdTester
|
|
57
57
|
write_file("#{root}/index.html", render_liquid('project_diff_report.html', liquid_env))
|
58
58
|
# generate array of violations in json
|
59
59
|
write_file("#{root}/project_data.js", dump_violations_json(project))
|
60
|
+
# copy original pmd reports
|
61
|
+
copy_file("#{root}/base_pmd_report.xml", project.report_diff.base_report.file)
|
62
|
+
copy_file("#{root}/patch_pmd_report.xml", project.report_diff.patch_report.file)
|
63
|
+
# render full pmd reports
|
64
|
+
write_file("#{root}/base_pmd_report.html",
|
65
|
+
render_liquid('project_pmd_report.html', pmd_report_liquid_env(project, BASE)))
|
66
|
+
write_file("#{root}/base_data.js", dump_violations_json(project, BASE))
|
67
|
+
write_file("#{root}/patch_pmd_report.html",
|
68
|
+
render_liquid('project_pmd_report.html', pmd_report_liquid_env(project, PATCH)))
|
69
|
+
write_file("#{root}/patch_data.js", dump_violations_json(project, PATCH))
|
60
70
|
end
|
61
71
|
|
62
|
-
def dump_violations_json(project)
|
72
|
+
def dump_violations_json(project, branch = 'diff')
|
73
|
+
violations_by_file = if branch == BASE
|
74
|
+
project.report_diff.base_report.violations_by_file.to_h
|
75
|
+
elsif branch == PATCH
|
76
|
+
project.report_diff.patch_report.violations_by_file.to_h
|
77
|
+
else
|
78
|
+
project.report_diff.violation_diffs_by_file
|
79
|
+
end
|
80
|
+
|
63
81
|
h = {
|
64
82
|
'source_link_base' => project.webview_url,
|
65
83
|
'source_link_template' => link_template(project),
|
66
|
-
**violations_to_hash(project)
|
84
|
+
**violations_to_hash(project, violations_by_file, branch == 'diff')
|
67
85
|
}
|
68
86
|
|
69
|
-
project_data = JSON.fast_generate(h)
|
87
|
+
project_data = JSON.fast_generate(h, indent: ' ', object_nl: "\n", array_nl: "\n")
|
70
88
|
"let project = #{project_data}"
|
71
89
|
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def copy_file(target_file, source_file)
|
94
|
+
if File.exist? source_file
|
95
|
+
FileUtils.cp(source_file, target_file)
|
96
|
+
logger&.info "Written #{target_file}"
|
97
|
+
else
|
98
|
+
logger&.warn "File #{source_file} not found"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def pmd_report_liquid_env(project, branch)
|
103
|
+
report = if branch == BASE
|
104
|
+
project.report_diff.base_report
|
105
|
+
else
|
106
|
+
project.report_diff.patch_report
|
107
|
+
end
|
108
|
+
{
|
109
|
+
'project_name' => project.name,
|
110
|
+
'branch' => branch,
|
111
|
+
'report' => report_to_h(project, report)
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
def report_to_h(project, report)
|
116
|
+
{
|
117
|
+
'violation_counts' => report.violations_by_file.total_size,
|
118
|
+
'error_counts' => report.errors_by_file.total_size,
|
119
|
+
'configerror_counts' => report.configerrors_by_rule.values.flatten.length,
|
120
|
+
|
121
|
+
'execution_time' => PmdReportDetail.convert_seconds(report.exec_time),
|
122
|
+
'timestamp' => report.timestamp,
|
123
|
+
|
124
|
+
'rules' => report.rule_summaries,
|
125
|
+
'errors' => report.errors_by_file.all_values.map { |e| error_to_hash(e, project) },
|
126
|
+
'configerrors' => report.configerrors_by_rule.values.flatten.map { |e| configerror_to_hash(e) }
|
127
|
+
}
|
128
|
+
end
|
72
129
|
end
|
73
130
|
end
|
@@ -69,7 +69,7 @@ module PmdTester
|
|
69
69
|
' -Dmaven.source.skip=true' \
|
70
70
|
' -Dcheckstyle.skip=true' \
|
71
71
|
' -Dpmd.skip=true' \
|
72
|
-
' -T1C'
|
72
|
+
' -T1C -B'
|
73
73
|
Cmd.execute(package_cmd)
|
74
74
|
end
|
75
75
|
|
@@ -97,11 +97,12 @@ module PmdTester
|
|
97
97
|
def generate_pmd_report(project)
|
98
98
|
error_recovery_options = @error_recovery ? 'PMD_JAVA_OPTS="-Dpmd.error_recovery -ea" ' : ''
|
99
99
|
run_path = "#{saved_distro_path(@pmd_branch_details.branch_last_sha)}/bin/run.sh"
|
100
|
+
fail_on_violation = should_use_long_cli_options ? '--fail-on-violation false' : '-failOnViolation false'
|
100
101
|
pmd_cmd = "#{error_recovery_options}" \
|
101
102
|
"#{run_path} pmd -d #{project.local_source_path} -f xml " \
|
102
103
|
"-R #{project.get_config_path(@pmd_branch_name)} " \
|
103
104
|
"-r #{project.get_pmd_report_path(@pmd_branch_name)} " \
|
104
|
-
"
|
105
|
+
"#{fail_on_violation} -t #{@threads} " \
|
105
106
|
"#{project.auxclasspath}"
|
106
107
|
start_time = Time.now
|
107
108
|
if File.exist?(project.get_pmd_report_path(@pmd_branch_name))
|
@@ -119,7 +120,7 @@ module PmdTester
|
|
119
120
|
doc = Nokogiri::XML(File.read(@branch_config))
|
120
121
|
ruleset = doc.at_css('ruleset')
|
121
122
|
ruleset.add_child("\n")
|
122
|
-
project.
|
123
|
+
project.exclude_patterns.each do |exclude_pattern|
|
123
124
|
ruleset.add_child(" <exclude-pattern>#{exclude_pattern}</exclude-pattern>\n")
|
124
125
|
end
|
125
126
|
|
@@ -194,5 +195,10 @@ module PmdTester
|
|
194
195
|
def wd_has_dirty_git_changes
|
195
196
|
!Cmd.execute('git status --porcelain').empty?
|
196
197
|
end
|
198
|
+
|
199
|
+
def should_use_long_cli_options
|
200
|
+
logger.debug "PMD Version: #{@pmd_version}"
|
201
|
+
Semver.compare(@pmd_version, '6.41.0') >= 0
|
202
|
+
end
|
197
203
|
end
|
198
204
|
end
|
@@ -17,11 +17,19 @@ module PmdTester
|
|
17
17
|
|
18
18
|
@projects.each do |project|
|
19
19
|
logger.info "Start cloning #{project.name} repository"
|
20
|
-
path = project.
|
21
|
-
|
20
|
+
path = project.clone_root_path
|
21
|
+
|
22
22
|
if File.exist?(path)
|
23
23
|
logger.warn "Skipping clone, project path #{path} already exists"
|
24
24
|
else
|
25
|
+
raise "Unsupported project type '#{project.type}' - only git is supported" unless project.type == 'git'
|
26
|
+
|
27
|
+
# git:
|
28
|
+
# Don't download whole history
|
29
|
+
# Note we don't use --single-branch, because the repo is downloaded
|
30
|
+
# once but may be used with several tags.
|
31
|
+
clone_cmd = "git clone --no-single-branch --depth 1 #{project.connection} #{path}"
|
32
|
+
|
25
33
|
Cmd.execute(clone_cmd)
|
26
34
|
end
|
27
35
|
|
@@ -38,7 +46,7 @@ module PmdTester
|
|
38
46
|
logger.info 'Building projects started'
|
39
47
|
|
40
48
|
@projects.each do |project|
|
41
|
-
path = project.
|
49
|
+
path = project.clone_root_path
|
42
50
|
Dir.chdir(path) do
|
43
51
|
progress_logger = SimpleProgressLogger.new("building #{project.name} in #{path}")
|
44
52
|
progress_logger.start
|
@@ -87,12 +95,9 @@ module PmdTester
|
|
87
95
|
end
|
88
96
|
|
89
97
|
def execute_reset_cmd(type, tag)
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
when 'hg'
|
94
|
-
reset_cmd = "hg up #{tag}"
|
95
|
-
end
|
98
|
+
raise "Unsupported project type '#{type}' - only git is supported" unless type == 'git'
|
99
|
+
|
100
|
+
reset_cmd = "git checkout #{tag}; git reset --hard #{tag}"
|
96
101
|
|
97
102
|
Cmd.execute(reset_cmd)
|
98
103
|
end
|
@@ -25,24 +25,14 @@ module PmdTester
|
|
25
25
|
}
|
26
26
|
end
|
27
27
|
|
28
|
-
def
|
29
|
-
errors = project.report_diff.error_diffs_by_file.values.flatten
|
30
|
-
errors.map { |e| error_to_hash(e, project) }
|
31
|
-
end
|
32
|
-
|
33
|
-
def configerrors_to_h(project)
|
34
|
-
configerrors = project.report_diff.configerror_diffs_by_rule.values.flatten
|
35
|
-
configerrors.map { |e| configerror_to_hash(e) }
|
36
|
-
end
|
37
|
-
|
38
|
-
def violations_to_hash(project)
|
28
|
+
def violations_to_hash(project, violations_by_file, is_diff)
|
39
29
|
filename_index = []
|
40
30
|
all_vs = []
|
41
|
-
|
31
|
+
violations_by_file.each do |file, vs|
|
42
32
|
file_ref = filename_index.size
|
43
33
|
filename_index.push(project.get_local_path(file))
|
44
34
|
vs.each do |v|
|
45
|
-
all_vs.push(make_violation_hash(file_ref, v))
|
35
|
+
all_vs.push(make_violation_hash(file_ref, v, is_diff))
|
46
36
|
end
|
47
37
|
end
|
48
38
|
|
@@ -52,36 +42,19 @@ module PmdTester
|
|
52
42
|
}
|
53
43
|
end
|
54
44
|
|
55
|
-
def
|
56
|
-
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
def violation_type(violation)
|
61
|
-
if violation.changed?
|
62
|
-
'~'
|
63
|
-
elsif violation.branch == 'patch'
|
64
|
-
'+'
|
65
|
-
else
|
66
|
-
'-'
|
67
|
-
end
|
45
|
+
def errors_to_h(project)
|
46
|
+
errors = project.report_diff.error_diffs_by_file.values.flatten
|
47
|
+
errors.map { |e| error_to_hash(e, project) }
|
68
48
|
end
|
69
49
|
|
70
|
-
def
|
71
|
-
|
72
|
-
|
73
|
-
'l' => violation.line,
|
74
|
-
'f' => file_ref,
|
75
|
-
'r' => violation.rule_name,
|
76
|
-
'm' => violation.changed? ? diff_fragments(violation) : violation.message
|
77
|
-
}
|
78
|
-
h['ol'] = violation.old_line if violation.changed? && violation.line != violation.old_line
|
79
|
-
h
|
50
|
+
def configerrors_to_h(project)
|
51
|
+
configerrors = project.report_diff.configerror_diffs_by_rule.values.flatten
|
52
|
+
configerrors.map { |e| configerror_to_hash(e) }
|
80
53
|
end
|
81
54
|
|
82
|
-
def
|
83
|
-
|
84
|
-
|
55
|
+
def link_template(project)
|
56
|
+
l_str = project.type == 'git' ? 'L' : 'l'
|
57
|
+
"#{project.webview_url}/{file}##{l_str}{line}"
|
85
58
|
end
|
86
59
|
|
87
60
|
def error_to_hash(error, project)
|
@@ -122,5 +95,34 @@ module PmdTester
|
|
122
95
|
'added'
|
123
96
|
end
|
124
97
|
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def violation_type(violation)
|
102
|
+
if violation.changed?
|
103
|
+
'~'
|
104
|
+
elsif violation.branch == PATCH
|
105
|
+
'+'
|
106
|
+
else
|
107
|
+
'-'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def make_violation_hash(file_ref, violation, is_diff = TRUE)
|
112
|
+
h = {
|
113
|
+
't' => is_diff ? violation_type(violation) : '+',
|
114
|
+
'l' => violation.line,
|
115
|
+
'f' => file_ref,
|
116
|
+
'r' => violation.rule_name,
|
117
|
+
'm' => is_diff && violation.changed? ? diff_fragments(violation) : violation.message
|
118
|
+
}
|
119
|
+
h['ol'] = violation.old_line if is_diff && violation.changed? && violation.line != violation.old_line
|
120
|
+
h
|
121
|
+
end
|
122
|
+
|
123
|
+
def diff_fragments(violation)
|
124
|
+
diff = Differ.diff_by_word(violation.message, violation.old_message)
|
125
|
+
diff.format_as(:html)
|
126
|
+
end
|
125
127
|
end
|
126
128
|
end
|
@@ -9,69 +9,70 @@ module PmdTester
|
|
9
9
|
# Attention: we only consider java rulesets now.
|
10
10
|
class RuleSetBuilder
|
11
11
|
include PmdTester
|
12
|
-
ALL_CATEGORIES = Set['bestpractices.xml', 'codestyle.xml', 'design.xml', 'documentation.xml',
|
13
|
-
'errorprone.xml', 'multithreading.xml', 'performance.xml',
|
14
|
-
'security.xml'].freeze
|
15
|
-
PATH_TO_PMD_JAVA_BASED_RULES =
|
16
|
-
'pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule'
|
17
|
-
PATH_TO_PMD_XPATH_BASED_RULES = 'pmd-java/src/main/resources/category/java'
|
18
|
-
PATH_TO_ALL_JAVA_RULES =
|
19
|
-
ResourceLocator.locate('config/all-java.xml')
|
20
12
|
PATH_TO_DYNAMIC_CONFIG = 'target/dynamic-config.xml'
|
21
|
-
|
13
|
+
NO_RULES_CHANGED_MESSAGE = 'No regression tested rules have been changed!'
|
22
14
|
|
23
15
|
def initialize(options)
|
24
16
|
@options = options
|
25
17
|
end
|
26
18
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
19
|
+
#
|
20
|
+
# Creates a dynamic ruleset based on the changed sources.
|
21
|
+
# Returns true, when rules are affected by the changed sources.
|
22
|
+
# Returns false, when no rules are affected and regression tester can be skipped.
|
23
|
+
#
|
24
|
+
def build?
|
25
|
+
languages = determine_languages
|
26
|
+
filenames = diff_filenames(languages)
|
27
|
+
run_required, rule_refs = get_rule_refs(filenames)
|
28
|
+
if run_required
|
29
|
+
output_filter_set(rule_refs)
|
30
|
+
build_config_file(rule_refs)
|
31
|
+
logger.debug "Dynamic configuration: #{[rule_refs]}"
|
32
|
+
else
|
33
|
+
logger.info NO_RULES_CHANGED_MESSAGE
|
34
|
+
end
|
35
|
+
run_required
|
36
|
+
end
|
37
|
+
|
38
|
+
def calculate_filter_set
|
39
|
+
output_filter_set([])
|
34
40
|
end
|
35
41
|
|
36
42
|
def output_filter_set(rule_refs)
|
37
|
-
if rule_refs
|
38
|
-
if @options.mode == Options::ONLINE
|
43
|
+
if rule_refs.empty?
|
44
|
+
if @options.mode == Options::ONLINE && @options.filter_with_patch_config
|
39
45
|
@options.filter_set = Set[]
|
40
46
|
doc = File.open(@options.patch_config) { |f| Nokogiri::XML(f) }
|
41
47
|
rules = doc.css('ruleset rule')
|
42
48
|
rules.each do |r|
|
43
49
|
ref = r.attributes['ref'].content
|
44
|
-
ref.delete_prefix!('category/
|
50
|
+
ref.delete_prefix!('category/')
|
45
51
|
@options.filter_set.add(ref)
|
46
52
|
end
|
47
53
|
|
48
|
-
logger.
|
54
|
+
logger.info "Using filter based on patch config #{@options.patch_config}: " \
|
49
55
|
"#{@options.filter_set}"
|
50
56
|
else
|
51
|
-
# if `rule_refs`
|
52
|
-
logger.
|
57
|
+
# if `rule_refs` is empty, then no filter can be used when comparing to the baseline
|
58
|
+
logger.info 'No filter when comparing patch to baseline'
|
53
59
|
@options.filter_set = nil
|
54
60
|
end
|
55
61
|
else
|
56
|
-
logger.
|
62
|
+
logger.info "Filter is now #{rule_refs}"
|
57
63
|
@options.filter_set = rule_refs
|
58
64
|
end
|
59
65
|
end
|
60
66
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
diff_cmd = "git diff --name-only #{base}..#{patch} -- pmd-core/src/main pmd-java/src/main"
|
68
|
-
filenames = Cmd.execute(diff_cmd)
|
69
|
-
end
|
70
|
-
filenames.split("\n")
|
71
|
-
end
|
72
|
-
|
67
|
+
#
|
68
|
+
# Determines the rules or category rulesets, that are potentially affected by the change.
|
69
|
+
# Returns an empty set, if all rules are affected and there is no
|
70
|
+
# filtering possible or if no rules are affected.
|
71
|
+
# Whether to run the regression test is returned as an additional boolean flag.
|
72
|
+
#
|
73
73
|
def get_rule_refs(filenames)
|
74
|
-
categories, rules = determine_categories_rules(filenames)
|
74
|
+
run_required, categories, rules = determine_categories_rules(filenames)
|
75
|
+
logger.debug "Regression test required: #{run_required}"
|
75
76
|
logger.debug "Categories: #{categories}"
|
76
77
|
logger.debug "Rules: #{rules}"
|
77
78
|
|
@@ -83,48 +84,11 @@ module PmdTester
|
|
83
84
|
refs = Set[]
|
84
85
|
refs.merge(categories)
|
85
86
|
refs.merge(rules)
|
86
|
-
refs
|
87
|
-
end
|
88
|
-
|
89
|
-
def determine_categories_rules(filenames)
|
90
|
-
categories = Set[]
|
91
|
-
rules = Set[]
|
92
|
-
filenames.each do |filename|
|
93
|
-
match_data = check_single_filename(filename)
|
94
|
-
|
95
|
-
unless match_data.nil?
|
96
|
-
if match_data.size == 2
|
97
|
-
categories.add("#{match_data[1]}.xml")
|
98
|
-
else
|
99
|
-
rules.add("#{match_data[1]}.xml/#{match_data[2]}")
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
next unless match_data.nil?
|
104
|
-
|
105
|
-
logger.debug "Change doesn't match specific rule/category - enable all rules"
|
106
|
-
categories = ALL_CATEGORIES
|
107
|
-
rules.clear
|
108
|
-
break
|
109
|
-
end
|
110
|
-
[categories, rules]
|
111
|
-
end
|
112
|
-
|
113
|
-
def check_single_filename(filename)
|
114
|
-
logger.debug "Checking #{filename}"
|
115
|
-
match_data = %r{#{PATH_TO_PMD_JAVA_BASED_RULES}/([^/]+)/([^/]+)Rule.java}.match(filename)
|
116
|
-
match_data = %r{#{PATH_TO_PMD_XPATH_BASED_RULES}/([^/]+).xml}.match(filename) if match_data.nil?
|
117
|
-
logger.debug "Matches: #{match_data.inspect}"
|
118
|
-
match_data
|
87
|
+
[run_required, refs]
|
119
88
|
end
|
120
89
|
|
121
90
|
def build_config_file(rule_refs)
|
122
91
|
if rule_refs.empty?
|
123
|
-
logger.info NO_JAVA_RULES_CHANGED_MESSAGE
|
124
|
-
return
|
125
|
-
end
|
126
|
-
|
127
|
-
if rule_refs == ALL_CATEGORIES
|
128
92
|
logger.debug 'All rules are used. Not generating a dynamic ruleset.'
|
129
93
|
logger.debug "Using the configured/default ruleset base_config=#{@options.base_config} "\
|
130
94
|
"patch_config=#{@options.patch_config}"
|
@@ -143,7 +107,7 @@ module PmdTester
|
|
143
107
|
'name' => 'Dynamic PmdTester Ruleset') do
|
144
108
|
xml.description 'The ruleset generated by PmdTester dynamically'
|
145
109
|
rule_refs.each do |entry|
|
146
|
-
xml.rule('ref' => "category
|
110
|
+
xml.rule('ref' => "category/#{entry}")
|
147
111
|
end
|
148
112
|
end
|
149
113
|
end
|
@@ -154,5 +118,82 @@ module PmdTester
|
|
154
118
|
@options.base_config = PATH_TO_DYNAMIC_CONFIG
|
155
119
|
@options.patch_config = PATH_TO_DYNAMIC_CONFIG
|
156
120
|
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def determine_categories_rules(filenames)
|
125
|
+
regression_test_required = false
|
126
|
+
categories = Set[]
|
127
|
+
rules = Set[]
|
128
|
+
filenames.each do |filename|
|
129
|
+
matched = check_single_filename(filename, categories, rules)
|
130
|
+
regression_test_required = true if matched
|
131
|
+
|
132
|
+
next if matched
|
133
|
+
|
134
|
+
logger.info "Change in file #{filename} doesn't match specific rule/category - enable all rules"
|
135
|
+
regression_test_required = true
|
136
|
+
categories.clear
|
137
|
+
rules.clear
|
138
|
+
break
|
139
|
+
end
|
140
|
+
[regression_test_required, categories, rules]
|
141
|
+
end
|
142
|
+
|
143
|
+
def check_single_filename(filename, categories, rules)
|
144
|
+
logger.debug "Checking #{filename}"
|
145
|
+
|
146
|
+
# matches Java-based rule implementations
|
147
|
+
match_data = %r{.+/src/main/java/.+/lang/([^/]+)/rule/([^/]+)/([^/]+)Rule.java}.match(filename)
|
148
|
+
unless match_data.nil?
|
149
|
+
logger.debug "Matches: #{match_data.inspect}"
|
150
|
+
rules.add("#{match_data[1]}/#{match_data[2]}.xml/#{match_data[3]}")
|
151
|
+
return true
|
152
|
+
end
|
153
|
+
|
154
|
+
# matches xpath rules
|
155
|
+
match_data = %r{.+/src/main/resources/category/([^/]+)/([^/]+).xml}.match(filename)
|
156
|
+
unless match_data.nil?
|
157
|
+
logger.debug "Matches: #{match_data.inspect}"
|
158
|
+
categories.add("#{match_data[1]}/#{match_data[2]}.xml")
|
159
|
+
return true
|
160
|
+
end
|
161
|
+
|
162
|
+
false
|
163
|
+
end
|
164
|
+
|
165
|
+
def diff_filenames(languages)
|
166
|
+
filenames = nil
|
167
|
+
Dir.chdir(@options.local_git_repo) do
|
168
|
+
base = @options.base_branch
|
169
|
+
patch = @options.patch_branch
|
170
|
+
|
171
|
+
filepath_filter = ''
|
172
|
+
unless languages.empty?
|
173
|
+
filepath_filter = '-- pmd-core/src/main'
|
174
|
+
languages.each { |l| filepath_filter = "#{filepath_filter} pmd-#{l}/src/main" }
|
175
|
+
end
|
176
|
+
|
177
|
+
# We only need to support git here, since PMD's repo is using git.
|
178
|
+
diff_cmd = "git diff --name-only #{base}..#{patch} #{filepath_filter}"
|
179
|
+
filenames = Cmd.execute(diff_cmd)
|
180
|
+
end
|
181
|
+
filenames.split("\n")
|
182
|
+
end
|
183
|
+
|
184
|
+
#
|
185
|
+
# Determines all languages, that are part of the regression test.
|
186
|
+
# This is based on the configured rules/rulesets.
|
187
|
+
#
|
188
|
+
def determine_languages
|
189
|
+
languages = Set[]
|
190
|
+
doc = File.open(@options.patch_config) { |f| Nokogiri::XML(f) }
|
191
|
+
rules = doc.css('ruleset rule')
|
192
|
+
rules.each do |r|
|
193
|
+
ref = r.attributes['ref'].content
|
194
|
+
languages.add(ref.split('/')[1])
|
195
|
+
end
|
196
|
+
languages
|
197
|
+
end
|
157
198
|
end
|
158
199
|
end
|
@@ -80,6 +80,7 @@ module PmdTester
|
|
80
80
|
'sha' => details.branch_last_sha,
|
81
81
|
'message' => details.branch_last_message
|
82
82
|
},
|
83
|
+
'timestamp' => details.timestamp,
|
83
84
|
'execution_time' => PmdReportDetail.convert_seconds(details.execution_time),
|
84
85
|
'jdk_info' => details.jdk_version,
|
85
86
|
'locale' => details.language,
|
@@ -29,6 +29,7 @@ module PmdTester
|
|
29
29
|
attr_reader :threads
|
30
30
|
attr_reader :html_flag
|
31
31
|
attr_reader :auto_config_flag
|
32
|
+
attr_reader :filter_with_patch_config
|
32
33
|
attr_reader :debug_flag
|
33
34
|
attr_accessor :filter_set
|
34
35
|
attr_reader :keep_reports
|
@@ -48,6 +49,7 @@ module PmdTester
|
|
48
49
|
@threads = options[:t]
|
49
50
|
@html_flag = options[:f]
|
50
51
|
@auto_config_flag = options[:a]
|
52
|
+
@filter_with_patch_config = options.filter_with_patch_config?
|
51
53
|
@debug_flag = options[:d]
|
52
54
|
@filter_set = nil
|
53
55
|
@keep_reports = options.keep_reports?
|
@@ -100,6 +102,9 @@ module PmdTester
|
|
100
102
|
o.bool '-a', '--auto-gen-config',
|
101
103
|
'whether to generate configurations automatically based on branch differences,' \
|
102
104
|
'this option only works in online and local mode'
|
105
|
+
o.bool '--filter-with-patch-config',
|
106
|
+
'whether to use patch config to filter baseline result as if --auto-gen-config ' \
|
107
|
+
'has been used. This option only works in online mode.'
|
103
108
|
o.bool '--keep-reports',
|
104
109
|
'whether to keep old reports and skip running PMD again if possible'
|
105
110
|
o.bool '-d', '--debug',
|
@@ -8,14 +8,12 @@ module PmdTester
|
|
8
8
|
attr_reader :violations
|
9
9
|
attr_reader :errors
|
10
10
|
attr_reader :configerrors
|
11
|
-
attr_reader :infos_by_rules
|
12
11
|
|
13
12
|
def initialize(branch_name, working_dir, filter_set = nil)
|
14
13
|
@violations = CollectionByFile.new
|
15
14
|
@errors = CollectionByFile.new
|
16
15
|
@configerrors = Hash.new { |hash, key| hash[key] = [] }
|
17
16
|
|
18
|
-
@infos_by_rules = {}
|
19
17
|
@current_violations = []
|
20
18
|
@current_violation = nil
|
21
19
|
@current_error = nil
|
@@ -94,7 +92,7 @@ module PmdTester
|
|
94
92
|
ruleset_attr = violation.ruleset_name.delete(' ').downcase! << '.xml'
|
95
93
|
return true if @filter_set.include?(ruleset_attr)
|
96
94
|
|
97
|
-
rule_ref = "#{ruleset_attr}/#{violation.rule_name}"
|
95
|
+
rule_ref = "#{violation.language}/#{ruleset_attr}/#{violation.rule_name}"
|
98
96
|
|
99
97
|
@filter_set.include?(rule_ref)
|
100
98
|
end
|
@@ -10,6 +10,8 @@ module PmdTester
|
|
10
10
|
attr_accessor :branch_last_sha
|
11
11
|
attr_accessor :branch_last_message
|
12
12
|
attr_accessor :branch_name
|
13
|
+
# Start of the regression report
|
14
|
+
attr_accessor :timestamp
|
13
15
|
# The branch's execution time on all standard projects
|
14
16
|
attr_accessor :execution_time
|
15
17
|
attr_accessor :jdk_version
|
@@ -26,6 +28,7 @@ module PmdTester
|
|
26
28
|
@branch_name = branch_name
|
27
29
|
branch_filename = PmdBranchDetail.branch_filename(branch_name)
|
28
30
|
@base_branch_dir = "target/reports/#{branch_filename}" unless @branch_name.nil?
|
31
|
+
@timestamp = Time.now
|
29
32
|
@execution_time = 0
|
30
33
|
# the result of command 'java -version' is going to stderr
|
31
34
|
@jdk_version = Cmd.stderr_of('java -version')
|
@@ -42,11 +45,13 @@ module PmdTester
|
|
42
45
|
details.branch_last_sha = hash['branch_last_sha']
|
43
46
|
details.branch_last_message = hash['branch_last_message']
|
44
47
|
details.branch_name = hash['branch_name']
|
48
|
+
details.timestamp = hash['timestamp']
|
45
49
|
details.execution_time = hash['execution_time']
|
46
50
|
details.jdk_version = hash['jdk_version']
|
47
51
|
details.language = hash['language']
|
48
52
|
details.pull_request = hash['pull_request']
|
49
53
|
else
|
54
|
+
details.timestamp = Time.now
|
50
55
|
details.jdk_version = ''
|
51
56
|
details.language = ''
|
52
57
|
logger&.warn "#{details.path_to_save_file} doesn't exist!"
|
@@ -58,6 +63,7 @@ module PmdTester
|
|
58
63
|
hash = { branch_last_sha: @branch_last_sha,
|
59
64
|
branch_last_message: @branch_last_message,
|
60
65
|
branch_name: @branch_name,
|
66
|
+
timestamp: @timestamp,
|
61
67
|
execution_time: @execution_time,
|
62
68
|
jdk_version: @jdk_version,
|
63
69
|
language: @language,
|
@@ -27,7 +27,7 @@ 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
|
30
|
+
attr_reader :fname, :info_url, :line, :old_line, :old_message, :rule_name, :ruleset_name, :language
|
31
31
|
attr_accessor :message
|
32
32
|
|
33
33
|
# rubocop:disable Metrics/ParameterLists
|
@@ -43,6 +43,8 @@ module PmdTester
|
|
43
43
|
|
44
44
|
@ruleset_name = ruleset_name
|
45
45
|
|
46
|
+
@language = determine_language_from_info_url
|
47
|
+
|
46
48
|
@changed = false
|
47
49
|
@old_message = nil
|
48
50
|
@old_line = nil
|
@@ -102,5 +104,13 @@ module PmdTester
|
|
102
104
|
'changed' => changed?
|
103
105
|
}
|
104
106
|
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def determine_language_from_info_url
|
111
|
+
# @info_url is e.g. http://pmd.sourceforge.net/snapshot/pmd_rules_java_codestyle.html#fielddeclarationsshouldbeatstartofclass
|
112
|
+
m = @info_url.match(/pmd_rules_(\w+)_/)
|
113
|
+
m[1]
|
114
|
+
end
|
105
115
|
end
|
106
116
|
end
|