blob: b688880b1a83f005102eaf70e461f45a310079bf [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
tomlua155b532017-11-08 20:12:47 +010018import com.google.common.base.Preconditions;
lberkiaea56b32017-05-30 12:35:33 +020019import com.google.common.collect.ImmutableList;
Mark Schaller908f4c92015-10-21 18:33:25 +000020import com.google.common.collect.ImmutableMap;
21import com.google.common.collect.ImmutableSet;
janakr5fb2a482018-03-02 17:48:57 -080022import com.google.common.collect.Interner;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010023import com.google.common.collect.Iterables;
janakr5fb2a482018-03-02 17:48:57 -080024import com.google.devtools.build.lib.concurrent.BlazeInterners;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010025import com.google.devtools.build.lib.events.Event;
ulfjack52b4f682017-07-18 10:12:09 +020026import com.google.devtools.build.lib.events.ExtendedEventHandler.Postable;
janakr5fb2a482018-03-02 17:48:57 -080027import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010028import com.google.devtools.build.lib.util.Pair;
Mark Schalleraff46bc2015-10-07 21:25:19 +000029import com.google.devtools.build.skyframe.SkyFunction.Environment;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010031import java.util.HashMap;
32import java.util.LinkedHashMap;
33import java.util.LinkedHashSet;
34import java.util.Map;
35import java.util.Set;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010036import javax.annotation.Nullable;
37
38/**
39 * A helper class to create graphs and run skyframe tests over these graphs.
40 *
41 * <p>There are two types of values, computing values, which may not be set to a constant value,
42 * and leaf values, which must be set to a constant value and may not have any dependencies.
43 *
44 * <p>Note that the value builder looks into the test values created here to determine how to
45 * behave. However, skyframe will only re-evaluate the value and call the value builder if any of
46 * its dependencies has changed. That means in order to change the set of dependencies of a value,
47 * you need to also change one of its previous dependencies to force re-evaluation. Changing a
48 * computing value does not mark it as modified.
49 */
50public class GraphTester {
51
Michajlo Matijkiw66304262015-10-15 20:54:33 +000052 public static final SkyFunctionName NODE_TYPE = SkyFunctionName.FOR_TESTING;
janakrbdba40f2018-06-04 06:50:17 -070053 private final Map<SkyFunctionName, SkyFunction> functionMap = new HashMap<>();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010054
55 private final Map<SkyKey, TestFunction> values = new HashMap<>();
56 private final Set<SkyKey> modifiedValues = new LinkedHashSet<>();
57
janakrbdba40f2018-06-04 06:50:17 -070058 public GraphTester() {
59 functionMap.put(NODE_TYPE, new DelegatingFunction());
janakre54491e2018-07-11 16:29:13 -070060 functionMap.put(FOR_TESTING_NONHERMETIC, new DelegatingFunction());
janakrbdba40f2018-06-04 06:50:17 -070061 }
62
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010063 public TestFunction getOrCreate(String name) {
64 return getOrCreate(skyKey(name));
65 }
66
67 public TestFunction getOrCreate(SkyKey key) {
68 return getOrCreate(key, false);
69 }
70
71 public TestFunction getOrCreate(SkyKey key, boolean markAsModified) {
72 TestFunction result = values.get(key);
73 if (result == null) {
74 result = new TestFunction();
75 values.put(key, result);
76 } else if (markAsModified) {
77 modifiedValues.add(key);
78 }
79 return result;
80 }
81
82 public TestFunction set(String key, SkyValue value) {
83 return set(skyKey(key), value);
84 }
85
86 public TestFunction set(SkyKey key, SkyValue value) {
87 return getOrCreate(key, true).setConstantValue(value);
88 }
89
Mark Schaller908f4c92015-10-21 18:33:25 +000090 public ImmutableSet<SkyKey> getModifiedValues() {
91 return ImmutableSet.copyOf(modifiedValues);
92 }
93
94 public void clearModifiedValues() {
95 modifiedValues.clear();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010096 }
97
98 public SkyFunction getFunction() {
99 return new SkyFunction() {
100 @Override
101 public SkyValue compute(SkyKey key, Environment env)
102 throws SkyFunctionException, InterruptedException {
103 TestFunction builder = values.get(key);
104 Preconditions.checkState(builder != null, "No TestFunction for " + key);
105 if (builder.builder != null) {
106 return builder.builder.compute(key, env);
107 }
108 if (builder.warning != null) {
109 env.getListener().handle(Event.warn(builder.warning));
110 }
111 if (builder.progress != null) {
112 env.getListener().handle(Event.progress(builder.progress));
113 }
ulfjack52b4f682017-07-18 10:12:09 +0200114 if (builder.postable != null) {
115 env.getListener().post(builder.postable);
116 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100117 Map<SkyKey, SkyValue> deps = new LinkedHashMap<>();
118 boolean oneMissing = false;
119 for (Pair<SkyKey, SkyValue> dep : builder.deps) {
120 SkyValue value;
121 if (dep.second == null) {
122 value = env.getValue(dep.first);
123 } else {
124 try {
125 value = env.getValueOrThrow(dep.first, SomeErrorException.class);
126 } catch (SomeErrorException e) {
127 value = dep.second;
128 }
129 }
130 if (value == null) {
131 oneMissing = true;
132 } else {
133 deps.put(dep.first, value);
134 }
Janak Ramakrishnanb2b7b352016-03-04 20:54:29 +0000135 Preconditions.checkState(
136 oneMissing == env.valuesMissing(), "%s %s %s", dep, value, env.valuesMissing());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100137 }
138 if (env.valuesMissing()) {
139 return null;
140 }
141
142 if (builder.hasTransientError) {
143 throw new GenericFunctionException(new SomeErrorException(key.toString()),
144 Transience.TRANSIENT);
145 }
146 if (builder.hasError) {
147 throw new GenericFunctionException(new SomeErrorException(key.toString()),
148 Transience.PERSISTENT);
149 }
150
151 if (builder.value != null) {
152 return builder.value;
153 }
154
155 if (Thread.currentThread().isInterrupted()) {
156 throw new InterruptedException(key.toString());
157 }
158
159 return builder.computer.compute(deps, env);
160 }
161
162 @Nullable
163 @Override
164 public String extractTag(SkyKey skyKey) {
165 return values.get(skyKey).tag;
166 }
167 };
168 }
169
170 public static SkyKey skyKey(String key) {
janakr5fb2a482018-03-02 17:48:57 -0800171 return Key.create(key);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100172 }
173
janakre54491e2018-07-11 16:29:13 -0700174 public static NonHermeticKey nonHermeticKey(String key) {
175 return NonHermeticKey.create(key);
176 }
177
mschallercfe25a62017-08-05 01:40:57 +0200178 /** A value in the testing graph that is constructed in the tester. */
179 public static class TestFunction {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100180 // TODO(bazel-team): We could use a multiset here to simulate multi-pass dependency discovery.
181 private final Set<Pair<SkyKey, SkyValue>> deps = new LinkedHashSet<>();
182 private SkyValue value;
183 private ValueComputer computer;
184 private SkyFunction builder = null;
185
186 private boolean hasTransientError;
187 private boolean hasError;
188
189 private String warning;
190 private String progress;
ulfjack52b4f682017-07-18 10:12:09 +0200191 private Postable postable;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100192
193 private String tag;
194
195 public TestFunction addDependency(String name) {
196 return addDependency(skyKey(name));
197 }
198
199 public TestFunction addDependency(SkyKey key) {
200 deps.add(Pair.<SkyKey, SkyValue>of(key, null));
201 return this;
202 }
203
204 public TestFunction removeDependency(String name) {
205 return removeDependency(skyKey(name));
206 }
207
208 public TestFunction removeDependency(SkyKey key) {
209 deps.remove(Pair.<SkyKey, SkyValue>of(key, null));
210 return this;
211 }
212
213 public TestFunction addErrorDependency(String name, SkyValue altValue) {
214 return addErrorDependency(skyKey(name), altValue);
215 }
216
217 public TestFunction addErrorDependency(SkyKey key, SkyValue altValue) {
218 deps.add(Pair.of(key, altValue));
219 return this;
220 }
221
222 public TestFunction setConstantValue(SkyValue value) {
223 Preconditions.checkState(this.computer == null);
224 this.value = value;
225 return this;
226 }
227
mschallercfe25a62017-08-05 01:40:57 +0200228 public TestFunction unsetConstantValue() {
229 this.value = null;
230 return this;
231 }
232
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100233 public TestFunction setComputedValue(ValueComputer computer) {
234 Preconditions.checkState(this.value == null);
235 this.computer = computer;
236 return this;
237 }
238
Mark Schaller342b0b82016-03-07 17:36:37 +0000239 public TestFunction unsetComputedValue() {
240 this.computer = null;
241 return this;
242 }
243
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100244 public TestFunction setBuilder(SkyFunction builder) {
245 Preconditions.checkState(this.value == null);
246 Preconditions.checkState(this.computer == null);
247 Preconditions.checkState(deps.isEmpty());
248 Preconditions.checkState(!hasTransientError);
249 Preconditions.checkState(!hasError);
250 Preconditions.checkState(warning == null);
251 Preconditions.checkState(progress == null);
252 this.builder = builder;
253 return this;
254 }
255
shreyaxa3c95c52018-03-19 09:22:24 -0700256 public TestFunction setBuilderUnconditionally(SkyFunction builder) {
257 this.builder = builder;
258 return this;
259 }
260
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100261 public TestFunction setHasTransientError(boolean hasError) {
262 this.hasTransientError = hasError;
263 return this;
264 }
265
266 public TestFunction setHasError(boolean hasError) {
267 // TODO(bazel-team): switch to an enum for hasError.
268 this.hasError = hasError;
269 return this;
270 }
271
272 public TestFunction setWarning(String warning) {
273 this.warning = warning;
274 return this;
275 }
276
277 public TestFunction setProgress(String info) {
278 this.progress = info;
279 return this;
280 }
281
282 public TestFunction setTag(String tag) {
283 this.tag = tag;
284 return this;
285 }
286
ulfjack52b4f682017-07-18 10:12:09 +0200287 public TestFunction setPostable(Postable postable) {
288 this.postable = postable;
289 return this;
290 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100291 }
292
lberkiaea56b32017-05-30 12:35:33 +0200293 public static ImmutableList<SkyKey> toSkyKeys(String... names) {
294 ImmutableList.Builder<SkyKey> result = ImmutableList.builder();
janakr5fb2a482018-03-02 17:48:57 -0800295 for (String element : names) {
296 result.add(Key.create(element));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100297 }
lberkiaea56b32017-05-30 12:35:33 +0200298 return result.build();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100299 }
300
301 public static SkyKey toSkyKey(String name) {
lberkiaea56b32017-05-30 12:35:33 +0200302 return toSkyKeys(name).get(0);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100303 }
304
305 private class DelegatingFunction implements SkyFunction {
306 @Override
307 public SkyValue compute(SkyKey skyKey, Environment env) throws SkyFunctionException,
308 InterruptedException {
309 return getFunction().compute(skyKey, env);
310 }
311
312 @Nullable
313 @Override
314 public String extractTag(SkyKey skyKey) {
315 return getFunction().extractTag(skyKey);
316 }
317 }
318
Mark Schaller908f4c92015-10-21 18:33:25 +0000319 public ImmutableMap<SkyFunctionName, ? extends SkyFunction> getSkyFunctionMap() {
janakrbdba40f2018-06-04 06:50:17 -0700320 return ImmutableMap.copyOf(functionMap);
321 }
322
323 public void putSkyFunction(SkyFunctionName functionName, SkyFunction function) {
324 functionMap.put(functionName, function);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100325 }
326
327 /**
328 * Simple value class that stores strings.
329 */
330 public static class StringValue implements SkyValue {
Eric Fellheimer26b3d852015-11-19 02:16:24 +0000331 protected final String value;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100332
333 public StringValue(String value) {
334 this.value = value;
335 }
336
337 public String getValue() {
338 return value;
339 }
340
341 @Override
342 public boolean equals(Object o) {
343 if (!(o instanceof StringValue)) {
344 return false;
345 }
346 return value.equals(((StringValue) o).value);
347 }
348
349 @Override
350 public int hashCode() {
351 return value.hashCode();
352 }
353
354 @Override
355 public String toString() {
356 return "StringValue: " + getValue();
357 }
Mark Schalleraff46bc2015-10-07 21:25:19 +0000358
359 public static StringValue of(String string) {
360 return new StringValue(string);
361 }
362
363 public static StringValue from(SkyValue skyValue) {
364 assertThat(skyValue).isInstanceOf(StringValue.class);
365 return (StringValue) skyValue;
366 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100367 }
368
Eric Fellheimer26b3d852015-11-19 02:16:24 +0000369 /** A StringValue that is also a NotComparableSkyValue. */
370 public static class NotComparableStringValue extends StringValue
371 implements NotComparableSkyValue {
372 public NotComparableStringValue(String value) {
373 super(value);
374 }
375
376 @Override
377 public boolean equals(Object o) {
378 throw new UnsupportedOperationException(value + " is incomparable - what are you doing?");
379 }
380
381 @Override
382 public int hashCode() {
383 throw new UnsupportedOperationException(value + " is incomparable - what are you doing?");
384 }
385 }
386
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100387 /**
388 * A callback interface to provide the value computation.
389 */
390 public interface ValueComputer {
391 /** This is called when all the declared dependencies exist. It may request new dependencies. */
392 SkyValue compute(Map<SkyKey, SkyValue> deps, SkyFunction.Environment env)
393 throws InterruptedException;
394 }
395
396 public static final ValueComputer COPY = new ValueComputer() {
397 @Override
398 public SkyValue compute(Map<SkyKey, SkyValue> deps, SkyFunction.Environment env) {
399 return Iterables.getOnlyElement(deps.values());
400 }
401 };
402
403 public static final ValueComputer CONCATENATE = new ValueComputer() {
404 @Override
405 public SkyValue compute(Map<SkyKey, SkyValue> deps, SkyFunction.Environment env) {
406 StringBuilder result = new StringBuilder();
407 for (SkyValue value : deps.values()) {
408 result.append(((StringValue) value).value);
409 }
410 return new StringValue(result.toString());
411 }
412 };
Mark Schalleraff46bc2015-10-07 21:25:19 +0000413
414 public static ValueComputer formatter(final SkyKey key, final String format) {
415 return new ValueComputer() {
416 @Override
417 public SkyValue compute(Map<SkyKey, SkyValue> deps, Environment env)
418 throws InterruptedException {
419 return StringValue.of(String.format(format, StringValue.from(deps.get(key)).getValue()));
420 }
421 };
422 }
janakr5fb2a482018-03-02 17:48:57 -0800423
424 @AutoCodec.VisibleForSerialization
425 @AutoCodec
426 static class Key extends AbstractSkyKey<String> {
427 private static final Interner<Key> interner = BlazeInterners.newWeakInterner();
428
janakre54491e2018-07-11 16:29:13 -0700429 private Key(String arg) {
janakr5fb2a482018-03-02 17:48:57 -0800430 super(arg);
431 }
432
janakre54491e2018-07-11 16:29:13 -0700433 @AutoCodec.VisibleForSerialization
434 @AutoCodec.Instantiator
janakr5fb2a482018-03-02 17:48:57 -0800435 static Key create(String arg) {
436 return interner.intern(new Key(arg));
437 }
438
439 @Override
440 public SkyFunctionName functionName() {
441 return SkyFunctionName.FOR_TESTING;
442 }
443 }
janakre54491e2018-07-11 16:29:13 -0700444
445 @AutoCodec.VisibleForSerialization
446 @AutoCodec
447 static class NonHermeticKey extends AbstractSkyKey<String> {
448 private static final Interner<NonHermeticKey> interner = BlazeInterners.newWeakInterner();
449
450 private NonHermeticKey(String arg) {
451 super(arg);
452 }
453
454 @AutoCodec.VisibleForSerialization
455 @AutoCodec.Instantiator
456 static NonHermeticKey create(String arg) {
457 return interner.intern(new NonHermeticKey(arg));
458 }
459
460 @Override
461 public SkyFunctionName functionName() {
462 return FOR_TESTING_NONHERMETIC;
463 }
464 }
465
466 private static final SkyFunctionName FOR_TESTING_NONHERMETIC =
467 SkyFunctionName.createNonHermetic("FOR_TESTING_NONHERMETIC");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100468}