blob: a80db9474b6b799f74dfa081518b369c93848dc4 [file] [log] [blame]
// Copyright 2019 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.rules.python;
import static com.google.common.truth.Truth.assertThat;
import com.google.devtools.build.lib.actions.Action;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.testutil.TestConstants;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for {@code <tools repo>//tools/python:srcs_version.bzl%find_requirements}. */
@RunWith(JUnit4.class)
public class PythonSrcsVersionAspectTest extends BuildViewTestCase {
private static String join(String... args) {
return String.join("\n", args);
}
/**
* Returns the contents of the {@code -pyversioninfo.txt} file that would be produced by running
* the aspect on the given target.
*/
private String evaluateAspectFor(String label) throws Exception {
scratch.file(
"asp/BUILD",
"load('" + TestConstants.TOOLS_REPOSITORY + "//tools/python:srcs_version.bzl', ",
" 'apply_find_requirements_for_testing')",
"apply_find_requirements_for_testing(",
" name = 'asp',",
" target = '" + label + "',",
" out = 'out',",
")");
ConfiguredTarget ct = getConfiguredTarget("//asp");
assertThat(ct).isNotNull();
Artifact out = getBinArtifact("out", ct);
Action action = getGeneratingAction(out);
assertThat(action).isInstanceOf(FileWriteAction.class);
return ((FileWriteAction) action).getFileContents();
}
@Test
public void simpleCase() throws Exception {
scratch.file(
"pkg/BUILD",
"py_library(",
" name = 'lib',",
" srcs = ['lib.py'],",
" srcs_version = 'PY3ONLY',",
")",
"py_binary(",
" name = 'bin',",
" srcs = ['bin.py'],",
" deps = [':lib'],",
")");
String result = evaluateAspectFor("//pkg:bin");
String golden =
join(
"Python 2-only deps:",
"<None>",
"",
"Python 3-only deps:",
"//pkg:lib",
"",
"Paths to these deps:",
"//pkg:bin -> //pkg:lib",
"");
assertThat(result).isEqualTo(golden);
}
@Test
public void noRequirements() throws Exception {
scratch.file(
"pkg/BUILD",
"py_library(",
" name = 'lib',",
" srcs = ['lib.py'],",
")",
"py_binary(",
" name = 'bin',",
" srcs = ['bin.py'],",
" deps = [':lib'],",
")");
String result = evaluateAspectFor("//pkg:bin");
String golden =
join(
"Python 2-only deps:",
"<None>",
"",
"Python 3-only deps:",
"<None>",
"",
"Paths to these deps:",
"<None>",
"");
assertThat(result).isEqualTo(golden);
}
@Test
public void twoContradictoryRequirements() throws Exception {
scratch.file(
"pkg/BUILD",
"py_library(",
" name = 'lib2',",
" srcs = ['lib.py'],",
" srcs_version = 'PY2ONLY',",
")",
"py_library(",
" name = 'lib3',",
" srcs = ['lib.py'],",
" srcs_version = 'PY3',", // PY3 should be same as PY3ONLY
")",
"py_binary(",
" name = 'bin',",
" srcs = ['bin.py'],",
" deps = [':lib2', ':lib3'],",
")");
String result = evaluateAspectFor("//pkg:bin");
String golden =
join(
"Python 2-only deps:",
"//pkg:lib2",
"",
"Python 3-only deps:",
"//pkg:lib3",
"",
"Paths to these deps:",
"//pkg:bin -> //pkg:lib2",
"//pkg:bin -> //pkg:lib3",
"");
assertThat(result).isEqualTo(golden);
}
@Test
public void toplevelSelfContradictory() throws Exception {
scratch.file(
"pkg/BUILD",
"py_binary(",
" name = 'bin',",
" srcs = ['bin.py'],",
" srcs_version = 'PY3ONLY',",
" python_version = 'PY2',",
")");
String result = evaluateAspectFor("//pkg:bin");
String golden =
join(
"Python 2-only deps:",
"<None>",
"",
"Python 3-only deps:",
"//pkg:bin",
"",
"Paths to these deps:",
"//pkg:bin",
"");
assertThat(result).isEqualTo(golden);
}
@Test
public void indirectDependencies() throws Exception {
// A <- B <- C <- bin, where only B has the constraint.
scratch.file(
"pkg/BUILD",
"py_library(",
" name = 'libA',",
" srcs = ['libA.py'],",
")",
"py_library(",
" name = 'libB',",
" srcs = ['libB.py'],",
" deps = [':libA'],",
" srcs_version = 'PY3ONLY',",
")",
"py_library(",
" name = 'libC',",
" srcs = ['libC.py'],",
" deps = [':libB'],",
")",
"py_binary(",
" name = 'bin',",
" srcs = ['bin.py'],",
" deps = [':libC'],",
")");
String result = evaluateAspectFor("//pkg:bin");
String golden =
join(
"Python 2-only deps:",
"<None>",
"",
"Python 3-only deps:",
"//pkg:libB",
"",
"Paths to these deps:",
"//pkg:bin -> //pkg:libC -> //pkg:libB",
"");
assertThat(result).isEqualTo(golden);
}
@Test
public void onlyReportTopmost() throws Exception {
// A <- B <- C <- bin, where A and C have the constraint.
scratch.file(
"pkg/BUILD",
"py_library(",
" name = 'libA',",
" srcs = ['libA.py'],",
" srcs_version = 'PY3ONLY',",
")",
"py_library(",
" name = 'libB',",
" srcs = ['libB.py'],",
" deps = [':libA'],",
")",
"py_library(",
" name = 'libC',",
" srcs = ['libC.py'],",
" deps = [':libB'],",
" srcs_version = 'PY3ONLY',",
")",
"py_binary(",
" name = 'bin',",
" srcs = ['bin.py'],",
" deps = [':libC'],",
")");
String result = evaluateAspectFor("//pkg:bin");
String golden =
join(
"Python 2-only deps:",
"<None>",
"",
"Python 3-only deps:",
"//pkg:libC",
"",
"Paths to these deps:",
"//pkg:bin -> //pkg:libC",
"");
assertThat(result).isEqualTo(golden);
}
@Test
public void oneTopmostReachesAnother() throws Exception {
// A <- B <- C, where A and C have the constraint.
// A <- bin and C <- bin, so both A and C are top-most even though C has a path to A.
scratch.file(
"pkg/BUILD",
"py_library(",
" name = 'libA',",
" srcs = ['libA.py'],",
" srcs_version = 'PY3ONLY',",
")",
"py_library(",
" name = 'libB',",
" srcs = ['libB.py'],",
" deps = [':libA'],",
")",
"py_library(",
" name = 'libC',",
" srcs = ['libC.py'],",
" deps = [':libB'],",
" srcs_version = 'PY3ONLY',",
")",
"py_binary(",
" name = 'bin',",
" srcs = ['bin.py'],",
" deps = [':libA', ':libC'],",
")");
String result = evaluateAspectFor("//pkg:bin");
String golden =
join(
"Python 2-only deps:",
"<None>",
"",
"Python 3-only deps:",
"//pkg:libA",
"//pkg:libC",
"",
"Paths to these deps:",
"//pkg:bin -> //pkg:libA",
"//pkg:bin -> //pkg:libC",
"");
assertThat(result).isEqualTo(golden);
}
@Test
public void multiplePathsToRequirement() throws Exception {
// Diamond graph A <- B, A <- C, B <- bin, C <- bin, where only A has the constraint.
// A is reached through two different paths but reported only once.
scratch.file(
"pkg/BUILD",
"py_library(",
" name = 'libA',",
" srcs = ['libA.py'],",
" srcs_version = 'PY3ONLY',",
")",
"py_library(",
" name = 'libB',",
" srcs = ['libB.py'],",
" deps = [':libA'],",
")",
"py_library(",
" name = 'libC',",
" srcs = ['libC.py'],",
" deps = [':libA'],",
")",
"py_binary(",
" name = 'bin',",
" srcs = ['bin.py'],",
" deps = [':libB', ':libC'],",
")");
String result = evaluateAspectFor("//pkg:bin");
String golden =
join(
"Python 2-only deps:",
"<None>",
"",
"Python 3-only deps:",
"//pkg:libA",
"",
"Paths to these deps:",
"//pkg:bin -> //pkg:libB -> //pkg:libA",
"");
assertThat(result).isEqualTo(golden);
}
@Test
public void noSrcsVersionButIntroducesRequirement() throws Exception {
// A <- B <- C <- bin, B introduces the requirement but not via srcs_version.
// dummy_rule propagates sources and sets the py3-only bit.
scratch.file(
"pkg/rules.bzl",
"def _dummy_rule_impl(ctx):",
" info = PyInfo(",
" transitive_sources = depset(",
" transitive=[d[PyInfo].transitive_sources for d in ctx.attr.deps],",
" order='postorder'),",
" has_py3_only_sources = True)",
" return [info]",
"dummy_rule = rule(",
" implementation = _dummy_rule_impl,",
" attrs = {'deps': attr.label_list()},",
")");
scratch.file(
"pkg/BUILD",
"load(':rules.bzl', 'dummy_rule')",
"py_library(",
" name = 'libA',",
" srcs = ['libA.py'],",
")",
"dummy_rule(",
" name = 'libB',",
" deps = [':libA'],",
")",
"dummy_rule(",
" name = 'libC',",
" deps = [':libB'],",
")",
"py_binary(",
" name = 'bin',",
" srcs = ['bin.py'],",
" deps = [':libC'],",
")");
String result = evaluateAspectFor("//pkg:bin");
String golden =
join(
"Python 2-only deps:",
"<None>",
"",
"Python 3-only deps:",
"//pkg:libB",
"",
"Paths to these deps:",
"//pkg:bin -> //pkg:libC -> //pkg:libB",
"");
assertThat(result).isEqualTo(golden);
}
@Test
public void requirementNotPropagated() throws Exception {
// A <- B <- C <- bin, A introduces the requirement, but B doesn't propagate it.
// dummy_rule propagates sources but nothing else. It also has a srcs_version attr that is
// ignored because the provider field is false.
scratch.file(
"pkg/rules.bzl",
"def _dummy_rule_impl(ctx):",
" info = PyInfo(",
" transitive_sources = depset(",
" transitive=[d[PyInfo].transitive_sources for d in ctx.attr.deps],",
" order='postorder'))",
" return [info]",
"dummy_rule = rule(",
" implementation = _dummy_rule_impl,",
" attrs = {",
" 'deps': attr.label_list(),",
" 'srcs_version': attr.string(),",
" },",
")");
scratch.file(
"pkg/BUILD",
"load(':rules.bzl', 'dummy_rule')",
"py_library(",
" name = 'libA',",
" srcs = ['libA.py'],",
" srcs_version = 'PY3ONLY',",
")",
"dummy_rule(",
" name = 'libB',",
" deps = [':libA'],",
" srcs_version = 'PY3ONLY',",
")",
"py_binary(",
" name = 'bin',",
" srcs = ['bin.py'],",
" deps = [':libB'],",
")");
String result = evaluateAspectFor("//pkg:bin");
String golden =
join(
"Python 2-only deps:",
"<None>",
"",
"Python 3-only deps:",
"<None>",
"",
"Paths to these deps:",
"<None>",
"");
assertThat(result).isEqualTo(golden);
}
@Test
public void toleratesTargetsWithoutDepsAttr() throws Exception {
scratch.file(
"pkg/rules.bzl",
"def _dummy_rule_impl(ctx):",
" info = PyInfo(transitive_sources = depset([]))",
" return [info]",
"dummy_rule = rule(",
" implementation = _dummy_rule_impl,",
")");
scratch.file(
"pkg/BUILD",
"load(':rules.bzl', 'dummy_rule')",
"dummy_rule(",
" name = 'lib',",
")",
"py_binary(",
" name = 'bin',",
" srcs = ['bin.py'],",
" deps = [':lib'],",
")");
evaluateAspectFor("//pkg:bin");
}
}