// 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> functionCollector = new DependencyCollector>() { @Override public List initInfo(Path path) { return new ArrayList<>(); } @Override public List loadDependency( List currentFileInfo, LoadStatement stmt, Path loadedPath, List loadedFileInfo) { for (String name : loadedFileInfo) { currentFileInfo.add(loadedPath + " - " + name); } return currentFileInfo; } @Override public List collectInfo(Path path, BuildFileAST ast, List info) { for (Statement stmt : ast.getStatements()) { if (stmt instanceof FunctionDefStatement) { info.add(((FunctionDefStatement) stmt).getIdentifier().getName()); } } return info; } }; public static FileFacade toFileFacade(Map 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 getFunctionNames(Map files, String path) { return new DependencyAnalyzer<>(toFileFacade(files), functionCollector) .collectTransitiveInfo(Paths.get(path)); } @Test public void externalRepositoryDependency() throws Exception { Map files = ImmutableMap.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 files = ImmutableMap.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 files = ImmutableMap.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 files = ImmutableMap.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 files = ImmutableMap.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 files = ImmutableMap.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 files = ImmutableMap.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 files = ImmutableMap.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 files = ImmutableMap.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 files = ImmutableMap.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 files = ImmutableMap.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.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")); } }