pmdtester 1.1.2 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|