blob: 6479f58041a7046ec398b6c40d7869441237a1ad [file] [log] [blame]
// Copyright 2016 The Bazel Authors. All Rights Reserved.
//
// 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.devtools.build.lib.syntax;
import static com.google.common.truth.Truth.assertThat;
import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkInterfaceUtils;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import java.lang.reflect.Method;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Test Skylark interface annotations and utilities.
*/
@RunWith(JUnit4.class)
public class SkylarkInterfaceUtilsTest {
/** MockClassA */
@SkylarkModule(name = "MockClassA", doc = "MockClassA")
public static class MockClassA {
@SkylarkCallable(doc = "MockClassA#foo")
public void foo() {}
@SkylarkCallable(doc = "MockClassA#bar")
public void bar() {}
public void baz() {}
}
/** MockInterfaceB1 */
@SkylarkModule(name = "MockInterfaceB1", doc = "MockInterfaceB1")
public static interface MockInterfaceB1 {
@SkylarkCallable(doc = "MockInterfaceB1#foo")
void foo();
@SkylarkCallable(doc = "MockInterfaceB1#bar")
void bar();
@SkylarkCallable(doc = "MockInterfaceB1#baz")
void baz();
}
/** MockInterfaceB2 */
@SkylarkModule(name = "MockInterfaceB2", doc = "MockInterfaceB2")
public static interface MockInterfaceB2 {
@SkylarkCallable(doc = "MockInterfaceB2#baz")
void baz();
@SkylarkCallable(doc = "MockInterfaceB2#qux")
void qux();
}
/** MockClassC */
@SkylarkModule(name = "MockClassC", doc = "MockClassC")
public static class MockClassC extends MockClassA implements MockInterfaceB1, MockInterfaceB2 {
@Override
@SkylarkCallable(doc = "MockClassC#foo")
public void foo() {}
@Override
public void bar() {}
@Override
public void baz() {}
@Override
public void qux() {}
}
/** MockClassD */
public static class MockClassD extends MockClassC {
@Override
@SkylarkCallable(doc = "MockClassD#foo")
public void foo() {}
}
/** MockClassZ */
public static class MockClassZ {
}
// The tests for getSkylarkModule() double as tests for getParentWithSkylarkModule(),
// since they share an implementation.
@Test
public void testGetSkylarkModuleBasic() throws Exception {
// Normal case.
SkylarkModule ann = SkylarkInterfaceUtils.getSkylarkModule(MockClassA.class);
Class<?> cls = SkylarkInterfaceUtils.getParentWithSkylarkModule(MockClassA.class);
assertThat(ann).isNotNull();
assertThat(ann.doc()).isEqualTo("MockClassA");
assertThat(cls).isNotNull();
assertThat(cls).isEqualTo(MockClassA.class);
}
@Test
public void testGetSkylarkModuleSubclass() throws Exception {
// Subclass's annotation is used.
SkylarkModule ann = SkylarkInterfaceUtils.getSkylarkModule(MockClassC.class);
Class<?> cls = SkylarkInterfaceUtils.getParentWithSkylarkModule(MockClassC.class);
assertThat(ann).isNotNull();
assertThat(ann.doc()).isEqualTo("MockClassC");
assertThat(cls).isNotNull();
assertThat(cls).isEqualTo(MockClassC.class);
}
@Test
public void testGetSkylarkModuleSubclassNoSubannotation() throws Exception {
// Falls back on superclass's annotation.
SkylarkModule ann = SkylarkInterfaceUtils.getSkylarkModule(MockClassD.class);
Class<?> cls = SkylarkInterfaceUtils.getParentWithSkylarkModule(MockClassD.class);
assertThat(ann).isNotNull();
assertThat(ann.doc()).isEqualTo("MockClassC");
assertThat(cls).isNotNull();
assertThat(cls).isEqualTo(MockClassC.class);
}
@Test
public void testGetSkylarkModuleNotFound() throws Exception {
// Doesn't exist.
SkylarkModule ann = SkylarkInterfaceUtils.getSkylarkModule(MockClassZ.class);
Class<?> cls = SkylarkInterfaceUtils.getParentWithSkylarkModule(MockClassZ.class);
assertThat(ann).isNull();
assertThat(cls).isNull();
}
@Test
public void testGetSkylarkCallableBasic() throws Exception {
// Normal case. Ensure two-arg form is consistent with one-arg form.
Method method = MockClassA.class.getMethod("foo");
SkylarkCallable ann = SkylarkInterfaceUtils.getSkylarkCallable(method);
assertThat(ann).isNotNull();
assertThat(ann.doc()).isEqualTo("MockClassA#foo");
SkylarkCallable ann2 = SkylarkInterfaceUtils.getSkylarkCallable(MockClassA.class, method);
assertThat(ann2).isEqualTo(ann);
}
@Test
public void testGetSkylarkCallableSubclass() throws Exception {
// Subclass's annotation is used.
Method method = MockClassC.class.getMethod("foo");
SkylarkCallable ann = SkylarkInterfaceUtils.getSkylarkCallable(method);
assertThat(ann).isNotNull();
assertThat(ann.doc()).isEqualTo("MockClassC#foo");
}
@Test
public void testGetSkylarkCallableSubclassNoSubannotation() throws Exception {
// Falls back on superclass's annotation. Superclass takes precedence over interface.
Method method = MockClassC.class.getMethod("bar");
SkylarkCallable ann = SkylarkInterfaceUtils.getSkylarkCallable(method);
assertThat(ann).isNotNull();
assertThat(ann.doc()).isEqualTo("MockClassA#bar");
}
@Test
public void testGetSkylarkCallableTwoargForm() throws Exception {
// Ensure that when passing superclass in directly, we bypass subclass's annotation.
Method method = MockClassC.class.getMethod("foo");
SkylarkCallable ann = SkylarkInterfaceUtils.getSkylarkCallable(MockClassA.class, method);
assertThat(ann).isNotNull();
assertThat(ann.doc()).isEqualTo("MockClassA#foo");
}
@Test
public void testGetSkylarkCallableNotFound() throws Exception {
// Null result when no annotation present...
Method method = MockClassA.class.getMethod("baz");
SkylarkCallable ann = SkylarkInterfaceUtils.getSkylarkCallable(method);
assertThat(ann).isNull();
// ... including when it's only present in a subclass that was bypassed...
method = MockClassC.class.getMethod("baz");
ann = SkylarkInterfaceUtils.getSkylarkCallable(MockClassA.class, method);
assertThat(ann).isNull();
// ... or when the method itself is only in the subclass that was bypassed.
method = MockClassC.class.getMethod("qux");
ann = SkylarkInterfaceUtils.getSkylarkCallable(MockClassA.class, method);
assertThat(ann).isNull();
}
@Test
public void testGetSkylarkCallableInterface() throws Exception {
// Search through parent interfaces. First interface takes priority.
Method method = MockClassC.class.getMethod("baz");
SkylarkCallable ann = SkylarkInterfaceUtils.getSkylarkCallable(method);
assertThat(ann).isNotNull();
assertThat(ann.doc()).isEqualTo("MockInterfaceB1#baz");
// Make sure both are still traversed.
method = MockClassC.class.getMethod("qux");
ann = SkylarkInterfaceUtils.getSkylarkCallable(method);
assertThat(ann).isNotNull();
assertThat(ann.doc()).isEqualTo("MockInterfaceB2#qux");
}
}