pmdtester 1.0.1 → 1.1.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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.ci/build.sh +91 -0
  3. data/.ci/files/env.gpg +1 -0
  4. data/.github/workflows/build.yml +39 -0
  5. data/.gitignore +9 -0
  6. data/.hoerc +1 -1
  7. data/.rubocop.yml +6 -2
  8. data/.ruby-version +1 -0
  9. data/History.md +40 -0
  10. data/Manifest.txt +24 -9
  11. data/README.rdoc +45 -30
  12. data/Rakefile +5 -3
  13. data/config/all-java.xml +1 -1
  14. data/config/design.xml +1 -1
  15. data/config/projectlist_1_0_0.xsd +2 -1
  16. data/config/projectlist_1_1_0.xsd +31 -0
  17. data/lib/pmdtester.rb +8 -7
  18. data/lib/pmdtester/builders/liquid_renderer.rb +73 -0
  19. data/lib/pmdtester/builders/pmd_report_builder.rb +102 -78
  20. data/lib/pmdtester/builders/project_builder.rb +100 -0
  21. data/lib/pmdtester/builders/project_hasher.rb +126 -0
  22. data/lib/pmdtester/builders/rule_set_builder.rb +92 -47
  23. data/lib/pmdtester/builders/simple_progress_logger.rb +4 -4
  24. data/lib/pmdtester/builders/summary_report_builder.rb +62 -131
  25. data/lib/pmdtester/collection_by_file.rb +55 -0
  26. data/lib/pmdtester/parsers/options.rb +19 -0
  27. data/lib/pmdtester/parsers/pmd_report_document.rb +74 -29
  28. data/lib/pmdtester/parsers/projects_parser.rb +2 -4
  29. data/lib/pmdtester/pmd_branch_detail.rb +29 -19
  30. data/lib/pmdtester/pmd_configerror.rb +23 -24
  31. data/lib/pmdtester/pmd_error.rb +34 -34
  32. data/lib/pmdtester/pmd_report_detail.rb +9 -12
  33. data/lib/pmdtester/pmd_tester_utils.rb +55 -0
  34. data/lib/pmdtester/pmd_violation.rb +66 -28
  35. data/lib/pmdtester/project.rb +21 -48
  36. data/lib/pmdtester/report_diff.rb +179 -111
  37. data/lib/pmdtester/resource_locator.rb +4 -0
  38. data/lib/pmdtester/runner.rb +66 -64
  39. data/pmdtester.gemspec +27 -36
  40. data/resources/_includes/diff_pill_row.html +6 -0
  41. data/resources/css/bootstrap.min.css +7 -0
  42. data/resources/css/datatables.min.css +36 -0
  43. data/resources/css/pmd-tester.css +131 -0
  44. data/resources/js/bootstrap.min.js +7 -0
  45. data/resources/js/code-snippets.js +66 -0
  46. data/resources/js/datatables.min.js +726 -0
  47. data/resources/js/jquery-3.2.1.slim.min.js +4 -0
  48. data/resources/js/jquery.min.js +2 -0
  49. data/resources/js/popper.min.js +5 -0
  50. data/resources/js/project-report.js +136 -0
  51. data/resources/project_diff_report.html +205 -0
  52. data/resources/project_index.html +102 -0
  53. metadata +64 -20
  54. data/.travis.yml +0 -40
  55. data/lib/pmdtester/builders/diff_builder.rb +0 -31
  56. data/lib/pmdtester/builders/diff_report/configerrors.rb +0 -50
  57. data/lib/pmdtester/builders/diff_report/errors.rb +0 -71
  58. data/lib/pmdtester/builders/diff_report/violations.rb +0 -77
  59. data/lib/pmdtester/builders/diff_report_builder.rb +0 -99
  60. data/lib/pmdtester/builders/html_report_builder.rb +0 -56
  61. data/resources/css/maven-base.css +0 -155
  62. 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
@@ -30,6 +31,9 @@ module PmdTester
30
31
  attr_reader :auto_config_flag
31
32
  attr_reader :debug_flag
32
33
  attr_accessor :filter_set
34
+ attr_reader :keep_reports
35
+ attr_reader :error_recovery
36
+ attr_reader :baseline_download_url_prefix
33
37
 
34
38
  def initialize(argv)
35
39
  options = parse(argv)
@@ -46,6 +50,14 @@ module PmdTester
46
50
  @auto_config_flag = options[:a]
47
51
  @debug_flag = options[:d]
48
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
49
61
 
50
62
  # if the 'config' option is selected then `config` overrides `base_config` and `patch_config`
51
63
  @base_config = @config if !@config.nil? && @mode == 'local'
@@ -88,8 +100,15 @@ module PmdTester
88
100
  o.bool '-a', '--auto-gen-config',
89
101
  'whether to generate configurations automatically based on branch differences,' \
90
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'
91
105
  o.bool '-d', '--debug',
92
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
93
112
  o.on '-v', '--version' do
94
113
  puts VERSION
95
114
  exit
@@ -8,80 +8,125 @@ module PmdTester
8
8
  attr_reader :violations
