pmdtester 1.0.0 → 1.2.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/.ci/build.sh +99 -0
  3. data/.ci/inc/fetch_ci_scripts.bash +19 -0
  4. data/.ci/manual-integration-tests.sh +37 -0
  5. data/.github/workflows/build.yml +55 -0
  6. data/.github/workflows/manual-integration-tests.yml +43 -0
  7. data/.gitignore +9 -0
  8. data/.hoerc +1 -1
  9. data/.rubocop.yml +9 -2
  10. data/.ruby-version +1 -0
  11. data/History.md +79 -0
  12. data/Manifest.txt +28 -9
  13. data/README.rdoc +59 -33
  14. data/Rakefile +7 -5
  15. data/config/all-java.xml +1 -1
  16. data/config/design.xml +1 -1
  17. data/config/project-list.xml +8 -7
  18. data/config/projectlist_1_0_0.xsd +2 -1
  19. data/config/projectlist_1_1_0.xsd +31 -0
  20. data/config/projectlist_1_2_0.xsd +39 -0
  21. data/lib/pmdtester.rb +8 -7
  22. data/lib/pmdtester/builders/liquid_renderer.rb +130 -0
  23. data/lib/pmdtester/builders/pmd_report_builder.rb +107 -79
  24. data/lib/pmdtester/builders/project_builder.rb +105 -0
  25. data/lib/pmdtester/builders/project_hasher.rb +128 -0
  26. data/lib/pmdtester/builders/rule_set_builder.rb +96 -47
  27. data/lib/pmdtester/builders/simple_progress_logger.rb +4 -4
  28. data/lib/pmdtester/builders/summary_report_builder.rb +63 -131
  29. data/lib/pmdtester/collection_by_file.rb +55 -0
  30. data/lib/pmdtester/parsers/options.rb +24 -0
  31. data/lib/pmdtester/parsers/pmd_report_document.rb +72 -28
  32. data/lib/pmdtester/parsers/projects_parser.rb +2 -4
  33. data/lib/pmdtester/pmd_branch_detail.rb +35 -19
  34. data/lib/pmdtester/pmd_configerror.rb +23 -24
  35. data/lib/pmdtester/pmd_error.rb +34 -34
  36. data/lib/pmdtester/pmd_report_detail.rb +10 -13
  37. data/lib/pmdtester/pmd_tester_utils.rb +58 -0
  38. data/lib/pmdtester/pmd_violation.rb +66 -28
  39. data/lib/pmdtester/project.rb +42 -56
  40. data/lib/pmdtester/report_diff.rb +203 -109
  41. data/lib/pmdtester/resource_locator.rb +4 -0
  42. data/lib/pmdtester/runner.rb +67 -64
  43. data/pmdtester.gemspec +28 -37
  44. data/resources/_includes/diff_pill_row.html +6 -0
  45. data/resources/css/bootstrap.min.css +7 -0
  46. data/resources/css/datatables.min.css +36 -0
  47. data/resources/css/pmd-tester.css +132 -0
  48. data/resources/js/bootstrap.min.js +7 -0
  49. data/resources/js/code-snippets.js +73 -0
  50. data/resources/js/datatables.min.js +726 -0
  51. data/resources/js/jquery-3.2.1.slim.min.js +4 -0
  52. data/resources/js/jquery.min.js +2 -0
  53. data/resources/js/popper.min.js +5 -0
  54. data/resources/js/project-report.js +137 -0
  55. data/resources/project_diff_report.html +214 -0
  56. data/resources/project_index.html +113 -0
  57. data/resources/project_pmd_report.html +186 -0
  58. metadata +73 -25
  59. data/.travis.yml +0 -40
  60. data/lib/pmdtester/builders/diff_builder.rb +0 -31
  61. data/lib/pmdtester/builders/diff_report/configerrors.rb +0 -50
  62. data/lib/pmdtester/builders/diff_report/errors.rb +0 -71
  63. data/lib/pmdtester/builders/diff_report/violations.rb +0 -77
  64. data/lib/pmdtester/builders/diff_report_builder.rb +0 -99
  65. data/lib/pmdtester/builders/html_report_builder.rb +0 -56
  66. data/resources/css/maven-base.css +0 -155
  67. data/resources/css/maven-theme.css +0 -171
