pmdtester 1.0.0.pre.beta3 → 1.0.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.
@@ -4,6 +4,7 @@ module PmdTester
4
4
  # This class is the parent of all classes which is used to build html report
5
5
  class HtmlReportBuilder
6
6
  CSS_SRC_DIR = ResourceLocator.locate('resources/css')
7
+ NO_DIFFERENCES_MESSAGE = 'No differences found!'
7
8
 
8
9
  def build_html_report(title_name)
9
10
  html_builder = Nokogiri::HTML::Builder.new do |doc|
@@ -25,9 +26,31 @@ module PmdTester
25
26
  end
26
27
  end
27
28
 
29
+ def build_table_head(doc, *columns)
30
+ doc.thead do
31
+ doc.tr do
32
+ columns.each do |column|
33
+ doc.th column
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ def build_table_anchor_column(doc, prefix, index)
40
+ doc.td do
41
+ doc.a(id: "#{prefix}#{index}", href: "##{prefix}#{index}") { doc.text '#' }
42
+ end
43
+ end
44
+
28
45
  def copy_css(report_dir)
29
46
  css_dest_dir = "#{report_dir}/css"
30
47
  FileUtils.copy_entry(CSS_SRC_DIR, css_dest_dir)
31
48
  end
49
+
50
+ def build_table_content_for(doc, removed_size, new_size)
51
+ doc.font(color: 'red') { doc.text "-#{removed_size}" }
52
+ doc.text ' | '
53
+ doc.font(color: 'green') { doc.text "+#{new_size}" }
54
+ end
32
55
  end
33
56
  end
@@ -1,17 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'fileutils'
4
+ require 'rufus-scheduler'
4
5
 
5
6
  module PmdTester
6
7
  # Building pmd xml reports according to a list of standard
7
8
  # projects and branch of pmd source code
8
9
  class PmdReportBuilder
9
10
  include PmdTester
10
- def initialize(branch_config, projects, local_git_repo, pmd_branch_name)
11
+ def initialize(branch_config, projects, local_git_repo, pmd_branch_name, threads = 1)
11
12
  @branch_config = branch_config
12
13
  @projects = projects
13
14
  @local_git_repo = local_git_repo
14
15
  @pmd_branch_name = pmd_branch_name
16
+ @threads = threads
15
17
  @pwd = Dir.getwd
16
18
 
17
19
  @pmd_branch_details = PmdBranchDetail.new(pmd_branch_name)
@@ -28,7 +30,7 @@ module PmdTester
28
30
  Cmd.execute(reset_cmd)
29
31
  end
30
32
 
31
- def get_projects
33
+ def clone_projects
32
34
  logger.info 'Cloning projects started'
33
35
 
34
36
  @projects.each do |project|
@@ -46,29 +48,58 @@ module PmdTester
46
48
  end
47
49
  logger.info "Cloning #{project.name} completed"
48
50
  end
51
+
52
+ logger.info 'Cloning projects completed'
49
53
  end
50
54
 
51
55
  def get_pmd_binary_file
52
- logger.info 'Start packaging PMD'
56
+ logger.info "#{@pmd_branch_name}: Start packaging PMD"
53
57
  Dir.chdir(@local_git_repo) do
54
- checkout_cmd = "git checkout #{@pmd_branch_name}"
55
- Cmd.execute(checkout_cmd)
58
+ current_head_sha = Cmd.execute('git rev-parse HEAD')
59
+ current_branch_sha = Cmd.execute("git rev-parse #{@pmd_branch_name}")
56
60
 
57
- @pmd_branch_details.branch_last_sha = get_last_commit_sha
58
- @pmd_branch_details.branch_last_message = get_last_commit_message
61
+ @pmd_version = determine_pmd_version
59
62
 
60
- package_cmd = './mvnw clean package -Dpmd.skip=true -Dmaven.test.skip=true' \
61
- ' -Dmaven.checkstyle.skip=true -Dmaven.javadoc.skip=true'
62
- Cmd.execute(package_cmd)
63
+ # in case we are already on the correct branch
64
+ # and a binary zip already exists...
65
+ if current_head_sha == current_branch_sha &&
66
+ File.exist?("pmd-dist/target/pmd-bin-#{@pmd_version}.zip")
67
+ logger.warn "#{@pmd_branch_name}: Skipping packaging - zip for " \
68
+ "#{@pmd_version} already exists"
69
+ else
70
+ build_pmd
71
+ end
63
72
 
64
- version_cmd = "./mvnw -q -Dexec.executable=\"echo\" -Dexec.args='${project.version}' " \
65
- '--non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec'
66
- @pmd_version = Cmd.execute(version_cmd)
73
+ @pmd_branch_details.branch_last_sha = get_last_commit_sha
74
+ @pmd_branch_details.branch_last_message = get_last_commit_message
67
75
 
