pmdtester 1.0.0.pre.beta3 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.ci/build.sh +67 -0
  3. data/.ci/files/env.gpg +1 -0
  4. data/.ci/inc/install-openjdk.inc +26 -0
  5. data/.ci/manual-integration-tests.sh +20 -0
  6. data/.github/workflows/build.yml +39 -0
  7. data/.github/workflows/manual-integration-tests.yml +32 -0
  8. data/.gitignore +9 -0
  9. data/.hoerc +1 -1
  10. data/.rubocop.yml +13 -2
  11. data/.rubocop_todo.yml +7 -8
  12. data/.ruby-version +1 -0
  13. data/Gemfile +1 -12
  14. data/History.md +104 -0
  15. data/Manifest.txt +30 -6
  16. data/README.rdoc +110 -60
  17. data/Rakefile +27 -15
  18. data/config/all-java.xml +1 -1
  19. data/config/design.xml +1 -1
  20. data/config/projectlist_1_0_0.xsd +2 -1
  21. data/config/projectlist_1_1_0.xsd +31 -0
  22. data/lib/pmdtester.rb +12 -4
  23. data/lib/pmdtester/builders/liquid_renderer.rb +73 -0
  24. data/lib/pmdtester/builders/pmd_report_builder.rb +134 -60
  25. data/lib/pmdtester/builders/project_builder.rb +100 -0
  26. data/lib/pmdtester/builders/project_hasher.rb +126 -0
  27. data/lib/pmdtester/builders/rule_set_builder.rb +94 -48
  28. data/lib/pmdtester/builders/simple_progress_logger.rb +27 -0
  29. data/lib/pmdtester/builders/summary_report_builder.rb +62 -117
  30. data/lib/pmdtester/cmd.rb +15 -1
  31. data/lib/pmdtester/collection_by_file.rb +55 -0
  32. data/lib/pmdtester/parsers/options.rb +25 -2
  33. data/lib/pmdtester/parsers/pmd_report_document.rb +79 -27
  34. data/lib/pmdtester/parsers/projects_parser.rb +2 -4
  35. data/lib/pmdtester/pmd_branch_detail.rb +36 -12
  36. data/lib/pmdtester/pmd_configerror.rb +62 -0
  37. data/lib/pmdtester/pmd_error.rb +34 -34
  38. data/lib/pmdtester/pmd_report_detail.rb +10 -13
  39. data/lib/pmdtester/pmd_tester_utils.rb +57 -0
  40. data/lib/pmdtester/pmd_violation.rb +66 -26
  41. data/lib/pmdtester/project.rb +28 -23
  42. data/lib/pmdtester/report_diff.rb +194 -70
  43. data/lib/pmdtester/resource_locator.rb +4 -0
  44. data/lib/pmdtester/runner.rb +81 -54
  45. data/pmdtester.gemspec +64 -0
  46. data/resources/_includes/diff_pill_row.html +6 -0
  47. data/resources/css/bootstrap.min.css +7 -0
  48. data/resources/css/datatables.min.css +36 -0
  49. data/resources/css/pmd-tester.css +132 -0
  50. data/resources/js/bootstrap.min.js +7 -0
  51. data/resources/js/code-snippets.js +73 -0
  52. data/resources/js/datatables.min.js +726 -0
  53. data/resources/js/jquery-3.2.1.slim.min.js +4 -0
  54. data/resources/js/jquery.min.js +2 -0
  55. data/resources/js/popper.min.js +5 -0
  56. data/resources/js/project-report.js +136 -0
  57. data/resources/project_diff_report.html +205 -0
  58. data/resources/project_index.html +102 -0
  59. metadata +122 -38
  60. data/.travis.yml +0 -22
  61. data/lib/pmdtester/builders/diff_builder.rb +0 -30
  62. data/lib/pmdtester/builders/diff_report_builder.rb +0 -225
  63. data/lib/pmdtester/builders/html_report_builder.rb +0 -33
  64. data/resources/css/maven-base.css +0 -155
  65. 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
