1 /******************************************************************************* 2 * Copyright (c) 2014 Moritz Eysholdt and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * Moritz Eysholdt <moritz.eysholdt@itemis.de> - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.jdt.internal.junit4.runner; 15 16 import java.util.ArrayList; 17 import java.util.List; 18 import java.util.regex.Matcher; 19 import java.util.regex.Pattern; 20 21 import org.junit.runner.Description; 22 import org.junit.runners.Parameterized; 23 24 /** 25 * This class matches JUnit's {@link Description} against a string. 26 * 27 * See {@link #create(Class, String)} for details. 28 */ 29 public abstract class DescriptionMatcher { 30 31 private static class CompositeMatcher extends DescriptionMatcher { 32 private final List<DescriptionMatcher> fMatchers; 33 CompositeMatcher(List<DescriptionMatcher> matchers)34 public CompositeMatcher(List<DescriptionMatcher> matchers) { 35 fMatchers= matchers; 36 } 37 38 @Override matches(Description description)39 public boolean matches(Description description) { 40 for (DescriptionMatcher matcher : fMatchers) 41 if (matcher.matches(description)) 42 return true; 43 return false; 44 } 45 46 @Override toString()47 public String toString() { 48 return fMatchers.toString(); 49 } 50 } 51 52 private static class ExactMatcher extends DescriptionMatcher { 53 private final String fDisplayName; 54 ExactMatcher(String className)55 public ExactMatcher(String className) { 56 fDisplayName= className; 57 } 58 ExactMatcher(String className, String testName)59 public ExactMatcher(String className, String testName) { 60 // see org.junit.runner.Description.formatDisplayName(String, String) 61 fDisplayName= String.format("%s(%s)", testName, className); //$NON-NLS-1$ 62 } 63 64 @Override matches(Description description)65 public boolean matches(Description description) { 66 return fDisplayName.equals(description.getDisplayName()); 67 } 68 69 @Override toString()70 public String toString() { 71 return String.format("{%s:fDisplayName=%s]", getClass().getSimpleName(), fDisplayName); //$NON-NLS-1$ 72 } 73 } 74 75 /** 76 * This class extracts the leading chars from {@link Description#getMethodName()} which are a 77 * valid Java identifier. If this identifier equals this class' identifier, the Description is 78 * matched. 79 * 80 * Please be aware that {@link Description#getMethodName()} can be any value a JUnit runner has 81 * computed. It is not necessarily a valid method name. For example, {@link Parameterized} uses 82 * the format 'methodname[i]', with 'i' being the row index in the table of test data. 83 */ 84 private static class LeadingIdentifierMatcher extends DescriptionMatcher { 85 86 private final String fClassName; 87 88 private final String fLeadingIdentifier; 89 LeadingIdentifierMatcher(String className, String leadingIdentifier)90 public LeadingIdentifierMatcher(String className, String leadingIdentifier) { 91 super(); 92 fClassName= className; 93 fLeadingIdentifier= leadingIdentifier; 94 } 95 96 @Override matches(Description description)97 public boolean matches(Description description) { 98 String className= description.getClassName(); 99 if (fClassName.equals(className)) { 100 String methodName= description.getMethodName(); 101 if (methodName != null) { 102 return fLeadingIdentifier.equals(extractLeadingIdentifier(methodName)); 103 } 104 } 105 return false; 106 } 107 108 @Override toString()109 public String toString() { 110 return String.format("{%s:fClassName=%s,fLeadingIdentifier=%s]", getClass().getSimpleName(), fClassName, fLeadingIdentifier); //$NON-NLS-1$ 111 } 112 113 } 114 115 /** 116 * Creates a matcher object that can decide for {@link Description}s whether they match the 117 * supplied 'matchString' or not. 118 * 119 * Several strategies for matching are applied: 120 * <ul> 121 * <li>if 'matchString' equals {@link Description#getDisplayName()}, it's always a match</li> 122 * <li>if 'matchString' has the format foo(bar), which is JUnit's format, it tries to match 123 * methods base on leading identifiers. See {@link LeadingIdentifierMatcher}.</li> 124 * <li>if 'matchString' does not have the format foo(bar), it also tries to match Descriptions 125 * that equal clazz(matchString), with 'clazz' being this method's parameter. Furthermore, if 126 * 'matchString' is a Java identifier, it matches Descriptions via leading identifiers. See 127 * {@link LeadingIdentifierMatcher}</li> 128 * </ul> 129 * 130 * @param clazz A class that is used when 'matchString' does not have the format 131 * methodName(className). 132 * 133 * @param matchString A string to match JUnit's {@link Description}s against. 134 * 135 * @return A matcher object. 136 */ create(Class<?> clazz, String matchString)137 public static DescriptionMatcher create(Class<?> clazz, String matchString) { 138 String className= clazz.getName(); 139 List<DescriptionMatcher> matchers= new ArrayList<DescriptionMatcher>(); 140 matchers.add(new ExactMatcher(matchString)); 141 Matcher parsed= METHOD_AND_CLASS_NAME_PATTERN.matcher(matchString); 142 if (parsed.matches()) { 143 String testName= parsed.group(1); 144 if (testName.equals(extractLeadingIdentifier(testName))) 145 matchers.add(new LeadingIdentifierMatcher(className, testName)); 146 } else { 147 if (!className.equals(matchString)) { 148 matchers.add(new ExactMatcher(className, matchString)); 149 if (matchString.equals(extractLeadingIdentifier(matchString))) 150 matchers.add(new LeadingIdentifierMatcher(className, matchString)); 151 } 152 } 153 return new CompositeMatcher(matchers); 154 } 155 extractLeadingIdentifier(String string)156 private static String extractLeadingIdentifier(String string) { 157 if (string.length() == 0) 158 return null; 159 if (!Character.isJavaIdentifierStart(string.charAt(0))) 160 return null; 161 for (int i= 1; i < string.length(); i++) { 162 if (!Character.isJavaIdentifierPart(string.charAt(i))) { 163 return string.substring(0, i); 164 } 165 } 166 return string; 167 } 168 169 // see org.junit.runner.Description.METHOD_AND_CLASS_NAME_PATTERN 170 private static final Pattern METHOD_AND_CLASS_NAME_PATTERN= Pattern.compile("(.*)\\((.*)\\)"); //$NON-NLS-1$ 171 matches(Description description)172 public abstract boolean matches(Description description); 173 } 174