76
+ logger.info "#{@pmd_branch_name}: Extracting the zip"
68
77
  unzip_cmd = "unzip -qo pmd-dist/target/pmd-bin-#{@pmd_version}.zip -d #{@pwd}/target"
69
78
  Cmd.execute(unzip_cmd)
70
79
  end
71
- logger.info 'Packaging PMD completed'
80
+ logger.info "#{@pmd_branch_name}: Packaging PMD completed"
81
+ end
82
+
83
+ def build_pmd
84
+ logger.info "#{@pmd_branch_name}: Checking out the branch"
85
+ checkout_cmd = "git checkout #{@pmd_branch_name}"
86
+ Cmd.execute(checkout_cmd)
87
+
88
+ # determine the version again - it might be different in the other branch
89
+ @pmd_version = determine_pmd_version
90
+
91
+ logger.info "#{@pmd_branch_name}: Building PMD #{@pmd_version}..."
92
+ package_cmd = './mvnw clean package' \
93
+ ' -Dmaven.test.skip=true' \
94
+ ' -Dmaven.javadoc.skip=true' \
95
+ ' -Dmaven.source.skip=true'
96
+ Cmd.execute(package_cmd)
97
+ end
98
+
99
+ def determine_pmd_version
100
+ version_cmd = "./mvnw -q -Dexec.executable=\"echo\" -Dexec.args='${project.version}' " \
101
+ '--non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec'
102
+ Cmd.execute(version_cmd)
72
103
  end
73
104
 
74
105
  def get_last_commit_sha
@@ -81,26 +112,40 @@ module PmdTester
81
112
  Cmd.execute(get_last_commit_message_cmd)
82
113
  end
83
114
 
84
- def generate_pmd_report(src_root_dir, report_file)
115
+ def generate_pmd_report(project)
85
116
  run_path = "target/pmd-bin-#{@pmd_version}/bin/run.sh"
86
- pmd_cmd = "#{run_path} pmd -d #{src_root_dir} -f xml -R #{@branch_config} " \
87
- "-r #{report_file} -failOnViolation false"
117
+ pmd_cmd = "#{run_path} pmd -d #{project.local_source_path} -f xml " \
118
+ "-R #{project.get_config_path(@pmd_branch_name)} " \
119
+ "-r #{project.get_pmd_report_path(@pmd_branch_name)} " \
120
+ "-failOnViolation false -t #{@threads}"
88
121
  start_time = Time.now
89
122
  Cmd.execute(pmd_cmd)
90
123
  end_time = Time.now
91
124
  [end_time - start_time, end_time]
92
125
  end
93
126
 
127
+ def generate_config_for(project)
128
+ doc = Nokogiri::XML(File.read(@branch_config))
129
+ ruleset = doc.at_css('ruleset')
130
+ project.exclude_pattern.each do |exclude_pattern|
131
+ ruleset.add_child("<exclude-pattern>#{exclude_pattern}</exclude-pattern>")
132
+ end
133
+
134
+ File.open(project.get_config_path(@pmd_branch_name), 'w') do |x|
135
+ x << doc.to_s
136
+ end
137
+ end
138
+
94
139
  def generate_pmd_reports
95
140
  logger.info "Generating PMD report started -- branch #{@pmd_branch_name}"
96
- get_pmd_binary_file
97
141
 
98
142
  sum_time = 0
99
143
  @projects.each do |project|
100
- logger.info "Generating #{project.name}'s PMD report"
101
- execution_time, end_time =
102
- generate_pmd_report(project.local_source_path,
103
- project.get_pmd_report_path(@pmd_branch_name))
144
+ progress_logger = SimpleProgressLogger.new(project.name)
145
+ progress_logger.start
146
+ generate_config_for(project)
147
+ execution_time, end_time = generate_pmd_report(project)
148
+ progress_logger.stop
104
149
  sum_time += execution_time
105
150
 
106
151
  report_details = PmdReportDetail.new
@@ -117,7 +162,8 @@ module PmdTester
117
162
  end
118
163
 
119
164
  def build
120
- get_projects
165
+ clone_projects
166
+ get_pmd_binary_file
121
167
  generate_pmd_reports
122
168
  end
123
169
  end
@@ -29,6 +29,7 @@ module PmdTester
29
29
  output_filter_set(rule_sets)
30
30
  build_config_file(rule_sets)
31
31
  logger.debug "Dynamic configuration: #{[rule_sets]}"
32
+ rule_sets
32
33
  end
33
34
 
34
35
  def output_filter_set(rule_sets)