9
9
  attr_reader :errors
10
10
  attr_reader :configerrors
11
+ attr_reader :infos_by_rules
12
+
11
13
  def initialize(branch_name, working_dir, filter_set = nil)
12
- @violations = PmdViolations.new
13
- @errors = PmdErrors.new
14
- @configerrors = PmdConfigErrors.new
14
+ @violations = CollectionByFile.new
15
+ @errors = CollectionByFile.new
16
+ @configerrors = Hash.new { |hash, key| hash[key] = [] }
17
+
18
+ @infos_by_rules = {}
15
19
  @current_violations = []
16
20
  @current_violation = nil
17
21
  @current_error = nil
18
22
  @current_configerror = nil
19
- @current_element = ''
20
- @filename = ''
21
23
  @filter_set = filter_set
22
24
  @working_dir = working_dir
23
25
  @branch_name = branch_name
26
+
27
+ @cur_text = String.new(capacity: 200)
24
28
  end
25
29
 
26
30
  def start_element(name, attrs = [])
27
31
  attrs = attrs.to_h
28
- @current_element = name
29
32
 
30
33
  case name
31
34
  when 'file'
32
- @current_violations = []
33
- @current_filename = remove_work_dir!(attrs['name'])
35
+ handle_start_file attrs
34
36
  when 'violation'
35
- @current_violation = PmdViolation.new(attrs, @branch_name)
37
+ handle_start_violation attrs
36
38
  when 'error'
37
- @current_filename = remove_work_dir!(attrs['filename'])
38
- remove_work_dir!(attrs['msg'])
39
- @current_error = PmdError.new(attrs, @branch_name)
39
+ handle_start_error attrs
40
40
  when 'configerror'
41
- @current_configerror = PmdConfigError.new(attrs, @branch_name)
41
+ handle_start_configerror attrs
42
42
  end
43
43
  end
44
44
 
45
- def remove_work_dir!(str)
46
- str.sub!(/#{@working_dir}/, '')
45
+ def characters(string)
46
+ @cur_text << remove_work_dir!(string)
47
47
  end
48
48
 
49
- def characters(string)
50
- @current_violation&.text += string
49
+ def cdata_block(string)
50
+ @cur_text << remove_work_dir!(string)
51
51
  end
52
52
 
53
53
  def end_element(name)
54
54
  case name
55
55
  when 'file'
56
- @violations.add_violations_by_filename(@current_filename, @current_violations)
56
+ @violations.add_all(@current_filename, @current_violations)
57
57
  @current_filename = nil
58
58
  when 'violation'
59
- @current_violation.text.strip!
60
- @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
61
63
  @current_violation = nil
62
64
  when 'error'
63
- @errors.add_error_by_filename(@current_filename, @current_error)
65
+ @current_error.stack_trace = finish_text!
66
+ @errors.add_all(@current_filename, [@current_error])
64
67
  @current_filename = nil
65
68
  @current_error = nil
66
69
  when 'configerror'
67
- @configerrors.add_error(@current_configerror)
70
+ @configerrors[@current_configerror.rulename].push(@current_configerror)
68
71
  @current_configerror = nil
69
72
  end
73
+ @cur_text.clear
70
74
  end
71
75
 
72
- def cdata_block(string)
73
- remove_work_dir!(string)
74
- @current_error&.text = string
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
75
89
  end
76
90
 
77
91
  def match_filter_set?(violation)
78
92
  return true if @filter_set.nil?
79
93
 
80
- @filter_set.each do |ruleset|
81
- return true if ruleset.eql?(violation.attrs['ruleset'].delete(' ').downcase)
82
- 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
83
127
 
84
- false
128
+ def handle_start_configerror(attrs)
129
+ @current_configerror = PmdConfigError.new(attrs, @branch_name)
85
130
  end
86
131
  end
87
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
 
@@ -12,8 +12,9 @@ module PmdTester
12
12
  attr_accessor :branch_name
13
13
  # The branch's execution time on all standard projects
14
14
  attr_accessor :execution_time
15
- attr_reader :jdk_version
16
- attr_reader :language
15
+ attr_accessor :jdk_version
16
+ attr_accessor :language
17
+ attr_accessor :pull_request
17
18
 
18
19
  def self.branch_filename(branch_name)
19
20
  branch_name&.tr('/', '_')
@@ -28,24 +29,29 @@ module PmdTester
28
29
  @execution_time = 0
29
30
  # the result of command 'java -version' is going to stderr
30
31
  @jdk_version = Cmd.stderr_of('java -version')
31
- @language = ENV['LANG']
32
+ @language = ENV['LANG'] # the locale
33
+
34
+ prnum = ENV[PR_NUM_ENV_VAR]
35
+ @pull_request = prnum == 'false' ? nil : prnum
32
36
  end
33
37
 
34
- def load
35
- if File.exist?(branch_details_path)
36
- hash = JSON.parse(File.read(branch_details_path))
37
- @branch_last_sha = hash['branch_last_sha']
38
- @branch_last_message = hash['branch_last_message']
39
- @branch_name = hash['branch_name']
40
- @execution_time = hash['execution_time']
41
- @jdk_version = hash['jdk_version']
42
- @language = hash['language']
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']
43
49
  else
44
- @jdk_version = ''
45
- @language = ''
46
- logger.warn "#{branch_details_path} doesn't exist!"
50
+ details.jdk_version = ''
51
+ details.language = ''
52
+ logger&.warn "#{details.path_to_save_file} doesn't exist!"
47
53
  end
48
- self
54
+ details
49
55
  end
50
56
 
51
57
  def save
@@ -54,14 +60,18 @@ module PmdTester
54
60
  branch_name: @branch_name,
55
61
  execution_time: @execution_time,
56
62
  jdk_version: @jdk_version,
57
- language: @language }
58
- file = File.new(branch_details_path, 'w')
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')
59
69
  file.puts JSON.generate(hash)
