pmdtester 1.0.0.pre.beta2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rubocop.yml +10 -0
- data/.rubocop_todo.yml +27 -0
- data/.travis.yml +22 -0
- data/Gemfile +20 -0
- data/History.md +36 -0
- data/LICENSE +25 -0
- data/README.rdoc +33 -0
- data/Rakefile +54 -0
- data/bin/pmdtester +7 -0
- data/config/all-java.xml +18 -0
- data/config/design.xml +78 -0
- data/config/project-list.xml +29 -0
- data/config/projectlist_1_0_0.xsd +28 -0
- data/lib/pmdtester/builders/diff_builder.rb +35 -0
- data/lib/pmdtester/builders/diff_report_builder.rb +226 -0
- data/lib/pmdtester/builders/html_report_builder.rb +34 -0
- data/lib/pmdtester/builders/pmd_report_builder.rb +128 -0
- data/lib/pmdtester/builders/rule_set_builder.rb +114 -0
- data/lib/pmdtester/builders/summary_report_builder.rb +149 -0
- data/lib/pmdtester/cmd.rb +40 -0
- data/lib/pmdtester/parsers/options.rb +147 -0
- data/lib/pmdtester/parsers/pmd_report_document.rb +82 -0
- data/lib/pmdtester/parsers/projects_parser.rb +41 -0
- data/lib/pmdtester/pmd_branch_detail.rb +67 -0
- data/lib/pmdtester/pmd_error.rb +67 -0
- data/lib/pmdtester/pmd_report_detail.rb +47 -0
- data/lib/pmdtester/pmd_violation.rb +66 -0
- data/lib/pmdtester/pmdtester.rb +17 -0
- data/lib/pmdtester/project.rb +112 -0
- data/lib/pmdtester/report_diff.rb +111 -0
- data/lib/pmdtester/resource_locator.rb +10 -0
- data/lib/pmdtester/runner.rb +130 -0
- data/resources/css/maven-base.css +155 -0
- data/resources/css/maven-theme.css +171 -0
- metadata +249 -0
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'slop'
|
4
|
+
require_relative '../pmdtester'
|
5
|
+
|
6
|
+
module PmdTester
|
7
|
+
class MissRequiredOptionError < StandardError; end
|
8
|
+
class InvalidModeError < StandardError; end
|
9
|
+
|
10
|
+
# The Options is a class responsible of parsing all the
|
11
|
+
# command line options
|
12
|
+
class Options
|
13
|
+
include PmdTester
|
14
|
+
ANY = 'any'
|
15
|
+
LOCAL = 'local'
|
16
|
+
ONLINE = 'online'
|
17
|
+
SINGLE = 'single'
|
18
|
+
VERSION = '1.0.0-beta2'
|
19
|
+
|
20
|
+
attr_reader :local_git_repo
|
21
|
+
attr_reader :base_branch
|
22
|
+
attr_reader :patch_branch
|
23
|
+
attr_accessor :base_config
|
24
|
+
attr_accessor :patch_config
|
25
|
+
attr_reader :config
|
26
|
+
attr_reader :project_list
|
27
|
+
attr_reader :mode
|
28
|
+
attr_reader :html_flag
|
29
|
+
attr_reader :auto_config_flag
|
30
|
+
attr_reader :debug_flag
|
31
|
+
attr_accessor :filter_set
|
32
|
+
|
33
|
+
def initialize(argv)
|
34
|
+
options = parse(argv)
|
35
|
+
@local_git_repo = options[:r]
|
36
|
+
@base_branch = options[:b]
|
37
|
+
@patch_branch = options[:p]
|
38
|
+
@base_config = options[:bc]
|
39
|
+
@patch_config = options[:pc]
|
40
|
+
@config = options[:c]
|
41
|
+
@project_list = options[:l]
|
42
|
+
@mode = options[:m]
|
43
|
+
@html_flag = options[:f]
|
44
|
+
@auto_config_flag = options[:a]
|
45
|
+
@debug_flag = options[:d]
|
46
|
+
@filter_set = nil
|
47
|
+
|
48
|
+
# if the 'config' option is selected then `config` overrides `base_config` and `patch_config`
|
49
|
+
@base_config = @config if !@config.nil? && @mode == 'local'
|
50
|
+
@patch_config = @config if !@config.nil? && @mode == 'local'
|
51
|
+
|
52
|
+
logger.level = @debug_flag ? Logger::DEBUG : Logger::INFO
|
53
|
+
check_options
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def parse(argv)
|
59
|
+
mode_message = <<-DOC
|
60
|
+
the mode of the tool: 'local', 'online' or 'single'
|
61
|
+
single: Set this option to 'single' if your patch branch contains changes
|
62
|
+
for any option that can't work on master/base branch
|
63
|
+
online: Set this option to 'online' if you want to download
|
64
|
+
'the PMD report of master/base branch rather than generating it locally
|
65
|
+
local: Default option is 'local'
|
66
|
+
DOC
|
67
|
+
|
68
|
+
Slop.parse argv do |o|
|
69
|
+
o.string '-r', '--local-git-repo', 'path to the local PMD repository'
|
70
|
+
o.string '-b', '--base-branch', 'name of the base branch in local PMD repository'
|
71
|
+
o.string '-p', '--patch-branch',
|
72
|
+
'name of the patch branch in local PMD repository'
|
73
|
+
o.string '-bc', '--base-config', 'path to the base PMD configuration file'
|
74
|
+
o.string '-pc', '--patch-config', 'path to the patch PMD configuration file'
|
75
|
+
o.string '-c', '--config', 'path to the base and patch PMD configuration file'
|
76
|
+
o.string '-l', '--list-of-project',
|
77
|
+
'path to the file which contains the list of standard projects'
|
78
|
+
o.string '-m', '--mode', mode_message, default: 'local'
|
79
|
+
o.bool '-f', '--html-flag',
|
80
|
+
'whether to not generate the html diff report in single mode'
|
81
|
+
o.bool '-a', '--auto-gen-config',
|
82
|
+
'whether to generate configurations automatically based on branch differences,' \
|
83
|
+
'this option only works in online and local mode'
|
84
|
+
o.bool '-d', '--debug',
|
85
|
+
'whether change log level to DEBUG to see more information'
|
86
|
+
o.on '-v', '--version' do
|
87
|
+
puts VERSION
|
88
|
+
exit
|
89
|
+
end
|
90
|
+
o.on '-h', '--help' do
|
91
|
+
puts o
|
92
|
+
exit
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def check_options
|
98
|
+
check_common_options
|
99
|
+
case @mode
|
100
|
+
when LOCAL
|
101
|
+
check_local_options
|
102
|
+
when SINGLE
|
103
|
+
check_single_options
|
104
|
+
when ONLINE
|
105
|
+
check_online_options
|
106
|
+
else
|
107
|
+
msg = "The mode '#{@mode}' is invalid!"
|
108
|
+
logger.error msg
|
109
|
+
raise InvalidModeError, msg
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def check_local_options
|
114
|
+
check_option(LOCAL, 'base branch name', @base_branch)
|
115
|
+
check_option(LOCAL, 'base branch config path', @base_config) unless @auto_config_flag
|
116
|
+
check_option(LOCAL, 'patch branch name', @patch_branch)
|
117
|
+
check_option(LOCAL, 'patch branch config path', @patch_config) unless @auto_config_flag
|
118
|
+
check_option(LOCAL, 'list of projects file path', @project_list)
|
119
|
+
end
|
120
|
+
|
121
|
+
def check_single_options
|
122
|
+
check_option(SINGLE, 'patch branch name', @patch_branch)
|
123
|
+
check_option(SINGLE, 'patch branch config path', @patch_config)
|
124
|
+
check_option(SINGLE, 'list of projects file path', @project_list)
|
125
|
+
end
|
126
|
+
|
127
|
+
def check_online_options
|
128
|
+
check_option(ONLINE, 'base branch name', @base_branch)
|
129
|
+
check_option(ONLINE, 'patch branch name', @patch_branch)
|
130
|
+
end
|
131
|
+
|
132
|
+
def check_common_options
|
133
|
+
check_option(ANY, 'local git repository path', @local_git_repo)
|
134
|
+
check_option(ANY, 'patch branch name', @patch_branch)
|
135
|
+
end
|
136
|
+
|
137
|
+
def check_option(mode, option_name, option)
|
138
|
+
if option.nil?
|
139
|
+
msg = "#{option_name} is required in #{mode} mode."
|
140
|
+
logger.error msg
|
141
|
+
raise MissRequiredOptionError, msg
|
142
|
+
else
|
143
|
+
logger.info "#{option_name}: #{option}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
require_relative '../pmd_violation'
|
5
|
+
require_relative '../pmd_error'
|
6
|
+
module PmdTester
|
7
|
+
# This class is used for registering types of events you are interested in handling.
|
8
|
+
# Also see: https://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/SAX/Document
|
9
|
+
class PmdReportDocument < Nokogiri::XML::SAX::Document
|
10
|
+
attr_reader :violations
|
11
|
+
attr_reader :errors
|
12
|
+
def initialize(branch_name, working_dir, filter_set = nil)
|
13
|
+
@violations = PmdViolations.new
|
14
|
+
@errors = PmdErrors.new
|
15
|
+
@current_violations = []
|
16
|
+
@current_violation = nil
|
17
|
+
@current_error = nil
|
18
|
+
@current_element = ''
|
19
|
+
@filename = ''
|
20
|
+
@filter_set = filter_set
|
21
|
+
@working_dir = working_dir
|
22
|
+
@branch_name = branch_name
|
23
|
+
end
|
24
|
+
|
25
|
+
def start_element(name, attrs = [])
|
26
|
+
attrs = attrs.to_h
|
27
|
+
@current_element = name
|
28
|
+
|
29
|
+
case name
|
30
|
+
when 'file'
|
31
|
+
@current_violations = []
|
32
|
+
@current_filename = remove_work_dir!(attrs['name'])
|
33
|
+
when 'violation'
|
34
|
+
@current_violation = PmdViolation.new(attrs, @branch_name)
|
35
|
+
when 'error'
|
36
|
+
@current_filename = remove_work_dir!(attrs['filename'])
|
37
|
+
remove_work_dir!(attrs['msg'])
|
38
|
+
@current_error = PmdError.new(attrs, @branch_name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def remove_work_dir!(str)
|
43
|
+
str.sub!(/#{@working_dir}/, '')
|
44
|
+
end
|
45
|
+
|
46
|
+
def characters(string)
|
47
|
+
@current_violation.text = string unless @current_violation.nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
def end_element(name)
|
51
|
+
case name
|
52
|
+
when 'file'
|
53
|
+
unless @current_violations.empty?
|
54
|
+
@violations.add_violations_by_filename(@current_filename, @current_violations)
|
55
|
+
end
|
56
|
+
@current_filename = nil
|
57
|
+
when 'violation'
|
58
|
+
@current_violations.push(@current_violation) if match_filter_set?(@current_violation)
|
59
|
+
@current_violation = nil
|
60
|
+
when 'error'
|
61
|
+
@errors.add_error_by_filename(@current_filename, @current_error)
|
62
|
+
@current_filename = nil
|
63
|
+
@current_error = nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def cdata_block(string)
|
68
|
+
remove_work_dir!(string)
|
69
|
+
@current_error.text = string unless @current_error.nil?
|
70
|
+
end
|
71
|
+
|
72
|
+
def match_filter_set?(violation)
|
73
|
+
return true if @filter_set.nil?
|
74
|
+
|
75
|
+
@filter_set.each do |ruleset|
|
76
|
+
return true if ruleset.eql?(violation.attrs['ruleset'].delete(' ').downcase)
|
77
|
+
end
|
78
|
+
|
79
|
+
false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
require_relative '../project'
|
5
|
+
require_relative '../resource_locator'
|
6
|
+
|
7
|
+
module PmdTester
|
8
|
+
# The ProjectsParser is a class responsible of parsing
|
9
|
+
# the projects XML file to get the Project object array
|
10
|
+
class ProjectsParser
|
11
|
+
def parse(list_file)
|
12
|
+
schema = Nokogiri::XML::Schema(File.read(schema_file_path))
|
13
|
+
document = Nokogiri::XML(File.read(list_file))
|
14
|
+
|
15
|
+
errors = schema.validate(document)
|
16
|
+
unless errors.empty?
|
17
|
+
raise ProjectsParserException.new(errors), "Schema validate failed: In #{list_file}"
|
18
|
+
end
|
19
|
+
|
20
|
+
projects = []
|
21
|
+
document.xpath('//project').each do |project|
|
22
|
+
projects.push(Project.new(project))
|
23
|
+
end
|
24
|
+
projects
|
25
|
+
end
|
26
|
+
|
27
|
+
def schema_file_path
|
28
|
+
ResourceLocator.locate('config/projectlist_1_0_0.xsd')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# When this exception is raised, it means that
|
33
|
+
# schema validate of 'project-list' xml file failed
|
34
|
+
class ProjectsParserException < RuntimeError
|
35
|
+
attr_reader :errors
|
36
|
+
|
37
|
+
def initialize(errors)
|
38
|
+
@errors = errors
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require_relative './pmd_report_detail'
|
5
|
+
|
6
|
+
module PmdTester
|
7
|
+
# This class represents all details about branch of pmd
|
8
|
+
class PmdBranchDetail
|
9
|
+
attr_accessor :branch_last_sha
|
10
|
+
attr_accessor :branch_last_message
|
11
|
+
attr_accessor :branch_name
|
12
|
+
# The branch's execution time on all standard projects
|
13
|
+
attr_accessor :execution_time
|
14
|
+
|
15
|
+
def self.branch_filename(branch_name)
|
16
|
+
branch_name&.tr('/', '_')
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(branch_name)
|
20
|
+
@branch_last_sha = ''
|
21
|
+
@branch_last_message = ''
|
22
|
+
@branch_name = branch_name
|
23
|
+
branch_filename = PmdBranchDetail.branch_filename(branch_name)
|
24
|
+
@base_branch_dir = "target/reports/#{branch_filename}" unless @branch_name.nil?
|
25
|
+
@execution_time = 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def load
|
29
|
+
if File.exist?(branch_details_path)
|
30
|
+
hash = JSON.parse(File.read(branch_details_path))
|
31
|
+
@branch_last_sha = hash['branch_last_sha']
|
32
|
+
@branch_last_message = hash['branch_last_message']
|
33
|
+
@branch_name = hash['branch_name']
|
34
|
+
@execution_time = hash['execution_time']
|
35
|
+
hash
|
36
|
+
else
|
37
|
+
{}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def save
|
42
|
+
hash = { branch_last_sha: @branch_last_sha,
|
43
|
+
branch_last_message: @branch_last_message,
|
44
|
+
branch_name: @branch_name,
|
45
|
+
execution_time: @execution_time }
|
46
|
+
file = File.new(branch_details_path, 'w')
|
47
|
+
file.puts JSON.generate(hash)
|
48
|
+
file.close
|
49
|
+
end
|
50
|
+
|
51
|
+
def branch_details_path
|
52
|
+
"#{@base_branch_dir}/branch_info.json"
|
53
|
+
end
|
54
|
+
|
55
|
+
def target_branch_config_path
|
56
|
+
"#{@base_branch_dir}/config.xml"
|
57
|
+
end
|
58
|
+
|
59
|
+
def target_branch_project_list_path
|
60
|
+
"#{@base_branch_dir}/project-list.xml"
|
61
|
+
end
|
62
|
+
|
63
|
+
def format_execution_time
|
64
|
+
PmdReportDetail.convert_seconds(@execution_time)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PmdTester
|
4
|
+
# This class is used to store pmd errors and its size.
|
5
|
+
class PmdErrors
|
6
|
+
attr_reader :errors
|
7
|
+
attr_reader :errors_size
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
# key:filename as String => value:PmdError Array
|
11
|
+
@errors = {}
|
12
|
+
@errors_size = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_error_by_filename(filename, error)
|
16
|
+
if @errors.key?(filename)
|
17
|
+
@errors[filename].push(error)
|
18
|
+
else
|
19
|
+
@errors.store(filename, [error])
|
20
|
+
end
|
21
|
+
@errors_size += 1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# This class represents a 'error' element of Pmd xml report
|
26
|
+
# and which Pmd branch the 'error' is from
|
27
|
+
class PmdError
|
28
|
+
# The pmd branch type, 'base' or 'patch'
|
29
|
+
attr_reader :branch
|
30
|
+
|
31
|
+
# The schema of 'error' node:
|
32
|
+
# <xs:complexType name="error">
|
33
|
+
# <xs:simpleContent>
|
34
|
+
# <xs:extension base="xs:string">
|
35
|
+
# <xs:attribute name="filename" type="xs:string" use="required"/>
|
36
|
+
# <xs:attribute name="msg" type="xs:string" use="required"/>
|
37
|
+
# </xs:extension>
|
38
|
+
# </xs:simpleContent>
|
39
|
+
# </xs:complexType>
|
40
|
+
attr_reader :attrs
|
41
|
+
attr_accessor :text
|
42
|
+
|
43
|
+
def initialize(attrs, branch)
|
44
|
+
@attrs = attrs
|
45
|
+
|
46
|
+
@branch = branch
|
47
|
+
@text = ''
|
48
|
+
end
|
49
|
+
|
50
|
+
def filename
|
51
|
+
@attrs['filename']
|
52
|
+
end
|
53
|
+
|
54
|
+
def msg
|
55
|
+
@attrs['msg']
|
56
|
+
end
|
57
|
+
|
58
|
+
def eql?(other)
|
59
|
+
filename.eql?(other.filename) && msg.eql?(other.msg) &&
|
60
|
+
@text.eql?(other.text)
|
61
|
+
end
|
62
|
+
|
63
|
+
def hash
|
64
|
+
[filename, msg, @text].hash
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module PmdTester
|
6
|
+
# This class represents all details about report of pmd
|
7
|
+
class PmdReportDetail
|
8
|
+
attr_accessor :execution_time
|
9
|
+
attr_accessor :timestamp
|
10
|
+
attr_reader :working_dir
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@execution_time = 0
|
14
|
+
@timestamp = ''
|
15
|
+
@working_dir = Dir.getwd
|
16
|
+
end
|
17
|
+
|
18
|
+
def save(report_info_path)
|
19
|
+
hash = { execution_time: @execution_time, timestamp: @timestamp, working_dir: @working_dir }
|
20
|
+
file = File.new(report_info_path, 'w')
|
21
|
+
file.puts JSON.generate(hash)
|
22
|
+
file.close
|
23
|
+
end
|
24
|
+
|
25
|
+
def load(report_info_path)
|
26
|
+
if File.exist?(report_info_path)
|
27
|
+
hash = JSON.parse(File.read(report_info_path))
|
28
|
+
@execution_time = hash['execution_time']
|
29
|
+
@timestamp = hash['timestamp']
|
30
|
+
@working_dir = hash['working_dir']
|
31
|
+
hash
|
32
|
+
else
|
33
|
+
puts "#{report_info_path} doesn't exist"
|
34
|
+
{}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def format_execution_time
|
39
|
+
self.class.convert_seconds(@execution_time)
|
40
|
+
end
|
41
|
+
|
42
|
+
# convert seconds into HH::MM::SS
|
43
|
+
def self.convert_seconds(seconds)
|
44
|
+
Time.at(seconds.abs).utc.strftime('%H:%M:%S')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PmdTester
|
4
|
+
# This class is used to store pmd violations and its size.
|
5
|
+
class PmdViolations
|
6
|
+
attr_reader :violations
|
7
|
+
attr_reader :violations_size
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
# key:filename as String => value:PmdViolation Array
|
11
|
+
@violations = {}
|
12
|
+
@violations_size = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_violations_by_filename(filename, violations)
|
16
|
+
@violations.store(filename, violations)
|
17
|
+
@violations_size += violations.size
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# This class represents a 'violation' element of Pmd xml report
|
22
|
+
# and which pmd branch the 'violation' is from
|
23
|
+
class PmdViolation
|
24
|
+
# The pmd branch type, 'base' or 'patch'
|
25
|
+
attr_reader :branch
|
26
|
+
|
27
|
+
# The schema of 'violation' element:
|
28
|
+
# <xs:complexType name="violation">
|
29
|
+
# <xs:simpleContent>
|
30
|
+
# <xs:extension base="xs:string">
|
31
|
+
# <xs:attribute name="beginline" type="xs:integer" use="required" />
|
32
|
+
# <xs:attribute name="endline" type="xs:integer" use="required" />
|
33
|
+
# <xs:attribute name="begincolumn" type="xs:integer" use="required" />
|
34
|
+
# <xs:attribute name="endcolumn" type="xs:integer" use="required" />
|
35
|
+
# <xs:attribute name="rule" type="xs:string" use="required" />
|
36
|
+
# <xs:attribute name="ruleset" type="xs:string" use="required" />
|
37
|
+
# <xs:attribute name="package" type="xs:string" use="optional" />
|
38
|
+
# <xs:attribute name="class" type="xs:string" use="optional" />
|
39
|
+
# <xs:attribute name="method" type="xs:string" use="optional" />
|
40
|
+
# <xs:attribute name="variable" type="xs:string" use="optional" />
|
41
|
+
# <xs:attribute name="externalInfoUrl" type="xs:string" use="optional" />
|
42
|
+
# <xs:attribute name="priority" type="xs:string" use="required" />
|
43
|
+
# </xs:extension>
|
44
|
+
# </xs:simpleContent>
|
45
|
+
# </xs:complexType>
|
46
|
+
|
47
|
+
attr_reader :attrs
|
48
|
+
attr_accessor :text
|
49
|
+
|
50
|
+
def initialize(attrs, branch)
|
51
|
+
@attrs = attrs
|
52
|
+
@branch = branch
|
53
|
+
@text = ''
|
54
|
+
end
|
55
|
+
|
56
|
+
def eql?(other)
|
57
|
+
@attrs['beginline'].eql?(other.attrs['beginline']) &&
|
58
|
+
@attrs['rule'].eql?(other.attrs['rule']) &&
|
59
|
+
@text.eql?(other.text)
|
60
|
+
end
|
61
|
+
|
62
|
+
def hash
|
63
|
+
[@attrs['beginline'], @attrs['rule'], @text].hash
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
# PmdTester is a regression testing tool ensure that new problems
|
6
|
+
# and unexpected behaviors will not be introduced to PMD project
|
7
|
+
# after fixing an issue , and new rules can work as expected.
|
8
|
+
module PmdTester
|
9
|
+
def logger
|
10
|
+
PmdTester.logger
|
11
|
+
end
|
12
|
+
|
13
|
+
# Global, memoized, lazy initialized instance of a logger
|
14
|
+
def self.logger
|
15
|
+
@logger ||= Logger.new(STDOUT)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './pmd_branch_detail'
|
4
|
+
|
5
|
+
module PmdTester
|
6
|
+
# This class represents all the information about the project
|
7
|
+
class Project
|
8
|
+
REPOSITORIES_PATH = 'target/repositories'
|
9
|
+
|
10
|
+
attr_reader :name
|
11
|
+
attr_reader :type
|
12
|
+
attr_reader :connection
|
13
|
+
attr_reader :webview_url
|
14
|
+
attr_reader :tag
|
15
|
+
attr_reader :exclude_pattern
|
16
|
+
attr_accessor :report_diff
|
17
|
+
# key: pmd branch name as String => value: local path of pmd report
|
18
|
+
|
19
|
+
def initialize(project)
|
20
|
+
@name = project.at_xpath('name').text
|
21
|
+
@type = project.at_xpath('type').text
|
22
|
+
@connection = project.at_xpath('connection').text
|
23
|
+
|
24
|
+
@tag = 'master'
|
25
|
+
tag_element = project.at_xpath('tag')
|
26
|
+
@tag = tag_element.text unless tag_element.nil?
|
27
|
+
|
28
|
+
webview_url_element = project.at_xpath('webview-url')
|
29
|
+
@webview_url = default_webview_url
|
30
|
+
@webview_url = webview_url_element.text unless webview_url_element.nil?
|
31
|
+
|
32
|
+
@exclude_pattern = []
|
33
|
+
project.xpath('exclude-pattern').each do |ep|
|
34
|
+
@exclude_pattern.push(ep.text)
|
35
|
+
end
|
36
|
+
|
37
|
+
@report_diff = nil
|
38
|
+
end
|
39
|
+
|
40
|
+
# Generate the default webview url for the projects
|
41
|
+
# stored on github.
|
42
|
+
# For other projects return value is `connection`.
|
43
|
+
def default_webview_url
|
44
|
+
if @type.eql?('git') && @connection.include?('github.com')
|
45
|
+
"#{@connection}/tree/#{@tag}"
|
46
|
+
else
|
47
|
+
@connection
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Change the file path from 'LOCAL_DIR/SOURCE_CODE_PATH' to
|
52
|
+
# 'WEB_VIEW_URL/SOURCE_CODE_PATH'
|
53
|
+
def get_webview_url(file_path)
|
54
|
+
file_path.gsub(%r{/#{local_source_path}}, @webview_url)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Change the file path from 'LOCAL_DIR/SOURCE_CODE_PATH' to
|
58
|
+
# 'PROJECT_NAME/SOURCE_CODE_PATH'
|
59
|
+
def get_path_inside_project(file_path)
|
60
|
+
file_path.gsub(%r{/#{local_source_path}}, @name)
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_pmd_report_path(branch_name)
|
64
|
+
if branch_name.nil?
|
65
|
+
nil
|
66
|
+
else
|
67
|
+
"#{get_project_target_dir(branch_name)}/pmd_report.xml"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def get_report_info_path(branch_name)
|
72
|
+
if branch_name.nil?
|
73
|
+
nil
|
74
|
+
else
|
75
|
+
"#{get_project_target_dir(branch_name)}/report_info.json"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_project_target_dir(branch_name)
|
80
|
+
branch_filename = PmdBranchDetail.branch_filename(branch_name)
|
81
|
+
dir = "target/reports/#{branch_filename}/#{@name}"
|
82
|
+
FileUtils.mkdir_p(dir) unless File.directory?(dir)
|
83
|
+
dir
|
84
|
+
end
|
85
|
+
|
86
|
+
def local_source_path
|
87
|
+
"#{REPOSITORIES_PATH}/#{@name}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def target_diff_report_path
|
91
|
+
dir = "target/reports/diff/#{@name}"
|
92
|
+
FileUtils.mkdir_p(dir) unless File.directory?(dir)
|
93
|
+
dir
|
94
|
+
end
|
95
|
+
|
96
|
+
def diff_report_index_path
|
97
|
+
"#{target_diff_report_path}/index.html"
|
98
|
+
end
|
99
|
+
|
100
|
+
def diff_report_index_ref_path
|
101
|
+
"./#{name}/index.html"
|
102
|
+
end
|
103
|
+
|
104
|
+
def diffs_exist?
|
105
|
+
@report_diff.diffs_exist?
|
106
|
+
end
|
107
|
+
|
108
|
+
def introduce_new_errors?
|
109
|
+
@report_diff.introduce_new_errors?
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|