pmdtester 1.0.0.pre.beta2 → 1.1.1

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 +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 -0
  10. data/.rubocop.yml +21 -2
  11. data/.rubocop_todo.yml +7 -8
  12. data/.ruby-version +1 -0
  13. data/Gemfile +1 -13
  14. data/History.md +108 -0
  15. data/Manifest.txt +60 -0
  16. data/README.rdoc +130 -7
  17. data/Rakefile +31 -17
  18. data/bin/pmdtester +1 -1
  19. data/config/all-java.xml +1 -1
  20. data/config/design.xml +1 -1
  21. data/config/projectlist_1_0_0.xsd +2 -1
  22. data/config/projectlist_1_1_0.xsd +31 -0
  23. data/lib/pmdtester.rb +48 -0
  24. data/lib/pmdtester/builders/liquid_renderer.rb +73 -0
  25. data/lib/pmdtester/builders/pmd_report_builder.rb +133 -64
  26. data/lib/pmdtester/builders/project_builder.rb +100 -0
  27. data/lib/pmdtester/builders/project_hasher.rb +126 -0
  28. data/lib/pmdtester/builders/rule_set_builder.rb +95 -51
  29. data/lib/pmdtester/builders/simple_progress_logger.rb +27 -0
  30. data/lib/pmdtester/builders/summary_report_builder.rb +62 -120
  31. data/lib/pmdtester/cmd.rb +15 -1
  32. data/lib/pmdtester/collection_by_file.rb +55 -0
  33. data/lib/pmdtester/parsers/options.rb +33 -10
  34. data/lib/pmdtester/parsers/pmd_report_document.rb +79 -29
  35. data/lib/pmdtester/parsers/projects_parser.rb +2 -6
  36. data/lib/pmdtester/pmd_branch_detail.rb +36 -13
  37. data/lib/pmdtester/pmd_configerror.rb +62 -0
  38. data/lib/pmdtester/pmd_error.rb +34 -34
  39. data/lib/pmdtester/pmd_report_detail.rb +10 -13
  40. data/lib/pmdtester/pmd_tester_utils.rb +57 -0
  41. data/lib/pmdtester/pmd_violation.rb +66 -26
  42. data/lib/pmdtester/project.rb +28 -25
  43. data/lib/pmdtester/report_diff.rb +194 -70
  44. data/lib/pmdtester/resource_locator.rb +4 -0
  45. data/lib/pmdtester/runner.rb +82 -57
  46. data/pmdtester.gemspec +64 -0
  47. data/resources/_includes/diff_pill_row.html +6 -0
  48. data/resources/css/bootstrap.min.css +7 -0
  49. data/resources/css/datatables.min.css +36 -0
  50. data/resources/css/pmd-tester.css +131 -0
  51. data/resources/js/bootstrap.min.js +7 -0
  52. data/resources/js/code-snippets.js +66 -0
  53. data/resources/js/datatables.min.js +726 -0
  54. data/resources/js/jquery-3.2.1.slim.min.js +4 -0
  55. data/resources/js/jquery.min.js +2 -0
  56. data/resources/js/popper.min.js +5 -0
  57. data/resources/js/project-report.js +136 -0
  58. data/resources/project_diff_report.html +205 -0
  59. data/resources/project_index.html +102 -0
  60. metadata +117 -44
  61. data/.travis.yml +0 -22
  62. data/lib/pmdtester/builders/diff_builder.rb +0 -35
  63. data/lib/pmdtester/builders/diff_report_builder.rb +0 -226
  64. data/lib/pmdtester/builders/html_report_builder.rb +0 -34
  65. data/lib/pmdtester/pmdtester.rb +0 -17
  66. data/resources/css/maven-base.css +0 -155
  67. data/resources/css/maven-theme.css +0 -171
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'nokogiri'
4
- require_relative '../project'
5
- require_relative '../resource_locator'
6
4
 
7
5
  module PmdTester
8
6
  # The ProjectsParser is a class responsible of parsing
@@ -13,9 +11,7 @@ module PmdTester
13
11
  document = Nokogiri::XML(File.read(list_file))
14
12
 
15
13
  errors = schema.validate(document)
16
- unless errors.empty?
17
- raise ProjectsParserException.new(errors), "Schema validate failed: In #{list_file}"
18
- end
14
+ raise ProjectsParserException.new(errors), "Schema validate failed: In #{list_file}" unless errors.empty?
19
15
 
20
16
  projects = []
21
17
  document.xpath('//project').each do |project|
@@ -25,7 +21,7 @@ module PmdTester
25
21
  end
26
22
 
27
23
  def schema_file_path
28
- ResourceLocator.locate('config/projectlist_1_0_0.xsd')
24
+ ResourceLocator.locate('config/projectlist_1_1_0.xsd')
29
25
  end
30
26
  end
31
27
 
