pmdtester 1.0.0.pre.beta3 → 1.0.0

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