aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test/java/com/google/devtools/build/lib/rules/android/ResourceTestBase.java
blob: dad6ee04fea9727c8a55f9a15971b1ef98ed1246 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
// 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.build.lib.rules.android;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.Streams;
import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.ArtifactOwner;
import com.google.devtools.build.lib.actions.ArtifactRoot;
import com.google.devtools.build.lib.analysis.CachingAnalysisEnvironment;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.events.ExtendedEventHandler;
import com.google.devtools.build.lib.events.StoredEventHandler;
import com.google.devtools.build.lib.packages.AbstractRuleErrorConsumer;
import com.google.devtools.build.lib.packages.RuleErrorConsumer;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
import com.google.devtools.build.lib.vfs.FileSystem;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.After;
import org.junit.Before;

/** Base class for tests that work with resource artifacts. */
public abstract class ResourceTestBase extends AndroidBuildViewTestCase {
  public static final String RESOURCE_ROOT = "java/android/res";

  private static final ImmutableSet<String> TOOL_FILENAMES =
      ImmutableSet.of(
          "static_aapt_tool",
          "aapt.static",
          "aapt",
          "aapt2",
          "empty.sh",
          "android_blaze.jar",
          "android.jar",
          "ResourceProcessorBusyBox_deploy.jar");

  private static final ArtifactOwner OWNER = () -> {
    try {
      return Label.create("java", "all");
    } catch (LabelSyntaxException e) {
      assertWithMessage(e.getMessage()).fail();
      return null;
    }
  };

  /** A faked {@link RuleErrorConsumer} that validates that only expected errors were reported. */
  public static final class FakeRuleErrorConsumer extends AbstractRuleErrorConsumer
      implements RuleErrorConsumer {
    private String ruleErrorMessage = null;
    private String attributeErrorAttribute = null;
    private String attributeErrorMessage = null;

    private final List<String> ruleWarnings = new ArrayList<>();

    // Use an ArrayListMultimap since it allows duplicates - we'll want to know if a warning is
    // reported twice.
    private final Multimap<String, String> attributeWarnings = ArrayListMultimap.create();

    @Override
    public void ruleWarning(String message) {
      ruleWarnings.add(message);
    }

    @Override
    public void ruleError(String message) {
      ruleErrorMessage = message;
    }

    @Override
    public void attributeWarning(String attrName, String message) {
      attributeWarnings.put(attrName, message);
    }

    @Override
    public void attributeError(String attrName, String message) {
      attributeErrorAttribute = attrName;
      attributeErrorMessage = message;
    }

    @Override
    public boolean hasErrors() {
      return ruleErrorMessage != null || attributeErrorMessage != null;
    }

    public Collection<String> getAndClearRuleWarnings() {
      Collection<String> warnings = ImmutableList.copyOf(ruleWarnings);
      ruleWarnings.clear();
      return warnings;
    }

    public void assertNoRuleWarnings() {
      assertThat(ruleWarnings).isEmpty();
    }

    public Collection<String> getAndClearAttributeWarnings(String attrName) {
      if (!attributeWarnings.containsKey(attrName)) {
        return ImmutableList.of();
      }

      return attributeWarnings.removeAll(attrName);
    }

    public void assertNoAttributeWarnings(String attrName) {
      assertThat(attributeWarnings).doesNotContainKey(attrName);
    }

    /**
     * Called at the end of a test to assert that that test produced a rule error
     *
     * @param expectedMessage a substring of the expected message
     */
    public void assertRuleError(String expectedMessage) {
      // Clear the message before asserting so that if we fail here the error is not masked by the
      // @After call to assertNoUnexpectedErrors.

      String message = ruleErrorMessage;
      ruleErrorMessage = null;

      assertThat(message).contains(expectedMessage);
    }

    /**
     * Called at the end of a test to assert that that test produced an attribute error
     *
     * @param expectedAttribute the attribute that caused the error
     * @param expectedMessage a substring of the expected message
     */
    public void assertAttributeError(String expectedAttribute, String expectedMessage) {
      // Clear the message before asserting so that if we fail here the error is not masked by the
      // @After call to assertNoUnexpectedErrors.
      String attr = attributeErrorAttribute;
      String message = attributeErrorMessage;
      attributeErrorAttribute = null;
      attributeErrorMessage = null;

      assertThat(message).contains(expectedMessage);
      assertThat(attr).isEqualTo(expectedAttribute);
    }

    /**
     * Asserts this {@link RuleErrorConsumer} encountered no unexpected errors. To consume an
     * expected error, call {@link #assertRuleError(String)} or {@link #assertAttributeError(String,
     * String)} in your test after the error is produced.
     */
    private void assertNoUnexpectedErrors() {
      assertThat(ruleErrorMessage).isNull();
      assertThat(attributeErrorMessage).isNull();
      assertThat(attributeErrorAttribute).isNull();
    }
  };

