blob: f643783b297e10f22910bb7fcda1a1452c1cc40d [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.skyframe;
15
Mark Schalleraff46bc2015-10-07 21:25:19 +000016import static com.google.common.truth.Truth.assertThat;
17
Mark Schaller908f4c92015-10-21 18:33:25 +000018import com.google.common.collect.ImmutableMap;
19import com.google.common.collect.ImmutableSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010020import com.google.common.collect.Iterables;
21import com.google.devtools.build.lib.events.Event;
22import com.google.devtools.build.lib.util.Pair;
Mark Schaller6df81792015-12-10 18:47:47 +000023import com.google.devtools.build.lib.util.Preconditions;
Mark Schalleraff46bc2015-10-07 21:25:19 +000024import com.google.devtools.build.skyframe.SkyFunction.Environment;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010025import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010026import java.util.HashMap;
27import java.util.LinkedHashMap;
28import java.util.LinkedHashSet;
29import java.util.Map;
30import java.util.Set;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010031import javax.annotation.Nullable;
32
33/**
34 * A helper class to create graphs and run skyframe tests over these graphs.
35 *
36 * <p>There are two types of values, computing values, which may not be set to a constant value,
37 * and leaf values, which must be set to a constant value and may not have any dependencies.
38 *
39 * <p>Note that the value builder looks into the test values created here to determine how to
40 * behave. However, skyframe will only re-evaluate the value and call the value builder if any of
41 * its dependencies has changed. That means in order to change the set of dependencies of a value,
42 * you need to also change one of its previous dependencies to force re-evaluation. Changing a
43 * computing value does not mark it as modified.
44 */
45public class GraphTester {
46
Michajlo Matijkiw66304262015-10-15 20:54:33 +000047 public static final SkyFunctionName NODE_TYPE = SkyFunctionName.FOR_TESTING;
Mark Schaller908f4c92015-10-21 18:33:25 +000048 private final ImmutableMap<SkyFunctionName, ? extends SkyFunction> functionMap =
49 ImmutableMap.of(GraphTester.NODE_TYPE, new DelegatingFunction());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010050
51 private final Map<SkyKey, TestFunction> values = new HashMap<>();
52 private final Set<SkyKey> modifiedValues = new LinkedHashSet<>();
53
54 public TestFunction getOrCreate(String name) {
55 return getOrCreate(skyKey(name));
56 }
57
58 public TestFunction getOrCreate(SkyKey key) {
59 return getOrCreate(key, false);
60 }
61
62 public TestFunction getOrCreate(SkyKey key, boolean markAsModified) {
63 TestFunction result = values.get(key);
64 if (result == null) {
65 result = new TestFunction();
66 values.put(key, result);
67 } else if (markAsModified) {
68 modifiedValues.add(key);
69 }
70 return result;
71 }
72
73 public TestFunction set(String key, SkyValue value) {
74 return set(skyKey(key), value);
75 }
76
77 public TestFunction set(SkyKey key, SkyValue value) {
78 return getOrCreate(key, true).setConstantValue(value);
79 }
80
Mark Schaller908f4c92015-10-21 18:33:25 +000081 public ImmutableSet<SkyKey> getModifiedValues() {
82 return ImmutableSet.copyOf(modifiedValues);
83 }
84
85 public void clearModifiedValues() {
86 modifiedValues.clear();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010087 }
88
89 public SkyFunction getFunction() {
90 return new SkyFunction() {
91 @Override
92 public SkyValue compute(SkyKey key, Environment env)
93 throws SkyFunctionException, InterruptedException {
94 TestFunction builder = values.get(key);
95 Preconditions.checkState(builder != null, "No TestFunction for " + key);
96 if (builder.builder != null) {
97 return builder.builder.compute(key, env);
98 }
99 if (builder.warning != null) {
100 env.getListener().handle(Event.warn(builder.warning));
101 }
102 if (builder.progress != null) {
103 env.getListener().handle(Event.progress(builder.progress));
104 }
105 Map<SkyKey, SkyValue> deps = new LinkedHashMap<>();
106 boolean oneMissing = false;
107 for (Pair<SkyKey, SkyValue> dep : builder.deps) {
108 SkyValue value;
109 if (dep.second == null) {
110 value = env.getValue(dep.first);
111 } else {
112 try {
113 value = env.getValueOrThrow(dep.first, SomeErrorException.class);
114 } catch (SomeErrorException e) {
115 value = dep.second;
116 }
117 }
118 if (value == null) {
119 oneMissing = true;
120 } else {
121 deps.put(dep.first, value);
122 }
Janak Ramakrishnanb2b7b352016-03-04 20:54:29 +0000123 Preconditions.checkState(
124 oneMissing == env.valuesMissing(), "%s %s %s", dep, value, env.valuesMissing());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100125 }
126 if (env.valuesMissing()) {
127 return null;
128 }
129
130 if (builder.hasTransientError) {
131 throw new GenericFunctionException(new SomeErrorException(key.toString()),
132 Transience.TRANSIENT);
133 }
134 if (builder.hasError) {
135 throw new GenericFunctionException(new SomeErrorException(key.toString()),
136 Transience.PERSISTENT);
137 }
138
139 if (builder.value != null) {
140 return builder.value;
141 }
142
143 if (Thread.currentThread().isInterrupted()) {
144 throw new InterruptedException(key.toString());
145 }
146
147 return builder.computer.compute(deps, env);
148 }
149
150 @Nullable
151 @Override
152 public String extractTag(SkyKey skyKey) {
153 return values.get(skyKey).tag;
154 }
155 };
156 }
157
158 public static SkyKey skyKey(String key) {
janakrbfdad902017-05-03 21:38:28 +0200159 return LegacySkyKey.create(NODE_TYPE, key);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100160 }
161
162 /**
163 * A value in the testing graph that is constructed in the tester.
164 */
165 public class TestFunction {
166 // TODO(bazel-team): We could use a multiset here to simulate multi-pass dependency discovery.
167 private final Set<Pair<SkyKey, SkyValue>> deps = new LinkedHashSet<>();
168 private SkyValue value;
169 private ValueComputer computer;
170 private SkyFunction builder = null;
171
172 private boolean hasTransientError;
173 private boolean hasError;
174
175 private String warning;
176 private String progress;
177
178 private String tag;
179
180 public TestFunction addDependency(String name) {
181 return addDependency(skyKey(name));
182 }
183
184 public TestFunction addDependency(SkyKey key) {
185 deps.add(Pair.<SkyKey, SkyValue>of(key, null));
186 return this;
187 }
188
189 public TestFunction removeDependency(String name) {
190 return removeDependency(skyKey(name));
191 }
192
193 public TestFunction removeDependency(SkyKey key) {
194 deps.remove(Pair.<SkyKey, SkyValue>of(key, null));
195 return this;
196 }
197
198 public TestFunction addErrorDependency(String name, SkyValue altValue) {
199 return addErrorDependency(skyKey(name), altValue);
200 }
201
202 public TestFunction addErrorDependency(SkyKey key, SkyValue altValue) {
203 deps.add(Pair.of(key, altValue));
204 return this;
205 }
206
207 public TestFunction setConstantValue(SkyValue value) {
208 Preconditions.checkState(this.computer == null);
209 this.value = value;
210 return this;
211 }
212
213 public TestFunction setComputedValue(ValueComputer computer) {
214 Preconditions.checkState(this.value == null);
215 this.computer = computer;
216 return this;
217 }
218
Mark Schaller342b0b82016-03-07 17:36:37 +0000219 public TestFunction unsetComputedValue() {
220 this.computer = null;
221 return this;
222 }
223
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100224 public TestFunction setBuilder(SkyFunction builder) {
225 Preconditions.checkState(this.value == null);
226 Preconditions.checkState(this.computer == null);
227 Preconditions.checkState(deps.isEmpty());
228 Preconditions.checkState(!hasTransientError);
229 Preconditions.checkState(!hasError);
230 Preconditions.checkState(warning == null);
231 Preconditions.checkState(progress == null);
232 this.builder = builder;
233 return this;
234 }
235
236 public TestFunction setHasTransientError(boolean hasError) {
237 this.hasTransientError = hasError;
238 return this;
239 }
240
241 public TestFunction setHasError(boolean hasError) {
242 // TODO(bazel-team): switch to an enum for hasError.
243 this.hasError = hasError;
244 return this;
245 }
246
247 public TestFunction setWarning(String warning) {
248 this.warning = warning;
249 return this;
250 }
251
252 public TestFunction setProgress(String info) {
253 this.progress = info;
254 return this;
255 }
256
257 public TestFunction setTag(String tag) {
258 this.tag = tag;
259 return this;
260 }
261
262 }
263
264 public static SkyKey[] toSkyKeys(String... names) {
265 SkyKey[] result = new SkyKey[names.length];
266 for (int i = 0; i < names.length; i++) {
janakrbfdad902017-05-03 21:38:28 +0200267 result[i] = LegacySkyKey.create(GraphTester.NODE_TYPE, names[i]);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100268 }
269 return result;
270 }
271
272 public static SkyKey toSkyKey(String name) {
273 return toSkyKeys(name)[0];
274 }
275
276 private class DelegatingFunction implements SkyFunction {
277 @Override
278 public SkyValue compute(SkyKey skyKey, Environment env) throws SkyFunctionException,
279 InterruptedException {
280 return getFunction().compute(skyKey, env);
281 }
282
283 @Nullable
284 @Override
285 public String extractTag(SkyKey skyKey) {
286 return getFunction().extractTag(skyKey);
287 }
288 }
289
Mark Schaller908f4c92015-10-21 18:33:25 +0000290 public ImmutableMap<SkyFunctionName, ? extends SkyFunction> getSkyFunctionMap() {
291 return functionMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100292 }
293
294 /**
295 * Simple value class that stores strings.
296 */
297 public static class StringValue implements SkyValue {
Eric Fellheimer26b3d852015-11-19 02:16:24 +0000298 protected final String value;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100299
300 public StringValue(String value) {
301 this.value = value;
302 }
303
304 public String getValue() {
305 return value;
306 }
307
308 @Override
309 public boolean equals(Object o) {
310 if (!(o instanceof StringValue)) {
311 return false;
312 }
313 return value.equals(((StringValue) o).value);
314 }
315
316 @Override
317 public int hashCode() {
318 return value.hashCode();
319 }
320
321 @Override
322 public String toString() {
323 return "StringValue: " + getValue();
324 }
Mark Schalleraff46bc2015-10-07 21:25:19 +0000325
326 public static StringValue of(String string) {
327 return new StringValue(string);
328 }
329
330 public static StringValue from(SkyValue skyValue) {
331 assertThat(skyValue).isInstanceOf(StringValue.class);
332 return (StringValue) skyValue;
333 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100334 }
335
Eric Fellheimer26b3d852015-11-19 02:16:24 +0000336 /** A StringValue that is also a NotComparableSkyValue. */
337 public static class NotComparableStringValue extends StringValue
338 implements NotComparableSkyValue {
339 public NotComparableStringValue(String value) {
340 super(value);
341 }
342
343 @Override
344 public boolean equals(Object o) {
345 throw new UnsupportedOperationException(value + " is incomparable - what are you doing?");
346 }
347
348 @Override
349 public int hashCode() {
350 throw new UnsupportedOperationException(value + " is incomparable - what are you doing?");
351 }
352 }
353
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100354 /**
355 * A callback interface to provide the value computation.
356 */
357 public interface ValueComputer {
358 /** This is called when all the declared dependencies exist. It may request new dependencies. */
359 SkyValue compute(Map<SkyKey, SkyValue> deps, SkyFunction.Environment env)
360 throws InterruptedException;
361 }
362
363 public static final ValueComputer COPY = new ValueComputer() {
364 @Override
365 public SkyValue compute(Map<SkyKey, SkyValue> deps, SkyFunction.Environment env) {
366 return Iterables.getOnlyElement(deps.values());
367 }
368 };
369
370 public static final ValueComputer CONCATENATE = new ValueComputer() {
371 @Override
372 public SkyValue compute(Map<SkyKey, SkyValue> deps, SkyFunction.Environment env) {
373 StringBuilder result = new StringBuilder();
374 for (SkyValue value : deps.values()) {
375 result.append(((StringValue) value).value);
376 }
377 return new StringValue(result.toString());
378 }
379 };
Mark Schalleraff46bc2015-10-07 21:25:19 +0000380
381 public static ValueComputer formatter(final SkyKey key, final String format) {
382 return new ValueComputer() {
383 @Override
384 public SkyValue compute(Map<SkyKey, SkyValue> deps, Environment env)
385 throws InterruptedException {
386 return StringValue.of(String.format(format, StringValue.from(deps.get(key)).getValue()));
387 }
388 };
389 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100390}