@@ -1,16 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
- require_relative './pmd_report_detail'
5
4
 
6
5
  module PmdTester
7
6
  # This class represents all details about branch of pmd
8
7
  class PmdBranchDetail
8
+ include PmdTester
9
+
9
10
  attr_accessor :branch_last_sha
10
11
  attr_accessor :branch_last_message
11
12
  attr_accessor :branch_name
12
13
  # The branch's execution time on all standard projects
13
14
  attr_accessor :execution_time
15
+ attr_accessor :jdk_version
16
+ attr_accessor :language
17
+ attr_accessor :pull_request
14
18
 
15
19
  def self.branch_filename(branch_name)
16
20
  branch_name&.tr('/', '_')
@@ -23,32 +27,51 @@ module PmdTester
23
27
  branch_filename = PmdBranchDetail.branch_filename(branch_name)
24
28
  @base_branch_dir = "target/reports/#{branch_filename}" unless @branch_name.nil?
25
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
26
36
  end
27
37
 
28
- def load
29
- if File.exist?(branch_details_path)
30
- hash = JSON.parse(File.read(branch_details_path))
31
- @branch_last_sha = hash['branch_last_sha']
32
- @branch_last_message = hash['branch_last_message']
33
- @branch_name = hash['branch_name']
34
- @execution_time = hash['execution_time']
35
- 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']
36
49
  else
37
- {}
50
+ details.jdk_version = ''
51
+ details.language = ''
52
+ logger&.warn "#{details.path_to_save_file} doesn't exist!"
38
53
  end
54
+ details
39
55
  end
40
56
 
41
57
  def save
42
58
  hash = { branch_last_sha: @branch_last_sha,
43
59
  branch_last_message: @branch_last_message,
44
60
  branch_name: @branch_name,
45
- execution_time: @execution_time }
46
- 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')
47
69
  file.puts JSON.generate(hash)
48
70
  file.close
71
+ self
49
72
  end
50
73
 
51
- def branch_details_path
74
+ def path_to_save_file
52
75
  "#{@base_branch_dir}/branch_info.json"
53
76
  end
54
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
@@ -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
@@ -7,12 +7,12 @@ module PmdTester
7
7
  class PmdReportDetail
8
8
  attr_accessor :execution_time
9
9
  attr_accessor :timestamp
10
- attr_reader :working_dir
10
+ attr_accessor :working_dir
11
11
 
12
- def initialize
13
- @execution_time = 0
14
- @timestamp = ''
15
- @working_dir = Dir.getwd
12
+ def initialize(execution_time: 0, timestamp: '', working_dir: Dir.getwd)
13
+ @execution_time = execution_time
14
+ @timestamp = timestamp
15
+ @working_dir = working_dir
16
16
  end
17
17
 
18
18
  def save(report_info_path)
@@ -22,16 +22,13 @@ module PmdTester
22
22
  file.close
23
23
  end
24
24
 
25
- def load(report_info_path)
25
+ def self.load(report_info_path)
26
26
  if File.exist?(report_info_path)
27
- hash = JSON.parse(File.read(report_info_path))
28
- @execution_time = hash['execution_time']
29
- @timestamp = hash['timestamp']
30
- @working_dir = hash['working_dir']
31
- hash
27
+ hash = JSON.parse(File.read(report_info_path), symbolize_names: true)
28
+ PmdReportDetail.new(**hash)
32
29
  else
33
- puts "#{report_info_path} doesn't exist"
34
- {}
30
+ PmdTester.logger.warn("#{report_info_path} doesn't exist")
31
+ PmdReportDetail.new
35
32
  end
36
33
  end