  public FakeRuleErrorConsumer errorConsumer;
  public FileSystem fileSystem;
  public ArtifactRoot root;

  @Before
  public void setup() {
    errorConsumer = new FakeRuleErrorConsumer();
    fileSystem = new InMemoryFileSystem();
    root = ArtifactRoot.asSourceRoot(Root.fromPath(fileSystem.getPath("/")));
  }

  @After
  public void assertNoErrors() {
    errorConsumer.assertNoUnexpectedErrors();
  }

  public ImmutableList<Artifact> getResources(String... pathStrings) {
    ImmutableList.Builder<Artifact> builder = ImmutableList.builder();
    for (String pathString : pathStrings) {
      builder.add(getResource(pathString));
    }

    return builder.build();
  }

  public Artifact getResource(String pathString) {
    Path path = fileSystem.getPath("/" + RESOURCE_ROOT + "/" + pathString);
    return new Artifact(
        root, root.getExecPath().getRelative(root.getRoot().relativize(path)), OWNER);
  }

  /**
   * Gets a RuleContext that can be used to register actions and test that they are created
   * correctly.
   *
   * <p>Takes in a dummy target which will be used to configure the RuleContext's {@link
   * AndroidConfiguration}.
   */
  public RuleContext getRuleContextForActionTesting(ConfiguredTarget dummyTarget) throws Exception {

    RuleContext dummy = getRuleContext(dummyTarget);

    ExtendedEventHandler eventHandler = new StoredEventHandler();
    return view.getRuleContextForTesting(
        eventHandler,
        dummyTarget,
        /* env= */ new CachingAnalysisEnvironment(
            view.getArtifactFactory(),
            skyframeExecutor.getActionKeyContext(),
            ConfiguredTargetKey.of(dummyTarget.getLabel(), targetConfig),
            /*isSystemEnv=*/ false,
            targetConfig.extendedSanityChecks(),
            eventHandler,
            null,
            /*sourceDependencyListener=*/ unused -> {}),
        new BuildConfigurationCollection(
            ImmutableList.of(dummy.getConfiguration()), dummy.getHostConfiguration()));
  }

  /**
   * Assets that the action used to generate the given outputs has the expected inputs and outputs.
   */
  void assertActionArtifacts(
      RuleContext ruleContext, ImmutableList<Artifact> inputs, ImmutableList<Artifact> outputs) {
    // Actions must have at least one output
    assertThat(outputs).isNotEmpty();

    // Get the action from one of the outputs
    ActionAnalysisMetadata action =
        ruleContext.getAnalysisEnvironment().getLocalGeneratingAction(outputs.get(0));
    assertThat(action).isNotNull();

    assertThat(removeToolingArtifacts(action.getInputs())).containsExactlyElementsIn(inputs);

    assertThat(action.getOutputs()).containsExactlyElementsIn(outputs);
  }

  /** Remove busybox and aapt2 tooling artifacts from a list of action inputs */
  private Iterable<Artifact> removeToolingArtifacts(Iterable<Artifact> inputArtifacts) {
    return Streams.stream(inputArtifacts)
        .filter(
            artifact ->
                // Not a known tool
                !TOOL_FILENAMES.contains(artifact.getFilename())
                    // Not one of the various busybox tools (we get different ones on different OSs)
                    && !artifact.getFilename().contains("busybox")
                    // Not a params file
                    && !artifact.getFilename().endsWith(".params"))
        .collect(Collectors.toList());
  }
}