60
70
  file.close
61
71
  self
62
72
  end
63
73
 
64
- def branch_details_path
74
+ def path_to_save_file
65
75
  "#{@base_branch_dir}/branch_info.json"
66
76
  end
67
77
 
@@ -1,28 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PmdTester
4
- # This class is used to store pmd config errors and its size.
5
- class PmdConfigErrors
6
- attr_reader :errors
7
- attr_reader :size
8
-
9
- def initialize
10
- # key:rulename as String => value:PmdConfigError Array
11
- @errors = {}
12
- @size = 0
13
- end
14
-
15
- def add_error(error)
16
- rulename = error.rulename
17
- if @errors.key?(rulename)
18
- @errors[rulename].push(error)
19
- else
20
- @errors.store(rulename, [error])
21
- end
22
- @size += 1
23
- end
24
- end
25
-
26
4
  # This class represents a 'configerror' element of Pmd xml report
27
5
  # and which Pmd branch the 'configerror' is from
28
6
  class PmdConfigError
@@ -35,13 +13,13 @@ module PmdTester
35
13
  # <xs:attribute name="msg" type="xs:string" use="required" />
36
14
  # </xs:complexType>
37
15
  attr_reader :attrs
38
- attr_accessor :text
16
+ attr_accessor :old_error
39
17
 
40
18
  def initialize(attrs, branch)
41
19
  @attrs = attrs
42
20
 
21
+ @changed = false
43
22
  @branch = branch
44
- @text = ''
45
23
  end
46
24
 
47
25
  def rulename
@@ -52,10 +30,31 @@ module PmdTester
52
30
  @attrs['msg']
53
31
  end
54
32
 
33
+ def sort_key
34
+ rulename
35
+ end
36
+
37
+ def changed?
38
+ @changed
39
+ end
40
+
55
41
  def eql?(other)
56
42
  rulename.eql?(other.rulename) && msg.eql?(other.msg)
57
43
  end
58
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
+
59
58
  def hash
60
59
  [rulename, msg].hash
61
60
  end
@@ -1,30 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
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
4
  # This class represents a 'error' element of Pmd xml report
26
5
  # and which Pmd branch the 'error' is from
27
6
  class PmdError
7
+ include PmdTester
28
8
  # The pmd branch type, 'base' or 'patch'
29
9
  attr_reader :branch
30
10
 
@@ -37,31 +17,51 @@ module PmdTester
37
17
  # </xs:extension>
38
18
  # </xs:simpleContent>
39
19
  # </xs:complexType>
40
- attr_reader :attrs
41
- attr_accessor :text
42
-
43
- def initialize(attrs, branch)
44
- @attrs = attrs
20
+ attr_accessor :stack_trace
21
+ attr_accessor :old_error
22
+ attr_reader :filename, :short_message
45
23
 
24
+ def initialize(branch:, filename:, short_message:)
46
25
  @branch = branch
47
- @text = ''
26
+ @stack_trace = ''
27
+ @changed = false
28
+ @short_message = short_message
29
+ @filename = filename
48
30
  end
49
31
 
50
- def filename
51
- @attrs['filename']
32
+ def short_filename
33
+ filename.gsub(%r{([^/]*+/)+}, '')
52
34
  end
53
35
 
54
- def msg
55
- @attrs['msg']
36
+ def changed?
37
+ @changed
56
38
  end
57
39
 
58
40
  def eql?(other)
59
- filename.eql?(other.filename) && msg.eql?(other.msg) &&
60
- @text.eql?(other.text)
41
+ filename.eql?(other.filename) &&
42
+ short_message.eql?(other.short_message) &&
43
+ stack_trace.eql?(other.stack_trace)
61
44
  end
62
45
 
63
46
  def hash
64
- [filename, msg, @text].hash
47
+ [filename, stack_trace].hash
48
+ end
49
+
50
+ def sort_key
51
+ filename
52
+ end
53
+
54
+ def try_merge?(other)
55
+ if branch != BASE &&
56
+ branch != other.branch &&
57
+ filename == other.filename &&
58
+ !changed? # not already changed
59
+ @changed = true
60
+ @old_error = other
61
+ true
62
+ else
63
+ false
64
+ end
65
65
  end
66
66
  end
67
67
  end