pmdtester 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,39 @@
1
+ <?xml version="1.0" ?>
2
+ <!-- version 1.1.0 -->
3
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
4
+ <xs:element name="projectlist">
5
+ <xs:complexType>
6
+ <xs:sequence>
7
+ <xs:element name="description" type="xs:string" minOccurs="1" maxOccurs="1"/>
8
+ <xs:element name="project" type="project" minOccurs="1" maxOccurs="unbounded"/>
9
+ </xs:sequence>
10
+ </xs:complexType>
11
+ </xs:element>
12
+ <xs:complexType name="project">
13
+ <xs:sequence>
14
+ <xs:element name="name" type="xs:string" minOccurs="1" maxOccurs="1"/>
15
+ <xs:element name="type" minOccurs="1" maxOccurs="1">
16
+ <xs:simpleType>
17
+ <xs:restriction base="xs:string">
18
+ <xs:enumeration value="git"/>
19
+ </xs:restriction>
20
+ </xs:simpleType>
21
+ </xs:element>
22
+ <xs:element name="connection" type="xs:string" minOccurs="1" maxOccurs="1"/>
23
+ <xs:element name="webview-url" type="xs:string" minOccurs="0" maxOccurs="1"/>
24
+ <xs:element name="tag" type="xs:string" minOccurs="0" maxOccurs="1"/>
25
+ <xs:element name="src-subpath" type="xs:string" minOccurs="0" maxOccurs="1" default=".">
26
+ <xs:annotation>
27
+ <xs:documentation>
28
+ Value of the -dir option for the PMD run.
29
+ The value must be a directory path relative to the root directory of the clone.
30
+ Defaults to just '.', ie the entire directory.
31
+ </xs:documentation>
32
+ </xs:annotation>
33
+ </xs:element>
34
+ <xs:element name="exclude-pattern" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
35
+ <xs:element name="build-command" type="xs:string" minOccurs="0" maxOccurs="1"/>
36
+ <xs:element name="auxclasspath-command" type="xs:string" minOccurs="0" maxOccurs="1"/>
37
+ </xs:sequence>
38
+ </xs:complexType>
39
+ </xs:schema>
data/lib/pmdtester.rb CHANGED
@@ -32,7 +32,7 @@ require_relative 'pmdtester/parsers/projects_parser'
32
32
  # and unexpected behaviors will not be introduced to PMD project
33
33
  # after fixing an issue and new rules can work as expected.
34
34
  module PmdTester
35
- VERSION = '1.1.2'
35
+ VERSION = '1.2.0'
36
36
  BASE = 'base'
37
37
  PATCH = 'patch'
38
38
  PR_NUM_ENV_VAR = 'PMD_CI_PULL_REQUEST_NUMBER' # see PmdBranchDetail
@@ -57,17 +57,74 @@ module PmdTester
57
57
  write_file("#{root}/index.html", render_liquid('project_diff_report.html', liquid_env))
58
58
  # generate array of violations in json
59
59
  write_file("#{root}/project_data.js", dump_violations_json(project))
60
+ # copy original pmd reports
61
+ copy_file("#{root}/base_pmd_report.xml", project.report_diff.base_report.file)
62
+ copy_file("#{root}/patch_pmd_report.xml", project.report_diff.patch_report.file)
63
+ # render full pmd reports
64
+ write_file("#{root}/base_pmd_report.html",
65
+ render_liquid('project_pmd_report.html', pmd_report_liquid_env(project, BASE)))
66
+ write_file("#{root}/base_data.js", dump_violations_json(project, BASE))
67
+ write_file("#{root}/patch_pmd_report.html",
68
+ render_liquid('project_pmd_report.html', pmd_report_liquid_env(project, PATCH)))
69
+ write_file("#{root}/patch_data.js", dump_violations_json(project, PATCH))
60
70
  end
61
71
 
62
- def dump_violations_json(project)
72
+ def dump_violations_json(project, branch = 'diff')
73
+ violations_by_file = if branch == BASE
74
+ project.report_diff.base_report.violations_by_file.to_h
75
+ elsif branch == PATCH
76
+ project.report_diff.patch_report.violations_by_file.to_h
77
+ else
78
+ project.report_diff.violation_diffs_by_file
79
+ end
80
+
63
81
  h = {
64
82
  'source_link_base' => project.webview_url,
65
83
  'source_link_template' => link_template(project),
66
- **violations_to_hash(project)
84
+ **violations_to_hash(project, violations_by_file, branch == 'diff')
67
85
  }
