1+ /*
2+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
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+ * A copy of the License is located at
7+ *
8+ * http://aws.amazon.com/apache2.0
9+ *
10+ * or in the "license" file accompanying this file. This file is distributed
11+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+ * express or implied. See the License for the specific language governing
13+ * permissions and limitations under the License.
14+ */
15+
16+ package software .amazon .awssdk .enhanced .dynamodb ;
17+
18+ import static java .util .stream .Collectors .toList ;
19+ import static org .junit .jupiter .api .Assertions .assertThrows ;
20+ import static org .mockito .Mockito .CALLS_REAL_METHODS ;
21+ import static org .mockito .Mockito .mock ;
22+ import static org .mockito .Mockito .when ;
23+
24+ import java .io .File ;
25+ import java .lang .reflect .Method ;
26+ import java .net .URL ;
27+ import java .util .Arrays ;
28+ import java .util .Collections ;
29+ import java .util .List ;
30+ import java .util .Optional ;
31+ import java .util .function .Consumer ;
32+ import java .util .regex .Pattern ;
33+ import java .util .stream .Stream ;
34+ import org .junit .jupiter .api .DynamicTest ;
35+ import org .junit .jupiter .api .TestFactory ;
36+
37+ /**
38+ * Test class that discovers all interfaces with default methods that throw UnsupportedOperationException. Shows individual test
39+ * scenarios and results using DynamicTest.
40+ */
41+ public class DefaultMethodsUnsupportedOperationTest {
42+
43+ private static final String BASE_PACKAGE = "software.amazon.awssdk.enhanced.dynamodb" ;
44+ private static final Pattern CLASS_PATTERN = Pattern .compile (".class" , Pattern .LITERAL );
45+
46+ private static final List <String > testScenarios = Collections .synchronizedList (new java .util .ArrayList <>());
47+
48+ @ TestFactory
49+ Stream <DynamicTest > testDefaultMethodsThrowUnsupportedOperation () {
50+ return scanPackageForClasses (BASE_PACKAGE )
51+ .filter (Class ::isInterface )
52+ .filter (this ::hasDefaultMethods )
53+ .collect (toList ())
54+ .stream ()
55+ .flatMap (this ::createTestsForInterface )
56+ .collect (toList ())
57+ .stream ();
58+ }
59+
60+ private Stream <Class <?>> scanPackageForClasses (String packageName ) {
61+ try {
62+ ClassLoader loader = Thread .currentThread ().getContextClassLoader ();
63+ return Collections .list (loader .getResources (packageName .replace ('.' , '/' )))
64+ .stream ()
65+ .map (URL ::getFile )
66+ .map (File ::new )
67+ .filter (File ::exists )
68+ .flatMap (dir -> findClassesInDirectory (dir , packageName ));
69+ } catch (Exception e ) {
70+ return Stream .empty ();
71+ }
72+ }
73+
74+ private Stream <Class <?>> findClassesInDirectory (File dir , String packageName ) {
75+ return Optional .ofNullable (dir .listFiles ())
76+ .map (Arrays ::stream )
77+ .orElseGet (Stream ::empty )
78+ .flatMap (file ->
79+ file .isDirectory ()
80+ ? findClassesInDirectory (file , packageName + "." + file .getName ())
81+ : loadClassFromFile (file , packageName ));
82+ }
83+
84+ private Stream <Class <?>> loadClassFromFile (File file , String packageName ) {
85+ if (!file .getName ().endsWith (".class" )) {
86+ return Stream .empty ();
87+ }
88+
89+ String className = packageName + '.' + CLASS_PATTERN .matcher (file .getName ()).replaceAll ("" );
90+ try {
91+ return Stream .of (Class .forName (className ));
92+ } catch (ClassNotFoundException | NoClassDefFoundError e ) {
93+ return Stream .empty ();
94+ }
95+ }
96+
97+ private boolean hasDefaultMethods (Class <?> interfaceClass ) {
98+ return Arrays .stream (interfaceClass .getDeclaredMethods ())
99+ .anyMatch (Method ::isDefault );
100+ }
101+
102+ private Stream <DynamicTest > createTestsForInterface (Class <?> interfaceClass ) {
103+ return Arrays .stream (interfaceClass .getDeclaredMethods ())
104+ .filter (Method ::isDefault )
105+ .filter (method -> throwsUnsupportedOperation (interfaceClass , method ))
106+ .map (method -> {
107+ String testName = String .format ("%s.%s() → throws UnsupportedOperationException" ,
108+ interfaceClass .getSimpleName (),
109+ method .getName ());
110+ testScenarios .add (testName );
111+
112+ return DynamicTest .dynamicTest (testName , () ->
113+ testMethodThrowsUnsupportedOperation (interfaceClass , method ));
114+ });
115+ }
116+
117+ private boolean throwsUnsupportedOperation (Class <?> interfaceClass , Method method ) {
118+ try {
119+ Object mockInstance = createMockInstance (interfaceClass );
120+ Object [] args = createArguments (method );
121+ method .invoke (mockInstance , args );
122+ return false ;
123+ } catch (Exception e ) {
124+ Throwable cause = e .getCause () != null ? e .getCause () : e ;
125+ return cause instanceof UnsupportedOperationException ;
126+ }
127+ }
128+
129+ private void testMethodThrowsUnsupportedOperation (Class <?> interfaceClass , Method method ) {
130+ Object mockInstance = createMockInstance (interfaceClass );
131+ Object [] args = createArguments (method );
132+
133+ assertThrows (UnsupportedOperationException .class , () -> {
134+ try {
135+ method .invoke (mockInstance , args );
136+ } catch (Exception e ) {
137+ Throwable cause = e .getCause () != null ? e .getCause () : e ;
138+ if (cause instanceof UnsupportedOperationException ) {
139+ throw cause ;
140+ }
141+ throw new RuntimeException (cause );
142+ }
143+ }, () -> String .format ("Expected %s.%s() to throw UnsupportedOperationException" ,
144+ interfaceClass .getSimpleName (), method .getName ()));
145+ }
146+
147+ private <T > T createMockInstance (Class <T > interfaceClass ) {
148+ T mock = mock (interfaceClass , CALLS_REAL_METHODS );
149+ if (mock instanceof MappedTableResource ) {
150+ when (((MappedTableResource <?>) mock ).tableName ()).thenReturn ("test-table" );
151+ }
152+ return mock ;
153+ }
154+
155+ private Object [] createArguments (Method method ) {
156+ return Arrays .stream (method .getParameterTypes ()).map (this ::createArgument ).toArray ();
157+ }
158+
159+ private Object createArgument (Class <?> paramType ) {
160+ if (paramType == String .class ) {
161+ return "test" ;
162+ }
163+ if (paramType == Key .class ) {
164+ return Key .builder ().partitionValue ("test" ).build ();
165+ }
166+ if (Consumer .class .isAssignableFrom (paramType )) {
167+ return (Consumer <?>) obj -> {
168+ };
169+ }
170+ if (paramType .isInterface ()) {
171+ return mock (paramType );
172+ }
173+ try {
174+ return mock (paramType );
175+ } catch (Exception e ) {
176+ return null ;
177+ }
178+ }
179+ }
0 commit comments