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
@@ -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
- 'the PMD report of master/base branch rather than generating it locally
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 = PmdViolations.new
12
- @errors = PmdErrors.new
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
- @current_element = ''
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
- @current_violations = []
30
- @current_filename = remove_work_dir!(attrs['name'])
35
+ handle_start_file attrs
31
36
  when 'violation'
32
- @current_violation = PmdViolation.new(attrs, @branch_name)
37
+ handle_start_violation attrs
33
38
  when 'error'
34
- @current_filename = remove_work_dir!(attrs['filename'])
35
- remove_work_dir!(attrs['msg'])
36
- @current_error = PmdError.new(attrs, @branch_name)
39
+ handle_start_error attrs
40
+ when 'configerror'
41
+ handle_start_configerror attrs
37
42
  end
38
43
  end
39
44
 
40
- def remove_work_dir!(str)
41
- str.sub!(/#{@working_dir}/, '')
45
+ def characters(string)
46
+ @cur_text << remove_work_dir!(string)
42
47
  end
43
48
 
44
- def characters(string)
45
- @current_violation.text = string unless @current_violation.nil?
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
- unless @current_violations.empty?
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
- @current_violations.push(@current_violation) if match_filter_set?(@current_violation)
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
- @errors.add_error_by_filename(@current_filename, @current_error)
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
- def cdata_block(string)
66
- remove_work_dir!(string)
67
- @current_error.text = string unless @current_error.nil?
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
- @filter_set.each do |ruleset|
74
- return true if ruleset.eql?(violation.attrs['ruleset'].delete(' ').downcase)
75
- end
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
- false
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/projectlist_1_0_0.xsd')
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
- if File.exist?(branch_details_path)
29
- hash = JSON.parse(File.read(branch_details_path))
30
- @branch_last_sha = hash['branch_last_sha']
31
- @branch_last_message = hash['branch_last_message']
32
- @branch_name = hash['branch_name']
33
- @execution_time = hash['execution_time']
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
- file = File.new(branch_details_path, 'w')
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 branch_details_path
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