68
86
 
69
- project_data = JSON.fast_generate(h)
87
+ project_data = JSON.fast_generate(h, indent: ' ', object_nl: "\n", array_nl: "\n")
70
88
  "let project = #{project_data}"
71
89
  end
90
+
91
+ private
92
+
93
+ def copy_file(target_file, source_file)
94
+ if File.exist? source_file
95
+ FileUtils.cp(source_file, target_file)
96
+ logger&.info "Written #{target_file}"
97
+ else
98
+ logger&.warn "File #{source_file} not found"
99
+ end
100
+ end
101
+
102
+ def pmd_report_liquid_env(project, branch)
103
+ report = if branch == BASE
104
+ project.report_diff.base_report
105
+ else
106
+ project.report_diff.patch_report
107
+ end
108
+ {
109
+ 'project_name' => project.name,
110
+ 'branch' => branch,
111
+ 'report' => report_to_h(project, report)
112
+ }
113
+ end
114
+
115
+ def report_to_h(project, report)
116
+ {
117
+ 'violation_counts' => report.violations_by_file.total_size,
118
+ 'error_counts' => report.errors_by_file.total_size,
119
+ 'configerror_counts' => report.configerrors_by_rule.values.flatten.length,
120
+
121
+ 'execution_time' => PmdReportDetail.convert_seconds(report.exec_time),
122
+ 'timestamp' => report.timestamp,
123
+
124
+ 'rules' => report.rule_summaries,
125
+ 'errors' => report.errors_by_file.all_values.map { |e| error_to_hash(e, project) },
126
+ 'configerrors' => report.configerrors_by_rule.values.flatten.map { |e| configerror_to_hash(e) }
127
+ }
128
+ end
72
129
  end
73
130
  end
@@ -69,7 +69,7 @@ module PmdTester
69
69
  ' -Dmaven.source.skip=true' \
70
70
  ' -Dcheckstyle.skip=true' \
71
71
  ' -Dpmd.skip=true' \
72
- ' -T1C'
72
+ ' -T1C -B'
73
73
  Cmd.execute(package_cmd)
74
74
  end
75
75
 
@@ -119,7 +119,7 @@ module PmdTester
119
119
  doc = Nokogiri::XML(File.read(@branch_config))
120
120
  ruleset = doc.at_css('ruleset')
121
121
  ruleset.add_child("\n")
122
- project.exclude_pattern.each do |exclude_pattern|
122
+ project.exclude_patterns.each do |exclude_pattern|
123
123
  ruleset.add_child(" <exclude-pattern>#{exclude_pattern}</exclude-pattern>\n")
124
124
  end
125
125
 
@@ -17,11 +17,19 @@ module PmdTester
17
17
 
18
18
  @projects.each do |project|
19
19
  logger.info "Start cloning #{project.name} repository"
20
- path = project.local_source_path
21
- clone_cmd = "#{project.type} clone #{project.connection} #{path}"
20
+ path = project.clone_root_path
21
+
22
22
  if File.exist?(path)
23
23
  logger.warn "Skipping clone, project path #{path} already exists"
24
24
  else
25
+ raise "Unsupported project type '#{project.type}' - only git is supported" unless project.type == 'git'
26
+
27
+ # git:
28
+ # Don't download whole history
29
+ # Note we don't use --single-branch, because the repo is downloaded
30
+ # once but may be used with several tags.
31
+ clone_cmd = "git clone --no-single-branch --depth 1 #{project.connection} #{path}"
32
+
25
33
  Cmd.execute(clone_cmd)
26
34
  end
27
35
 
@@ -38,7 +46,7 @@ module PmdTester
38
46
  logger.info 'Building projects started'
39
47
 
40
48
  @projects.each do |project|
41
- path = project.local_source_path
49
+ path = project.clone_root_path
42
50
  Dir.chdir(path) do
43
51
  progress_logger = SimpleProgressLogger.new("building #{project.name} in #{path}")
44
52
  progress_logger.start
@@ -87,12 +95,9 @@ module PmdTester
87
95
  end
88
96
 
89
97
  def execute_reset_cmd(type, tag)