@@ -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
@@ -28,8 +29,12 @@ module PmdTester
28
29
  attr_reader :threads
29
30
  attr_reader :html_flag
30
31
  attr_reader :auto_config_flag
32
+ attr_reader :filter_with_patch_config
31
33
  attr_reader :debug_flag
32
34
  attr_accessor :filter_set
35
+ attr_reader :keep_reports
36
+ attr_reader :error_recovery
37
+ attr_reader :baseline_download_url_prefix
33
38
 
34
39
  def initialize(argv)
35
40
  options = parse(argv)
@@ -44,8 +49,17 @@ module PmdTester
44
49
  @threads = options[:t]
45
50
  @html_flag = options[:f]
46
51
  @auto_config_flag = options[:a]
52
+ @filter_with_patch_config = options.filter_with_patch_config?
47
53
  @debug_flag = options[:d]
48
54
  @filter_set = nil
55
+ @keep_reports = options.keep_reports?
56
+ @error_recovery = options.error_recovery?
57
+ url = options[:baseline_download_url]
58
+ @baseline_download_url_prefix = if url[-1] == '/'
59
+ url
60
+ else
61
+ "#{url}/"
62
+ end
49
63
 
50
64
  # if the 'config' option is selected then `config` overrides `base_config` and `patch_config`
51
65
  @base_config = @config if !@config.nil? && @mode == 'local'
@@ -88,8 +102,18 @@ module PmdTester
88
102
  o.bool '-a', '--auto-gen-config',
89
103
  'whether to generate configurations automatically based on branch differences,' \
90
104
  'this option only works in online and local mode'
105
+ o.bool '--filter-with-patch-config',
106
+ 'whether to use patch config to filter baseline result as if --auto-gen-config ' \
107
+ 'has been used. This option only works in online mode.'
108
+ o.bool '--keep-reports',
109
+ 'whether to keep old reports and skip running PMD again if possible'
91
110
  o.bool '-d', '--debug',
92
111
  'whether change log level to DEBUG to see more information'
112
+ o.bool '--error-recovery',
113
+ 'enable error recovery mode when executing PMD. Might help to analyze errors.'
114
+ o.string '--baseline-download-url',
115
+ 'download url prefix from where to download the baseline in online mode',
116
+ default: DEFAULT_BASELINE_URL_PREFIX
93
117
  o.on '-v', '--version' do
94
118
  puts VERSION
95
119
  exit
@@ -8,79 +8,123 @@ module PmdTester
8
8
  attr_reader :violations
9
9
  attr_reader :errors
10
10
  attr_reader :configerrors
11
+
11
12
  def initialize(branch_name, working_dir, filter_set = nil)
12
- @violations = PmdViolations.new
13
- @errors = PmdErrors.new
14
- @configerrors = PmdConfigErrors.new
13
+ @violations = CollectionByFile.new
14
+ @errors = CollectionByFile.new
15
+ @configerrors = Hash.new { |hash, key| hash[key] = [] }
16
+
15
17
  @current_violations = []
16
18
  @current_violation = nil
17
19
  @current_error = nil
18
20
  @current_configerror = nil
19
- @current_element = ''
20
- @filename = ''
21
21
  @filter_set = filter_set
22
22
  @working_dir = working_dir
23
23
  @branch_name = branch_name
24
+
25
+ @cur_text = String.new(capacity: 200)
24
26
  end
25
27
 
26
28
  def start_element(name, attrs = [])
27
29
  attrs = attrs.to_h
28
- @current_element = name
29
30
 
30
31
  case name
31
32
  when 'file'
32
- @current_violations = []
33
- @current_filename = remove_work_dir!(attrs['name'])
33
+ handle_start_file attrs
34
34
  when 'violation'