37
34
 
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PmdTester
4
+ # Some functions that that don't belong in a specific class,
5
+ module PmdTesterUtils
6
+ include PmdTester
7
+
8
+ # Parse the base and the patch report, compute their diff
9
+ # Returns a +ReportDiff+
10
+ def build_report_diff(base_report_file, patch_report_file, base_info, patch_info, filter_set = nil)
11
+ base_details = PmdReportDetail.load(base_info)
12
+ patch_details = PmdReportDetail.load(patch_info)
13
+
14
+ base_report = parse_pmd_report(base_report_file, BASE, base_details, filter_set)
15
+ patch_report = parse_pmd_report(patch_report_file, PATCH, patch_details)
16
+
17
+ logger.info 'Calculating diffs'
18
+ ReportDiff.new(base_report: base_report, patch_report: patch_report)
19
+ end
20
+
21
+ # Parse the +report_file+ to produce a +Report+.
22
+ # For the schema of xml reports, refer to http://pmd.sourceforge.net/report_2_0_0.xsd
23
+ def parse_pmd_report(report_file, branch, report_details, filter_set = nil)
24
+ require 'nokogiri'
25
+
26
+ logger.info "Parsing #{report_file}"
27
+ doc = PmdReportDocument.new(branch, report_details.working_dir, filter_set)
28
+ parser = Nokogiri::XML::SAX::Parser.new(doc)
29
+ parser.parse_file(report_file) if File.exist?(report_file)
30
+ Report.new(
31
+ report_document: doc,
32
+
33
+ timestamp: report_details.timestamp,
34
+ exec_time: report_details.execution_time
35
+ )
36
+ end
37
+
38
+ # Fill the report_diff field of every project
39
+ def compute_project_diffs(projects, base_branch, patch_branch, filter_set = nil)
40
+ projects.each do |project|
41
+ logger.info "Preparing report for #{project.name}"
42
+ logger.info " with filter #{filter_set}" unless filter_set.nil?
43
+ project.compute_report_diff(base_branch, patch_branch, filter_set)
44
+ end
45
+ end
46
+
47
+ # Build the diff reports and write them all
48
+ def build_html_reports(projects, base_branch_details, patch_branch_details, filter_set = nil)
49
+ compute_project_diffs(projects, base_branch_details.branch_name, patch_branch_details.branch_name,
50
+ filter_set)
51
+
52
+ SummaryReportBuilder.new.write_all_projects(projects,
53
+ base_branch_details,
54
+ patch_branch_details)
55
+ end
56
+ end
57
+ end
@@ -1,23 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PmdTester
4
- # This class is used to store pmd violations and its size.
5
- class PmdViolations
6
- attr_reader :violations
7
- attr_reader :violations_size
8
-
9
- def initialize
10
- # key:filename as String => value:PmdViolation Array
11
- @violations = {}
12
- @violations_size = 0
13
- end
14
-
15
- def add_violations_by_filename(filename, violations)
16
- @violations.store(filename, violations)
17
- @violations_size += violations.size
18
- end
19
- end
20
-
21
4
  # This class represents a 'violation' element of Pmd xml report
22
5
  # and which pmd branch the 'violation' is from
23
6
  class PmdViolation
@@ -44,23 +27,80 @@ module PmdTester
44
27
  # </xs:simpleContent>
45
28
  # </xs:complexType>
46
29
 
47
- attr_reader :attrs
48
- attr_accessor :text
30
+ attr_reader :fname, :info_url, :line, :old_line, :old_message, :rule_name, :ruleset_name
31
+ attr_accessor :message
49
32
 
50
- def initialize(attrs, branch)
51
- @attrs = attrs
33
+ # rubocop:disable Metrics/ParameterLists
34
+ # Disable it: how is replacing a long parameter list with a single hash helping?
35
+ def initialize(branch:, fname:, info_url:, bline:, rule_name:, ruleset_name:)
52
36
  @branch = branch
53
- @text = ''
37
+ @fname = fname
38
+ @message = ''
39
+
40
+ @info_url = info_url
41
+ @line = bline
42
+ @rule_name = rule_name
43
+
44
+ @ruleset_name = ruleset_name
45
+
46
+ @changed = false
47
+ @old_message = nil
48
+ @old_line = nil
49
+ end
50
+ # rubocop:enable Metrics/ParameterLists
51
+
52
+ def line_move?(other)
53
+ message.eql?(other.message) && (line - other.line).abs <= 5
54
+ end
55
+
56
+ def try_merge?(other)
57
+ if branch != BASE && branch != other.branch && rule_name == other.rule_name &&
58
+ !changed? && # not already changed
59
+ (line == other.line || line_move?(other))
60
+ @changed = true
61
+ @old_message = other.message
62
+ @old_line = other.line
63
+ true
64
+ else
65
+ false
66
+ end
67
+ end
68
+
69
+ # only makes sense if this is a diff
70
+ def added?
71
+ branch != BASE && !changed?
72
+ end
73
+
74
+ # only makes sense if this is a diff
75
+ def changed?
76
+ @changed
77
+ end
78
+
79
+ # only makes sense if this is a diff
80
+ def removed?
81
+ branch == BASE
82
+ end
83
+
84
+ def sort_key
85
+ line
54
86
  end
55
87
 
56
88
  def eql?(other)
57
- @attrs['beginline'].eql?(other.attrs['beginline']) &&
58
- @attrs['rule'].eql?(other.attrs['rule']) &&
59
- @text.eql?(other.text)
89
+ rule_name.eql?(other.rule_name) &&
90
+ line.eql?(other.line) &&
91
+ fname.eql?(other.fname) &&
92
+ message.eql?(other.message)
60
93
  end
61
94
 
62
95
  def hash
63
- [@attrs['beginline'], @attrs['rule'], @text].hash
96
+ [line, rule_name, message].hash
97
+ end
98
+
99
+ def to_liquid
100
+ {
101
+ 'branch' => branch,
102
+ 'changed' => changed?
103
+ }
64
104
  end
65
105
  end
66
106
  end