90
- case type
91
- when 'git'
92
- reset_cmd = "git checkout #{tag}; git reset --hard #{tag}"
93
- when 'hg'
94
- reset_cmd = "hg up #{tag}"
95
- end
98
+ raise "Unsupported project type '#{type}' - only git is supported" unless type == 'git'
99
+
100
+ reset_cmd = "git checkout #{tag}; git reset --hard #{tag}"
96
101
 
97
102
  Cmd.execute(reset_cmd)
98
103
  end
@@ -25,24 +25,14 @@ module PmdTester
25
25
  }
26
26
  end
27
27
 
28
- def errors_to_h(project)
29
- errors = project.report_diff.error_diffs_by_file.values.flatten
30
- errors.map { |e| error_to_hash(e, project) }
31
- end
32
-
33
- def configerrors_to_h(project)
34
- configerrors = project.report_diff.configerror_diffs_by_rule.values.flatten
35
- configerrors.map { |e| configerror_to_hash(e) }
36
- end
37
-
38
- def violations_to_hash(project)
28
+ def violations_to_hash(project, violations_by_file, is_diff)
39
29
  filename_index = []
40
30
  all_vs = []
41
- project.report_diff.violation_diffs_by_file.each do |file, vs|
31
+ violations_by_file.each do |file, vs|
42
32
  file_ref = filename_index.size
43
33
  filename_index.push(project.get_local_path(file))
44
34
  vs.each do |v|
45
- all_vs.push(make_violation_hash(file_ref, v))
35
+ all_vs.push(make_violation_hash(file_ref, v, is_diff))
46
36
  end
47
37
  end
48
38
 
@@ -52,36 +42,19 @@ module PmdTester
52
42
  }
53
43
  end
54
44
 
55
- def link_template(project)
56
- l_str = project.type == 'git' ? 'L' : 'l'
57
- "#{project.webview_url}/{file}##{l_str}{line}"
58
- end
59
-
60
- def violation_type(violation)
61
- if violation.changed?
62
- '~'
63
- elsif violation.branch == 'patch'
64
- '+'
65
- else
66
- '-'
67
- end
45
+ def errors_to_h(project)
46
+ errors = project.report_diff.error_diffs_by_file.values.flatten
47
+ errors.map { |e| error_to_hash(e, project) }
68
48
  end
69
49
 
70
- def make_violation_hash(file_ref, violation)
71
- h = {
72
- 't' => violation_type(violation),
73
- 'l' => violation.line,
74
- 'f' => file_ref,
75
- 'r' => violation.rule_name,
76
- 'm' => violation.changed? ? diff_fragments(violation) : violation.message
77
- }
78
- h['ol'] = violation.old_line if violation.changed? && violation.line != violation.old_line
79
- h
50
+ def configerrors_to_h(project)
51
+ configerrors = project.report_diff.configerror_diffs_by_rule.values.flatten
52
+ configerrors.map { |e| configerror_to_hash(e) }
80
53
  end
81
54
 
82
- def diff_fragments(violation)
83
- diff = Differ.diff_by_word(violation.message, violation.old_message)
84
- diff.format_as(:html)
55
+ def link_template(project)
56
+ l_str = project.type == 'git' ? 'L' : 'l'
57
+ "#{project.webview_url}/{file}##{l_str}{line}"
85
58
  end
86
59
 
87
60
  def error_to_hash(error, project)
@@ -122,5 +95,34 @@ module PmdTester
122
95
  'added'
123
96
  end
124
97
  end
98
+
99
+ private
100
+
101
+ def violation_type(violation)
102
+ if violation.changed?
103
+ '~'
104
+ elsif violation.branch == PATCH
105
+ '+'
106
+ else
107
+ '-'
108
+ end
109
+ end
110
+
111
+ def make_violation_hash(file_ref, violation, is_diff = TRUE)
112
+ h = {
113
+ 't' => is_diff ? violation_type(violation) : '+',
114
+ 'l' => violation.line,
115
+ 'f' => file_ref,
116
+ 'r' => violation.rule_name,
117
+ 'm' => is_diff && violation.changed? ? diff_fragments(violation) : violation.message
118
+ }
119
+ h['ol'] = violation.old_line if is_diff && violation.changed? && violation.line != violation.old_line
120
+ h
121
+ end
122
+
123
+ def diff_fragments(violation)
124
+ diff = Differ.diff_by_word(violation.message, violation.old_message)
125
+ diff.format_as(:html)
126
+ end
125
127
  end
