/* * Copyright 2015 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.template.soy.basetree; import static org.junit.Assert.fail; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; import com.google.common.reflect.ClassPath; import com.google.common.reflect.ClassPath.ClassInfo; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.LinkedHashSet; import java.util.Set; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Tests for how {@link Node} subtypes implement {@link Node#copy(CopyState)}. * * <p>{@link Node#copy(CopyState)} specifies a policy for how {@code copy} is implemented. This * tries to test for certain violations of that policy. */ @RunWith(JUnit4.class) public final class CopyPolicyTest { @Test public void testCopy() throws IOException, SecurityException { // We use top level classes to ignore node types defined as inner classes for tests. ImmutableSet<ClassInfo> topLevelClasses = ClassPath.from(ClassLoader.getSystemClassLoader()).getTopLevelClasses(); Set<String> errors = new LinkedHashSet<>(); for (ClassInfo clazz : topLevelClasses) { if (clazz.getPackageName().startsWith("com.google.template.soy")) { Class<?> cls = clazz.load(); if (Node.class.isAssignableFrom(cls)) { if (cls.isInterface()) { continue; } if (Modifier.isAbstract(cls.getModifiers())) { checkAbstractNode(cls, errors); } else { checkConcreteNode(cls, errors); } } } } if (!errors.isEmpty()) { fail("Copy policy failure:\n" + Joiner.on('\n').join(errors)); } } @SuppressWarnings("MissingFail") private static void checkConcreteNode(Class<?> node, Set<String> errors) { if (!Modifier.isFinal(node.getModifiers())) { errors.add("Non abstract Node types should be final. " + node.getName()); } try { Constructor<?> copyConstructor = node.getDeclaredConstructor(node, CopyState.class); if (!Modifier.isPrivate(copyConstructor.getModifiers())) { errors.add( "Node class: " + node.getName() + " should have a private copy constructor. Found " + copyConstructor + " with incompatible modifiers"); } } catch (NoSuchMethodException e) { errors.add("Node class: " + node.getName() + " should have a private copy constructor"); } try { node.getDeclaredMethod("clone"); errors.add("Node type: " + node.getName() + " has overridden clone()"); } catch (NoSuchMethodException expected) { } } private static void checkAbstractNode(Class<?> node, Set<String> errors) { // should have a protected copy constructor try { Constructor<?> copyConstructor = node.getDeclaredConstructor(node, CopyState.class); if (!Modifier.isProtected(copyConstructor.getModifiers())) { errors.add( "Node class: " + node.getName() + " should have a protected copy constructor. Found " + copyConstructor + " with incompatible modifiers"); } } catch (NoSuchMethodException e) { errors.add("Node class: " + node.getName() + " should have a protected copy constructor"); } // if it has a copy method, it should be abstract try { Method m = node.getDeclaredMethod("copy", CopyState.class); // the method exists if (!Modifier.isAbstract(m.getModifiers())) { errors.add( "Abstract node type: " + node.getName() + " has a copy(CopyState) method that is not abstract"); } } catch (NoSuchMethodException e) { // fine } } }