blob: fa98a9de126be45a47e33f7ea7f4528e8933ffe6 [file] [log] [blame]
// Copyright 2015 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.packages;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.base.Strings;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.packages.PackageFactory.EnvironmentExtension;
import com.google.devtools.build.lib.packages.util.PackageSerializationTestCase;
import com.google.devtools.build.lib.query2.proto.proto2api.Build;
import com.google.devtools.build.lib.query2.proto.proto2api.Build.Attribute.Discriminator;
import com.google.devtools.build.lib.syntax.GlobCriteria;
import com.google.devtools.build.lib.syntax.GlobList;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.ExtensionRegistryLite;
import java.io.IOException;
import java.util.List;
/**
* Unit tests for package serialization and deserialization.
*/
public class PackageSerializationTest extends PackageSerializationTestCase {
@Override
protected List<EnvironmentExtension> getPackageEnvironmentExtensions() {
return ImmutableList.of();
}
public void testLocationsOmitted() throws Exception {
Package pkg = scratchPackage("bacon",
"sh_library(name='bacon',",
" srcs=['bacon.sh'])");
// By default we keep full location.
Package pkg2 = deserializer.deserialize(codedInputFromPackage(pkg));
Rule rule2 = pkg2.getRule("bacon");
assertEmpty(rule2.getLocation());
assertEmpty(rule2.getAttributeLocation("name"));
assertEmpty(rule2.getAttributeLocation("srcs"));
}
public void testGlobInformationKept() throws Exception {
scratch.file("ham/head.sh");
scratch.file("ham/tbone.sh");
Package pkg = scratchPackage("ham",
"sh_library(name='ham',",
" srcs=glob(['*.sh'], exclude=['tbo*.sh']))");
Package pkg2 = deserializer.deserialize(codedInputFromPackage(pkg));
Rule ham = pkg2.getRule("ham");
GlobList<?> globs = Rule.getGlobInfo(RawAttributeMapper.of(ham)
.get("srcs", BuildType.LABEL_LIST));
assertThat(globs).containsExactly(Label.parseAbsolute("//ham:head.sh"));
List<GlobCriteria> criteria = globs.getCriteria();
assertThat(criteria).hasSize(1);
assertThat(criteria.get(0).getIncludePatterns()).containsExactly("*.sh");
assertThat(criteria.get(0).getExcludePatterns()).containsExactly("tbo*.sh");
assertTrue(criteria.get(0).isGlob());
}
public void testCanConcatenateGlobs() throws Exception {
scratch.file("serrano/sinew.sh");
scratch.file("serrano/muscle.sh");
scratch.file("serrano/bone.sh");
Package pkg = scratchPackage("serrano",
"sh_library(name='serrano',",
" srcs=glob(['s*.sh']) + ['muscle.sh'] + glob(['b*.sh']))");
Package pkg2 = deserializer.deserialize(codedInputFromPackage(pkg));
Rule ham = pkg2.getRule("serrano");
GlobList<?> globs = Rule.getGlobInfo(RawAttributeMapper.of(ham)
.get("srcs", BuildType.LABEL_LIST));
assertThat(globs).containsExactly(
Label.parseAbsolute("//serrano:sinew.sh"),
Label.parseAbsolute("//serrano:muscle.sh"),
Label.parseAbsolute("//serrano:bone.sh"));
List<GlobCriteria> criteria = globs.getCriteria();
assertThat(criteria).hasSize(3);
assertThat(criteria.get(0).getIncludePatterns()).containsExactly("s*.sh");
assertTrue(criteria.get(0).isGlob());
assertFalse(criteria.get(1).isGlob());
assertThat(criteria.get(2).getIncludePatterns()).containsExactly("b*.sh");
assertTrue(criteria.get(2).isGlob());
}
public void testEvents() throws Exception {
Package pkg = scratchPackage("j", "sh_library(name='s', srcs='s.sh')");
Package pkg2 = deserializer.deserialize(codedInputFromPackage(pkg));
assertThat(pkg2.getEvents()).hasSize(1);
Event ev = pkg2.getEvents().get(0);
assertThat(ev.getMessage()).contains("expected value of type 'list(label)'");
}
public void testPermanentErrorBitIsKept() throws Exception {
Package pkg = scratchPackage("g", "sh_library(name='g', srcs=g.sh)");
Package pkg2 = deserializer.deserialize(codedInputFromPackage(pkg));
assertTrue(pkg2.containsErrors());
}
public void testBasicSerialization() throws Exception {
checkSerialization(scratchPackage("bacon",
"sh_library(name='bacon',",
" srcs=['bacon.sh'],",
" data=glob(['*.txt']))"));
}
public void testIntList() throws Exception {
Build.Attribute.Builder attrPb = Build.Attribute.newBuilder();
attrPb.setName("has_int_list").setType(Discriminator.INTEGER_LIST);
attrPb.addIntListValue(1).addIntListValue(2).addIntListValue(3).addIntListValue(4);
assertThat(PackageDeserializer.deserializeAttributeValue(
com.google.devtools.build.lib.syntax.Type.INTEGER_LIST, attrPb.build()))
.isEqualTo(ImmutableList.of(1, 2, 3, 4));
}
public void testExternalPackageLabel() throws Exception {
PackageIdentifier packageId = PackageIdentifier.create("@foo", new PathFragment("p"));
Package pkg = scratchPackage(packageId, "filegroup(name = 'rumple', srcs = ['dark_one'])");
assertEquals(packageId, pkg.getPackageIdentifier());
Package pkg2 = deserializer.deserialize(codedInputFromPackage(pkg));
assertEquals(pkg.getPackageIdentifier(), pkg2.getPackageIdentifier());
}
public void testConfigurableAttributes() throws Exception {
Package pkg = scratchPackage("pkg",
"sh_library(",
" name = 'bread',",
" srcs = select({",
" ':one': ['rye.sh'],",
" ':two': ['wheat.sh'],",
" }))");
Package deserialized = deserializer.deserialize(codedInputFromPackage(pkg));
AttributeMap attrs = RawAttributeMapper.of(deserialized.getRule("bread"));
// We expect package serialization to "flatten" configurable attributes, e.g. the deserialized
// rule should look like "srcs = ['rye.sh', 'wheat.sh']" (without the select). Eventually
// we'll want to preserve the original structure, but that requires syntactic changes to the
// proto which we'll need to ensure the depserver understands.
assertThat(attrs.get("srcs", BuildType.LABEL_LIST)).containsExactly(
Label.parseAbsolute("//pkg:rye.sh"), Label.parseAbsolute("//pkg:wheat.sh"));
}
public void testConfigurableDictionaryAttribute() throws Exception {
// Although we expect package serialization to "flatten" configurable attributes, as described
// above, package deserialization should not crash if multiple configuration entries for a
// map valued attribute have keys in common.
checkSerialization(scratchPackage("pkg",
"cc_library(",
" name = 'bread',",
" srcs = ['PROTECTED/rye.cc'],",
" abi_deps = select({",
" ':one': {'duplicated_key': [':value1']},",
" ':two': {'duplicated_key': [':value2']},",
" }))"));
}
public void testRuleAttributesWithNullValuesAreIncludedInSerializedRepresentation()
throws Exception {
Package pkg = scratchPackage("lettuce",
"sh_library(name='lettuce',",
" srcs=['romaine.sh'])");
// Manually parse stream, we're interested in wire representation.
CodedInputStream codedIn = codedInputFromPackage(pkg);
readPackage(codedIn);
Multimap<Build.Target.Discriminator, Build.Target> targets = readTargets(codedIn);
// Check that we encoded "deprecation" in the rule proto output even though it had no value.
Build.Rule pbRule =
Iterables.getOnlyElement(targets.get(Build.Target.Discriminator.RULE)).getRule();
boolean foundEmptyAttribute = false;
for (Build.Attribute attribute : pbRule.getAttributeList()) {
if (attribute.getName().equals("deprecation")) {
assertFalse(attribute.hasStringValue());
foundEmptyAttribute = true;
}
}
assertTrue(foundEmptyAttribute);
}
public void testTargetsIndividuallySerializedAndDeserialized() throws Exception {
scratchPackage("empty",
"sh_library(name='noop')");
Package pkg = scratchPackage("food",
"sh_library(name='pork',",
" srcs=['pig.sh'])",
"sh_library(name='salmon',",
" srcs=['river.sh'])",
"package_group(name='self',",
" packages=['//food'])");
// Manually parse stream, we're interested in wire representation.
CodedInputStream codedIn = codedInputFromPackage(pkg);
readPackage(codedIn);
Multimap<Build.Target.Discriminator, Build.Target> targets = readTargets(codedIn);
assertThat(targets).hasSize(6);
assertThat(targets.get(Build.Target.Discriminator.SOURCE_FILE)).hasSize(3);
assertThat(targets.get(Build.Target.Discriminator.RULE)).hasSize(2);
assertThat(targets.get(Build.Target.Discriminator.PACKAGE_GROUP)).hasSize(1);
// Make sure we see the same thing when deserializing all the way.
Package deserialized = deserializer.deserialize(codedInputFromPackage(pkg));
assertThat(deserialized.getTargets(InputFile.class)).hasSize(3);
assertThat(deserialized.getTargets(Rule.class)).hasSize(2);
assertThat(deserialized.getTargets(PackageGroup.class)).hasSize(1);
}
public void testMassivePackageDeserializesFine() throws Exception {
// Create a package definition which exports 2^16 files with name lengths 2^10 each, which
// should result in 2^26 (64MB) of targets. With overhead this should push us comfortably
// over the 64MB default protocol buffer deserialization limit.
StringBuilder sb = new StringBuilder();
sb.append("exports_files([");
String srcName = Strings.repeat("x", 1 << 10);
for (int i = 0; i < (1 << 16); i++) {
sb.append("'").append(srcName).append(i).append("',");
}
sb.append("'last'])");
Package pkg = scratchPackage("meat", sb.toString());
// Check that we created our package correctly. There should be 2^16 + 1 dummy targets from
// our exports_files, plus the BUILD file.
assertThat(pkg.containsErrors()).isFalse();
assertThat(pkg.getTargets()).hasSize((1 << 16) + 2);
checkSerialization(pkg);
}
private static Multimap<Build.Target.Discriminator, Build.Target> readTargets(
CodedInputStream codedIn) throws IOException {
Multimap<Build.Target.Discriminator, Build.Target> targets = ArrayListMultimap.create();
Build.TargetOrTerminator tot;
while (!(tot = readNext(codedIn)).getIsTerminator()) {
Build.Target pbTarget = tot.getTarget();
targets.put(pbTarget.getType(), pbTarget);
}
return targets;
}
private static Build.Package readPackage(CodedInputStream codedIn) throws IOException {
Build.Package.Builder builder = Build.Package.newBuilder();
codedIn.readMessage(builder, ExtensionRegistryLite.getEmptyRegistry());
return builder.build();
}
private static Build.TargetOrTerminator readNext(CodedInputStream codedIn) throws IOException {
Build.TargetOrTerminator.Builder builder = Build.TargetOrTerminator.newBuilder();
codedIn.readMessage(builder, ExtensionRegistryLite.getEmptyRegistry());
return builder.build();
}
private void assertEmpty(Location location) {
assertEquals(0, location.getStartOffset());
assertEquals(0, location.getEndOffset());
assertEquals(new PathFragment("/dev/null"), location.getPath());
assertEquals(0, location.getStartLineAndColumn().getLine());
assertEquals(0, location.getStartLineAndColumn().getColumn());
assertEquals(0, location.getEndLineAndColumn().getLine());
assertEquals(0, location.getEndLineAndColumn().getColumn());
}
}