126
128
  end
@@ -33,6 +33,10 @@ module PmdTester
33
33
  rule_refs
34
34
  end
35
35
 
36
+ def calculate_filter_set
37
+ output_filter_set(ALL_CATEGORIES)
38
+ end
39
+
36
40
  def output_filter_set(rule_refs)
37
41
  if rule_refs == ALL_CATEGORIES
38
42
  if @options.mode == Options::ONLINE
@@ -80,6 +80,7 @@ module PmdTester
80
80
  'sha' => details.branch_last_sha,
81
81
  'message' => details.branch_last_message
82
82
  },
83
+ 'timestamp' => details.timestamp,
83
84
  'execution_time' => PmdReportDetail.convert_seconds(details.execution_time),
84
85
  'jdk_info' => details.jdk_version,
85
86
  'locale' => details.language,
@@ -29,6 +29,7 @@ module PmdTester
29
29
  attr_reader :threads
30
30
  attr_reader :html_flag
31
31
  attr_reader :auto_config_flag
32
+ attr_reader :filter_with_patch_config
32
33
  attr_reader :debug_flag
33
34
  attr_accessor :filter_set
34
35
  attr_reader :keep_reports
@@ -48,6 +49,7 @@ module PmdTester
48
49
  @threads = options[:t]
49
50
  @html_flag = options[:f]
50
51
  @auto_config_flag = options[:a]
52
+ @filter_with_patch_config = options.filter_with_patch_config?
51
53
  @debug_flag = options[:d]
52
54
  @filter_set = nil
53
55
  @keep_reports = options.keep_reports?
@@ -100,6 +102,9 @@ module PmdTester
100
102
  o.bool '-a', '--auto-gen-config',
101
103
  'whether to generate configurations automatically based on branch differences,' \
102
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.'
103
108
  o.bool '--keep-reports',
104
109
  'whether to keep old reports and skip running PMD again if possible'
105
110
  o.bool '-d', '--debug',
@@ -8,14 +8,12 @@ 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
12
  def initialize(branch_name, working_dir, filter_set = nil)
14
13
  @violations = CollectionByFile.new
15
14
  @errors = CollectionByFile.new
16
15
  @configerrors = Hash.new { |hash, key| hash[key] = [] }
17
16
 
18
- @infos_by_rules = {}
19
17
  @current_violations = []
20
18
  @current_violation = nil
21
19
  @current_error = nil
@@ -21,7 +21,7 @@ module PmdTester
21
21
  end
22
22
 
23
23
  def schema_file_path
24
- ResourceLocator.locate('config/projectlist_1_1_0.xsd')
24
+ ResourceLocator.locate('config/projectlist_1_2_0.xsd')
25
25
  end
26
26
  end
27
27
 
@@ -10,6 +10,8 @@ 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
17
  attr_accessor :jdk_version
@@ -26,6 +28,7 @@ module PmdTester
26
28
  @branch_name = branch_name
27
29
  branch_filename = PmdBranchDetail.branch_filename(branch_name)
28
30
  @base_branch_dir = "target/reports/#{branch_filename}" unless @branch_name.nil?
31
+ @timestamp = Time.now
29
32
  @execution_time = 0
30
33
  # the result of command 'java -version' is going to stderr
31
34
  @jdk_version = Cmd.stderr_of('java -version')
@@ -42,11 +45,13 @@ module PmdTester
42
45
  details.branch_last_sha = hash['branch_last_sha']
43
46
  details.branch_last_message = hash['branch_last_message']
44
47
  details.branch_name = hash['branch_name']
48
+ details.timestamp = hash['timestamp']
45
49
  details.execution_time = hash['execution_time']
46
50
  details.jdk_version = hash['jdk_version']
47
51
  details.language = hash['language']
48
52
  details.pull_request = hash['pull_request']
49
53
  else
54
+ details.timestamp = Time.now
50
55
  details.jdk_version = ''
51
56
  details.language = ''
52
57
  logger&.warn "#{details.path_to_save_file} doesn't exist!"
