blob: 57fc583b84c7ee02ae2b868d8b6ddbf2ed7fc17c [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.android;
import static com.google.common.truth.Truth.assertAbout;
import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.android.ParsedAndroidDataBuilder.xml;
import com.android.aapt.Resources.Attribute;
import com.android.aapt.Resources.CompoundValue;
import com.android.aapt.Resources.Value;
import com.google.auto.value.AutoValue;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.jimfs.Jimfs;
import com.google.common.truth.Subject;
import com.google.devtools.build.android.resources.Visibility;
import com.google.devtools.build.android.xml.AttrXmlResourceValue;
import com.google.devtools.build.android.xml.AttrXmlResourceValue.ReferenceResourceXmlAttrValue;
import com.google.devtools.build.android.xml.AttrXmlResourceValue.StringResourceXmlAttrValue;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
/** Parameterized tests for {@link AndroidDataMerger} focusing on merging attribute resoutces. */
@RunWith(Parameterized.class)
public final class AttributeResourcesAndroidDataMergerTest {
/** For test data readability, represent strength as a 2-state enum rather than a boolean. */
enum Strength {
STRONG,
WEAK
}
@Parameters(name = "{index}: {0}")
public static Collection<Object[]> data() {
return Arrays.asList(
new Object[][] {
{
TestParameters.builder()
.set1(Strength.STRONG, 1)
.set2(Strength.STRONG, 0xFFFF)
.setExpectedMergeConflict(
ctx ->
MergeConflict.of(
ctx.fqnFactory.parse("attr/ambiguousName"),
DataResourceXml.createWithNoNamespace(
ctx.transitiveRoot1.resolve("res/values/attrs.xml"),
AttrXmlResourceValue.fromFormatEntries(
ReferenceResourceXmlAttrValue.asEntry())),
DataResourceXml.createWithNoNamespace(
ctx.transitiveRoot2.resolve("res/values/attrs.xml"),
AttrXmlResourceValue.fromFormatEntries())))
.build()
},
{
TestParameters.builder()
.set1(Strength.WEAK, 1)
.set2(Strength.WEAK, 0xFFFF)
.setExpectedMergedAndroidData(
ctx ->
UnwrittenMergedAndroidData.of(
ctx.primary.getManifest(),
ParsedAndroidDataBuilder.empty(),
ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
.overwritable(
xml("attr/ambiguousName")
.root(ctx.primaryRoot)
.source(ctx.transitiveAttr1.overwrite(ctx.transitiveAttr2))
.value(
AttrXmlResourceValue.weakFromFormatEntries(
ReferenceResourceXmlAttrValue.asEntry())))
.build()))
.build()
},
{
TestParameters.builder()
.set1(Strength.WEAK, 0xFFFF)
.set2(Strength.WEAK, 1)
.setExpectedMergedAndroidData(
ctx ->
UnwrittenMergedAndroidData.of(
ctx.primary.getManifest(),
ParsedAndroidDataBuilder.empty(),
ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
.overwritable(
xml("attr/ambiguousName")
.root(ctx.primaryRoot)
.source(ctx.transitiveAttr2.overwrite(ctx.transitiveAttr1))
.value(
AttrXmlResourceValue.weakFromFormatEntries(
ReferenceResourceXmlAttrValue.asEntry())))
.build()))
.build()
},
{
TestParameters.builder()
.set1(Strength.STRONG, 1)
.set2(Strength.STRONG, 1)
.setExpectedMergedAndroidData(
ctx ->
UnwrittenMergedAndroidData.of(
ctx.primary.getManifest(),
ParsedAndroidDataBuilder.empty(),
ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
.overwritable(
xml("attr/ambiguousName")
.root(ctx.primaryRoot)
.source(ctx.transitiveAttr2.overwrite(ctx.transitiveAttr1))
.value(
AttrXmlResourceValue.fromFormatEntries(
ReferenceResourceXmlAttrValue.asEntry())))
.build()))
.build()
},
{
TestParameters.builder()
.set1(Strength.WEAK, 1)
.set2(Strength.WEAK, 1)
.setExpectedMergedAndroidData(
ctx ->
UnwrittenMergedAndroidData.of(
ctx.primary.getManifest(),
ParsedAndroidDataBuilder.empty(),
ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
.overwritable(
xml("attr/ambiguousName")
.root(ctx.primaryRoot)
.source(ctx.transitiveAttr2.overwrite(ctx.transitiveAttr1))
.value(
AttrXmlResourceValue.weakFromFormatEntries(
ReferenceResourceXmlAttrValue.asEntry())))
.build()))
.build()
},
{
TestParameters.builder()
.set1(Strength.STRONG, 1)
.set2(Strength.WEAK, 0xFFFF)
.setExpectedMergedAndroidData(
ctx ->
UnwrittenMergedAndroidData.of(
ctx.primary.getManifest(),
ParsedAndroidDataBuilder.empty(),
ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
.overwritable(
xml("attr/ambiguousName")
.root(ctx.primaryRoot)
.source(ctx.transitiveAttr1.overwrite(ctx.transitiveAttr2))
.value(
AttrXmlResourceValue.fromFormatEntries(
ReferenceResourceXmlAttrValue.asEntry())))
.build()))
.build()
},
{
TestParameters.builder()
.set1(Strength.WEAK, 0xFFFF)
.set2(Strength.STRONG, 2)
.setExpectedMergedAndroidData(
ctx ->
UnwrittenMergedAndroidData.of(
ctx.primary.getManifest(),
ParsedAndroidDataBuilder.empty(),
ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
.overwritable(
xml("attr/ambiguousName")
.root(ctx.primaryRoot)
.source(ctx.transitiveAttr2.overwrite(ctx.transitiveAttr1))
.value(
AttrXmlResourceValue.fromFormatEntries(
StringResourceXmlAttrValue.asEntry())))
.build()))
.build()
},
{
TestParameters.builder()
.set1(Strength.STRONG, 1)
.set2(Strength.WEAK, 2)
.setExpectedMergeConflict(
ctx ->
MergeConflict.of(
ctx.fqnFactory.parse("attr/ambiguousName"),
DataResourceXml.createWithNoNamespace(
ctx.transitiveRoot1.resolve("res/values/attrs.xml"),
AttrXmlResourceValue.fromFormatEntries(
ReferenceResourceXmlAttrValue.asEntry())),
DataResourceXml.createWithNoNamespace(
ctx.transitiveRoot2.resolve("res/values/attrs.xml"),
AttrXmlResourceValue.weakFromFormatEntries(
StringResourceXmlAttrValue.asEntry()))))
.build()
},
{
TestParameters.builder()
.set1(Strength.WEAK, 1)
.set2(Strength.STRONG, 2)
.setExpectedMergeConflict(
ctx ->
MergeConflict.of(
ctx.fqnFactory.parse("attr/ambiguousName"),
DataResourceXml.createWithNoNamespace(
ctx.transitiveRoot1.resolve("res/values/attrs.xml"),
AttrXmlResourceValue.weakFromFormatEntries(
ReferenceResourceXmlAttrValue.asEntry())),
DataResourceXml.createWithNoNamespace(
ctx.transitiveRoot2.resolve("res/values/attrs.xml"),
AttrXmlResourceValue.fromFormatEntries(
StringResourceXmlAttrValue.asEntry()))))
.build()
},
{
TestParameters.builder()
.set1(Strength.WEAK, 1)
.set2(Strength.WEAK, 2)
.setExpectedMergeConflict(
ctx ->
MergeConflict.of(
ctx.fqnFactory.parse("attr/ambiguousName"),
DataResourceXml.createWithNoNamespace(
ctx.transitiveRoot1.resolve("res/values/attrs.xml"),
AttrXmlResourceValue.weakFromFormatEntries(
ReferenceResourceXmlAttrValue.asEntry())),
DataResourceXml.createWithNoNamespace(
ctx.transitiveRoot2.resolve("res/values/attrs.xml"),
AttrXmlResourceValue.weakFromFormatEntries(
StringResourceXmlAttrValue.asEntry()))))
.build()
},
{
TestParameters.builder()
.set1(Strength.STRONG, 1)
.set2(Strength.WEAK, 1)
.setExpectedMergedAndroidData(
ctx ->
UnwrittenMergedAndroidData.of(
ctx.primary.getManifest(),
ParsedAndroidDataBuilder.empty(),
ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
.overwritable(
xml("attr/ambiguousName")
.root(ctx.primaryRoot)
.source(ctx.transitiveAttr1.overwrite(ctx.transitiveAttr2))
.value(
AttrXmlResourceValue.fromFormatEntries(
ReferenceResourceXmlAttrValue.asEntry())))
.build()))
.build()
},
{
TestParameters.builder()
.set1(Strength.WEAK, 1)
.set2(Strength.STRONG, 1)
.setExpectedMergedAndroidData(
ctx ->
UnwrittenMergedAndroidData.of(
ctx.primary.getManifest(),
ParsedAndroidDataBuilder.empty(),
ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
.overwritable(
xml("attr/ambiguousName")
.root(ctx.primaryRoot)
.source(ctx.transitiveAttr2.overwrite(ctx.transitiveAttr1))
.value(
AttrXmlResourceValue.fromFormatEntries(
ReferenceResourceXmlAttrValue.asEntry())))
.build()))
.build()
},
{
TestParameters.builder()
.set1(Strength.STRONG, 0xFFFF)
.set2(Strength.STRONG, 0xFFFF)
.setExpectedMergedAndroidData(
ctx ->
UnwrittenMergedAndroidData.of(
ctx.primary.getManifest(),
ParsedAndroidDataBuilder.empty(),
ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
.overwritable(
xml("attr/ambiguousName")
.root(ctx.primaryRoot)
.source(ctx.transitiveAttr2.overwrite(ctx.transitiveAttr1))
.value(AttrXmlResourceValue.fromFormatEntries()))
.build()))
.build()
},
{
TestParameters.builder()
.set1(Strength.WEAK, 0xFFFF)
.set2(Strength.WEAK, 0xFFFF)
.setExpectedMergedAndroidData(
ctx ->
UnwrittenMergedAndroidData.of(
ctx.primary.getManifest(),
ParsedAndroidDataBuilder.empty(),
ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
.overwritable(
xml("attr/ambiguousName")
.root(ctx.primaryRoot)
.source(ctx.transitiveAttr2.overwrite(ctx.transitiveAttr1))
.value(AttrXmlResourceValue.weakFromFormatEntries()))
.build()))
.build()
},
{
TestParameters.builder()
.set1(Strength.STRONG, 0xFFFF)
.set2(Strength.WEAK, 0xFFFF)
.setExpectedMergedAndroidData(
ctx ->
UnwrittenMergedAndroidData.of(
ctx.primary.getManifest(),
ParsedAndroidDataBuilder.empty(),
ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
.overwritable(
xml("attr/ambiguousName")
.root(ctx.primaryRoot)
.source(ctx.transitiveAttr1.overwrite(ctx.transitiveAttr2))
.value(AttrXmlResourceValue.fromFormatEntries()))
.build()))
.build()
},
{
TestParameters.builder()
.set1(Strength.WEAK, 0xFFFF)
.set2(Strength.STRONG, 0xFFFF)
.setExpectedMergedAndroidData(
ctx ->
UnwrittenMergedAndroidData.of(
ctx.primary.getManifest(),
ParsedAndroidDataBuilder.empty(),
ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
.overwritable(
xml("attr/ambiguousName")
.root(ctx.primaryRoot)
.source(ctx.transitiveAttr2.overwrite(ctx.transitiveAttr1))
.value(AttrXmlResourceValue.fromFormatEntries()))
.build()))
.build()
},
{
TestParameters.builder()
.set1(Strength.STRONG, 0xFFFF)
.set2(Strength.WEAK, 1)
.setExpectedMergeConflict(
ctx ->
MergeConflict.of(
ctx.fqnFactory.parse("attr/ambiguousName"),
DataResourceXml.createWithNoNamespace(
ctx.transitiveRoot1.resolve("res/values/attrs.xml"),
AttrXmlResourceValue.fromFormatEntries()),
DataResourceXml.createWithNoNamespace(
ctx.transitiveRoot2.resolve("res/values/attrs.xml"),
AttrXmlResourceValue.weakFromFormatEntries(
ReferenceResourceXmlAttrValue.asEntry()))))
.build()
},
{
TestParameters.builder()
.set1(Strength.WEAK, 1)
.set2(Strength.STRONG, 0xFFFF)
.setExpectedMergeConflict(
ctx ->
MergeConflict.of(
ctx.fqnFactory.parse("attr/ambiguousName"),
DataResourceXml.createWithNoNamespace(
ctx.transitiveRoot1.resolve("res/values/attrs.xml"),
AttrXmlResourceValue.weakFromFormatEntries(
ReferenceResourceXmlAttrValue.asEntry())),
DataResourceXml.createWithNoNamespace(
ctx.transitiveRoot2.resolve("res/values/attrs.xml"),
AttrXmlResourceValue.fromFormatEntries())))
.build()
}
});
}
@Parameter public TestParameters testParameters;
private FullyQualifiedName.Factory fqnFactory;
private TestLoggingHandler loggingHandler;
private Logger mergerLogger;
private Path primaryRoot;
private Path transitiveRoot1;
private Path transitiveRoot2;
private DataSource transitiveAttr1;
private DataSource transitiveAttr2;
private UnvalidatedAndroidData primary;
@Before
public void setUp() throws Exception {
FileSystem fileSystem = Jimfs.newFileSystem();
fqnFactory = FullyQualifiedName.Factory.from(ImmutableList.of());
mergerLogger = Logger.getLogger(AndroidDataMerger.class.getCanonicalName());
loggingHandler = new TestLoggingHandler();
mergerLogger.addHandler(loggingHandler);
primaryRoot = fileSystem.getPath("primary");
transitiveRoot1 = fileSystem.getPath("transitive1");
transitiveRoot2 = fileSystem.getPath("transitive2");
transitiveAttr1 =
DataSource.of(
DependencyInfo.UNKNOWN, transitiveRoot1.resolve("res").resolve("values/attrs.xml"));
transitiveAttr2 =
DataSource.of(
DependencyInfo.UNKNOWN, transitiveRoot2.resolve("res").resolve("values/attrs.xml"));
primary =
AndroidDataBuilder.of(primaryRoot)
.createManifest("AndroidManifest.xml", "com.google.mergetest")
.buildUnvalidated();
}
@After
public void removeLoggingHandler() {
mergerLogger.removeHandler(loggingHandler);
}
@Test
public void test() throws Exception {
ParsedAndroidData transitiveDependency =
ParsedAndroidDataBuilder.buildOn(fqnFactory)
.overwritable(
xml("attr/ambiguousName")
.root(transitiveRoot1)
.source("values/attrs.xml")
.value(
AttrXmlResourceValue.from(
Value.newBuilder()
.setCompoundValue(
CompoundValue.newBuilder()
.setAttr(
Attribute.newBuilder()
.setFormatFlags(testParameters.formatFlags1())))
.setWeak(testParameters.strength1() == Strength.WEAK)
.build(),
Visibility.UNKNOWN)))
.overwritable(
xml("attr/ambiguousName")
.root(transitiveRoot2)
.source("values/attrs.xml")
.value(
AttrXmlResourceValue.from(
Value.newBuilder()
.setCompoundValue(
CompoundValue.newBuilder()
.setAttr(
Attribute.newBuilder()
.setFormatFlags(testParameters.formatFlags2())))
.setWeak(testParameters.strength2() == Strength.WEAK)
.build(),
Visibility.UNKNOWN)))
.build();
ParsedAndroidData directDependency = ParsedAndroidDataBuilder.empty();
AndroidDataMerger merger = AndroidDataMerger.createWithDefaults();
UnwrittenMergedAndroidData data =
merger.merge(transitiveDependency, directDependency, primary, false, false);
if (testParameters.expectedMergedAndroidData().isPresent()) {
assertAbout(unwrittenMergedAndroidData)
.that(data)
.isEqualTo(testParameters.expectedMergedAndroidData().get().apply(this));
}
if (testParameters.expectedMergeConflict().isPresent()) {
assertThat(loggingHandler.warnings)
.containsExactly(
testParameters.expectedMergeConflict().get().apply(this).toConflictMessage());
} else {
assertThat(loggingHandler.warnings).isEmpty();
}
}
private final Subject.Factory<UnwrittenMergedAndroidDataSubject, UnwrittenMergedAndroidData>
unwrittenMergedAndroidData = UnwrittenMergedAndroidDataSubject::new;
private static final class TestLoggingHandler extends Handler {
public final List<String> warnings = new ArrayList<>();
@Override
public void publish(LogRecord record) {
if (record.getLevel().equals(Level.WARNING)) {
warnings.add(record.getMessage());
}
}
@Override
public void flush() {}
@Override
public void close() {}
}
@AutoValue
abstract static class TestParameters {
abstract Strength strength1();
abstract int formatFlags1();
abstract Strength strength2();
abstract int formatFlags2();
abstract Optional<Function<AttributeResourcesAndroidDataMergerTest, UnwrittenMergedAndroidData>>
expectedMergedAndroidData();
abstract Optional<Function<AttributeResourcesAndroidDataMergerTest, MergeConflict>>
expectedMergeConflict();
@Override
public final String toString() {
return Joiner.on(", ")
.join(
strength1(),
formatFlags1(),
strength2(),
formatFlags2(),
expectedMergeConflict().isPresent()
? "conflict expected"
: "successful merge expected");
}
static Builder builder() {
return new AutoValue_AttributeResourcesAndroidDataMergerTest_TestParameters.Builder();
}
@AutoValue.Builder
abstract static class Builder {
Builder set1(Strength strength, int formatFlags) {
return setStrength1(strength).setFormatFlags1(formatFlags);
}
Builder set2(Strength strength, int formatFlags) {
return setStrength2(strength).setFormatFlags2(formatFlags);
}
abstract Builder setStrength1(Strength value);
abstract Builder setFormatFlags1(int value);
abstract Builder setStrength2(Strength value);
abstract Builder setFormatFlags2(int value);
abstract Builder setExpectedMergedAndroidData(
Function<AttributeResourcesAndroidDataMergerTest, UnwrittenMergedAndroidData> value);
abstract Builder setExpectedMergeConflict(
Function<AttributeResourcesAndroidDataMergerTest, MergeConflict> value);
abstract TestParameters build();
}
}
}