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
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PmdTester
|
4
|
+
# A collection of things, grouped by file.
|
5
|
+
#
|
6
|
+
# (Note: this replaces PmdErrors and PmdViolations)
|
7
|
+
class CollectionByFile
|
8
|
+
def initialize
|
9
|
+
# a hash of filename -> [list of items]
|
10
|
+
@hash = Hash.new([])
|
11
|
+
@total = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_all(filename, values)
|
15
|
+
return if values.empty?
|
16
|
+
|
17
|
+
if @hash.key?(filename)
|
18
|
+
@hash[filename].concat(values)
|
19
|
+
else
|
20
|
+
@hash[filename] = values
|
21
|
+
end
|
22
|
+
@total += values.size
|
23
|
+
end
|
24
|
+
|
25
|
+
def total_size
|
26
|
+
@total
|
27
|
+
end
|
28
|
+
|
29
|
+
def all_files
|
30
|
+
@hash.keys
|
31
|
+
end
|
32
|
+
|
33
|
+
def num_files
|
34
|
+
@hash.size
|
35
|
+
end
|
36
|
+
|
37
|
+
def all_values
|
38
|
+
@hash.values.flatten
|
39
|
+
end
|
40
|
+
|
41
|
+
def each_value(&block)
|
42
|
+
@hash.each_value do |vs|
|
43
|
+
vs.each(&block)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def [](fname)
|
48
|
+
@hash[fname]
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_h
|
52
|
+
@hash
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -16,6 +16,7 @@ module PmdTester
|
|
16
16
|
SINGLE = 'single'
|
17
17
|
DEFAULT_CONFIG_PATH = ResourceLocator.locate('config/all-java.xml')
|
18
18
|
DEFAULT_LIST_PATH = ResourceLocator.locate('config/project-list.xml')
|
19
|
+
DEFAULT_BASELINE_URL_PREFIX = 'https://sourceforge.net/projects/pmd/files/pmd-regression-tester/'
|
19
20
|
|
20
21
|
attr_reader :local_git_repo
|
21
22
|
attr_reader :base_branch
|
@@ -25,10 +26,14 @@ module PmdTester
|
|
25
26
|
attr_reader :config
|
26
27
|
attr_reader :project_list
|
27
28
|
attr_reader :mode
|
29
|
+
attr_reader :threads
|
28
30
|
attr_reader :html_flag
|
29
31
|
attr_reader :auto_config_flag
|
30
32
|
attr_reader :debug_flag
|
31
33
|
attr_accessor :filter_set
|
34
|
+
attr_reader :keep_reports
|
35
|
+
attr_reader :error_recovery
|
36
|
+
attr_reader :baseline_download_url_prefix
|
32
37
|
|
33
38
|
def initialize(argv)
|
34
39
|
options = parse(argv)
|
@@ -40,10 +45,19 @@ module PmdTester
|
|
40
45
|
@config = options[:c]
|
41
46
|
@project_list = options[:l]
|
42
47
|
@mode = options[:m]
|
48
|
+
@threads = options[:t]
|
43
49
|
@html_flag = options[:f]
|
44
50
|
@auto_config_flag = options[:a]
|
45
51
|
@debug_flag = options[:d]
|
46
52
|
@filter_set = nil
|
53
|
+
@keep_reports = options.keep_reports?
|
54
|
+
@error_recovery = options.error_recovery?
|
55
|
+
url = options[:baseline_download_url]
|
56
|
+
@baseline_download_url_prefix = if url[-1] == '/'
|
57
|
+
url
|
58
|
+
else
|
59
|
+
"#{url}/"
|
60
|
+
end
|
47
61
|
|
48
62
|
# if the 'config' option is selected then `config` overrides `base_config` and `patch_config`
|
49
63
|
@base_config = @config if !@config.nil? && @mode == 'local'
|
@@ -61,8 +75,8 @@ module PmdTester
|
|
61
75
|
single: Set this option to 'single' if your patch branch contains changes
|
62
76
|
for any option that can't work on master/base branch
|
63
77
|
online: Set this option to 'online' if you want to download
|
64
|
-
|
65
|
-
local: Default option is 'local'
|
78
|
+
the PMD report of master/base branch rather than generating it locally
|
79
|
+
local: Default option is 'local', PMD reports for the base and patch branches are generated locally.
|
66
80
|
DOC
|
67
81
|
|
68
82
|
Slop.parse argv do |o|
|
@@ -79,13 +93,22 @@ module PmdTester
|
|
79
93
|
'path to the file which contains the list of standard projects',
|
80
94
|
default: DEFAULT_LIST_PATH
|
81
95
|
o.string '-m', '--mode', mode_message, default: 'local'
|
96
|
+
o.integer '-t', '--threads', 'Sets the number of threads used by PMD.' \
|
97
|
+
' Set threads to 0 to disable multi-threading processing.', default: 1
|
82
98
|
o.bool '-f', '--html-flag',
|
83
99
|
'whether to not generate the html diff report in single mode'
|
84
100
|
o.bool '-a', '--auto-gen-config',
|
85
101
|
'whether to generate configurations automatically based on branch differences,' \
|
86
102
|
'this option only works in online and local mode'
|
103
|
+
o.bool '--keep-reports',
|
104
|
+
'whether to keep old reports and skip running PMD again if possible'
|
87
105
|
o.bool '-d', '--debug',
|
88
106
|
'whether change log level to DEBUG to see more information'
|
107
|
+
o.bool '--error-recovery',
|
108
|
+
'enable error recovery mode when executing PMD. Might help to analyze errors.'
|
109
|
+
o.string '--baseline-download-url',
|
110
|
+
'download url prefix from where to download the baseline in online mode',
|
111
|
+
default: DEFAULT_BASELINE_URL_PREFIX
|
89
112
|
o.on '-v', '--version' do
|
90
113
|
puts VERSION
|
91
114
|
exit
|
@@ -7,74 +7,126 @@ module PmdTester
|
|
7
7
|
class PmdReportDocument < Nokogiri::XML::SAX::Document
|
8
8
|
attr_reader :violations
|
9
9
|
attr_reader :errors
|
10
|
+
attr_reader :configerrors
|
11
|
+
attr_reader :infos_by_rules
|
12
|
+
|
10
13
|
def initialize(branch_name, working_dir, filter_set = nil)
|
11
|
-
@violations =
|
12
|
-
@errors =
|
14
|
+
@violations = CollectionByFile.new
|
15
|
+
@errors = CollectionByFile.new
|
16
|
+
@configerrors = Hash.new { |hash, key| hash[key] = [] }
|
17
|
+
|
18
|
+
@infos_by_rules = {}
|
13
19
|
@current_violations = []
|
14
20
|
@current_violation = nil
|
15
21
|
@current_error = nil
|
16
|
-
@
|
17
|
-
@filename = ''
|
22
|
+
@current_configerror = nil
|
18
23
|
@filter_set = filter_set
|
19
24
|
@working_dir = working_dir
|
20
25
|
@branch_name = branch_name
|
26
|
+
|
27
|
+
@cur_text = String.new(capacity: 200)
|
21
28
|
end
|
22
29
|
|
23
30
|
def start_element(name, attrs = [])
|
24
31
|
attrs = attrs.to_h
|
25
|
-
@current_element = name
|
26
32
|
|
27
33
|
case name
|
28
34
|
when 'file'
|
29
|
-
|
30
|
-
@current_filename = remove_work_dir!(attrs['name'])
|
35
|
+
handle_start_file attrs
|
31
36
|
when 'violation'
|
32
|
-
|
37
|
+
handle_start_violation attrs
|
33
38
|
when 'error'
|
34
|
-
|
35
|
-
|
36
|
-
|
39
|
+
handle_start_error attrs
|
40
|
+
when 'configerror'
|
41
|
+
handle_start_configerror attrs
|
37
42
|
end
|
38
43
|
end
|
39
44
|
|
40
|
-
def
|
41
|
-
|
45
|
+
def characters(string)
|
46
|
+
@cur_text << remove_work_dir!(string)
|
42
47
|
end
|
43
48
|
|
44
|
-
def
|
45
|
-
@
|
49
|
+
def cdata_block(string)
|
50
|
+
@cur_text << remove_work_dir!(string)
|
46
51
|
end
|
47
52
|
|
48
53
|
def end_element(name)
|
49
54
|
case name
|
50
55
|
when 'file'
|
51
|
-
|
52
|
-
@violations.add_violations_by_filename(@current_filename, @current_violations)
|
53
|
-
end
|
56
|
+
@violations.add_all(@current_filename, @current_violations)
|
54
57
|
@current_filename = nil
|
55
58
|
when 'violation'
|
56
|
-
|
59
|
+
if match_filter_set?(@current_violation)
|
60
|
+
@current_violation.message = finish_text!
|
61
|
+
@current_violations.push(@current_violation)
|
62
|
+
end
|
57
63
|
@current_violation = nil
|
58
64
|
when 'error'
|
59
|
-
@
|
65
|
+
@current_error.stack_trace = finish_text!
|
66
|
+
@errors.add_all(@current_filename, [@current_error])
|
60
67
|
@current_filename = nil
|
61
68
|
@current_error = nil
|
69
|
+
when 'configerror'
|
70
|
+
@configerrors[@current_configerror.rulename].push(@current_configerror)
|
71
|
+
@current_configerror = nil
|
62
72
|
end
|
73
|
+
@cur_text.clear
|
63
74
|
end
|
64
75
|
|
65
|
-
|
66
|
-
|
67
|
-
|
76
|
+
private
|
77
|
+
|
78
|
+
# Modifies the string in place and returns it
|
79
|
+
# (this is what sub! does, except it returns nil if no replacement occurred)
|
80
|
+
def remove_work_dir!(str)
|
81
|
+
str.sub!(/#{@working_dir}/, '')
|
82
|
+
str
|
83
|
+
end
|
84
|
+
|
85
|
+
def finish_text!
|
86
|
+
res = @cur_text.strip!.dup.freeze
|
87
|
+
@cur_text.clear
|
88
|
+
res
|
68
89
|
end
|
69
90
|
|
70
91
|
def match_filter_set?(violation)
|
71
92
|
return true if @filter_set.nil?
|
72
93
|
|
73
|
-
|
74
|
-
|
75
|
-
|
94
|
+
ruleset_attr = violation.ruleset_name.delete(' ').downcase! << '.xml'
|
95
|
+
return true if @filter_set.include?(ruleset_attr)
|
96
|
+
|
97
|
+
rule_ref = "#{ruleset_attr}/#{violation.rule_name}"
|
98
|
+
|
99
|
+
@filter_set.include?(rule_ref)
|
100
|
+
end
|
101
|
+
|
102
|
+
def handle_start_file(attrs)
|
103
|
+
@current_filename = remove_work_dir!(attrs['name'])
|
104
|
+
@current_violations = []
|
105
|
+
end
|
106
|
+
|
107
|
+
def handle_start_violation(attrs)
|
108
|
+
@current_violation = PmdViolation.new(
|
109
|
+
branch: @branch_name,
|
110
|
+
fname: @current_filename,
|
111
|
+
info_url: attrs['externalInfoUrl'],
|
112
|
+
bline: attrs['beginline'].to_i,
|
113
|
+
rule_name: attrs['rule'],
|
114
|
+
ruleset_name: attrs['ruleset'].freeze
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
118
|
+
def handle_start_error(attrs)
|
119
|
+
@current_filename = remove_work_dir!(attrs['filename'])
|
120
|
+
|
121
|
+
@current_error = PmdError.new(
|
122
|
+
branch: @branch_name,
|
123
|
+
filename: @current_filename,
|
124
|
+
short_message: remove_work_dir!(attrs['msg'])
|
125
|
+
)
|
126
|
+
end
|
76
127
|
|
77
|
-
|
128
|
+
def handle_start_configerror(attrs)
|
129
|
+
@current_configerror = PmdConfigError.new(attrs, @branch_name)
|
78
130
|
end
|
79
131
|
end
|
80
132
|
end
|
@@ -11,9 +11,7 @@ module PmdTester
|
|
11
11
|
document = Nokogiri::XML(File.read(list_file))
|
12
12
|
|
13
13
|
errors = schema.validate(document)
|
14
|
-
unless errors.empty?
|
15
|
-
raise ProjectsParserException.new(errors), "Schema validate failed: In #{list_file}"
|
16
|
-
end
|
14
|
+
raise ProjectsParserException.new(errors), "Schema validate failed: In #{list_file}" unless errors.empty?
|
17
15
|
|
18
16
|
projects = []
|
19
17
|
document.xpath('//project').each do |project|
|
@@ -23,7 +21,7 @@ module PmdTester
|
|
23
21
|
end
|
24
22
|
|
25
23
|
def schema_file_path
|
26
|
-
ResourceLocator.locate('config/
|
24
|
+
ResourceLocator.locate('config/projectlist_1_1_0.xsd')
|
27
25
|
end
|
28
26
|
end
|
29
27
|
|
@@ -5,11 +5,16 @@ require 'json'
|
|
5
5
|
module PmdTester
|
6
6
|
# This class represents all details about branch of pmd
|
7
7
|
class PmdBranchDetail
|
8
|
+
include PmdTester
|
9
|
+
|
8
10
|
attr_accessor :branch_last_sha
|
9
11
|
attr_accessor :branch_last_message
|
10
12
|
attr_accessor :branch_name
|
11
13
|
# The branch's execution time on all standard projects
|
12
14
|
attr_accessor :execution_time
|
15
|
+
attr_accessor :jdk_version
|
16
|
+
attr_accessor :language
|
17
|
+
attr_accessor :pull_request
|
13
18
|
|
14
19
|
def self.branch_filename(branch_name)
|
15
20
|
branch_name&.tr('/', '_')
|
@@ -22,32 +27,51 @@ module PmdTester
|
|
22
27
|
branch_filename = PmdBranchDetail.branch_filename(branch_name)
|
23
28
|
@base_branch_dir = "target/reports/#{branch_filename}" unless @branch_name.nil?
|
24
29
|
@execution_time = 0
|
30
|
+
# the result of command 'java -version' is going to stderr
|
31
|
+
@jdk_version = Cmd.stderr_of('java -version')
|
32
|
+
@language = ENV['LANG'] # the locale
|
33
|
+
|
34
|
+
prnum = ENV[PR_NUM_ENV_VAR]
|
35
|
+
@pull_request = prnum == 'false' ? nil : prnum
|
25
36
|
end
|
26
37
|
|
27
|
-
def load
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
hash
|
38
|
+
def self.load(branch_name, logger)
|
39
|
+
details = PmdBranchDetail.new(branch_name)
|
40
|
+
if File.exist?(details.path_to_save_file)
|
41
|
+
hash = JSON.parse(File.read(details.path_to_save_file))
|
42
|
+
details.branch_last_sha = hash['branch_last_sha']
|
43
|
+
details.branch_last_message = hash['branch_last_message']
|
44
|
+
details.branch_name = hash['branch_name']
|
45
|
+
details.execution_time = hash['execution_time']
|
46
|
+
details.jdk_version = hash['jdk_version']
|
47
|
+
details.language = hash['language']
|
48
|
+
details.pull_request = hash['pull_request']
|
35
49
|
else
|
36
|
-
|
50
|
+
details.jdk_version = ''
|
51
|
+
details.language = ''
|
52
|
+
logger&.warn "#{details.path_to_save_file} doesn't exist!"
|
37
53
|
end
|
54
|
+
details
|
38
55
|
end
|
39
56
|
|
40
57
|
def save
|
41
58
|
hash = { branch_last_sha: @branch_last_sha,
|
42
59
|
branch_last_message: @branch_last_message,
|
43
60
|
branch_name: @branch_name,
|
44
|
-
execution_time: @execution_time
|
45
|
-
|
61
|
+
execution_time: @execution_time,
|
62
|
+
jdk_version: @jdk_version,
|
63
|
+
language: @language,
|
64
|
+
pull_request: @pull_request }
|
65
|
+
|
66
|
+
FileUtils.mkdir_p(@base_branch_dir) unless File.directory?(@base_branch_dir)
|
67
|
+
|
68
|
+
file = File.new(path_to_save_file, 'w')
|
46
69
|
file.puts JSON.generate(hash)
|
47
70
|
file.close
|
71
|
+
self
|
48
72
|
end
|
49
73
|
|
50
|
-
def
|
74
|
+
def path_to_save_file
|
51
75
|
"#{@base_branch_dir}/branch_info.json"
|
52
76
|
end
|
53
77
|
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PmdTester
|
4
|
+
# This class represents a 'configerror' element of Pmd xml report
|
5
|
+
# and which Pmd branch the 'configerror' is from
|
6
|
+
class PmdConfigError
|
7
|
+
# The pmd branch type, 'base' or 'patch'
|
8
|
+
attr_reader :branch
|
9
|
+
|
10
|
+
# The schema of 'configerror' node:
|
11
|
+
# <xs:complexType name="configerror">
|
12
|
+
# <xs:attribute name="rule" type="xs:string" use="required" />
|
13
|
+
# <xs:attribute name="msg" type="xs:string" use="required" />
|
14
|
+
# </xs:complexType>
|
15
|
+
attr_reader :attrs
|
16
|
+
attr_accessor :old_error
|
17
|
+
|
18
|
+
def initialize(attrs, branch)
|
19
|
+
@attrs = attrs
|
20
|
+
|
21
|
+
@changed = false
|
22
|
+
@branch = branch
|
23
|
+
end
|
24
|
+
|
25
|
+
def rulename
|
26
|
+
@attrs['rule']
|
27
|
+
end
|
28
|
+
|
29
|
+
def msg
|
30
|
+
@attrs['msg']
|
31
|
+
end
|
32
|
+
|
33
|
+
def sort_key
|
34
|
+
rulename
|
35
|
+
end
|
36
|
+
|
37
|
+
def changed?
|
38
|
+
@changed
|
39
|
+
end
|
40
|
+
|
41
|
+
def eql?(other)
|
42
|
+
rulename.eql?(other.rulename) && msg.eql?(other.msg)
|
43
|
+
end
|
44
|
+
|
45
|
+
def try_merge?(other)
|
46
|
+
if branch != BASE &&
|
47
|
+
branch != other.branch &&
|
48
|
+
rulename == other.rulename &&
|
49
|
+
!changed? # not already changed
|
50
|
+
@changed = true
|
51
|
+
@old_error = other
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
false
|
56
|
+
end
|
57
|
+
|
58
|
+
def hash
|
59
|
+
[rulename, msg].hash
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|