@@ -58,6 +63,7 @@ module PmdTester
58
63
  hash = { branch_last_sha: @branch_last_sha,
59
64
  branch_last_message: @branch_last_message,
60
65
  branch_name: @branch_name,
66
+ timestamp: @timestamp,
61
67
  execution_time: @execution_time,
62
68
  jdk_version: @jdk_version,
63
69
  language: @language,
@@ -29,6 +29,7 @@ module PmdTester
29
29
  parser.parse_file(report_file) if File.exist?(report_file)
30
30
  Report.new(
31
31
  report_document: doc,
32
+ file: report_file,
32
33
 
33
34
  timestamp: report_details.timestamp,
34
35
  exec_time: report_details.execution_time
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pathname'
4
+
3
5
  module PmdTester
4
6
  # This class represents all the information about the project
5
7
  class Project
@@ -12,7 +14,8 @@ module PmdTester
12
14
  attr_reader :connection
13
15
  attr_reader :webview_url
14
16
  attr_reader :tag
15
- attr_reader :exclude_pattern
17
+ attr_reader :exclude_patterns
18
+ attr_reader :src_subpath
16
19
  attr_accessor :report_diff
17
20
  # key: pmd branch name as String => value: local path of pmd report
18
21
  attr_reader :build_command
@@ -20,7 +23,7 @@ module PmdTester
20
23
  # stores the auxclasspath calculated after cloning/preparing the project
21
24
  attr_accessor :auxclasspath
22
25
 
23
- def initialize(project)
26
+ def initialize(project) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
24
27
  @name = project.at_xpath('name').text
25
28
  @type = project.at_xpath('type').text
26
29
  @connection = project.at_xpath('connection').text
@@ -31,9 +34,10 @@ module PmdTester
31
34
  @webview_url = default_webview_url
32
35
  @webview_url = webview_url_element.text unless webview_url_element.nil?
33
36
 
34
- @exclude_pattern = []
37
+ @src_subpath = project.at_xpath('src-subpath')&.text || '.'
38
+ @exclude_patterns = []
35
39
  project.xpath('exclude-pattern').each do |ep|
36
- @exclude_pattern.push(ep.text)
40
+ @exclude_patterns.push(ep.text)
37
41
  end
38
42
 
39
43
  @build_command = project.at_xpath('build-command')&.text
@@ -56,17 +60,17 @@ module PmdTester
56
60
  # Change the file path from 'LOCAL_DIR/SOURCE_CODE_PATH' to
57
61
  # 'WEB_VIEW_URL/SOURCE_CODE_PATH'
58
62
  def get_webview_url(file_path)
59
- file_path.gsub(%r{/#{local_source_path}}, @webview_url)
63
+ file_path.gsub(%r{/#{clone_root_path}}, @webview_url)
60
64
  end
61
65
 
62
66
  # Change the file path from 'LOCAL_DIR/SOURCE_CODE_PATH' to
63
67
  # 'PROJECT_NAME/SOURCE_CODE_PATH'
64
68
  def get_path_inside_project(file_path)
65
- file_path.gsub(%r{/#{local_source_path}}, @name)
69
+ file_path.gsub(%r{/#{clone_root_path}}, @name)
66
70
  end
67
71
 
68
72
  def get_local_path(file_path)
69
- file_path.sub(%r{/#{local_source_path}/}, '')
73
+ file_path.sub(%r{/#{clone_root_path}/}, '')
70
74
  end
71
75
 
72
76
  def get_pmd_report_path(branch_name)
@@ -93,6 +97,19 @@ module PmdTester
93
97
  end
94
98
  end
95
99
 
100
+ ##
101
+ # Path to the sources to analyze (below or equal to clone_root_path)
102
+ def local_source_path
103
+ # normalize path
104
+ Pathname.new("#{clone_root_path}/#{src_subpath}").cleanpath
105
+ end
106
+
107
+ ##
108
+ # Path to the clone directory
109
+ def clone_root_path
110
+ "#{REPOSITORIES_PATH}/#{@name}"
111
+ end
112
+
96
113
  def get_project_target_dir(branch_name)
97
114
  branch_filename = PmdBranchDetail.branch_filename(branch_name)
98
115
  dir = "target/reports/#{branch_filename}/#{@name}"
@@ -100,10 +117,6 @@ module PmdTester
100
117
  dir
101
118
  end
102
119
 
103
- def local_source_path
104
- "#{REPOSITORIES_PATH}/#{@name}"
105
- end
106
-
107
120
  def compute_report_diff(base_branch, patch_branch, filter_set)
108
121
  self.report_diff = build_report_diff(get_pmd_report_path(base_branch),
109
122
  get_pmd_report_path(patch_branch),