pmdtester 1.0.0.pre.beta3 → 1.1.2
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 +67 -0
- data/.ci/files/env.gpg +1 -0
- data/.ci/inc/install-openjdk.inc +26 -0
- data/.ci/manual-integration-tests.sh +20 -0
- data/.github/workflows/build.yml +39 -0
- data/.github/workflows/manual-integration-tests.yml +32 -0
- data/.gitignore +9 -0
- data/.hoerc +1 -1
- data/.rubocop.yml +13 -2
- data/.rubocop_todo.yml +7 -8
- data/.ruby-version +1 -0
- data/Gemfile +1 -12
- data/History.md +104 -0
- data/Manifest.txt +30 -6
- data/README.rdoc +110 -60
- data/Rakefile +27 -15
- data/config/all-java.xml +1 -1
- data/config/design.xml +1 -1
- data/config/projectlist_1_0_0.xsd +2 -1
- data/config/projectlist_1_1_0.xsd +31 -0
- data/lib/pmdtester.rb +12 -4
- data/lib/pmdtester/builders/liquid_renderer.rb +73 -0
- data/lib/pmdtester/builders/pmd_report_builder.rb +134 -60
- data/lib/pmdtester/builders/project_builder.rb +100 -0
- data/lib/pmdtester/builders/project_hasher.rb +126 -0
- data/lib/pmdtester/builders/rule_set_builder.rb +94 -48
- data/lib/pmdtester/builders/simple_progress_logger.rb +27 -0
- data/lib/pmdtester/builders/summary_report_builder.rb +62 -117
- data/lib/pmdtester/cmd.rb +15 -1
- data/lib/pmdtester/collection_by_file.rb +55 -0
- data/lib/pmdtester/parsers/options.rb +25 -2
- data/lib/pmdtester/parsers/pmd_report_document.rb +79 -27
- data/lib/pmdtester/parsers/projects_parser.rb +2 -4
- data/lib/pmdtester/pmd_branch_detail.rb +36 -12
- data/lib/pmdtester/pmd_configerror.rb +62 -0
- data/lib/pmdtester/pmd_error.rb +34 -34
- data/lib/pmdtester/pmd_report_detail.rb +10 -13
- data/lib/pmdtester/pmd_tester_utils.rb +57 -0
- data/lib/pmdtester/pmd_violation.rb +66 -26
- data/lib/pmdtester/project.rb +28 -23
- data/lib/pmdtester/report_diff.rb +194 -70
- data/lib/pmdtester/resource_locator.rb +4 -0
- data/lib/pmdtester/runner.rb +81 -54
- data/pmdtester.gemspec +64 -0
- data/resources/_includes/diff_pill_row.html +6 -0
- data/resources/css/bootstrap.min.css +7 -0
- data/resources/css/datatables.min.css +36 -0
- data/resources/css/pmd-tester.css +132 -0
- data/resources/js/bootstrap.min.js +7 -0
- data/resources/js/code-snippets.js +73 -0
- data/resources/js/datatables.min.js +726 -0
- data/resources/js/jquery-3.2.1.slim.min.js +4 -0
- data/resources/js/jquery.min.js +2 -0
- data/resources/js/popper.min.js +5 -0
- data/resources/js/project-report.js +136 -0
- data/resources/project_diff_report.html +205 -0
- data/resources/project_index.html +102 -0
- metadata +122 -38
- data/.travis.yml +0 -22
- data/lib/pmdtester/builders/diff_builder.rb +0 -30
- data/lib/pmdtester/builders/diff_report_builder.rb +0 -225
- data/lib/pmdtester/builders/html_report_builder.rb +0 -33
- data/resources/css/maven-base.css +0 -155
- data/resources/css/maven-theme.css +0 -171
@@ -9,8 +9,9 @@ module PmdTester
|
|
9
9
|
# Attention: we only consider java rulesets now.
|
10
10
|
class RuleSetBuilder
|
11
11
|
include PmdTester
|
12
|
-
|
13
|
-
|
12
|
+
ALL_CATEGORIES = Set['bestpractices.xml', 'codestyle.xml', 'design.xml', 'documentation.xml',
|
13
|
+
'errorprone.xml', 'multithreading.xml', 'performance.xml',
|
14
|
+
'security.xml'].freeze
|
14
15
|
PATH_TO_PMD_JAVA_BASED_RULES =
|
15
16
|
'pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule'
|
16
17
|
PATH_TO_PMD_XPATH_BASED_RULES = 'pmd-java/src/main/resources/category/java'
|
@@ -25,18 +26,35 @@ module PmdTester
|
|
25
26
|
|
26
27
|
def build
|
27
28
|
filenames = diff_filenames
|
28
|
-
|
29
|
-
output_filter_set(
|
30
|
-
build_config_file(
|
31
|
-
logger.debug "Dynamic configuration: #{[
|
29
|
+
rule_refs = get_rule_refs(filenames)
|
30
|
+
output_filter_set(rule_refs)
|
31
|
+
build_config_file(rule_refs)
|
32
|
+
logger.debug "Dynamic configuration: #{[rule_refs]}"
|
33
|
+
rule_refs
|
32
34
|
end
|
33
35
|
|
34
|
-
def output_filter_set(
|
35
|
-
if
|
36
|
-
|
37
|
-
|
36
|
+
def output_filter_set(rule_refs)
|
37
|
+
if rule_refs == ALL_CATEGORIES
|
38
|
+
if @options.mode == Options::ONLINE
|
39
|
+
@options.filter_set = Set[]
|
40
|
+
doc = File.open(@options.patch_config) { |f| Nokogiri::XML(f) }
|
41
|
+
rules = doc.css('ruleset rule')
|
42
|
+
rules.each do |r|
|
43
|
+
ref = r.attributes['ref'].content
|
44
|
+
ref.delete_prefix!('category/java/')
|
45
|
+
@options.filter_set.add(ref)
|
46
|
+
end
|
47
|
+
|
48
|
+
logger.debug "Using filter based on patch config #{@options.patch_config}: " \
|
49
|
+
"#{@options.filter_set}"
|
50
|
+
else
|
51
|
+
# if `rule_refs` contains all categories, then no need to filter the baseline
|
52
|
+
logger.debug 'No filter when comparing patch to baseline'
|
53
|
+
@options.filter_set = nil
|
54
|
+
end
|
38
55
|
else
|
39
|
-
|
56
|
+
logger.debug "Filter is now #{rule_refs}"
|
57
|
+
@options.filter_set = rule_refs
|
40
58
|
end
|
41
59
|
end
|
42
60
|
|
@@ -46,64 +64,92 @@ module PmdTester
|
|
46
64
|
base = @options.base_branch
|
47
65
|
patch = @options.patch_branch
|
48
66
|
# We only need to support git here, since PMD's repo is using git.
|
49
|
-
diff_cmd = "git diff --name-only #{base}..#{patch} -- pmd-core pmd-java"
|
67
|
+
diff_cmd = "git diff --name-only #{base}..#{patch} -- pmd-core/src/main pmd-java/src/main"
|
50
68
|
filenames = Cmd.execute(diff_cmd)
|
51
69
|
end
|
52
70
|
filenames.split("\n")
|
53
71
|
end
|
54
72
|
|
55
|
-
def
|
56
|
-
|
73
|
+
def get_rule_refs(filenames)
|
74
|
+
categories, rules = determine_categories_rules(filenames)
|
75
|
+
logger.debug "Categories: #{categories}"
|
76
|
+
logger.debug "Rules: #{rules}"
|
77
|
+
|
78
|
+
# filter out all individual rules that are already covered by a complete category
|
79
|
+
categories.each do |cat|
|
80
|
+
rules.delete_if { |e| e.start_with?(cat) }
|
81
|
+
end
|
82
|
+
|
83
|
+
refs = Set[]
|
84
|
+
refs.merge(categories)
|
85
|
+
refs.merge(rules)
|
86
|
+
refs
|
87
|
+
end
|
88
|
+
|
89
|
+
def determine_categories_rules(filenames)
|
90
|
+
categories = Set[]
|
91
|
+
rules = Set[]
|
57
92
|
filenames.each do |filename|
|
58
|
-
match_data =
|
59
|
-
|
60
|
-
|
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
|
61
101
|
end
|
62
102
|
|
63
|
-
|
64
|
-
nil
|
65
|
-
else
|
66
|
-
match_data[1]
|
67
|
-
end
|
103
|
+
next unless match_data.nil?
|
68
104
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
rule_sets.add(category)
|
74
|
-
end
|
105
|
+
logger.debug "Change doesn't match specific rule/category - enable all rules"
|
106
|
+
categories = ALL_CATEGORIES
|
107
|
+
rules.clear
|
108
|
+
break
|
75
109
|
end
|
76
|
-
|
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
|
77
119
|
end
|
78
120
|
|
79
|
-
def build_config_file(
|
80
|
-
if
|
121
|
+
def build_config_file(rule_refs)
|
122
|
+
if rule_refs.empty?
|
81
123
|
logger.info NO_JAVA_RULES_CHANGED_MESSAGE
|
82
|
-
|
124
|
+
return
|
83
125
|
end
|
84
126
|
|
85
|
-
|
86
|
-
|
87
|
-
|
127
|
+
if rule_refs == ALL_CATEGORIES
|
128
|
+
logger.debug 'All rules are used. Not generating a dynamic ruleset.'
|
129
|
+
logger.debug "Using the configured/default ruleset base_config=#{@options.base_config} "\
|
130
|
+
"patch_config=#{@options.patch_config}"
|
131
|
+
return
|
88
132
|
end
|
89
133
|
|
90
|
-
|
91
|
-
description.content = 'The ruleset generated by PmdTester dynamically'
|
92
|
-
|
93
|
-
write_dynamic_file(doc)
|
134
|
+
write_dynamic_file(rule_refs)
|
94
135
|
end
|
95
136
|
|
96
|
-
def
|
97
|
-
|
98
|
-
|
137
|
+
def write_dynamic_file(rule_refs)
|
138
|
+
logger.debug "Generating dynamic configuration for: #{[rule_refs]}"
|
139
|
+
builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
140
|
+
xml.ruleset('xmlns' => 'http://pmd.sourceforge.net/ruleset/2.0.0',
|
141
|
+
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
|
142
|
+
'xsi:schemaLocation' => 'http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd',
|
143
|
+
'name' => 'Dynamic PmdTester Ruleset') do
|
144
|
+
xml.description 'The ruleset generated by PmdTester dynamically'
|
145
|
+
rule_refs.each do |entry|
|
146
|
+
xml.rule('ref' => "category/java/#{entry}")
|
147
|
+
end
|
148
|
+
end
|
99
149
|
end
|
100
|
-
|
101
|
-
false
|
102
|
-
end
|
103
|
-
|
104
|
-
def write_dynamic_file(doc)
|
150
|
+
doc = builder.to_xml(indent: 4, encoding: 'UTF-8')
|
105
151
|
File.open(PATH_TO_DYNAMIC_CONFIG, 'w') do |x|
|
106
|
-
x << doc.
|
152
|
+
x << doc.gsub(/\n\s+\n/, "\n")
|
107
153
|
end
|
108
154
|
@options.base_config = PATH_TO_DYNAMIC_CONFIG
|
109
155
|
@options.patch_config = PATH_TO_DYNAMIC_CONFIG
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rufus-scheduler'
|
4
|
+
|
5
|
+
module PmdTester
|
6
|
+
# Helper class that provides a simple progress logging
|
7
|
+
class SimpleProgressLogger
|
8
|
+
include PmdTester
|
9
|
+
def initialize(task_name)
|
10
|
+
@task_name = task_name
|
11
|
+
end
|
12
|
+
|
13
|
+
def start
|
14
|
+
logger.info "Starting #{@task_name}"
|
15
|
+
message_counter = 1
|
16
|
+
@scheduler = Rufus::Scheduler.new
|
17
|
+
@scheduler.every '2m' do
|
18
|
+
logger.info "Still #{@task_name} (#{message_counter})..."
|
19
|
+
message_counter += 1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def stop
|
24
|
+
@scheduler.shutdown
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -2,145 +2,90 @@
|
|
2
2
|
|
3
3
|
module PmdTester
|
4
4
|
# Building summary report to show the details about projects and pmd branchs
|
5
|
-
class SummaryReportBuilder
|
5
|
+
class SummaryReportBuilder
|
6
6
|
include PmdTester
|
7
|
-
|
8
|
-
|
9
|
-
PATCH_CONFIG_PATH = 'target/reports/diff/patch_config.xml'
|
10
|
-
INDEX_PATH = 'target/reports/diff/index.html'
|
11
|
-
|
12
|
-
def build(projects, base_name, patch_name)
|
13
|
-
@projects = projects
|
14
|
-
@base_details = get_branch_details(base_name)
|
15
|
-
@patch_details = get_branch_details(patch_name)
|
16
|
-
|
17
|
-
FileUtils.mkdir_p(REPORT_DIR) unless File.directory?(REPORT_DIR)
|
18
|
-
index = File.new(INDEX_PATH, 'w')
|
19
|
-
|
20
|
-
html_report = build_html_report('Summary report')
|
21
|
-
copy_css(REPORT_DIR)
|
22
|
-
|
23
|
-
index.puts html_report
|
24
|
-
index.close
|
25
|
-
|
26
|
-
logger.info 'Built summary report successfully!'
|
27
|
-
end
|
28
|
-
|
29
|
-
def get_branch_details(branch_name)
|
30
|
-
details = PmdBranchDetail.new(branch_name)
|
31
|
-
details.load
|
32
|
-
details
|
33
|
-
end
|
7
|
+
include LiquidRenderer
|
8
|
+
include ProjectHasher
|
34
9
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
10
|
+
REPORT_DIR = 'target/reports/diff'
|
11
|
+
BASE_CONFIG_NAME = 'base_config.xml'
|
12
|
+
PATCH_CONFIG_NAME = 'patch_config.xml'
|
39
13
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
build_branch_details_table(doc)
|
14
|
+
def write_all_projects(projects, base_details, patch_details)
|
15
|
+
projects.each do |project|
|
16
|
+
process_project(project, "#{REPORT_DIR}/#{project.name}")
|
44
17
|
end
|
45
|
-
|
18
|
+
logger.info 'Built all difference reports successfully!'
|
46
19
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
20
|
+
FileUtils.mkdir_p(REPORT_DIR)
|
21
|
+
write_structure(REPORT_DIR)
|
22
|
+
copy_configs(REPORT_DIR, base_details, patch_details)
|
23
|
+
write_index(REPORT_DIR, base_details, patch_details, projects)
|
24
|
+
logger.info "Built summary report successfully in #{REPORT_DIR}!"
|
52
25
|
end
|
53
26
|
|
54
|
-
|
55
|
-
doc.thead do
|
56
|
-
doc.tr do
|
57
|
-
doc.th 'Item'
|
58
|
-
doc.th 'base'
|
59
|
-
doc.th 'patch'
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
27
|
+
private
|
63
28
|
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
@patch_details.branch_name)
|
68
|
-
build_branch_table_row(doc, 'branch last commit sha', @base_details.branch_last_sha,
|
69
|
-
@patch_details.branch_last_sha)
|
70
|
-
build_branch_table_row(doc, 'branch last commit message', @base_details.branch_last_message,
|
71
|
-
@patch_details.branch_last_message)
|
72
|
-
build_branch_table_row(doc, 'total execution time', @base_details.format_execution_time,
|
73
|
-
@patch_details.format_execution_time)
|
74
|
-
build_branch_config_table_row(doc)
|
75
|
-
end
|
29
|
+
def process_project(project, dir)
|
30
|
+
logger.info "Rendering #{project.name}..."
|
31
|
+
LiquidProjectRenderer.new.write_project_index(project, dir)
|
76
32
|
end
|
77
33
|
|
78
|
-
def
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
copy_branch_config_file(base_config_src_path, BASE_CONFIG_PATH)
|
83
|
-
doc.td(class: 'a') do
|
84
|
-
doc.a(href: './base_config.xml') { doc.text 'base config' }
|
85
|
-
end
|
86
|
-
patch_config_stc_path = @patch_details.target_branch_config_path
|
87
|
-
FileUtils.cp(patch_config_stc_path, PATCH_CONFIG_PATH)
|
88
|
-
doc.td(class: 'b') do
|
89
|
-
doc.a(href: './patch_config.xml') { doc.text 'patch config' }
|
90
|
-
end
|
91
|
-
end
|
34
|
+
def write_structure(target_root)
|
35
|
+
logger.info 'Copying resources...'
|
36
|
+
copy_resource('css', target_root)
|
37
|
+
copy_resource('js', target_root)
|
92
38
|
end
|
93
39
|
|
94
|
-
def
|
95
|
-
|
40
|
+
def copy_configs(target_root, base_details, patch_details)
|
41
|
+
copy_file(base_details.target_branch_config_path, "#{target_root}/#{BASE_CONFIG_NAME}")
|
42
|
+
copy_file(patch_details.target_branch_config_path, "#{target_root}/#{PATCH_CONFIG_NAME}")
|
96
43
|
end
|
97
44
|
|
98
|
-
def
|
99
|
-
|
100
|
-
doc.td(class: 'c') { doc.text item }
|
101
|
-
doc.td(class: 'a') { doc.text base }
|
102
|
-
doc.td(class: 'b') { doc.text patch }
|
103
|
-
end
|
45
|
+
def copy_file(src, dest)
|
46
|
+
FileUtils.cp(src, dest) if File.exist?(src)
|
104
47
|
end
|
105
48
|
|
106
|
-
def
|
107
|
-
|
108
|
-
|
109
|
-
|
49
|
+
def write_index(target_root, base_details, patch_details, projects)
|
50
|
+
projects = projects.map do |p|
|
51
|
+
{
|
52
|
+
'name' => p.name,
|
53
|
+
'tag' => p.tag,
|
54
|
+
'report_url' => "./#{p.name}/index.html",
|
55
|
+
**report_diff_to_h(p.report_diff)
|
56
|
+
}
|
110
57
|
end
|
111
|
-
end
|
112
58
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
59
|
+
env = {
|
60
|
+
'comparison_url' => create_comparison_url(base_details, patch_details),
|
61
|
+
'base' => to_liquid(base_details, BASE_CONFIG_NAME),
|
62
|
+
'patch' => to_liquid(patch_details, PATCH_CONFIG_NAME),
|
63
|
+
'projects' => projects
|
64
|
+
}
|
65
|
+
logger.info 'Writing /index.html...'
|
66
|
+
render_and_write('project_index.html', "#{target_root}/index.html", env)
|
118
67
|
end
|
119
68
|
|
120
|
-
def
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
doc.th 'project branch/tag'
|
125
|
-
doc.th 'diff exist?'
|
126
|
-
doc.th 'introduce new errors?'
|
127
|
-
end
|
128
|
-
end
|
69
|
+
def create_comparison_url(base_details, patch_details)
|
70
|
+
base = CGI.escape(base_details.branch_name)
|
71
|
+
patch = CGI.escape(patch_details.branch_last_sha)
|
72
|
+
"https://github.com/pmd/pmd/compare/#{base}...#{patch}"
|
129
73
|
end
|
130
74
|
|
131
|
-
def
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
75
|
+
def to_liquid(details, config_name)
|
76
|
+
{
|
77
|
+
'tree_url' => "https://github.com/pmd/pmd/tree/#{CGI.escape(details.branch_last_sha)}",
|
78
|
+
'name' => details.branch_name,
|
79
|
+
'tip' => {
|
80
|
+
'sha' => details.branch_last_sha,
|
81
|
+
'message' => details.branch_last_message
|
82
|
+
},
|
83
|
+
'execution_time' => PmdReportDetail.convert_seconds(details.execution_time),
|
84
|
+
'jdk_info' => details.jdk_version,
|
85
|
+
'locale' => details.language,
|
86
|
+
'config_url' => config_name,
|
87
|
+
'pr_number' => details.pull_request
|
88
|
+
}
|
144
89
|
end
|
145
90
|
end
|
146
91
|
end
|
data/lib/pmdtester/cmd.rb
CHANGED
@@ -7,20 +7,34 @@ module PmdTester
|
|
7
7
|
class Cmd
|
8
8
|
extend PmdTester
|
9
9
|
def self.execute(cmd)
|
10
|
+
stdout, _stderr, _status = internal_execute(cmd)
|
11
|
+
stdout&.chomp!
|
12
|
+
stdout
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.stderr_of(cmd)
|
16
|
+
_stdout, stderr, _status = internal_execute(cmd)
|
17
|
+
stderr
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.internal_execute(cmd)
|
10
21
|
logger.debug "execute command '#{cmd}'"
|
11
22
|
|
12
23
|
stdout, stderr, status = Open3.capture3("#{cmd};")
|
13
24
|
|
14
25
|
logger.debug stdout
|
15
26
|
unless status.success?
|
27
|
+
logger.error stdout
|
16
28
|
logger.error stderr
|
17
29
|
raise CmdException.new(cmd, stderr)
|
18
30
|
end
|
19
31
|
|
20
32
|
stdout&.chomp!
|
21
33
|
|
22
|
-
stdout
|
34
|
+
[stdout, stderr, status]
|
23
35
|
end
|
36
|
+
|
37
|
+
private_class_method :internal_execute
|
24
38
|
end
|
25
39
|
|
26
40
|
# The exception should be raised when the shell command failed.
|