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,9 +1,12 @@
1
1
  = pmdtester
2
2
 
3
+ home :: https://pmd.github.io
3
4
  code :: https://github.com/pmd/pmd-regression-tester
4
5
  bugs :: https://github.com/pmd/pmd-regression-tester/issues
5
6
 
6
- build-status :: {<img src="https://travis-ci.com/pmd/pmd-regression-tester.svg?branch=master" alt="Build Status" />}[https://travis-ci.com/pmd/pmd-regression-tester]
7
+ build-status :: {<img src="https://github.com/pmd/pmd-regression-tester/workflows/build/badge.svg?branch=master" alt="Build Status" />}[https://github.com/pmd/pmd-regression-tester/actions?query=workflow%3Abuild]
8
+
9
+ gem-version :: {<img src="https://badge.fury.io/rb/pmdtester.svg" alt="Gem Version" />}[https://rubygems.org/gems/pmdtester]
7
10
 
8
11
  == DESCRIPTION:
9
12
 
@@ -16,18 +19,138 @@ on a list of standard projects(Spring Framework, Hibernate, Solr, etc.)
16
19
 
17
20
  == SYNOPSIS:
18
21
 
19
- * FIX (code sample of usage)
22
+ === Options:
23
+ -r, --local-git-repo path to the local PMD repository
24
+ -b, --base-branch name of the base branch in local PMD repository
25
+ -p, --patch-branch name of the patch branch in local PMD repository
26
+ -bc, --base-config path to the base PMD configuration file default:PMDTESTER_INSTALLED_DIR/config/all-java.xml
27
+ -pc, --patch-config path to the patch PMD configuration file default:PMDTESTER_INSTALLED_DIR/config/all-java.xml
28
+ -c, --config path to the base and patch PMD configuration file
29
+ -l, --list-of-project path to the file which contains the list of standard projects default:PMDTESTER_INSTALLED_DIR/config/project-list.xml
30
+ -m, --mode the mode of the tool: 'local', 'online' or 'single'
31
+ single: Set this option to 'single' if your patch branch contains changes
32
+ for any option that can't work on master/base branch
33
+ online: Set this option to 'online' if you want to download
34
+ the PMD report of master/base branch rather than generating it locally
35
+ local: Default option is 'local', PMD reports for the base and patch branches are generated locally.
36
+
37
+ -t, --threads Sets the number of threads used by PMD. Set threads to 0 to disable multi-threading processing. default:1
38
+ -f, --html-flag whether to not generate the html diff report in single mode
39
+ -a, --auto-gen-config whether to generate configurations automatically based on branch differences,this option only works in online and local mode
40
+ --keep-reports whether to keep old reports and skip running PMD again if possible
41
+ -d, --debug whether change log level to DEBUG to see more information
42
+ --error-recovery enable error recovery mode when executing PMD. Might help to analyze errors.
43
+ --baseline-download-url download url prefix from where to download the baseline in online mode default:https://sourceforge.net/projects/pmd/files/pmd-regression-tester/
44
+ -v, --version
45
+ -h, --help
46
+
47
+ === Quick start
48
+
49
+ ==== Run local mode
50
+
51
+ pmdtester -b master -p YOUR_DEVELOPMENT_BRANCH -r PATH_TO_LOCAL_PMD_REPO -a
52
+
53
+ ==== Run single mode
54
+
55
+ pmdtester -p YOUR_DEVELOPMENT_BRANCH -pc CONFIG_ONLY_CONTAINS_NEW_PMD_JAVA_RULE -m single
56
+
57
+ ==== Run online mode
58
+
59
+ pmdtester -b master -p YOUR_DEVELOPMENT_BRANCH -r PATH_TO_LOCAL_PMD_REPO -m online -a
60
+
61
+ === Output
62
+ The tool creates the following folders:
63
+ target
64
+ ├── repositories <- the analyzed projects are cloned here
65
+ │ ├── PROJECT_NAME_1
66
+ │ ├── ......
67
+ │ └── PROJECT_NAME_n
68
+ ├── reports
69
+ │ ├── BASE_BRANCH_NAME <- the base baseline is placed here
70
+ │ ├── PATCH_BRANCH_NAME <- the patch baseline is placed here
71
+ │ └── diff
72
+ │ ├── index.html <- the summary report of diff reports
73
+ │ ├── base_config.xml <- pmd config from the base branch
74
+ │ ├── patch_config.xml <- pmd config from the patch branch
75
+ │ ├── css <- css resources are placed here
76
+ │ ├── js <- js resources
77
+ │ ├── PROJECT_NAME_1
78
+ │ │ ├── project_data.js <- contains the violations as js/json
79
+ │ │ └── index.html <- the diff report of PROJECT_1
80
+ │ ├── .......
81
+ │ └── PROJECT_NAME_n
82
+ │ ├── project_data.js <- contains the violations as js/json
83
+ │ └── index.xml <- the diff report of PROJECT_N
84
+ ├── pmd-bin-<version>-<branch_name>-<sha1> <- cached pmd builds that are reused
85
+ └── pmd-bin-....
86
+
87
+ ==== The baseline format
88
+ branch_name
89
+ ├── branch_info.json
90
+ ├── config.xml
91
+ ├── STANDARD_PROJECT_NAME_1
92
+ │ ├── report_info.json
93
+ │ └── pmd_report.xml
94
+ ├── ......................
95
+ │ ├── report_info.json
96
+ │ └── pmd_report.xml
97
+ └── STANDARD_PROJECT_NAME_n
98
+ ├── report_info.info
99
+ └── pmd_report.xml
20
100
 
21
101
  == REQUIREMENTS:
22
102
 
23
- * FIX (list of requirements)
103
+ * Ruby 2.7 or higher
24
104
 
25
- == INSTALL:
105
+ === Runtime dependency
26
106
 
27
- * FIX (sudo gem install, anything else)
107
+ nokogiri >= 1.11.0.rc4
108
+ slop ~> 4.6
109
+ differ ~> 0.1
110
+ rufus-scheduler ~> 3.5
111
+ logger-colors ~> 1.0
112
+ liquid >= 4.0
28
113
 
29
- == DEVELOPERS:
114
+ === Development dependency
30
115
 
31
- You can list all tasks by running `rake -T`.
116
+ hoe ~> 3.22
117
+ hoe-bundler ~> 1.5
118
+ hoe-git ~> 1.6
119
+ minitest ~> 5.10
120
+ mocha ~> 1.5
121
+ rdoc < 7, >= 4.0
122
+ rubocop ~> 0.81
123
+ test-unit ~> 3.2
32
124
 
125
+ == INSTALL:
126
+
127
+ gem install pmdtester --pre
33
128
 
129
+ == DEVELOPERS:
130
+ git clone https://github.com/pmd/pmd-regression-tester.git
131
+ cd pmd-regression-tester
132
+ gem install bundler
133
+ bundle config set path "vendor/cache"
134
+ bundle install # once
135
+ bundle exec rake verify # run this command before commit your changes
136
+ bundle exec pmdtester ... # run this to directly execute pmdtester from source
137
+
138
+ Run a single test class, e.g.:
139
+ bundle exec ruby -I test test/test_diff_report_builder.rb
140
+
141
+ Run a single test, e.g.:
142
+ bundle exec ruby -I test test/test_diff_report_builder.rb -n test_diff_report_builder
143
+
144
+ === Releasing
145
+
146
+ * Update +History.md+ (version and date)
147
+ * Update +lib/pmdtester.rb+ (version)
148
+ * Run "bundle exec rake verify" and add new +pmdtester.gemspec+ (new version)
149
+ * Commit ("Prepare release x.y.z").
150
+ * Tag this commit ("git tag vx.y.z").
151
+ * Update History.md and lib/pmdtester.rb for the next development version,
152
+ run again "bundle exec rake verify"
153
+ * Commit ("Prepare next development version x.y.z-SNAPSHOT").
154
+ * Push to master.
155
+ * Push the tag. Github Actions will build and publish the new gem
156
+ * Update the release notes on https://github.com/pmd/pmd-regression-tester/releases
data/Rakefile CHANGED
@@ -3,40 +3,43 @@
3
3
  # -*- ruby -*-
4
4
  require 'rake/testtask'
5
5
  require 'rubocop/rake_task'
6
- require_relative './lib/pmdtester/parsers/options.rb'
6
+ require_relative 'lib/pmdtester'
7
7
 
8
8
  gem 'hoe'
9
9
  require 'hoe'
10
+
10
11
  Hoe.plugin :bundler
11
- Hoe.plugin :gemspec
12
12
  Hoe.plugin :git
13
13
 
14
14
  hoe = Hoe.spec 'pmdtester' do
15
- self.version = PmdTester::Options::VERSION
15
+ self.version = PmdTester::VERSION
16
16
 
17
- developer 'Andreas Dangel', 'andreas.dangel@adangel.org'
17
+ developer 'Andreas Dangel', 'andreas.dangel@pmd-code.org'
18
18
  developer 'Binguo Bao', 'djydewang@gmail.com'
19
+ developer 'Clément Fournier', 'clement.fournier76@gmail.com'
19
20
 
20
- self.clean_globs = %w[target/reports/**/* target/test/**/*]
21
- self.extra_deps += [['nokogiri', '~> 1.8.2'], ['slop', '~> 4.6.2']]
22
- self.extra_dev_deps += [
23
- ['hoe-bundler', '~> 1.2'],
24
- ['hoe-gemspec', '~> 1.0'],
21
+ self.clean_globs = %w[target/reports/**/* target/test/**/* target/dynamic-config.xml Gemfile.lock]
22
+ self.extra_deps += [['nokogiri', '>= 1.11.0.rc4'], ['slop', '~> 4.6'], ['differ', '~> 0.1'],
23
+ ['rufus-scheduler', '~> 3.5'], ['logger-colors', '~> 1.0'],
24
+ ['liquid', '>= 4.0']]
25
+ self.extra_dev_deps += [
26
+ ['hoe-bundler', '~> 1.5'],
25
27
  ['hoe-git', '~> 1.6'],
26
- ['minitest', '~> 5.10.1'],
27
- ['mocha', '~> 1.5.0'],
28
- ['rubocop', '~> 0.56.0'],
29
- ['test-unit', '~> 3.2.3']
28
+ ['minitest', '~> 5.10'],
29
+ ['mocha', '~> 1.5'],
30
+ # use the same version of rubocop as codacy
31
+ ['rubocop', '~> 0.81'],
32
+ ['test-unit', '~> 3.2'],
33
+ ['rdoc', ['>= 4.0', '< 7']]
30
34
  ]
35
+ spec_extras[:required_ruby_version] = '>= 2.7'
31
36
 
32
37
  license 'BSD-2-Clause'
33
38
  end
34
39
 
35
40
  # Refers to
36
41
  # http://rubocop.readthedocs.io/en/latest/integration_with_other_tools/#rake-integration
37
- RuboCop::RakeTask.new(:rubocop) do |task|
38
- task.patterns = %w[lib/**/*.rb test/**/*.rb]
39
- end
42
+ RuboCop::RakeTask.new(:rubocop)
40
43
 
41
44
  # Run integration test cases
42
45
  Rake::TestTask.new('integration-test') do |task|
@@ -48,7 +51,18 @@ end
48
51
 
49
52
  desc 'generate the pmdtester.gemspec file'
50
53
  task 'hoe:spec' do
51
- File.open("#{hoe.name}.gemspec", "w") { |f| f.write hoe.spec.to_ruby}
54
+ attention_message = '# DO NOT EDIT THIS FILE. Instead, edit Rakefile, and run `rake hoe:spec`.'
55
+ File.open("#{hoe.name}.gemspec", 'w') do |f|
56
+ f.puts attention_message
57
+ f.puts
58
+ f.write hoe.spec.to_ruby
59
+ f.puts
60
+ f.puts attention_message
61
+ end
52
62
  end
53
63
 
64
+ desc 'verify code quality before committing changes'
65
+ task 'verify' => ['clean', 'test', 'rubocop', 'git:manifest', 'hoe:spec', 'check_manifest'] do
66
+ Rake.application.invoke_task('bundler:gemfile[,true]')
67
+ end
54
68
  # vim: syntax=ruby
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require_relative '../lib/pmdtester/runner'
4
+ require_relative '../lib/pmdtester'
5
5
 
6
6
  runner = PmdTester::Runner.new(ARGV)
7
7
  runner.run
@@ -3,7 +3,7 @@
3
3
  <ruleset name="All Java Rules"
4
4
  xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
5
5
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
6
- xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
6
+ xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
7
7
  <description>Every Java Rule in PMD</description>
8
8
 
9
9
  <rule ref="category/java/bestpractices.xml" />
@@ -3,7 +3,7 @@
3
3
  <ruleset name="Design"
4
4
  xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
5
5
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
6
- xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
6
+ xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
7
7
 
8
8
  <description>
9
9
  The Design ruleset contains rules that flag suboptimal code implementations. Alternate approaches
@@ -1,4 +1,5 @@
1
1
  <?xml version="1.0" ?>
2
+ <!-- version 1.0.0 -->
2
3
  <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3
4
  <xs:element name="projectlist">
4
5
  <xs:complexType>
@@ -25,4 +26,4 @@
25
26
  <xs:element name="exclude-pattern" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
26
27
  </xs:sequence>
27
28
  </xs:complexType>
28
- </xs:schema>
29
+ </xs:schema>
@@ -0,0 +1,31 @@
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:enumeration value="hg"/>
20
+ </xs:restriction>
21
+ </xs:simpleType>
22
+ </xs:element>
23
+ <xs:element name="connection" type="xs:string" minOccurs="1" maxOccurs="1"/>
24
+ <xs:element name="webview-url" type="xs:string" minOccurs="0" maxOccurs="1"/>
25
+ <xs:element name="tag" type="xs:string" minOccurs="0" maxOccurs="1"/>
26
+ <xs:element name="exclude-pattern" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
27
+ <xs:element name="build-command" type="xs:string" minOccurs="0" maxOccurs="1"/>
28
+ <xs:element name="auxclasspath-command" type="xs:string" minOccurs="0" maxOccurs="1"/>
29
+ </xs:sequence>
30
+ </xs:complexType>
31
+ </xs:schema>
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+ require 'logger/colors'
5
+
6
+ require_relative 'pmdtester/cmd'
7
+ require_relative 'pmdtester/collection_by_file'
8
+ require_relative 'pmdtester/pmd_branch_detail'
9
+ require_relative 'pmdtester/pmd_configerror'
10
+ require_relative 'pmdtester/pmd_error'
11
+ require_relative 'pmdtester/pmd_report_detail'
12
+ require_relative 'pmdtester/pmd_tester_utils'
13
+ require_relative 'pmdtester/pmd_violation'
14
+ require_relative 'pmdtester/project'
15
+ require_relative 'pmdtester/report_diff'
16
+ require_relative 'pmdtester/resource_locator'
17
+ require_relative 'pmdtester/runner'
18
+
19
+ require_relative 'pmdtester/builders/simple_progress_logger'
20
+ require_relative 'pmdtester/builders/project_builder'
21
+ require_relative 'pmdtester/builders/project_hasher'
22
+ require_relative 'pmdtester/builders/pmd_report_builder'
23
+ require_relative 'pmdtester/builders/liquid_renderer'
24
+ require_relative 'pmdtester/builders/rule_set_builder'
25
+ require_relative 'pmdtester/builders/summary_report_builder'
26
+
27
+ require_relative 'pmdtester/parsers/options'
28
+ require_relative 'pmdtester/parsers/pmd_report_document'
29
+ require_relative 'pmdtester/parsers/projects_parser'
30
+
31
+ # PmdTester is a regression testing tool ensure that new problems
32
+ # and unexpected behaviors will not be introduced to PMD project
33
+ # after fixing an issue and new rules can work as expected.
34
+ module PmdTester
35
+ VERSION = '1.1.1'
36
+ BASE = 'base'
37
+ PATCH = 'patch'
38
+ PR_NUM_ENV_VAR = 'PMD_CI_PULL_REQUEST_NUMBER' # see PmdBranchDetail
39
+
40
+ def logger
41
+ PmdTester.logger
42
+ end
43
+
44
+ # Global, memoized, lazy initialized instance of a logger
45
+ def self.logger
46
+ @logger ||= Logger.new(STDOUT)
47
+ end
48
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'liquid'
4
+ require 'json'
5
+
6
+ module PmdTester
7
+ # A module to include in classes that use a Liquid template
8
+ # to generate content.
9
+ module LiquidRenderer
10
+ include PmdTester
11
+
12
+ def render_liquid(template_path, env)
13
+ to_render = File.read(ResourceLocator.resource(template_path))
14
+ includes = Liquid::LocalFileSystem.new(ResourceLocator.resource('_includes'), '%s.html')
15
+ Liquid::Template.file_system = includes
16
+ template = Liquid::Template.parse(to_render, error_mode: :strict)
17
+ template.render!(env, { strict_variables: true })
18
+ end
19
+
20
+ def render_and_write(template_path, target_file, env)
21
+ write_file(target_file, render_liquid(template_path, env))
22
+ end
23
+
24
+ def write_file(target_file, contents)
25
+ dir = File.dirname(target_file)
26
+ FileUtils.mkdir_p(dir) unless File.directory?(dir)
27
+
28
+ index = File.new(target_file, 'w')
29
+ index&.puts contents # may be nil when stubbing
30
+ logger&.info "Written #{target_file}"
31
+ ensure
32
+ index&.close
33
+ end
34
+
35
+ def copy_resource(dir, to_root)
36
+ src = ResourceLocator.resource(dir)
37
+ dest = "#{to_root}/#{dir}"
38
+ FileUtils.copy_entry(src, dest)
39
+ end
40
+ end
41
+
42
+ # Renders the index of a project diff report.
43
+ class LiquidProjectRenderer
44
+ include PmdTester
45
+ include ProjectHasher
46
+ include LiquidRenderer
47
+
48
+ def write_project_index(project, root)
49
+ liquid_env = {
50
+ 'diff' => report_diff_to_h(project.report_diff),
51
+ 'error_diffs' => errors_to_h(project),
52
+ 'configerror_diffs' => configerrors_to_h(project),
53
+ 'project_name' => project.name
54
+ }
55
+
56
+ # Renders index.html using liquid
57
+ write_file("#{root}/index.html", render_liquid('project_diff_report.html', liquid_env))
58
+ # generate array of violations in json
59
+ write_file("#{root}/project_data.js", dump_violations_json(project))
60
+ end
61
+
62
+ def dump_violations_json(project)
63
+ h = {
64
+ 'source_link_base' => project.webview_url,
65
+ 'source_link_template' => link_template(project),
66
+ **violations_to_hash(project)
67
+ }
68
+
69
+ project_data = JSON.fast_generate(h)
70
+ "let project = #{project_data}"
71
+ end
72
+ end
73
+ end
@@ -1,82 +1,90 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'fileutils'
4
- require_relative '../cmd'
5
- require_relative '../project'
6
- require_relative '../pmd_branch_detail'
7
- require_relative '../pmd_report_detail'
4
+ require 'rufus-scheduler'
8
5
 
9
6
  module PmdTester
10
7
  # Building pmd xml reports according to a list of standard
11
8
  # projects and branch of pmd source code
12
9
  class PmdReportBuilder
13
10
  include PmdTester
14
- def initialize(branch_config, projects, local_git_repo, pmd_branch_name)
15
- @branch_config = branch_config
11
+
12
+ def initialize(projects, options, branch_config, branch_name)
16
13
  @projects = projects
17
- @local_git_repo = local_git_repo
18
- @pmd_branch_name = pmd_branch_name
14
+ @local_git_repo = options.local_git_repo
15
+ @threads = options.threads
16
+ @error_recovery = options.error_recovery
17
+ @branch_config = branch_config
18
+ @pmd_branch_name = branch_name
19
19
  @pwd = Dir.getwd
20
20
 
21
- @pmd_branch_details = PmdBranchDetail.new(pmd_branch_name)
21
+ @pmd_branch_details = PmdBranchDetail.new(@pmd_branch_name)
22
+ @project_builder = ProjectBuilder.new(@projects)
22
23
  end
23
24
 
24
- def execute_reset_cmd(type, tag)
25
- case type
26
- when 'git'
27
- reset_cmd = "git reset --hard #{tag}"
28
- when 'hg'
29
- reset_cmd = "hg up #{tag}"
30
- end
25
+ def get_pmd_binary_file
26
+ logger.info "#{@pmd_branch_name}: Start packaging PMD"
27
+ Dir.chdir(@local_git_repo) do
28
+ build_branch_sha = Cmd.execute("git rev-parse #{@pmd_branch_name}^{commit}")
31
29
 
32
- Cmd.execute(reset_cmd)
33
- end
30
+ checkout_build_branch # needs a clean working tree, otherwise fails
34
31
 
35
- def get_projects
36
- logger.info 'Cloning projects started'
32
+ raise "Wrong branch #{get_last_commit_sha}" unless build_branch_sha == get_last_commit_sha
37
33
 
38
- @projects.each do |project|
39
- logger.info "Start cloning #{project.name} repository"
40
- path = project.local_source_path
41
- clone_cmd = "#{project.type} clone #{project.connection} #{path}"
42
- if File.exist?(path)
43
- logger.warn "Skipping clone, project path #{path} already exists"
34
+ distro_path = saved_distro_path(build_branch_sha)
35
+ logger.debug "#{@pmd_branch_name}: PMD Version is #{@pmd_version} " \
36
+ "(sha=#{build_branch_sha})"
37
+ logger.debug "#{@pmd_branch_name}: distro_path=#{distro_path}"
38
+ if File.directory?(distro_path)
39
+ logger.info "#{@pmd_branch_name}: Skipping packaging - saved version exists " \
40
+ " in #{distro_path}"
44
41
  else
45
- Cmd.execute(clone_cmd)
42
+ build_pmd(into_dir: distro_path)
46
43
  end
47
44
 
48
- Dir.chdir(path) do
49
- execute_reset_cmd(project.type, project.tag)
50
- end
51
- logger.info "Cloning #{project.name} completed"
45
+ # we're still on the build branch
46
+ @pmd_branch_details.branch_last_sha = build_branch_sha
47
+ @pmd_branch_details.branch_last_message = get_last_commit_message
52
48
  end
49
+ logger.info "#{@pmd_branch_name}: Packaging PMD completed"
53
50
  end
54
51
 
55
- def get_pmd_binary_file
56
- logger.info 'Start packaging PMD'
57
- Dir.chdir(@local_git_repo) do
58
- checkout_cmd = "git checkout #{@pmd_branch_name}"
59
- Cmd.execute(checkout_cmd)
60
-
61
- @pmd_branch_details.branch_last_sha = get_last_commit_sha
62
- @pmd_branch_details.branch_last_message = get_last_commit_message
63
-
64
- package_cmd = './mvnw clean package -Dpmd.skip=true -Dmaven.test.skip=true' \
65
- ' -Dmaven.checkstyle.skip=true -Dmaven.javadoc.skip=true'
52
+ # builds pmd on currently checked out branch
53
+ def build_pmd(into_dir:)
54
+ # in CI there might have been a build performed already. In that case
55
+ # we reuse the build result, otherwise we build PMD freshly
56
+ pmd_dist_target = "pmd-dist/target/pmd-bin-#{@pmd_version}.zip"
57
+ binary_exists = File.exist?(pmd_dist_target)
58
+ logger.debug "#{@pmd_branch_name}: Does the file #{pmd_dist_target} exist? #{binary_exists} (cwd: #{Dir.getwd})"
59
+ if binary_exists
60
+ # that's a warning, because we don't know, whether this build really
61
+ # belongs to the current branch or whether it's from a previous branch.
62
+ # In CI, that's not a problem, because the workspace is always fresh.
63
+ logger.warn "#{@pmd_branch_name}: Reusing already existing #{pmd_dist_target}"
64
+ else
65
+ logger.info "#{@pmd_branch_name}: Building PMD #{@pmd_version}..."
66
+ package_cmd = './mvnw clean package' \
67
+ ' -Dmaven.test.skip=true' \
68
+ ' -Dmaven.javadoc.skip=true' \
69
+ ' -Dmaven.source.skip=true' \
70
+ ' -Dcheckstyle.skip=true' \
71
+ ' -T1C'
66
72
  Cmd.execute(package_cmd)
73
+ end
67
74
 
68
- version_cmd = "./mvnw -q -Dexec.executable=\"echo\" -Dexec.args='${project.version}' " \
69
- '--non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec'
70
- @pmd_version = Cmd.execute(version_cmd)
75
+ logger.info "#{@pmd_branch_name}: Extracting the zip"
76
+ Cmd.execute("unzip -qo #{pmd_dist_target} -d pmd-dist/target/exploded")
77
+ Cmd.execute("mv pmd-dist/target/exploded/pmd-bin-#{@pmd_version} #{into_dir}")
78
+ end
71
79
 
72
- unzip_cmd = "unzip -qo pmd-dist/target/pmd-bin-#{@pmd_version}.zip -d #{@pwd}/target"
73
- Cmd.execute(unzip_cmd)
74
- end
75
- logger.info 'Packaging PMD completed'
80
+ def determine_pmd_version
81
+ version_cmd = "./mvnw -q -Dexec.executable=\"echo\" -Dexec.args='${project.version}' " \
82
+ '--non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec'
83
+ Cmd.execute(version_cmd)
76
84
  end
77
85
 
78
86
  def get_last_commit_sha
79
- get_last_commit_sha_cmd = 'git rev-parse HEAD'
87
+ get_last_commit_sha_cmd = 'git rev-parse HEAD^{commit}'
80
88
  Cmd.execute(get_last_commit_sha_cmd)
81
89
  end
82
90
 
@@ -85,31 +93,55 @@ module PmdTester
85
93
  Cmd.execute(get_last_commit_message_cmd)
86
94
  end
87
95
 
88
- def generate_pmd_report(src_root_dir, report_file)
89
- run_path = "target/pmd-bin-#{@pmd_version}/bin/run.sh"
90
- pmd_cmd = "#{run_path} pmd -d #{src_root_dir} -f xml -R #{@branch_config} " \
91
- "-r #{report_file} -failOnViolation false"
96
+ def generate_pmd_report(project)
97
+ error_recovery_options = @error_recovery ? 'PMD_JAVA_OPTS="-Dpmd.error_recovery -ea" ' : ''
98
+ run_path = "#{saved_distro_path(@pmd_branch_details.branch_last_sha)}/bin/run.sh"
99
+ pmd_cmd = "#{error_recovery_options}" \
100
+ "#{run_path} pmd -d #{project.local_source_path} -f xml " \
101
+ "-R #{project.get_config_path(@pmd_branch_name)} " \
102
+ "-r #{project.get_pmd_report_path(@pmd_branch_name)} " \
103
+ "-failOnViolation false -t #{@threads} " \
104
+ "#{project.auxclasspath}"
92
105
  start_time = Time.now
93
- Cmd.execute(pmd_cmd)
106
+ if File.exist?(project.get_pmd_report_path(@pmd_branch_name))
107
+ logger.warn "#{@pmd_branch_name}: Skipping PMD run - report " \
108
+ "#{project.get_pmd_report_path(@pmd_branch_name)} already exists"
109
+ else
110
+ Cmd.execute(pmd_cmd)
111
+ end
94
112
  end_time = Time.now
95
113
  [end_time - start_time, end_time]
96
114
  end
97
115
 
116
+ def generate_config_for(project)
117
+ logger.debug "Generating ruleset with excludes from #{@branch_config}"
118
+ doc = Nokogiri::XML(File.read(@branch_config))
119
+ ruleset = doc.at_css('ruleset')
120
+ ruleset.add_child("\n")
121
+ project.exclude_pattern.each do |exclude_pattern|
122
+ ruleset.add_child(" <exclude-pattern>#{exclude_pattern}</exclude-pattern>\n")
123
+ end
124
+
125
+ File.open(project.get_config_path(@pmd_branch_name), 'w') do |x|
126
+ x << doc.to_s
127
+ end
128
+
129
+ logger.debug "Created file #{project.get_config_path(@pmd_branch_name)}"
130
+ end
131
+
98
132
  def generate_pmd_reports
99
133
  logger.info "Generating PMD report started -- branch #{@pmd_branch_name}"
100
- get_pmd_binary_file
101
134
 
102
135
  sum_time = 0
103
136
  @projects.each do |project|
104
- logger.info "Generating #{project.name}'s PMD report'"
105
- execution_time, end_time =
106
- generate_pmd_report(project.local_source_path,
107
- project.get_pmd_report_path(@pmd_branch_name))
137
+ progress_logger = SimpleProgressLogger.new("generating #{project.name}'s PMD report")
138
+ progress_logger.start
139
+ generate_config_for(project)
140
+ execution_time, end_time = generate_pmd_report(project)
141
+ progress_logger.stop
108
142
  sum_time += execution_time
109
143
 
110
- report_details = PmdReportDetail.new
111
- report_details.execution_time = execution_time
112
- report_details.timestamp = end_time
144
+ report_details = PmdReportDetail.new(execution_time: execution_time, timestamp: end_time)
113
145
  report_details.save(project.get_report_info_path(@pmd_branch_name))
114
146
  logger.info "#{project.name}'s PMD report was generated successfully"
115
147
  end
@@ -120,9 +152,46 @@ module PmdTester
120
152
  @pmd_branch_details
121
153
  end
122
154
 
155
+ # returns the branch details
123
156
  def build
124
- get_projects
157
+ @project_builder.clone_projects
158
+ @project_builder.build_projects
159
+ get_pmd_binary_file
125
160
  generate_pmd_reports
126
161
  end
162
+
163
+ private
164
+
165
+ def checkout_build_branch
166
+ logger.info "#{@pmd_branch_name}: Checking out the branch"
167
+ # note that this would fail if the tree is dirty
168
+ Cmd.execute("git checkout #{@pmd_branch_name}")
169
+
170
+ # determine the version
171
+ @pmd_version = determine_pmd_version
172
+
173
+ return unless wd_has_dirty_git_changes
174
+
175
+ # working dir is dirty....
176
+ # we don't allow this because we need the SHA to address the zip file
177
+ logger.error "#{@pmd_branch_name}: Won\'t build without a clean working tree, " \
178
+ 'commit your changes'
179
+ end
180
+
181
+ def work_dir
182
+ "#{@pwd}/target"
183
+ end
184
+
185
+ # path to the unzipped distribution
186
+ # e.g. <cwd>/pmd-bin-<version>-<branch>-<sha>
187
+ def saved_distro_path(build_sha)
188
+ "#{work_dir}/pmd-bin-#{@pmd_version}" \
189
+ "-#{PmdBranchDetail.branch_filename(@pmd_branch_name)}" \
190
+ "-#{build_sha}"
191
+ end
192
+
193
+ def wd_has_dirty_git_changes
194
+ !Cmd.execute('git status --porcelain').empty?
195
+ end
127
196
  end
128
197
  end