@@ -46,7 +47,7 @@ module PmdTester
46
47
  base = @options.base_branch
47
48
  patch = @options.patch_branch
48
49
  # 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"
50
+ diff_cmd = "git diff --name-only #{base}..#{patch} -- pmd-core/src/main pmd-java/src/main"
50
51
  filenames = Cmd.execute(diff_cmd)
51
52
  end
52
53
  filenames.split("\n")
@@ -79,7 +80,7 @@ module PmdTester
79
80
  def build_config_file(rule_sets)
80
81
  if rule_sets.empty?
81
82
  logger.info NO_JAVA_RULES_CHANGED_MESSAGE
82
- exit 0
83
+ return
83
84
  end
84
85
 
85
86
  doc = Nokogiri::XML(File.read(PATH_TO_ALL_JAVA_RULES))
@@ -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(project_name)
10
+ @project_name = project_name
11
+ end
12
+
13
+ def start
14
+ logger.info "Starting to generate #{@project_name}'s PMD report"
15
+ message_counter = 1
16
+ @scheduler = Rufus::Scheduler.new
17
+ @scheduler.every '2m' do
18
+ logger.info "Still generating #{@project_name}'s PMD report (#{message_counter})..."
19
+ message_counter += 1
20
+ end
21
+ end
22
+
23
+ def stop
24
+ @scheduler.shutdown
25
+ end
26
+ end
27
+ end
@@ -29,7 +29,6 @@ module PmdTester
29
29
  def get_branch_details(branch_name)
30
30
  details = PmdBranchDetail.new(branch_name)
31
31
  details.load
32
- details
33
32
  end
34
33
 
35
34
  def build_body(doc)
@@ -71,6 +70,10 @@ module PmdTester
71
70
  @patch_details.branch_last_message)
72
71
  build_branch_table_row(doc, 'total execution time', @base_details.format_execution_time,
73
72
  @patch_details.format_execution_time)
73
+ build_branch_table_row(doc, 'jdk version', @base_details.jdk_version,
74
+ @patch_details.jdk_version)
75
+ build_branch_table_row(doc, 'language', @base_details.language,
76
+ @patch_details.language)
74
77
  build_branch_config_table_row(doc)
75
78
  end
76
79
  end
@@ -122,8 +125,9 @@ module PmdTester
122
125
  doc.tr do
123
126
  doc.th 'project name'
124
127
  doc.th 'project branch/tag'
125
- doc.th 'diff exist?'
126
- doc.th 'introduce new errors?'
128
+ doc.th 'removed|new errors size'
129
+ doc.th 'removed|new violations size'
130
+ doc.th 'removed|new configerrors size'
127
131
  end
128
132
  end
129
133
  end
@@ -136,8 +140,18 @@ module PmdTester
136
140
  doc.a(href: project.diff_report_index_ref_path) { doc.text project.name }
137
141
  end
138
142
  doc.td project.tag
139
- doc.td project.diffs_exist? ? 'Yes' : 'No'
140
- doc.td project.introduce_new_errors? ? 'Yes' : 'No'
143
+ doc.td do
144
+ build_table_content_for(doc, project.removed_errors_size,
145
+ project.new_errors_size)
146
+ end
147
+ doc.td do
148
+ build_table_content_for(doc, project.removed_violations_size,
149
+ project.new_violations_size)
150
+ end
151
+ doc.td do
152
+ build_table_content_for(doc, project.removed_configerrors_size,
153
+ project.new_configerrors_size)
154
+ end
141
155
  end
142
156
  end
143
157
  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.
@@ -25,6 +25,7 @@ module PmdTester
25
25
  attr_reader :config
26
26
  attr_reader :project_list
27
27
  attr_reader :mode
28
+ attr_reader :threads
28
29
  attr_reader :html_flag
29
30
  attr_reader :auto_config_flag
30
31
  attr_reader :debug_flag
@@ -40,6 +41,7 @@ module PmdTester
40
41
  @config = options[:c]
41
42
  @project_list = options[:l]
42
43
  @mode = options[:m]
44
+ @threads = options[:t]
43
45
  @html_flag = options[:f]
44
46
  @auto_config_flag = options[:a]
45
47
  @debug_flag = options[:d]
@@ -61,8 +63,8 @@ module PmdTester
61
63
  single: Set this option to 'single' if your patch branch contains changes
62
64
  for any option that can't work on master/base branch
63
65
  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
+ the PMD report of master/base branch rather than generating it locally
67
+ local: Default option is 'local', PMD reports for the base and patch branches are generated locally.
66
68
  DOC
67
69
 
68
70
  Slop.parse argv do |o|
@@ -79,6 +81,8 @@ module PmdTester
79
81
  'path to the file which contains the list of standard projects',