- ALL_RULE_SETS = Set['bestpractices', 'codestyle', 'design', 'documentation',
13
- 'errorprone', 'multithreading', 'performance', 'security'].freeze
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
- rule_sets = get_rule_sets(filenames)
29
- output_filter_set(rule_sets)
30
- build_config_file(rule_sets)
31
- logger.debug "Dynamic configuration: #{[rule_sets]}"
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(rule_sets)
35
- if rule_sets == ALL_RULE_SETS
36
- # if `rule_sets` contains all rule sets, than no need to filter the baseline
37
- @options.filter_set = nil
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
- @options.filter_set = rule_sets
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 get_rule_sets(filenames)
56
- rule_sets = Set[]
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 = %r{#{PATH_TO_PMD_JAVA_BASED_RULES}/([^/]+)/[^/]+Rule.java}.match(filename)
59
- if match_data.nil?
60
- match_data = %r{#{PATH_TO_PMD_XPATH_BASED_RULES}/([^/]+).xml}.match(filename)
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
- category = if match_data.nil?
64
- nil
65
- else
66
- match_data[1]
67
- end
103
+ next unless match_data.nil?
68
104
 
69
- if category.nil?
70
- rule_sets = ALL_RULE_SETS
71
- break
72
- else
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
- rule_sets
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(rule_sets)
80
- if rule_sets.empty?
121
+ def build_config_file(rule_refs)
122
+ if rule_refs.empty?
81
123
  logger.info NO_JAVA_RULES_CHANGED_MESSAGE
82
- exit 0
124
+ return
83
125
  end
84
126
 
85
- doc = Nokogiri::XML(File.read(PATH_TO_ALL_JAVA_RULES))
86
- doc.search('rule').each do |rule|
87
- rule.remove unless match_ref?(rule, rule_sets)
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
- description = doc.at_css('description')
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 match_ref?(rule_node, rule_sets)
97
- rule_sets.each do |rule_set|
98
- return true unless rule_node['ref'].index(rule_set).nil?
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.to_s.gsub(/\n\s+\n/, "\n")
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 < HtmlReportBuilder
5
+ class SummaryReportBuilder
6
6
  include PmdTester
7
- REPORT_DIR = 'target/reports/diff'
8
- BASE_CONFIG_PATH = 'target/reports/diff/base_config.xml'
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
- def build_body(doc)
36
- build_branch_details_section(doc)
37
- build_projects_section(doc)
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 build_branch_details_section(doc)
41
- doc.div(class: 'section', id: 'branchdetails') do
42
- doc.h2 'Branch details:'
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
- end
18
+ logger.info 'Built all difference reports successfully!'
46
19
 
47
- def build_branch_details_table(doc)
48
- doc.table(class: 'bodyTable', border: '0') do
49
- build_branch_table_head(doc)
50
- build_branch_table_body(doc)
51
- end
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
- def build_branch_table_head(doc)
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 build_branch_table_body(doc)
65
- doc.tbody do
66
- build_branch_table_row(doc, 'branch name', @base_details.branch_name,
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 build_branch_config_table_row(doc)
79
- doc.tr do
80
- doc.td(class: 'c') { doc.text 'branch configuration' }
81
- base_config_src_path = @base_details.target_branch_config_path
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 copy_branch_config_file(src, dest)
95
- FileUtils.cp(src, dest) if File.exist?(src)
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 build_branch_table_row(doc, item, base, patch)
99
- doc.tr do
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 build_projects_section(doc)
107
- doc.div(class: 'section', id: 'projects') do
108
- doc.h2 'Projects:'
109
- build_projects_table(doc)
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
- def build_projects_table(doc)
114
- doc.table(class: 'bodyTable', border: '0') do
115
- build_projects_table_head(doc)
116
- build_projects_table_body(doc)
117
- end
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 build_projects_table_head(doc)
121
- doc.thead do
122
- doc.tr do
123
- doc.th 'project name'
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 build_projects_table_body(doc)
132
- doc.tbody do
133
- @projects.each do |project|
134
- doc.tr do
135
- doc.td do
136
- doc.a(href: project.diff_report_index_ref_path) { doc.text project.name }
137
- end
138
- doc.td project.tag
139
- doc.td project.diffs_exist? ? 'Yes' : 'No'
140
- doc.td project.introduce_new_errors? ? 'Yes' : 'No'
141
- end
142
- end
143
- end
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.