1#!/usr/bin/env python3 2# 3# Copyright (C) 2020 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17from build_log_simplifier import collapse_consecutive_blank_lines 18from build_log_simplifier import collapse_tasks_having_no_output 19from build_log_simplifier import extract_task_names 20from build_log_simplifier import remove_unmatched_exemptions 21from build_log_simplifier import suggest_missing_exemptions 22from build_log_simplifier import normalize_paths 23from build_log_simplifier import regexes_matcher 24from build_log_simplifier import remove_control_characters 25import re 26 27def fail(message): 28 print(message) 29 exit(1) 30 31def test_regexes_matcher_get_matching_regexes(): 32 print("test_regexes_matcher_get_matching_regexes") 33 # For each of the given queries, we ask a regexes_matcher to identify which regexes 34 # match them, and compare to the right answer 35 queries = ["", "a", "aa", "aaa", "b", "bb", "ab", "c", "a*"] 36 regexes = ["a", "a*", "aa*", "b", "b*", "a*b"] 37 matcher = regexes_matcher(regexes) 38 for query in queries: 39 simple_matches = [regex for regex in regexes if re.compile(regex).fullmatch(query)] 40 fast_matches = matcher.get_matching_regexes(query) 41 if simple_matches != fast_matches: 42 fail("regexes_matcher returned incorrect results for '" + query + "'. Expected = " + str(simple_matches) + ", actual = " + str(fast_matches)) 43 print("Query = '" + query + "', matching regexes = " + str(simple_matches)) 44 45def test_normalize_paths(): 46 print("test_normalize_paths") 47 lines = [ 48 "CHECKOUT=/usr/home/me/workspace", 49 "/usr/home/me/workspace/external/protobuf/somefile.h: somewarning", 50 "Building CXX object protobuf-target/CMakeFiles/libprotobuf.dir/usr/home/me/workspace/external/somefile.cc" 51 ] 52 expected_normalized = [ 53 "CHECKOUT=$CHECKOUT", 54 "$CHECKOUT/external/protobuf/somefile.h: somewarning", 55 "Building CXX object protobuf-target/CMakeFiles/libprotobuf.dir$CHECKOUT/external/somefile.cc" 56 ] 57 actual = normalize_paths(lines) 58 if expected_normalized != actual: 59 fail("test_normalize_paths returned incorrect response.\n" + 60 "Input: " + str(lines) + "\n" + 61 "Output: " + str(actual) + "\n" + 62 "Expected output: " + str(expected_normalized) 63 ) 64 65def test_regexes_matcher_index_first_matching_regex(): 66 print("test_regexes_matcher_index_first_matching_regex") 67 regexes = ["first", "double", "single", "double"] 68 matcher = regexes_matcher(regexes) 69 assert(matcher.index_first_matching_regex("first") == 0) 70 assert(matcher.index_first_matching_regex("double") == 1) 71 assert(matcher.index_first_matching_regex("single") == 2) 72 assert(matcher.index_first_matching_regex("absent") is None) 73 74def test_detect_task_names(): 75 print("test_detect_task_names") 76 lines = [ 77 "> Task :one\n", 78 "some output\n", 79 "> Task :two\n", 80 "more output\n" 81 ] 82 task_names = [":one", ":two"] 83 detected_names = extract_task_names(lines) 84 if detected_names != task_names: 85 fail("extract_task_names returned incorrect response\n" + 86 "Input : " + str(lines) + "\n" + 87 "Output : " + str(detected_names) + "\n" + 88 "Expected: " + str(task_names) 89 ) 90 91def test_remove_unmatched_exemptions(): 92 print("test_remove_unmatched_exemptions") 93 lines = [ 94 "task two message one", 95 "task four message one", 96 ] 97 98 current_config = [ 99 "# > Task :one", 100 "task one message one", 101 "# TODO(bug): remove this", 102 "# > Task :two", 103 "task two message one", 104 "# TODO(bug): remove this too", 105 "# > Task :three", 106 "task three message one", 107 "# > Task :four", 108 "task four message one", 109 "# TODO: maybe remove this too?", 110 "# > Task :five", 111 "task five message one" 112 ] 113 114 expected_config = [ 115 "# TODO(bug): remove this", 116 "# > Task :two", 117 "task two message one", 118 "# > Task :four", 119 "task four message one", 120 ] 121 122 actual_updated_config = remove_unmatched_exemptions(lines, current_config) 123 if actual_updated_config != expected_config: 124 fail("test_remove_unmatched_exemptions gave incorrect response.\n\n" + 125 "Input log : " + str(lines) + "\n\n" + 126 "Input config : " + str(current_config) + "\n\n" + 127 "Expected output config: " + str(expected_config) + "\n\n" + 128 "Actual output config : " + str(actual_updated_config)) 129 130def test_suggest_missing_exemptions(): 131 print("test_suggest_missing_exemptions") 132 lines = [ 133 "> Task :one", 134 "task one message one", 135 "task one message two", 136 "> Task :two", 137 "task two message one", 138 "duplicate line", 139 "> Task :three", 140 "task three message one", 141 "duplicate line" 142 ] 143 144 expect_config = [ 145 "# > Task :one", 146 "task one message one", 147 "task one message two", 148 "# > Task :two", 149 "task two message one", 150 "duplicate line", 151 "# > Task :three", 152 "task three message one" 153 ] 154 155 # generate config starting with nothing 156 validate_suggested_exemptions(lines, [], expect_config) 157 158 # remove one line from config, regenerate config, line should return 159 config2 = expect_config[:1] + expect_config[2:] 160 validate_suggested_exemptions(lines, config2, expect_config) 161 162 # if there is an existing config with the tasks in the other order, the tasks should stay in that order 163 # and the new line should be inserted after the previous matching line 164 config3 = [ 165 "# > Task :two", 166 "task two message one", 167 "duplicate line", 168 "# > Task :one", 169 "task one message two", 170 "# > Task :three", 171 "task three message one" 172 ] 173 expect_config3 = [ 174 "# > Task :two", 175 "task two message one", 176 "duplicate line", 177 "# > Task :one", 178 "task one message one", 179 "task one message two", 180 "# > Task :three", 181 "task three message one" 182 ] 183 validate_suggested_exemptions(lines, config3, expect_config3) 184 185 # also validate that "> Configure project" gets ignored too 186 config4 = [ 187 "# > Configure project a", 188 "some warning" 189 ] 190 lines4 = [ 191 "> Configure project b", 192 "some warning" 193 ] 194 expect_config4 = config4 195 validate_suggested_exemptions(lines4, config4, expect_config4) 196 197def test_collapse_tasks_having_no_output(): 198 print("test_collapse_tasks_having_no_output") 199 lines = [ 200 "> Task :no-output1", 201 "> Task :some-output1", 202 "output1", 203 "> Task :empty-output", 204 "", 205 "> Task :blanks-around-output", 206 "", 207 "output inside blanks", 208 "", 209 "> Task :no-output2", 210 "> Task :no-output3", 211 "FAILURE: Build failed with an exception.\n" 212 ] 213 expected = [ 214 "> Task :some-output1", 215 "output1", 216 "> Task :blanks-around-output", 217 "", 218 "output inside blanks", 219 "" 220 ] 221 actual = collapse_tasks_having_no_output(lines) 222 if (actual != expected): 223 fail("collapse_tasks_having_no_output gave incorrect error.\n" + 224 "Expected: " + str(expected) + "\n" + 225 "Actual = " + str(actual)) 226 227def test_collapse_consecutive_blank_lines(): 228 print("test_collapse_consecutive_blank_lines") 229 lines = [ 230 "", 231 "> Task :a", 232 "", 233 " ", 234 "\n\n", 235 "> Task :b", 236 " ", 237 "" 238 ] 239 expected_collapsed = [ 240 "> Task :a", 241 "", 242 "> Task :b", 243 " " 244 ] 245 actual_collapsed = collapse_consecutive_blank_lines(lines) 246 if actual_collapsed != expected_collapsed: 247 fail("collapse_consecutive_blank_lines returned incorrect response.\n" 248 "Input: " + lines + "\n" + 249 "Output: " + actual_collapsed + "\n" + 250 "Expected output: " + expected_collapsed 251 ) 252 253def validate_suggested_exemptions(lines, config, expected_config): 254 suggested_config = suggest_missing_exemptions(lines, config) 255 if suggested_config != expected_config: 256 fail("suggest_missing_exemptions incorrect response.\n" + 257 "Lines: " + str(lines) + ",\n" + 258 "config: " + str(config) + ",\n" + 259 "expected suggestion: " + str(expected_config) + ",\n" 260 "actual suggestion : " + str(suggested_config)) 261 262def test_remove_control_characters(): 263 print("test_remove_control_characters") 264 given = [ 265 # a line starting with several color codes in it 266 "[1msrc/main/java/androidx/arch/core/internal/FastSafeIterableMap.java:39: [33mwarning: [0mMethod androidx.arch.core.internal.FastSafeIterableMap.get(K) references hidden type androidx.arch.core.internal.SafeIterableMap.Entry<K,V>. [HiddenTypeParameter]", 267 # a line with a variety of characters, none of which are color codes 268 "space tab\tCAPITAL underscore_ slash/ colon: number 1 newline\n", 269 ] 270 expected = [ 271 "src/main/java/androidx/arch/core/internal/FastSafeIterableMap.java:39: warning: Method androidx.arch.core.internal.FastSafeIterableMap.get(K) references hidden type androidx.arch.core.internal.SafeIterableMap.Entry<K,V>. [HiddenTypeParameter]", 272 "space tab\tCAPITAL underscore_ slash/ colon: number 1 newline\n", 273 ] 274 actual = [remove_control_characters(line) for line in given] 275 if actual != expected: 276 fail("remove_control_charactres gave incorrect response.\n\n" + 277 "Input : " + str(given) + ".\n\n" + 278 "Expected output: " + str(expected) + ".\n\n" + 279 "Actual output : " + str(actual) + ".") 280 281 282def main(): 283 test_collapse_consecutive_blank_lines() 284 test_collapse_tasks_having_no_output() 285 test_detect_task_names() 286 test_suggest_missing_exemptions() 287 test_normalize_paths() 288 test_regexes_matcher_get_matching_regexes() 289 test_regexes_matcher_index_first_matching_regex() 290 test_remove_control_characters() 291 test_remove_unmatched_exemptions() 292 293if __name__ == "__main__": 294 main() 295