blob: e34d86ed0829aec544e0bf60458bf70b5e55c323 [file] [log] [blame]
// Copyright 2017 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.skylark.skylint;
import com.google.common.collect.ImmutableMap;
import com.google.common.truth.Truth;
import com.google.devtools.build.lib.syntax.BuildFileAST;
import com.google.devtools.build.lib.syntax.FunctionDefStatement;
import com.google.devtools.build.lib.syntax.LoadStatement;
import com.google.devtools.build.lib.syntax.Statement;
import com.google.devtools.skylark.skylint.DependencyAnalyzer.DependencyCollector;
import com.google.devtools.skylark.skylint.Linter.FileFacade;
import java.nio.charset.StandardCharsets;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for {@link com.google.devtools.skylark.skylint.DependencyAnalyzer} */
@RunWith(JUnit4.class)
public class DependencyAnalyzerTest {
private static final DependencyCollector<List<String>> functionCollector =
new DependencyCollector<List<String>>() {
@Override
public List<String> initInfo(Path path) {
return new ArrayList<>();
}
@Override
public List<String> loadDependency(
List<String> currentFileInfo,
LoadStatement stmt,
Path loadedPath,
List<String> loadedFileInfo) {
for (String name : loadedFileInfo) {
currentFileInfo.add(loadedPath + " - " + name);
}
return currentFileInfo;
}
@Override
public List<String> collectInfo(Path path, BuildFileAST ast, List<String> info) {
for (Statement stmt : ast.getStatements()) {
if (stmt instanceof FunctionDefStatement) {
info.add(((FunctionDefStatement) stmt).getIdentifier().getName());
}
}
return info;
}
};
public static FileFacade toFileFacade(Map<String, String> files) {
return path -> {
String contents = files.get(path.toString());
if (contents == null) {
throw new NoSuchFileException(path.toString());
}
return contents.getBytes(StandardCharsets.ISO_8859_1);
};
}
private static List<String> getFunctionNames(Map<String, String> files, String path) {
return new DependencyAnalyzer<>(toFileFacade(files), functionCollector)
.collectTransitiveInfo(Paths.get(path));
}
@Test
public void externalRepositoryDependency() throws Exception {
Map<String, String> files =
ImmutableMap.<String, String>builder()
.put("/WORKSPACE", "")
.put("/BUILD", "")
.put("/test.bzl", "load('@some-repo//foo:bar.bzl', 'baz')")
.build();
Truth.assertThat(getFunctionNames(files, "/test.bzl")).isEmpty();
}
@Test
public void nonexistentDependency() throws Exception {
Map<String, String> files =
ImmutableMap.<String, String>builder()
.put("/WORKSPACE", "")
.put("/BUILD", "")
.put("/test.bzl", "load(':does_not_exist.bzl', 'baz')")
.build();
Truth.assertThat(getFunctionNames(files, "/test.bzl")).isEmpty();
}
@Test
public void samePackageDependency() throws Exception {
Map<String, String> files =
ImmutableMap.<String, String>builder()
.put("/WORKSPACE", "")
.put("/BUILD", "")
.put("/test.bzl", "load(':bar.bzl', 'baz')\nload('//:bar.bzl', 'baz')")
.put("/bar.bzl", "def baz(): pass")
.build();
Truth.assertThat(getFunctionNames(files, "/test.bzl"))
.isEqualTo(Arrays.asList("/bar.bzl - baz", "/bar.bzl - baz"));
}
@Test
public void samePackageDependencyWithoutBuildFile() throws Exception {
Map<String, String> files =
ImmutableMap.<String, String>builder()
.put("/WORKSPACE", "")
.put("/test.bzl", "load(':bar.bzl', 'baz')")
.put("/bar.bzl", "def baz(): pass")
.build();
Truth.assertThat(getFunctionNames(files, "/test.bzl")).isEmpty();
}
@Test
public void subpackageDependency() throws Exception {
Map<String, String> files =
ImmutableMap.<String, String>builder()
.put("/WORKSPACE", "")
.put("/BUILD", "")
.put("/test.bzl", "load('subpackage:foo.bzl', 'foo')")
.put("/subpackage/BUILD", "")
.put("/subpackage/foo.bzl", "def foo(): pass")
.build();
Truth.assertThat(getFunctionNames(files, "/test.bzl"))
.isEqualTo(Collections.singletonList("/subpackage/foo.bzl - foo"));
}
@Test
public void dependencyInSiblingPackage() throws Exception {
Map<String, String> files =
ImmutableMap.<String, String>builder()
.put("/WORKSPACE", "")
.put("/pkg1/BUILD", "")
.put("/pkg1/test.bzl", "load('//pkg2:bar.bzl', 'bar')")
.put("/pkg2/BUILD", "")
.put("/pkg2/bar.bzl", "def bar(): pass")
.build();
Truth.assertThat(getFunctionNames(files, "/pkg1/test.bzl"))
.isEqualTo(Collections.singletonList("/pkg2/bar.bzl - bar"));
}
@Test
public void dependencyInSiblingPackageWithBuildDotBazelFile() throws Exception {
Map<String, String> files =
ImmutableMap.<String, String>builder()
.put("/WORKSPACE", "")
.put("/pkg1/BUILD.bazel", "")
.put("/pkg1/test.bzl", "load('//pkg2:bar.bzl', 'bar')")
.put("/pkg2/BUILD.bazel", "")
.put("/pkg2/bar.bzl", "def bar(): pass")
.build();
Truth.assertThat(getFunctionNames(files, "/pkg1/test.bzl"))
.isEqualTo(Collections.singletonList("/pkg2/bar.bzl - bar"));
}
@Test
public void dependencyLabelWithoutPrefix() throws Exception {
Map<String, String> files =
ImmutableMap.<String, String>builder()
.put("/WORKSPACE", "")
.put("/test/test.bzl", "load('bar.bzl', 'baz')")
.put("/bar.bzl", "def baz(): pass")
.build();
Truth.assertThat(getFunctionNames(files, "/test/test.bzl"))
.isEqualTo(Collections.singletonList("/bar.bzl - baz"));
}
@Test
public void transitiveDependencies() throws Exception {
Map<String, String> files =
ImmutableMap.<String, String>builder()
.put("/WORKSPACE", "")
.put("/BUILD", "")
.put("/test.bzl", "load(':foo.bzl', 'foo')")
.put("/foo.bzl", "load(':bar.bzl', foo = 'bar')")
.put("/bar.bzl", "def bar(): pass")
.build();
Truth.assertThat(getFunctionNames(files, "/test.bzl"))
.isEqualTo(Collections.singletonList("/foo.bzl - /bar.bzl - bar"));
}
@Test
public void diamondDependencies() throws Exception {
Map<String, String> files =
ImmutableMap.<String, String>builder()
.put("/WORKSPACE", "")
.put("/BUILD", "")
.put("/test.bzl", "load(':foo.bzl', 'foo')\nload(':bar.bzl', 'bar')")
.put("/foo.bzl", "load(':base.bzl', foo = 'base')")
.put("/bar.bzl", "load(':base.bzl', bar = 'base')")
.put("/base.bzl", "def base(): pass")
.build();
Truth.assertThat(getFunctionNames(files, "/test.bzl"))
.isEqualTo(Arrays.asList("/foo.bzl - /base.bzl - base", "/bar.bzl - /base.bzl - base"));
}
@Test
public void cyclicDependenciesAreHandledGracefully() throws Exception {
Map<String, String> files =
ImmutableMap.<String, String>builder()
.put("/WORKSPACE", "")
.put("/BUILD", "")
.put("/test.bzl", "load(':test.bzl', 'foo')\ndef test(): pass")
.build();
Truth.assertThat(getFunctionNames(files, "/test.bzl"))
.isEqualTo(Collections.singletonList("test"));
files =
ImmutableMap.<String, String>builder()
.put("/WORKSPACE", "")
.put("/BUILD", "")
.put("/test.bzl", "load(':foo.bzl', 'baz')\ndef test(): pass")
.put("/foo.bzl", "load(':bar.bzl', 'baz')")
.put("/bar.bzl", "load(':foo.bzl', 'baz')")
.build();
Truth.assertThat(getFunctionNames(files, "/test.bzl"))
.isEqualTo(Collections.singletonList("test"));
}
}