1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package util.build; 18 19 import java.io.BufferedWriter; 20 import java.io.File; 21 import java.io.FileOutputStream; 22 import java.io.FileReader; 23 import java.io.IOException; 24 import java.io.OutputStreamWriter; 25 import java.io.StringReader; 26 import java.net.URL; 27 import java.nio.charset.StandardCharsets; 28 import java.util.ArrayList; 29 import java.util.List; 30 import java.util.Map.Entry; 31 import java.util.Scanner; 32 import java.util.regex.MatchResult; 33 import java.util.regex.Matcher; 34 import java.util.regex.Pattern; 35 36 /** 37 * Helper base class for code generators. 38 */ 39 public abstract class BuildUtilBase { 40 41 public static boolean DEBUG = true; 42 43 public static class MethodData { 44 String methodBody, constraint, title; 45 } 46 47 public interface TestHandler { handleTest(String fqcn, List<String> methods)48 public void handleTest(String fqcn, List<String> methods); 49 } 50 run(TestHandler handler)51 public void run(TestHandler handler) { 52 System.out.println("Collecting all junit tests..."); 53 JUnitTestCollector tests = new JUnitTestCollector(getClass().getClassLoader()); 54 55 handleTests(tests, handler); 56 } 57 handleTests(JUnitTestCollector tests, TestHandler handler)58 protected void handleTests(JUnitTestCollector tests, TestHandler handler) { 59 System.out.println("collected " + tests.testMethodsCnt + " test methods in " + 60 tests.testClassCnt + " junit test classes"); 61 62 for (Entry<String, List<String>> entry : tests.map.entrySet()) { 63 handler.handleTest(entry.getKey(), entry.getValue()); 64 } 65 } 66 readURL(URL in)67 private static String readURL(URL in) { 68 // Use common scanner idiom to read a complete InputStream into a string. 69 try (Scanner scanner = new Scanner(in.openStream(), StandardCharsets.UTF_8.toString())) { 70 scanner.useDelimiter("\\A"); // This delimits by "start of content," of which there is 71 // only one. 72 return scanner.hasNext() ? scanner.next() : null; 73 } catch (IOException e) { 74 throw new RuntimeException(e); 75 } 76 } 77 parseTestMethod(String pname, String classOnlyName, String method)78 protected MethodData parseTestMethod(String pname, String classOnlyName, 79 String method) { 80 String searchPath = "src/" + pname.replaceAll("\\.", "/") + "/" + classOnlyName + ".java"; 81 String content; 82 { 83 URL resource = getClass().getClassLoader().getResource(searchPath); 84 if (resource == null) { 85 throw new RuntimeException("Could not find " + searchPath); 86 } 87 content = readURL(resource); 88 if (content == null) { 89 throw new RuntimeException("Could not retrieve content for " + searchPath); 90 } 91 } 92 93 final String methodPattern = "public\\s+void\\s+" + method + "[^\\{]+\\{"; 94 95 int methodSkip; 96 try (Scanner scanner = new Scanner(content)) { 97 String token = scanner.findWithinHorizon(methodPattern, content.length()); 98 if (token == null) { 99 throw new RuntimeException("cannot find method source of 'public void " + method + 100 "' in file '" + searchPath + "'"); 101 } 102 103 MatchResult result = scanner.match(); 104 result.start(); 105 methodSkip = result.end(); 106 } 107 108 StringBuilder builder = new StringBuilder(); 109 110 try { 111 StringReader reader = new StringReader(content); 112 reader.skip(methodSkip); 113 114 int readResult; 115 int blocks = 1; 116 while ((readResult = reader.read()) != -1 && blocks > 0) { 117 char currentChar = (char) readResult; 118 switch (currentChar) { 119 case '}': { 120 blocks--; 121 builder.append(currentChar); 122 break; 123 } 124 case '{': { 125 blocks++; 126 builder.append(currentChar); 127 break; 128 } 129 default: { 130 builder.append(currentChar); 131 break; 132 } 133 } 134 } 135 if (reader != null) { 136 reader.close(); 137 } 138 } catch (Exception e) { 139 throw new RuntimeException("failed to parse", e); 140 } 141 142 // find the @title/@constraint in javadoc comment for this method 143 // using platform's default charset 144 145 // System.out.println("grepping javadoc found for method " + method + 146 // " in " + pname + "," + classOnlyName); 147 String commentPattern = "/\\*\\*([^{]*)\\*/\\s*" + methodPattern; 148 Pattern p = Pattern.compile(commentPattern, Pattern.DOTALL); 149 Matcher m = p.matcher(content); 150 String title = null, constraint = null; 151 if (m.find()) { 152 String res = m.group(1); 153 // System.out.println("res: " + res); 154 // now grep @title and @constraint 155 Matcher titleM = Pattern.compile("@title (.*)", Pattern.DOTALL) 156 .matcher(res); 157 if (titleM.find()) { 158 title = titleM.group(1).replaceAll("\\n \\*", ""); 159 title = title.replaceAll("\\n", " "); 160 title = title.trim(); 161 // System.out.println("title: " + title); 162 } else { 163 System.err.println("warning: no @title found for method " + method + " in " + pname + 164 "," + classOnlyName); 165 } 166 // constraint can be one line only 167 Matcher constraintM = Pattern.compile("@constraint (.*)").matcher( 168 res); 169 if (constraintM.find()) { 170 constraint = constraintM.group(1); 171 constraint = constraint.trim(); 172 // System.out.println("constraint: " + constraint); 173 } else if (method.contains("VFE")) { 174 System.err 175 .println("warning: no @constraint for for a VFE method:" + method + " in " + 176 pname + "," + classOnlyName); 177 } 178 } else { 179 System.err.println("warning: no javadoc found for method " + method + " in " + pname + 180 "," + classOnlyName); 181 } 182 MethodData md = new MethodData(); 183 md.methodBody = builder.toString(); 184 md.constraint = constraint; 185 md.title = title; 186 return md; 187 } 188 189 /** 190 * @param pName 191 * @param classOnlyName 192 * @param methodSource 193 * @return testclass names 194 */ parseTestClassName(String pName, String classOnlyName, String methodSource)195 protected static List<String> parseTestClassName(String pName, String classOnlyName, 196 String methodSource) { 197 List<String> entries = new ArrayList<String>(2); 198 String opcodeName = classOnlyName.substring(5); 199 200 try (Scanner scanner = new Scanner(methodSource)) { 201 String[] patterns = new String[] { "new\\s(T_" + opcodeName + "\\w*)", 202 "(T_" + opcodeName + "\\w*)", "new\\s(T\\w*)" }; 203 204 String token = null; 205 for (String pattern : patterns) { 206 token = scanner.findWithinHorizon(pattern, methodSource.length()); 207 if (token != null) { 208 break; 209 } 210 } 211 212 if (token == null) { 213 System.err.println("warning: failed to find dependent test class name: " + pName 214 + ", " + classOnlyName + " in methodSource:\n" + methodSource); 215 return entries; 216 } 217 218 MatchResult result = scanner.match(); 219 220 entries.add((pName + ".d." + result.group(1)).trim()); 221 222 // search additional @uses directives 223 Pattern p = Pattern.compile("@uses\\s+(.*)\\s+", Pattern.MULTILINE); 224 Matcher m = p.matcher(methodSource); 225 while (m.find()) { 226 String res = m.group(1); 227 entries.add(0, res.trim()); 228 } 229 230 // search for " load(\"...\" " and add as dependency 231 Pattern loadPattern = Pattern.compile("load\\(\"([^\"]*)\"", Pattern.MULTILINE); 232 Matcher loadMatcher = loadPattern.matcher(methodSource); 233 while (loadMatcher.find()) { 234 String res = loadMatcher.group(1); 235 entries.add(res.trim()); 236 } 237 238 // search for " loadAndRun(\"...\" " and add as dependency 239 Pattern loadAndRunPattern = Pattern.compile("loadAndRun\\(\"([^\"]*)\"", 240 Pattern.MULTILINE); 241 Matcher loadAndRunMatcher = loadAndRunPattern.matcher(methodSource); 242 while (loadAndRunMatcher.find()) { 243 String res = loadAndRunMatcher.group(1); 244 entries.add(res.trim()); 245 } 246 247 // lines with the form @uses 248 // dot.junit.opcodes.add_double.jm.T_add_double_2 249 // one dependency per one @uses 250 // TODO 251 252 return entries; 253 } 254 } 255 writeToFileMkdir(File file, String content)256 public static void writeToFileMkdir(File file, String content) { 257 File parent = file.getParentFile(); 258 if (!parent.exists() && !parent.mkdirs()) { 259 throw new RuntimeException("failed to create directory: " + parent.getAbsolutePath()); 260 } 261 writeToFile(file, content); 262 } 263 writeToFile(File file, String content)264 public static void writeToFile(File file, String content) { 265 try { 266 if (file.exists() && file.length() == content.length()) { 267 FileReader reader = new FileReader(file); 268 char[] charContents = new char[(int) file.length()]; 269 reader.read(charContents); 270 reader.close(); 271 String contents = new String(charContents); 272 if (contents.equals(content)) { 273 // System.out.println("skipping identical: " 274 // + file.getAbsolutePath()); 275 return; 276 } 277 } 278 279 //System.out.println("writing file " + file.getAbsolutePath()); 280 281 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter( 282 new FileOutputStream(file), "utf-8")); 283 bw.write(content); 284 bw.close(); 285 } catch (Exception e) { 286 throw new RuntimeException("error while writing to file: " + e.getClass().getName() + 287 ", msg:" + e.getMessage()); 288 } 289 } 290 291 } 292