Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring #2821

Merged
merged 1 commit into from Nov 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
239 changes: 46 additions & 193 deletions testng-core-api/src/main/java/org/testng/internal/PackageUtils.java
Expand Up @@ -4,19 +4,22 @@

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.testng.collections.Lists;
import org.testng.internal.protocols.Input;
import org.testng.internal.protocols.Processor;
import org.testng.internal.protocols.UnhandledIOException;

/**
* Utility class that finds all the classes in a given package.
Expand All @@ -26,7 +29,6 @@
* @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
*/
public class PackageUtils {
private static final String PACKAGE_UTILS = PackageUtils.class.getSimpleName();
private static String[] testClassPaths;

/** The additional class loaders to find classes in. */
Expand All @@ -45,101 +47,36 @@ private PackageUtils() {
*/
public static String[] findClassesInPackage(
String packageName, List<String> included, List<String> excluded) throws IOException {
String packageOnly = packageName;
boolean recursive = false;
if (packageName.endsWith(".*")) {
packageOnly = packageName.substring(0, packageName.lastIndexOf(".*"));
recursive = true;
String packageNameWithoutWildCards = packageName;
boolean recursive = packageName.endsWith(".*");
if (recursive) {
packageNameWithoutWildCards = packageName.substring(0, packageName.lastIndexOf(".*"));
}

List<String> vResult = Lists.newArrayList();
String packageDirName = packageOnly.replace('.', '/') + (packageOnly.length() > 0 ? "/" : "");
String packageDirName =
packageNameWithoutWildCards.replace('.', '/')
+ (packageNameWithoutWildCards.length() > 0 ? "/" : "");

Input input =
Input.Builder.newBuilder()
.forPackageWithoutWildCards(packageNameWithoutWildCards)
.withRecursive(recursive)
.include(included)
.exclude(excluded)
.withPackageName(packageName)
.forPackageDirectory(packageDirName)
.build();

List<URL> dirs = Lists.newArrayList();
// go through additional class loaders
List<ClassLoader> allClassLoaders =
ClassHelper.appendContextualClassLoaders(Lists.newArrayList(classLoaders));

for (ClassLoader classLoader : allClassLoaders) {
if (null == classLoader) {
continue;
}
Enumeration<URL> dirEnumeration = classLoader.getResources(packageDirName);
while (dirEnumeration.hasMoreElements()) {
URL dir = dirEnumeration.nextElement();
dirs.add(dir);
}
}

for (URL url : dirs) {
String protocol = url.getProtocol();
if (!matchTestClasspath(url, packageDirName, recursive)) {
continue;
}

if ("file".equals(protocol)) {
findClassesInDirPackage(
packageOnly,
included,
excluded,
URLDecoder.decode(url.getFile(), UTF_8),
recursive,
vResult);
} else if ("jar".equals(protocol)) {
JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
if (name.startsWith("module-info") || name.startsWith("META-INF")) {
continue;
}
if (name.charAt(0) == '/') {
name = name.substring(1);
}
if (name.startsWith(packageDirName)) {
int idx = name.lastIndexOf('/');
if (idx != -1) {
packageName = name.substring(0, idx).replace('/', '.');
}

if (recursive || packageName.equals(packageOnly)) {
// it's not inside a deeper dir
Utils.log(PACKAGE_UTILS, 4, "Package name is " + packageName);
if (name.endsWith(".class") && !entry.isDirectory()) {
String className = name.substring(packageName.length() + 1, name.length() - 6);
Utils.log(
PACKAGE_UTILS,
4,
"Found class " + className + ", seeing it if it's included or excluded");
includeOrExcludeClass(packageName, className, included, excluded, vResult);
}
}
}
}
} else if ("bundleresource".equals(protocol)) {
try {
Class<?>[] params = {};
// BundleURLConnection
URLConnection connection = url.openConnection();
Method thisMethod =
url.openConnection().getClass().getDeclaredMethod("getFileURL", params);
Object[] paramsObj = {};
URL fileUrl = (URL) thisMethod.invoke(connection, paramsObj);
findClassesInDirPackage(
packageOnly,
included,
excluded,
URLDecoder.decode(fileUrl.getFile(), UTF_8),
recursive,
vResult);
} catch (Exception ex) {
// ignore - probably not an Eclipse OSGi bundle
}
}
}

return vResult.toArray(new String[0]);
return allClassLoaders.stream()
.filter(Objects::nonNull)
.flatMap(asURLs(packageDirName))
.filter(url -> matchTestClasspath(url, packageDirName, recursive))
.flatMap(url -> Processor.newInstance(url.getProtocol()).process(input, url).stream())
.toArray(String[]::new);
}

private static String[] getTestClasspath() {
Expand Down Expand Up @@ -174,14 +111,25 @@ private static String[] getTestClasspath() {
return testClassPaths;
}

private static Function<ClassLoader, Stream<URL>> asURLs(String packageDir) {
return cl -> {
try {
Iterator<URL> iterator = cl.getResources(packageDir).asIterator();
return StreamSupport.stream(
Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false);
} catch (IOException e) {
throw new UnhandledIOException(e);
}
};
}

private static boolean matchTestClasspath(URL url, String lastFragment, boolean recursive) {
String[] classpathFragments = getTestClasspath();
if (null == classpathFragments) {
return true;
}

String fileName = "";
fileName = URLDecoder.decode(url.getFile(), UTF_8);
String fileName = URLDecoder.decode(url.getFile(), UTF_8);

for (String classpathFrag : classpathFragments) {
String path = classpathFrag + lastFragment;
Expand All @@ -195,101 +143,6 @@ private static boolean matchTestClasspath(URL url, String lastFragment, boolean
return true;
}
}

return false;
}

private static void findClassesInDirPackage(
String packageName,
List<String> included,
List<String> excluded,
String packagePath,
final boolean recursive,
List<String> classes) {
File dir = new File(packagePath);

if (!dir.exists() || !dir.isDirectory()) {
return;
}

File[] dirfiles =
dir.listFiles(
file ->
(recursive && file.isDirectory())
|| (file.getName().endsWith(".class"))
|| (file.getName().endsWith(".groovy")));

Utils.log(PACKAGE_UTILS, 4, "Looking for test classes in the directory: " + dir);
if (dirfiles == null) {
return;
}
for (File file : dirfiles) {
if (file.isDirectory()) {
findClassesInDirPackage(
makeFullClassName(packageName, file.getName()),
included,
excluded,
file.getAbsolutePath(),
recursive,
classes);
} else {
String className = file.getName().substring(0, file.getName().lastIndexOf('.'));
Utils.log(
PACKAGE_UTILS,
4,
"Found class " + className + ", seeing it if it's included or excluded");
includeOrExcludeClass(packageName, className, included, excluded, classes);
}
}
}

private static String makeFullClassName(String pkg, String cls) {
return pkg.length() > 0 ? pkg + "." + cls : cls;
}

private static void includeOrExcludeClass(
String packageName,
String className,
List<String> included,
List<String> excluded,
List<String> classes) {
if (isIncluded(packageName, included, excluded)) {
Utils.log(PACKAGE_UTILS, 4, "... Including class " + className);
classes.add(makeFullClassName(packageName, className));
} else {
Utils.log(PACKAGE_UTILS, 4, "... Excluding class " + className);
}
}

/** @return true if name should be included. */
private static boolean isIncluded(String name, List<String> included, List<String> excluded) {
boolean result;

//
// If no includes nor excludes were specified, return true.
//
if (included.isEmpty() && excluded.isEmpty()) {
result = true;
} else {
boolean isIncluded = PackageUtils.find(name, included);
boolean isExcluded = PackageUtils.find(name, excluded);
if (isIncluded && !isExcluded) {
result = true;
} else if (isExcluded) {
result = false;
} else {
result = included.isEmpty();
}
}
return result;
}

private static boolean find(String name, List<String> list) {
for (String regexpStr : list) {
if (Pattern.matches(regexpStr, name)) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,43 @@
package org.testng.internal.protocols;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.util.List;
import org.testng.collections.Lists;

class BundledResourceProcessor extends Processor {
@Override
public List<String> process(Input input, URL url) {
return processBundledResources(
url,
input.getIncluded(),
input.getExcluded(),
input.getPackageWithoutWildCards(),
input.isRecursive());
}

private static List<String> processBundledResources(
URL url,
List<String> included,
List<String> excluded,
String packageOnly,
boolean recursive) {
try {
Class<?>[] params = {};
// BundleURLConnection
URLConnection connection = url.openConnection();
Method thisMethod = url.openConnection().getClass().getDeclaredMethod("getFileURL", params);
Object[] paramsObj = {};
URL fileUrl = (URL) thisMethod.invoke(connection, paramsObj);
return findClassesInDirPackage(
packageOnly, included, excluded, URLDecoder.decode(fileUrl.getFile(), UTF_8), recursive);
} catch (Exception ex) {
// ignore - probably not an Eclipse OSGi bundle
}
return Lists.newArrayList();
}
}
@@ -0,0 +1,20 @@
package org.testng.internal.protocols;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.net.URL;
import java.net.URLDecoder;
import java.util.List;

class FileProcessor extends Processor {

@Override
public List<String> process(Input input, URL url) {
return findClassesInDirPackage(
input.getPackageWithoutWildCards(),
input.getIncluded(),
input.getExcluded(),
URLDecoder.decode(url.getFile(), UTF_8),
input.isRecursive());
}
}