35
- @current_violation = PmdViolation.new(attrs, @branch_name)
35
+ handle_start_violation attrs
36
36
  when 'error'
37
- @current_filename = remove_work_dir!(attrs['filename'])
38
- remove_work_dir!(attrs['msg'])
39
- @current_error = PmdError.new(attrs, @branch_name)
37
+ handle_start_error attrs
40
38
  when 'configerror'
41
- @current_configerror = PmdConfigError.new(attrs, @branch_name)
39
+ handle_start_configerror attrs
42
40
  end
43
41
  end
44
42
 
45
- def remove_work_dir!(str)
46
- str.sub!(/#{@working_dir}/, '')
43
+ def characters(string)
44
+ @cur_text << remove_work_dir!(string)
47
45
  end
48
46
 
49
- def characters(string)
50
- @current_violation&.text = string
47
+ def cdata_block(string)
48
+ @cur_text << remove_work_dir!(string)
51
49
  end
52
50
 
53
51
  def end_element(name)
54
52
  case name
55
53
  when 'file'
56
- @violations.add_violations_by_filename(@current_filename, @current_violations)
54
+ @violations.add_all(@current_filename, @current_violations)
57
55
  @current_filename = nil
58
56
  when 'violation'
59
- @current_violations.push(@current_violation) if match_filter_set?(@current_violation)
57
+ if match_filter_set?(@current_violation)
58
+ @current_violation.message = finish_text!
59
+ @current_violations.push(@current_violation)
60
+ end
60
61
  @current_violation = nil
61
62
  when 'error'
62
- @errors.add_error_by_filename(@current_filename, @current_error)
63
+ @current_error.stack_trace = finish_text!
64
+ @errors.add_all(@current_filename, [@current_error])
63
65
  @current_filename = nil
64
66
  @current_error = nil
65
67
  when 'configerror'
66
- @configerrors.add_error(@current_configerror)
68
+ @configerrors[@current_configerror.rulename].push(@current_configerror)
67
69
  @current_configerror = nil
68
70
  end
71
+ @cur_text.clear
69
72
  end
70
73
 
71
- def cdata_block(string)
72
- remove_work_dir!(string)
73
- @current_error&.text = string
74
+ private
75
+
76
+ # Modifies the string in place and returns it
77
+ # (this is what sub! does, except it returns nil if no replacement occurred)
78
+ def remove_work_dir!(str)
79
+ str.sub!(/#{@working_dir}/, '')
80
+ str
81
+ end
82
+
83
+ def finish_text!
84
+ res = @cur_text.strip!.dup.freeze
85
+ @cur_text.clear
86
+ res
74
87
  end
75
88
 
76
89
  def match_filter_set?(violation)
77
90
  return true if @filter_set.nil?
78
91
 
79
- @filter_set.each do |ruleset|
80
- return true if ruleset.eql?(violation.attrs['ruleset'].delete(' ').downcase)
81
- end
92
+ ruleset_attr = violation.ruleset_name.delete(' ').downcase! << '.xml'
93
+ return true if @filter_set.include?(ruleset_attr)
94
+
95
+ rule_ref = "#{ruleset_attr}/#{violation.rule_name}"
96
+
97
+ @filter_set.include?(rule_ref)
98
+ end
99
+
100
+ def handle_start_file(attrs)
101
+ @current_filename = remove_work_dir!(attrs['name'])
102
+ @current_violations = []
103
+ end
104
+
105
+ def handle_start_violation(attrs)
106
+ @current_violation = PmdViolation.new(
107
+ branch: @branch_name,
108
+ fname: @current_filename,
109
+ info_url: attrs['externalInfoUrl'],
110
+ bline: attrs['beginline'].to_i,
111
+ rule_name: attrs['rule'],
112
+ ruleset_name: attrs['ruleset'].freeze
113
+ )
114
+ end
115
+
116
+ def handle_start_error(attrs)
117
+ @current_filename = remove_work_dir!(attrs['filename'])
118
+
119
+ @current_error = PmdError.new(
120
+ branch: @branch_name,
121
+ filename: @current_filename,
122
+ short_message: remove_work_dir!(attrs['msg'])
123
+ )
124
+ end
82
125
 
83
- false
126
+ def handle_start_configerror(attrs)
127
+ @current_configerror = PmdConfigError.new(attrs, @branch_name)
84
128
  end
85
129
  end
86
130
  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_2_0.xsd')
27
25
  end
28
26
  end
29
27
 
@@ -10,10 +10,13 @@ module PmdTester
10
10
  attr_accessor :branch_last_sha
11
11
  attr_accessor :branch_last_message
12
12
  attr_accessor :branch_name
13
+ # Start of the regression report
14
+ attr_accessor :timestamp
13
15
  # The branch's execution time on all standard projects
14
16
  attr_accessor :execution_time
15
- attr_reader :jdk_version
16
- attr_reader :language
17
+ attr_accessor :jdk_version
18
+ attr_accessor :language
19
+ attr_accessor :pull_request
17
20
 
18
21
  def self.branch_filename(branch_name)
19
22
  branch_name&.tr('/', '_')
@@ -25,43 +28,56 @@ module PmdTester
25
28
  @branch_name = branch_name
26
29
  branch_filename = PmdBranchDetail.branch_filename(branch_name)
27
30
  @base_branch_dir = "target/reports/#{branch_filename}" unless @branch_name.nil?
31
+ @timestamp = Time.now
28
32
  @execution_time = 0
29
33
  # the result of command 'java -version' is going to stderr
30
34
  @jdk_version = Cmd.stderr_of('java -version')
31
- @language = ENV['LANG']
35
+ @language = ENV['LANG'] # the locale
36
+
37
+ prnum = ENV[PR_NUM_ENV_VAR]
38
+ @pull_request = prnum == 'false' ? nil : prnum
32
39
  end
33
40
 
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']
41
+ def self.load(branch_name, logger)
42
+ details = PmdBranchDetail.new(branch_name)
43
+ if File.exist?(details.path_to_save_file)
44
+ hash = JSON.parse(File.read(details.path_to_save_file))
45
+ details.branch_last_sha = hash['branch_last_sha']
46
+ details.branch_last_message = hash['branch_last_message']
47
+ details.branch_name = hash['branch_name']
48
+ details.timestamp = hash['timestamp']
49
+ details.execution_time = hash['execution_time']
50
+ details.jdk_version = hash['jdk_version']
51
+ details.language = hash['language']
52
+ details.pull_request = hash['pull_request']
43
53
  else
44
- @jdk_version = ''
45
- @language = ''
46
- logger.warn "#{branch_details_path} doesn't exist!"
54
+ details.timestamp = Time.now
55
+ details.jdk_version = ''
56
+ details.language = ''
57
+ logger&.warn "#{details.path_to_save_file} doesn't exist!"
47
58
  end
48
- self
59
+ details
49
60
  end
50
61
 
51
62
  def save
52
63
  hash = { branch_last_sha: @branch_last_sha,
53
64
  branch_last_message: @branch_last_message,
54
65
  branch_name: @branch_name,
66
+ timestamp: @timestamp,
55
67
  execution_time: @execution_time,
56
68
  jdk_version: @jdk_version,
57
- language: @language }
58
- file = File.new(branch_details_path, 'w')
69
+ language: @language,
70
+ pull_request: @pull_request }
71
+
72
+ FileUtils.mkdir_p(@base_branch_dir) unless File.directory?(@base_branch_dir)
73
+
74
+ file = File.new(path_to_save_file, 'w')
59
75
  file.puts JSON.generate(hash)
60
76
  file.close
61
77
  self
62
78
  end
63
79
 
64
- def branch_details_path
80
+ def path_to_save_file
65
81
  "#{@base_branch_dir}/branch_info.json"
66
82
  end
67
83
 
@@ -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