Skip to content

Commit e0ad566

Browse files
authored
[Core] Log warning when resources could not be loaded (#2235)
Fixes: #2212, #2229
1 parent 3fd0746 commit e0ad566

File tree

8 files changed

+107
-9
lines changed

8 files changed

+107
-9
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1717

1818
### Fixed
1919
* [Core] Pass class loader to ServiceLoader.load invocations ([#2220](https://github.com/cucumber/cucumber-jvm/issues/2220) M.P. Korstanje)
20+
* [Core] Log warnings when classes or resource could not be loaded ([#2235](https://github.com/cucumber/cucumber-jvm/issues/2235) M.P. Korstanje)
2021

2122
## [6.9.1] (2020-12-14)
2223

core/src/main/java/io/cucumber/core/resource/ClasspathScanner.java

+13-7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.function.Predicate;
1515
import java.util.function.Supplier;
1616

17+
import static io.cucumber.core.resource.ClasspathSupport.classPathScanningExplanation;
1718
import static io.cucumber.core.resource.ClasspathSupport.determineFullyQualifiedClassName;
1819
import static io.cucumber.core.resource.ClasspathSupport.getUrisForPackage;
1920
import static io.cucumber.core.resource.ClasspathSupport.requireValidPackageName;
@@ -95,16 +96,21 @@ private Function<Path, Consumer<Path>> processClassFiles(
9596
) {
9697
return baseDir -> classFile -> {
9798
String fqn = determineFullyQualifiedClassName(baseDir, basePackageName, classFile);
98-
try {
99-
Optional.of(getClassLoader().loadClass(fqn))
100-
.filter(classFilter)
101-
.ifPresent(classConsumer);
102-
} catch (ClassNotFoundException | NoClassDefFoundError e) {
103-
log.debug(e, () -> "Failed to load class " + fqn);
104-
}
99+
safelyLoadClass(fqn)
100+
.filter(classFilter)
101+
.ifPresent(classConsumer);
105102
};
106103
}
107104

105+
private Optional<Class<?>> safelyLoadClass(String fqn) {
106+
try {
107+
return Optional.ofNullable(getClassLoader().loadClass(fqn));
108+
} catch (ClassNotFoundException | NoClassDefFoundError e) {
109+
log.warn(e, () -> "Failed to load class '" + fqn + "'.\n" + classPathScanningExplanation());
110+
}
111+
return Optional.empty();
112+
}
113+
108114
public List<Class<?>> scanForClassesInPackage(String packageName) {
109115
return scanForClassesInPackage(packageName, NULL_FILTER);
110116
}

core/src/main/java/io/cucumber/core/resource/ClasspathSupport.java

+10
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,14 @@ public static URI rootPackageUri() {
153153
return URI.create(CLASSPATH_SCHEME_PREFIX + RESOURCE_SEPARATOR_CHAR);
154154
}
155155

156+
public static String classPathScanningExplanation() {
157+
return "By default Cucumber scans the entire classpath for step definitions.\n" +
158+
"You can restrict this by configuring the glue path.\n" +
159+
"\n" +
160+
"Examples:\n" +
161+
" - @CucumberOptions(glue = \"com.example.application\")\n" +
162+
" - src/test/resources/junit-platform.properties cucumber.glue=com.example.application\n" +
163+
" - src/test/resources/cucumber.properties cucumber.glue=com.example.application\n";
164+
}
165+
156166
}

core/src/main/java/io/cucumber/core/resource/JarUriFileSystemService.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ private static CloseablePath handleJarUriScheme(URI uri) throws IOException, URI
101101

102102
private static CucumberException nestedJarEntriesAreUnsupported(URI uri) {
103103
return new CucumberException("" +
104-
"The resource " + uri + " is located in a nested jar.\n" +
104+
"The resource '" + uri + "' is located in a nested jar.\n" +
105105
"\n" +
106106
"This typically happens when trying to run Cucumber inside a Spring Boot Executable Jar.\n" +
107107
"Cucumber currently doesn't support classpath scanning in nested jars.\n" +

core/src/main/java/io/cucumber/core/resource/PathScanner.java

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.io.IOException;
77
import java.net.URI;
88
import java.net.URISyntaxException;
9+
import java.nio.file.FileSystemNotFoundException;
910
import java.nio.file.FileVisitOption;
1011
import java.nio.file.FileVisitResult;
1112
import java.nio.file.Path;
@@ -22,10 +23,14 @@
2223

2324
class PathScanner {
2425

26+
private static final Logger log = LoggerFactory.getLogger(PathScanner.class);
27+
2528
void findResourcesForUri(URI baseUri, Predicate<Path> filter, Function<Path, Consumer<Path>> consumer) {
2629
try (CloseablePath closeablePath = open(baseUri)) {
2730
Path baseDir = closeablePath.getPath();
2831
findResourcesForPath(baseDir, filter, consumer);
32+
} catch (FileSystemNotFoundException e) {
33+
log.warn(e, () -> "Failed to find resources for '" + baseUri + "'");
2934
} catch (IOException | URISyntaxException e) {
3035
throw new RuntimeException(e);
3136
}

core/src/test/java/io/cucumber/core/resource/ClasspathScannerTest.java

+54
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,59 @@
11
package io.cucumber.core.resource;
22

3+
import io.cucumber.core.logging.LogRecordListener;
4+
import io.cucumber.core.logging.LoggerFactory;
35
import io.cucumber.core.resource.test.ExampleClass;
46
import io.cucumber.core.resource.test.ExampleInterface;
57
import io.cucumber.core.resource.test.OtherClass;
8+
import org.hamcrest.CoreMatchers;
9+
import org.hamcrest.Matchers;
10+
import org.junit.jupiter.api.AfterEach;
11+
import org.junit.jupiter.api.BeforeEach;
612
import org.junit.jupiter.api.Test;
13+
import org.mockito.Mockito;
714

15+
import java.io.IOException;
16+
import java.net.URI;
17+
import java.net.URL;
18+
import java.net.URLConnection;
19+
import java.net.URLStreamHandler;
20+
import java.util.Arrays;
21+
import java.util.Collections;
22+
import java.util.Enumeration;
823
import java.util.List;
24+
import java.util.Vector;
25+
import java.util.logging.Level;
26+
import java.util.logging.LogRecord;
927

28+
import static java.util.Arrays.asList;
29+
import static java.util.Collections.enumeration;
30+
import static java.util.Collections.singletonList;
1031
import static org.hamcrest.MatcherAssert.assertThat;
32+
import static org.hamcrest.Matchers.containsString;
1133
import static org.hamcrest.collection.IsEmptyCollection.empty;
1234
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
1335
import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
36+
import static org.mockito.Mockito.mock;
37+
import static org.mockito.Mockito.when;
1438

1539
class ClasspathScannerTest {
1640

1741
private final ClasspathScanner scanner = new ClasspathScanner(
1842
ClasspathScannerTest.class::getClassLoader);
1943

44+
private LogRecordListener logRecordListener;
45+
46+
@BeforeEach
47+
void setup() {
48+
logRecordListener = new LogRecordListener();
49+
LoggerFactory.addListener(logRecordListener);
50+
}
51+
52+
@AfterEach
53+
void tearDown() {
54+
LoggerFactory.removeListener(logRecordListener);
55+
}
56+
2057
@Test
2158
void scanForSubClassesInPackage() {
2259
List<Class<? extends ExampleInterface>> classes = scanner.scanForSubClassesInPackage("io.cucumber",
@@ -49,4 +86,21 @@ void scanForClassesInNonExistingPackage() {
4986
assertThat(classes, empty());
5087
}
5188

89+
@Test
90+
void scanForResourcesInUnsupportedFileSystem() throws IOException {
91+
ClassLoader classLoader = mock(ClassLoader.class);
92+
ClasspathScanner scanner = new ClasspathScanner(() -> classLoader);
93+
URLStreamHandler handler = new URLStreamHandler() {
94+
@Override
95+
protected URLConnection openConnection(URL u) {
96+
return null;
97+
}
98+
};
99+
URL resourceUrl = new URL(null, "bundle-resource:com/cucumber/bundle", handler);
100+
when(classLoader.getResources("com/cucumber/bundle")).thenReturn(enumeration(singletonList(resourceUrl)));
101+
assertThat(scanner.scanForClassesInPackage("com.cucumber.bundle"), empty());
102+
assertThat(logRecordListener.getLogRecords().get(0).getMessage(),
103+
containsString("Failed to find resources for 'bundle-resource:com/cucumber/bundle'"));
104+
}
105+
52106
}

core/src/test/java/io/cucumber/core/resource/ResourceScannerTest.java

+6
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ void scanForResourcesDirectory() {
124124
new File("src/test/resources/io/cucumber/core/resource/test/spaces in name resource.txt").toURI()));
125125
}
126126

127+
@Test
128+
void shouldThrowIfForResourcesPathNotExist() {
129+
File file = new File("src/test/resources/io/cucumber/core/does/not/exist");
130+
assertThrows(IllegalArgumentException.class, () -> resourceScanner.scanForResourcesPath(file.toPath()));
131+
}
132+
127133
@Test
128134
@DisabledOnOs(value = OS.WINDOWS,
129135
disabledReason = "Only works if repository is explicitly cloned activated symlinks and " +

java/src/main/java/io/cucumber/java/MethodScanner.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
package io.cucumber.java;
22

3+
import io.cucumber.core.logging.Logger;
4+
import io.cucumber.core.logging.LoggerFactory;
5+
36
import java.lang.annotation.Annotation;
47
import java.lang.reflect.Method;
58
import java.lang.reflect.Modifier;
69
import java.util.function.BiConsumer;
710

11+
import static io.cucumber.core.resource.ClasspathSupport.classPathScanningExplanation;
812
import static io.cucumber.java.InvalidMethodException.createInvalidMethodException;
913

1014
final class MethodScanner {
1115

16+
private static final Logger log = LoggerFactory.getLogger(MethodScanner.class);
17+
1218
private MethodScanner() {
1319
}
1420

@@ -21,11 +27,21 @@ static void scan(Class<?> aClass, BiConsumer<Method, Annotation> consumer) {
2127
if (!isInstantiable(aClass)) {
2228
return;
2329
}
24-
for (Method method : aClass.getMethods()) {
30+
for (Method method : safelyGetMethods(aClass)) {
2531
scan(consumer, aClass, method);
2632
}
2733
}
2834

35+
private static Method[] safelyGetMethods(Class<?> aClass) {
36+
try {
37+
return aClass.getMethods();
38+
} catch (NoClassDefFoundError e) {
39+
log.warn(e,
40+
() -> "Failed to load methods of class '" + aClass.getName() + "'.\n" + classPathScanningExplanation());
41+
}
42+
return new Method[0];
43+
}
44+
2945
private static boolean isInstantiable(Class<?> clazz) {
3046
boolean isNonStaticInnerClass = !Modifier.isStatic(clazz.getModifiers()) && clazz.getEnclosingClass() != null;
3147
return Modifier.isPublic(clazz.getModifiers()) && !Modifier.isAbstract(clazz.getModifiers())

0 commit comments

Comments
 (0)