1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc.  All rights reserved.
3 // https://developers.google.com/protocol-buffers/
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 //     * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 //     * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 //     * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 
31 package com.google.protobuf;
32 
33 import protobuf_unittest.NonNestedExtension;
34 import protobuf_unittest.NonNestedExtensionLite;
35 import java.lang.reflect.Method;
36 import java.net.URLClassLoader;
37 import java.util.Arrays;
38 import java.util.Collections;
39 import java.util.HashSet;
40 import java.util.Set;
41 import junit.framework.Test;
42 import junit.framework.TestCase;
43 import junit.framework.TestSuite;
44 
45 /**
46  * Tests for {@link ExtensionRegistryFactory} and the {@link ExtensionRegistry} instances it
47  * creates.
48  *
49  * <p>This test simulates the runtime behaviour of the ExtensionRegistryFactory by delegating test
50  * definitions to two inner classes {@link InnerTest} and {@link InnerLiteTest}, the latter of which
51  * is executed using a custom ClassLoader, simulating the ProtoLite environment.
52  *
53  * <p>The test mechanism employed here is based on the pattern in {@code
54  * com.google.common.util.concurrent.AbstractFutureFallbackAtomicHelperTest}
55  */
56 public class ExtensionRegistryFactoryTest extends TestCase {
57 
58   // A classloader which blacklists some non-Lite classes.
59   private static final ClassLoader LITE_CLASS_LOADER = getLiteOnlyClassLoader();
60 
61   /** Defines the set of test methods which will be run. */
62   static interface RegistryTests {
testCreate()63     void testCreate();
64 
testEmpty()65     void testEmpty();
66 
testIsFullRegistry()67     void testIsFullRegistry();
68 
testAdd()69     void testAdd();
70 
testAdd_immutable()71     void testAdd_immutable();
72   }
73 
74   /** Test implementations for the non-Lite usage of ExtensionRegistryFactory. */
75   public static class InnerTest implements RegistryTests {
76 
77     @Override
testCreate()78     public void testCreate() {
79       ExtensionRegistryLite registry = ExtensionRegistryFactory.create();
80 
81       assertEquals(registry.getClass(), ExtensionRegistry.class);
82     }
83 
84     @Override
testEmpty()85     public void testEmpty() {
86       ExtensionRegistryLite emptyRegistry = ExtensionRegistryFactory.createEmpty();
87 
88       assertEquals(emptyRegistry.getClass(), ExtensionRegistry.class);
89       assertEquals(emptyRegistry, ExtensionRegistry.EMPTY_REGISTRY);
90     }
91 
92     @Override
testIsFullRegistry()93     public void testIsFullRegistry() {
94       ExtensionRegistryLite registry = ExtensionRegistryFactory.create();
95       assertTrue(ExtensionRegistryFactory.isFullRegistry(registry));
96     }
97 
98     @Override
testAdd()99     public void testAdd() {
100       ExtensionRegistryLite registry1 = ExtensionRegistryLite.newInstance();
101       NonNestedExtensionLite.registerAllExtensions(registry1);
102       registry1.add(NonNestedExtensionLite.nonNestedExtensionLite);
103 
104       ExtensionRegistryLite registry2 = ExtensionRegistryLite.newInstance();
105       NonNestedExtension.registerAllExtensions((ExtensionRegistry) registry2);
106       registry2.add(NonNestedExtension.nonNestedExtension);
107 
108       ExtensionRegistry fullRegistry1 = (ExtensionRegistry) registry1;
109       ExtensionRegistry fullRegistry2 = (ExtensionRegistry) registry2;
110 
111       assertTrue(
112           "Test is using a non-lite extension",
113           GeneratedMessageLite.GeneratedExtension.class.isAssignableFrom(
114               NonNestedExtensionLite.nonNestedExtensionLite.getClass()));
115       assertNull(
116           "Extension is not registered in masqueraded full registry",
117           fullRegistry1.findImmutableExtensionByName("protobuf_unittest.nonNestedExtension"));
118       GeneratedMessageLite.GeneratedExtension<NonNestedExtensionLite.MessageLiteToBeExtended, ?>
119           extension =
120               registry1.findLiteExtensionByNumber(
121                   NonNestedExtensionLite.MessageLiteToBeExtended.getDefaultInstance(), 1);
122       assertNotNull("Extension registered in lite registry", extension);
123 
124       assertTrue(
125           "Test is using a non-lite extension",
126           Extension.class.isAssignableFrom(NonNestedExtension.nonNestedExtension.getClass()));
127       assertNotNull(
128           "Extension is registered in masqueraded full registry",
129           fullRegistry2.findImmutableExtensionByName("protobuf_unittest.nonNestedExtension"));
130     }
131 
132     @Override
testAdd_immutable()133     public void testAdd_immutable() {
134       ExtensionRegistryLite registry1 = ExtensionRegistryLite.newInstance().getUnmodifiable();
135       try {
136         NonNestedExtensionLite.registerAllExtensions(registry1);
137         fail();
138       } catch (UnsupportedOperationException expected) {
139       }
140       try {
141         registry1.add(NonNestedExtensionLite.nonNestedExtensionLite);
142         fail();
143       } catch (UnsupportedOperationException expected) {
144       }
145 
146       ExtensionRegistryLite registry2 = ExtensionRegistryLite.newInstance().getUnmodifiable();
147       try {
148         NonNestedExtension.registerAllExtensions((ExtensionRegistry) registry2);
149         fail();
150       } catch (IllegalArgumentException expected) {
151       }
152       try {
153         registry2.add(NonNestedExtension.nonNestedExtension);
154         fail();
155       } catch (IllegalArgumentException expected) {
156       }
157     }
158   }
159 
160   /** Test implementations for the Lite usage of ExtensionRegistryFactory. */
161   public static final class InnerLiteTest implements RegistryTests {
162 
163     @Override
testCreate()164     public void testCreate() {
165       ExtensionRegistryLite registry = ExtensionRegistryFactory.create();
166 
167       assertEquals(registry.getClass(), ExtensionRegistryLite.class);
168     }
169 
170     @Override
testEmpty()171     public void testEmpty() {
172       ExtensionRegistryLite emptyRegistry = ExtensionRegistryFactory.createEmpty();
173 
174       assertEquals(emptyRegistry.getClass(), ExtensionRegistryLite.class);
175       assertEquals(emptyRegistry, ExtensionRegistryLite.EMPTY_REGISTRY_LITE);
176     }
177 
178     @Override
testIsFullRegistry()179     public void testIsFullRegistry() {
180       ExtensionRegistryLite registry = ExtensionRegistryFactory.create();
181       assertFalse(ExtensionRegistryFactory.isFullRegistry(registry));
182     }
183 
184     @Override
testAdd()185     public void testAdd() {
186       ExtensionRegistryLite registry = ExtensionRegistryLite.newInstance();
187       NonNestedExtensionLite.registerAllExtensions(registry);
188       GeneratedMessageLite.GeneratedExtension<NonNestedExtensionLite.MessageLiteToBeExtended, ?>
189           extension =
190               registry.findLiteExtensionByNumber(
191                   NonNestedExtensionLite.MessageLiteToBeExtended.getDefaultInstance(), 1);
192       assertNotNull("Extension is registered in Lite registry", extension);
193     }
194 
195     @Override
testAdd_immutable()196     public void testAdd_immutable() {
197       ExtensionRegistryLite registry = ExtensionRegistryLite.newInstance().getUnmodifiable();
198       try {
199         NonNestedExtensionLite.registerAllExtensions(registry);
200         fail();
201       } catch (UnsupportedOperationException expected) {
202       }
203     }
204   }
205 
206   /** Defines a suite of tests which the JUnit3 runner retrieves by reflection. */
suite()207   public static Test suite() {
208     TestSuite suite = new TestSuite();
209     for (Method method : RegistryTests.class.getMethods()) {
210       suite.addTest(TestSuite.createTest(ExtensionRegistryFactoryTest.class, method.getName()));
211     }
212     return suite;
213   }
214 
215   /**
216    * Sequentially runs first the Lite and then the non-Lite test variant via classloader
217    * manipulation.
218    */
219   @Override
runTest()220   public void runTest() throws Exception {
221     ClassLoader storedClassLoader = Thread.currentThread().getContextClassLoader();
222     Thread.currentThread().setContextClassLoader(LITE_CLASS_LOADER);
223     try {
224       runTestMethod(LITE_CLASS_LOADER, InnerLiteTest.class);
225     } finally {
226       Thread.currentThread().setContextClassLoader(storedClassLoader);
227     }
228     try {
229       runTestMethod(storedClassLoader, InnerTest.class);
230     } finally {
231       Thread.currentThread().setContextClassLoader(storedClassLoader);
232     }
233   }
234 
runTestMethod(ClassLoader classLoader, Class<? extends RegistryTests> testClass)235   private void runTestMethod(ClassLoader classLoader, Class<? extends RegistryTests> testClass)
236       throws Exception {
237     classLoader.loadClass(ExtensionRegistryFactory.class.getName());
238     Class<?> test = classLoader.loadClass(testClass.getName());
239     String testName = getName();
240     test.getMethod(testName).invoke(test.getDeclaredConstructor().newInstance());
241   }
242 
243   /**
244    * Constructs a custom ClassLoader blacklisting the classes which are inspected in the SUT to
245    * determine the Lite/non-Lite runtime.
246    */
getLiteOnlyClassLoader()247   private static ClassLoader getLiteOnlyClassLoader() {
248     ClassLoader testClassLoader = ExtensionRegistryFactoryTest.class.getClassLoader();
249     final Set<String> classNamesNotInLite =
250         Collections.unmodifiableSet(
251             new HashSet<String>(
252                 Arrays.asList(
253                     ExtensionRegistryFactory.FULL_REGISTRY_CLASS_NAME,
254                     ExtensionRegistry.EXTENSION_CLASS_NAME)));
255 
256     // Construct a URLClassLoader delegating to the system ClassLoader, and looking up classes
257     // in jar files based on the URLs already configured for this test's UrlClassLoader.
258     // Certain classes throw a ClassNotFoundException by design.
259     return new URLClassLoader(
260         ((URLClassLoader) testClassLoader).getURLs(), ClassLoader.getSystemClassLoader()) {
261       @Override
262       public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
263         if (classNamesNotInLite.contains(name)) {
264           throw new ClassNotFoundException("Class deliberately blacklisted by test.");
265         }
266         Class<?> loadedClass = null;
267         try {
268           loadedClass = findLoadedClass(name);
269           if (loadedClass == null) {
270             loadedClass = findClass(name);
271             if (resolve) {
272               resolveClass(loadedClass);
273             }
274           }
275         } catch (ClassNotFoundException | SecurityException e) {
276           // Java 8+ would throw a SecurityException if we attempt to find a loaded class from
277           // java.lang.* package. We don't really care about those anyway, so just delegate to the
278           // parent class loader.
279           loadedClass = super.loadClass(name, resolve);
280         }
281         return loadedClass;
282       }
283     };
284   }
285 }
286