@@ -9,73 +9,70 @@ module PmdTester
99 # Attention: we only consider java rulesets now.
1010 class RuleSetBuilder
1111 include PmdTester
12- ALL_CATEGORIES = Set [ 'bestpractices.xml' , 'codestyle.xml' , 'design.xml' , 'documentation.xml' ,
13- 'errorprone.xml' , 'multithreading.xml' , 'performance.xml' ,
14- 'security.xml' ] . freeze
15- PATH_TO_PMD_JAVA_BASED_RULES =
16- 'pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule'
17- PATH_TO_PMD_XPATH_BASED_RULES = 'pmd-java/src/main/resources/category/java'
18- PATH_TO_ALL_JAVA_RULES =
19- ResourceLocator . locate ( 'config/all-java.xml' )
2012 PATH_TO_DYNAMIC_CONFIG = 'target/dynamic-config.xml'
21- NO_JAVA_RULES_CHANGED_MESSAGE = 'No java rules have been changed!'
13+ NO_RULES_CHANGED_MESSAGE = 'No regression tested rules have been changed!'
2214
2315 def initialize ( options )
2416 @options = options
2517 end
2618
27- def build
28- filenames = diff_filenames
29- rule_refs = get_rule_refs ( filenames )
30- output_filter_set ( rule_refs )
31- build_config_file ( rule_refs )
32- logger . debug "Dynamic configuration: #{ [ rule_refs ] } "
33- rule_refs
19+ #
20+ # Creates a dynamic ruleset based on the changed sources.
21+ # Returns true, when rules are affected by the changed sources.
22+ # Returns false, when no rules are affected and regression tester can be skipped.
23+ #
24+ def build?
25+ languages = determine_languages
26+ filenames = diff_filenames ( languages )
27+ run_required , rule_refs = get_rule_refs ( filenames )
28+ if run_required
29+ output_filter_set ( rule_refs )
30+ build_config_file ( rule_refs )
31+ logger . debug "Dynamic configuration: #{ [ rule_refs ] } "
32+ else
33+ logger . info NO_RULES_CHANGED_MESSAGE
34+ end
35+ run_required
3436 end
3537
3638 def calculate_filter_set
37- output_filter_set ( ALL_CATEGORIES )
39+ output_filter_set ( [ ] )
3840 end
3941
4042 def output_filter_set ( rule_refs )
41- if rule_refs == ALL_CATEGORIES
42- if @options . mode == Options ::ONLINE
43+ if rule_refs . empty?
44+ if @options . mode == Options ::ONLINE && @options . filter_with_patch_config
4345 @options . filter_set = Set [ ]
4446 doc = File . open ( @options . patch_config ) { |f | Nokogiri ::XML ( f ) }
4547 rules = doc . css ( 'ruleset rule' )
4648 rules . each do |r |
4749 ref = r . attributes [ 'ref' ] . content
48- ref . delete_prefix! ( 'category/java/ ' )
50+ ref . delete_prefix! ( 'category/' )
4951 @options . filter_set . add ( ref )
5052 end
5153
52- logger . debug "Using filter based on patch config #{ @options . patch_config } : " \
54+ logger . info "Using filter based on patch config #{ @options . patch_config } : " \
5355 "#{ @options . filter_set } "
5456 else
55- # if `rule_refs` contains all categories , then no need to filter the baseline
56- logger . debug 'No filter when comparing patch to baseline'
57+ # if `rule_refs` is empty , then no filter can be used when comparing to the baseline
58+ logger . info 'No filter when comparing patch to baseline'
5759 @options . filter_set = nil
5860 end
5961 else
60- logger . debug "Filter is now #{ rule_refs } "
62+ logger . info "Filter is now #{ rule_refs } "
6163 @options . filter_set = rule_refs
6264 end
6365 end
6466
65- def diff_filenames
66- filenames = nil
67- Dir . chdir ( @options . local_git_repo ) do
68- base = @options . base_branch
69- patch = @options . patch_branch
70- # We only need to support git here, since PMD's repo is using git.
71- diff_cmd = "git diff --name-only #{ base } ..#{ patch } -- pmd-core/src/main pmd-java/src/main"
72- filenames = Cmd . execute ( diff_cmd )
73- end
74- filenames . split ( "\n " )
75- end
76-
67+ #
68+ # Determines the rules or category rulesets, that are potentially affected by the change.
69+ # Returns an empty set, if all rules are affected and there is no
70+ # filtering possible or if no rules are affected.
71+ # Whether to run the regression test is returned as an additional boolean flag.
72+ #
7773 def get_rule_refs ( filenames )
78- categories , rules = determine_categories_rules ( filenames )
74+ run_required , categories , rules = determine_categories_rules ( filenames )
75+ logger . debug "Regression test required: #{ run_required } "
7976 logger . debug "Categories: #{ categories } "
8077 logger . debug "Rules: #{ rules } "
8178
@@ -87,48 +84,11 @@ def get_rule_refs(filenames)
8784 refs = Set [ ]
8885 refs . merge ( categories )
8986 refs . merge ( rules )
90- refs
91- end
92-
93- def determine_categories_rules ( filenames )
94- categories = Set [ ]
95- rules = Set [ ]
96- filenames . each do |filename |
97- match_data = check_single_filename ( filename )
98-
99- unless match_data . nil?
100- if match_data . size == 2
101- categories . add ( "#{ match_data [ 1 ] } .xml" )
102- else
103- rules . add ( "#{ match_data [ 1 ] } .xml/#{ match_data [ 2 ] } " )
104- end
105- end
106-
107- next unless match_data . nil?
108-
109- logger . debug "Change doesn't match specific rule/category - enable all rules"
110- categories = ALL_CATEGORIES
111- rules . clear
112- break
113- end
114- [ categories , rules ]
115- end
116-
117- def check_single_filename ( filename )
118- logger . debug "Checking #{ filename } "
119- match_data = %r{#{ PATH_TO_PMD_JAVA_BASED_RULES } /([^/]+)/([^/]+)Rule.java} . match ( filename )
120- match_data = %r{#{ PATH_TO_PMD_XPATH_BASED_RULES } /([^/]+).xml} . match ( filename ) if match_data . nil?
121- logger . debug "Matches: #{ match_data . inspect } "
122- match_data
87+ [ run_required , refs ]
12388 end
12489
12590 def build_config_file ( rule_refs )
12691 if rule_refs . empty?
127- logger . info NO_JAVA_RULES_CHANGED_MESSAGE
128- return
129- end
130-
131- if rule_refs == ALL_CATEGORIES
13292 logger . debug 'All rules are used. Not generating a dynamic ruleset.'
13393 logger . debug "Using the configured/default ruleset base_config=#{ @options . base_config } " \
13494 "patch_config=#{ @options . patch_config } "
@@ -147,7 +107,7 @@ def write_dynamic_file(rule_refs)
147107 'name' => 'Dynamic PmdTester Ruleset' ) do
148108 xml . description 'The ruleset generated by PmdTester dynamically'
149109 rule_refs . each do |entry |
150- xml . rule ( 'ref' => "category/java/ #{ entry } " )
110+ xml . rule ( 'ref' => "category/#{ entry } " )
151111 end
152112 end
153113 end
@@ -158,5 +118,82 @@ def write_dynamic_file(rule_refs)
158118 @options . base_config = PATH_TO_DYNAMIC_CONFIG
159119 @options . patch_config = PATH_TO_DYNAMIC_CONFIG
160120 end
121+
122+ private
123+
124+ def determine_categories_rules ( filenames )
125+ regression_test_required = false
126+ categories = Set [ ]
127+ rules = Set [ ]
128+ filenames . each do |filename |
129+ matched = check_single_filename ( filename , categories , rules )
130+ regression_test_required = true if matched
131+
132+ next if matched
133+
134+ logger . info "Change in file #{ filename } doesn't match specific rule/category - enable all rules"
135+ regression_test_required = true
136+ categories . clear
137+ rules . clear
138+ break
139+ end
140+ [ regression_test_required , categories , rules ]
141+ end
142+
143+ def check_single_filename ( filename , categories , rules )
144+ logger . debug "Checking #{ filename } "
145+
146+ # matches Java-based rule implementations
147+ match_data = %r{.+/src/main/java/.+/lang/([^/]+)/rule/([^/]+)/([^/]+)Rule.java} . match ( filename )
148+ unless match_data . nil?
149+ logger . debug "Matches: #{ match_data . inspect } "
150+ rules . add ( "#{ match_data [ 1 ] } /#{ match_data [ 2 ] } .xml/#{ match_data [ 3 ] } " )
151+ return true
152+ end
153+
154+ # matches xpath rules
155+ match_data = %r{.+/src/main/resources/category/([^/]+)/([^/]+).xml} . match ( filename )
156+ unless match_data . nil?
157+ logger . debug "Matches: #{ match_data . inspect } "
158+ categories . add ( "#{ match_data [ 1 ] } /#{ match_data [ 2 ] } .xml" )
159+ return true
160+ end
161+
162+ false
163+ end
164+
165+ def diff_filenames ( languages )
166+ filenames = nil
167+ Dir . chdir ( @options . local_git_repo ) do
168+ base = @options . base_branch
169+ patch = @options . patch_branch
170+
171+ filepath_filter = ''
172+ unless languages . empty?
173+ filepath_filter = '-- pmd-core/src/main'
174+ languages . each { |l | filepath_filter = "#{ filepath_filter } pmd-#{ l } /src/main" }
175+ end
176+
177+ # We only need to support git here, since PMD's repo is using git.
178+ diff_cmd = "git diff --name-only #{ base } ..#{ patch } #{ filepath_filter } "
179+ filenames = Cmd . execute ( diff_cmd )
180+ end
181+ filenames . split ( "\n " )
182+ end
183+
184+ #
185+ # Determines all languages, that are part of the regression test.
186+ # This is based on the configured rules/rulesets.
187+ #
188+ def determine_languages
189+ languages = Set [ ]
190+ doc = File . open ( @options . patch_config ) { |f | Nokogiri ::XML ( f ) }
191+ rules = doc . css ( 'ruleset rule' )
192+ rules . each do |r |
193+ ref = r . attributes [ 'ref' ] . content
194+ languages . add ( ref . split ( '/' ) [ 1 ] )
195+ end
196+ languages
197+ end
161198 end
162199end
0 commit comments