80
82
  default: DEFAULT_LIST_PATH
81
83
  o.string '-m', '--mode', mode_message, default: 'local'
84
+ o.integer '-t', '--threads', 'Sets the number of threads used by PMD.' \
85
+ ' Set threads to 0 to disable multi-threading processing.', default: 1
82
86
  o.bool '-f', '--html-flag',
83
87
  'whether to not generate the html diff report in single mode'
84
88
  o.bool '-a', '--auto-gen-config',
@@ -7,12 +7,15 @@ module PmdTester
7
7
  class PmdReportDocument < Nokogiri::XML::SAX::Document
8
8
  attr_reader :violations
9
9
  attr_reader :errors
10
+ attr_reader :configerrors
10
11
  def initialize(branch_name, working_dir, filter_set = nil)
11
12
  @violations = PmdViolations.new
12
13
  @errors = PmdErrors.new
14
+ @configerrors = PmdConfigErrors.new
13
15
  @current_violations = []
14
16
  @current_violation = nil
15
17
  @current_error = nil
18
+ @current_configerror = nil
16
19
  @current_element = ''
17
20
  @filename = ''
18
21
  @filter_set = filter_set
@@ -34,6 +37,8 @@ module PmdTester
34
37
  @current_filename = remove_work_dir!(attrs['filename'])
35
38
  remove_work_dir!(attrs['msg'])
36
39
  @current_error = PmdError.new(attrs, @branch_name)
40
+ when 'configerror'
41
+ @current_configerror = PmdConfigError.new(attrs, @branch_name)
37
42
  end
38
43
  end
39
44
 
@@ -42,15 +47,13 @@ module PmdTester
42
47
  end
43
48
 
44
49
  def characters(string)
45
- @current_violation.text = string unless @current_violation.nil?
50
+ @current_violation&.text = string
46
51
  end
47
52
 
48
53
  def end_element(name)
49
54
  case name
50
55
  when 'file'
51
- unless @current_violations.empty?
52
- @violations.add_violations_by_filename(@current_filename, @current_violations)
53
- end
56
+ @violations.add_violations_by_filename(@current_filename, @current_violations)
54
57
  @current_filename = nil
55
58
  when 'violation'
56
59
  @current_violations.push(@current_violation) if match_filter_set?(@current_violation)
@@ -59,12 +62,15 @@ module PmdTester
59
62
  @errors.add_error_by_filename(@current_filename, @current_error)
60
63
  @current_filename = nil
61
64
  @current_error = nil
65
+ when 'configerror'
66
+ @configerrors.add_error(@current_configerror)
67
+ @current_configerror = nil
62
68
  end
63
69
  end
64
70
 
65
71
  def cdata_block(string)
66
72
  remove_work_dir!(string)
67
- @current_error.text = string unless @current_error.nil?
73
+ @current_error&.text = string
68
74
  end
69
75
 
70
76
  def match_filter_set?(violation)
@@ -5,11 +5,15 @@ 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_reader :jdk_version
16
+ attr_reader :language
13
17
 
14
18
  def self.branch_filename(branch_name)
15
19
  branch_name&.tr('/', '_')
@@ -22,6 +26,9 @@ module PmdTester
22
26
  branch_filename = PmdBranchDetail.branch_filename(branch_name)
23
27
  @base_branch_dir = "target/reports/#{branch_filename}" unless @branch_name.nil?
24
28
  @execution_time = 0
29
+ # the result of command 'java -version' is going to stderr
30
+ @jdk_version = Cmd.stderr_of('java -version')
31
+ @language = ENV['LANG']
25
32
  end
26
33
 
27
34
  def load
@@ -31,20 +38,27 @@ module PmdTester
31
38
  @branch_last_message = hash['branch_last_message']
32
39
  @branch_name = hash['branch_name']
33
40
  @execution_time = hash['execution_time']
34
- hash
41
+ @jdk_version = hash['jdk_version']
42
+ @language = hash['language']
35
43
  else
36
- {}
44
+ @jdk_version = ''
45
+ @language = ''
46
+ logger.warn "#{branch_details_path} doesn't exist!"
37
47
  end
48
+ self
38
49
  end
39
50
 
40
51
  def save
41
52
  hash = { branch_last_sha: @branch_last_sha,
42
53
  branch_last_message: @branch_last_message,
43
54
  branch_name: @branch_name,
44
- execution_time: @execution_time }
55
+ execution_time: @execution_time,
56
+ jdk_version: @jdk_version,
57
+ language: @language }
45
58
  file = File.new(branch_details_path, 'w')
46
59
  file.puts JSON.generate(hash)
47
60
  file.close
61
+ self
48
62
  end
49
63
 
50
64
  def branch_details_path