Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 1 | // Copyright 2015 The Bazel Authors. All rights reserved. |
| 2 | // |
| 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. |
| 14 | package com.google.devtools.build.lib.skyframe; |
| 15 | |
| 16 | import static com.google.common.truth.Truth.assertThat; |
Janak Ramakrishnan | 8d239cd | 2016-01-27 21:06:01 +0000 | [diff] [blame] | 17 | import static com.google.devtools.build.skyframe.EvaluationResultSubjectFactory.assertThatEvaluationResult; |
Florian Weikert | 92b2236 | 2015-12-03 10:17:18 +0000 | [diff] [blame] | 18 | import static org.junit.Assert.assertFalse; |
| 19 | import static org.junit.Assert.assertNotNull; |
| 20 | import static org.junit.Assert.assertNull; |
| 21 | import static org.junit.Assert.assertTrue; |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 22 | |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 23 | import com.google.common.collect.ImmutableList; |
Florian Weikert | cca703a | 2015-12-07 09:56:38 +0000 | [diff] [blame] | 24 | import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 25 | import com.google.devtools.build.lib.cmdline.Label; |
| 26 | import com.google.devtools.build.lib.packages.NoSuchPackageException; |
| 27 | import com.google.devtools.build.lib.packages.NoSuchTargetException; |
Mark Schaller | 6df8179 | 2015-12-10 18:47:47 +0000 | [diff] [blame] | 28 | import com.google.devtools.build.lib.util.Preconditions; |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 29 | import com.google.devtools.build.skyframe.EvaluationResult; |
| 30 | import com.google.devtools.build.skyframe.SkyKey; |
| 31 | import com.google.devtools.build.skyframe.SkyValue; |
| 32 | import com.google.devtools.build.skyframe.WalkableGraph; |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 33 | import java.io.IOException; |
Florian Weikert | 92b2236 | 2015-12-03 10:17:18 +0000 | [diff] [blame] | 34 | import org.junit.Test; |
| 35 | import org.junit.runner.RunWith; |
| 36 | import org.junit.runners.JUnit4; |
| 37 | |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 38 | /** Tests for {@link com.google.devtools.build.lib.skyframe.PrepareDepsOfPatternsFunction}. */ |
Florian Weikert | 92b2236 | 2015-12-03 10:17:18 +0000 | [diff] [blame] | 39 | @RunWith(JUnit4.class) |
Florian Weikert | cca703a | 2015-12-07 09:56:38 +0000 | [diff] [blame] | 40 | public class PrepareDepsOfPatternsFunctionTest extends BuildViewTestCase { |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 41 | |
| 42 | private static SkyKey getKeyForLabel(Label label) { |
| 43 | // Note that these tests used to look for TargetMarker SkyKeys before TargetMarker was |
| 44 | // inlined in TransitiveTraversalFunction. Because TargetMarker is now inlined, it doesn't |
| 45 | // appear in the graph. Instead, these tests now look for TransitiveTraversal keys. |
| 46 | return TransitiveTraversalValue.key(label); |
| 47 | } |
| 48 | |
Florian Weikert | 92b2236 | 2015-12-03 10:17:18 +0000 | [diff] [blame] | 49 | @Test |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 50 | public void testFunctionLoadsTargetAndNotUnspecifiedTargets() throws Exception { |
| 51 | // Given a package "//foo" with independent target rules ":foo" and ":foo2", |
| 52 | createFooAndFoo2(/*dependent=*/ false); |
| 53 | |
| 54 | // Given a target pattern sequence consisting of a single-target pattern for "//foo", |
| 55 | ImmutableList<String> patternSequence = ImmutableList.of("//foo"); |
| 56 | |
| 57 | // When PrepareDepsOfPatternsFunction successfully completes evaluation, |
Janak Ramakrishnan | 8d239cd | 2016-01-27 21:06:01 +0000 | [diff] [blame] | 58 | WalkableGraph walkableGraph = getGraphFromPatternsEvaluation(patternSequence); |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 59 | |
Brian Silverman | d7d6d62 | 2016-03-17 09:53:39 +0000 | [diff] [blame] | 60 | // Then the graph contains a value for the target "@//foo:foo", |
| 61 | assertValidValue(walkableGraph, getKeyForLabel(Label.create("@//foo", "foo"))); |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 62 | |
Brian Silverman | d7d6d62 | 2016-03-17 09:53:39 +0000 | [diff] [blame] | 63 | // And the graph does not contain a value for the target "@//foo:foo2". |
| 64 | assertFalse(walkableGraph.exists(getKeyForLabel(Label.create("@//foo", "foo2")))); |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 65 | } |
| 66 | |
Florian Weikert | 92b2236 | 2015-12-03 10:17:18 +0000 | [diff] [blame] | 67 | @Test |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 68 | public void testFunctionLoadsTargetDependencies() throws Exception { |
| 69 | // Given a package "//foo" with target rules ":foo" and ":foo2", |
| 70 | // And given ":foo" depends on ":foo2", |
| 71 | createFooAndFoo2(/*dependent=*/ true); |
| 72 | |
| 73 | // Given a target pattern sequence consisting of a single-target pattern for "//foo", |
| 74 | ImmutableList<String> patternSequence = ImmutableList.of("//foo"); |
| 75 | |
| 76 | // When PrepareDepsOfPatternsFunction successfully completes evaluation, |
Janak Ramakrishnan | 8d239cd | 2016-01-27 21:06:01 +0000 | [diff] [blame] | 77 | WalkableGraph walkableGraph = getGraphFromPatternsEvaluation(patternSequence); |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 78 | |
| 79 | // Then the graph contains an entry for ":foo"'s dependency, ":foo2". |
Brian Silverman | d7d6d62 | 2016-03-17 09:53:39 +0000 | [diff] [blame] | 80 | assertValidValue(walkableGraph, getKeyForLabel(Label.create("@//foo", "foo2"))); |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 81 | } |
| 82 | |
Florian Weikert | 92b2236 | 2015-12-03 10:17:18 +0000 | [diff] [blame] | 83 | @Test |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 84 | public void testFunctionExpandsTargetPatterns() throws Exception { |
Brian Silverman | d7d6d62 | 2016-03-17 09:53:39 +0000 | [diff] [blame] | 85 | // Given a package "@//foo" with independent target rules ":foo" and ":foo2", |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 86 | createFooAndFoo2(/*dependent=*/ false); |
| 87 | |
| 88 | // Given a target pattern sequence consisting of a pattern for "//foo:*", |
| 89 | ImmutableList<String> patternSequence = ImmutableList.of("//foo:*"); |
| 90 | |
| 91 | // When PrepareDepsOfPatternsFunction successfully completes evaluation, |
Janak Ramakrishnan | 8d239cd | 2016-01-27 21:06:01 +0000 | [diff] [blame] | 92 | WalkableGraph walkableGraph = getGraphFromPatternsEvaluation(patternSequence); |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 93 | |
| 94 | // Then the graph contains an entry for ":foo" and ":foo2". |
Brian Silverman | d7d6d62 | 2016-03-17 09:53:39 +0000 | [diff] [blame] | 95 | assertValidValue(walkableGraph, getKeyForLabel(Label.create("@//foo", "foo"))); |
| 96 | assertValidValue(walkableGraph, getKeyForLabel(Label.create("@//foo", "foo2"))); |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 97 | } |
| 98 | |
Florian Weikert | 92b2236 | 2015-12-03 10:17:18 +0000 | [diff] [blame] | 99 | @Test |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 100 | public void testTargetParsingException() throws Exception { |
| 101 | // Given no packages, and a target pattern sequence referring to a non-existent target, |
| 102 | String nonexistentTarget = "//foo:foo"; |
| 103 | ImmutableList<String> patternSequence = ImmutableList.of(nonexistentTarget); |
| 104 | |
| 105 | // When PrepareDepsOfPatternsFunction completes evaluation, |
Janak Ramakrishnan | 8d239cd | 2016-01-27 21:06:01 +0000 | [diff] [blame] | 106 | WalkableGraph walkableGraph = getGraphFromPatternsEvaluation(patternSequence); |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 107 | |
| 108 | // Then the graph does not contain an entry for ":foo", |
Brian Silverman | d7d6d62 | 2016-03-17 09:53:39 +0000 | [diff] [blame] | 109 | assertFalse(walkableGraph.exists(getKeyForLabel(Label.create("@//foo", "foo")))); |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 110 | } |
| 111 | |
Florian Weikert | 92b2236 | 2015-12-03 10:17:18 +0000 | [diff] [blame] | 112 | @Test |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 113 | public void testDependencyTraversalNoSuchPackageException() throws Exception { |
| 114 | // Given a package "//foo" with a target ":foo" that has a dependency on a non-existent target |
| 115 | // "//bar:bar" in a non-existent package "//bar", |
| 116 | createFooWithDependencyOnMissingBarPackage(); |
| 117 | |
| 118 | // Given a target pattern sequence consisting of a single-target pattern for "//foo", |
| 119 | ImmutableList<String> patternSequence = ImmutableList.of("//foo"); |
| 120 | |
| 121 | // When PrepareDepsOfPatternsFunction completes evaluation, |
Janak Ramakrishnan | 8d239cd | 2016-01-27 21:06:01 +0000 | [diff] [blame] | 122 | WalkableGraph walkableGraph = getGraphFromPatternsEvaluation(patternSequence); |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 123 | |
| 124 | // Then the graph contains an entry for ":foo", |
| 125 | assertValidValue( |
| 126 | walkableGraph, |
Brian Silverman | d7d6d62 | 2016-03-17 09:53:39 +0000 | [diff] [blame] | 127 | getKeyForLabel(Label.create("@//foo", "foo")), |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 128 | /*expectTransitiveException=*/ true); |
| 129 | |
| 130 | // And an entry with a NoSuchPackageException for "//bar:bar", |
Brian Silverman | d7d6d62 | 2016-03-17 09:53:39 +0000 | [diff] [blame] | 131 | Exception e = assertException(walkableGraph, getKeyForLabel(Label.create("@//bar", "bar"))); |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 132 | assertThat(e).isInstanceOf(NoSuchPackageException.class); |
| 133 | } |
| 134 | |
Florian Weikert | 92b2236 | 2015-12-03 10:17:18 +0000 | [diff] [blame] | 135 | @Test |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 136 | public void testDependencyTraversalNoSuchTargetException() throws Exception { |
| 137 | // Given a package "//foo" with a target ":foo" that has a dependency on a non-existent target |
| 138 | // "//bar:bar" in an existing package "//bar", |
| 139 | createFooWithDependencyOnBarPackageWithMissingTarget(); |
| 140 | |
| 141 | // Given a target pattern sequence consisting of a single-target pattern for "//foo", |
| 142 | ImmutableList<String> patternSequence = ImmutableList.of("//foo"); |
| 143 | |
| 144 | // When PrepareDepsOfPatternsFunction completes evaluation, |
Janak Ramakrishnan | 8d239cd | 2016-01-27 21:06:01 +0000 | [diff] [blame] | 145 | WalkableGraph walkableGraph = getGraphFromPatternsEvaluation(patternSequence); |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 146 | |
| 147 | // Then the graph contains an entry for ":foo" which has both a value and an exception, |
| 148 | assertValidValue( |
| 149 | walkableGraph, |
Brian Silverman | d7d6d62 | 2016-03-17 09:53:39 +0000 | [diff] [blame] | 150 | getKeyForLabel(Label.create("@//foo", "foo")), |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 151 | /*expectTransitiveException=*/ true); |
| 152 | |
| 153 | // And an entry with a NoSuchTargetException for "//bar:bar", |
Brian Silverman | d7d6d62 | 2016-03-17 09:53:39 +0000 | [diff] [blame] | 154 | Exception e = assertException(walkableGraph, getKeyForLabel(Label.create("@//bar", "bar"))); |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 155 | assertThat(e).isInstanceOf(NoSuchTargetException.class); |
| 156 | } |
| 157 | |
Florian Weikert | 92b2236 | 2015-12-03 10:17:18 +0000 | [diff] [blame] | 158 | @Test |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 159 | public void testParsingProblemsKeepGoing() throws Exception { |
| 160 | parsingProblem(/*keepGoing=*/ true); |
| 161 | } |
| 162 | |
| 163 | /** |
Janak Ramakrishnan | 18d0a5d | 2016-11-16 19:35:48 +0000 | [diff] [blame] | 164 | * PrepareDepsOfPatternsFunction always keeps going despite any target pattern parsing errors, in |
| 165 | * keeping with the original behavior of {@link WalkableGraph.WalkableGraphFactory#prepareAndGet}, |
| 166 | * which always used {@code keepGoing=true} during target pattern parsing because it was |
| 167 | * responsible for ensuring that queries had a complete graph to work on. |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 168 | */ |
Florian Weikert | 92b2236 | 2015-12-03 10:17:18 +0000 | [diff] [blame] | 169 | @Test |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 170 | public void testParsingProblemsNoKeepGoing() throws Exception { |
| 171 | parsingProblem(/*keepGoing=*/ false); |
| 172 | } |
| 173 | |
| 174 | private void parsingProblem(boolean keepGoing) throws Exception { |
| 175 | // Given a package "//foo" with target rule ":foo", |
| 176 | createFooAndFoo2(/*dependent=*/ false); |
| 177 | |
| 178 | // Given a target pattern sequence consisting of a pattern with parsing problems followed by |
| 179 | // a legit target pattern, |
| 180 | String bogusPattern = "//foo/...."; |
| 181 | ImmutableList<String> patternSequence = ImmutableList.of(bogusPattern, "//foo:foo"); |
| 182 | |
| 183 | // When PrepareDepsOfPatternsFunction runs in the selected keep-going mode, |
| 184 | WalkableGraph walkableGraph = |
Janak Ramakrishnan | 8d239cd | 2016-01-27 21:06:01 +0000 | [diff] [blame] | 185 | getGraphFromPatternsEvaluation(patternSequence, /*keepGoing=*/ keepGoing); |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 186 | |
| 187 | // Then it skips evaluation of the malformed target pattern, but logs about it, |
| 188 | assertContainsEvent("Skipping '" + bogusPattern + "': "); |
| 189 | |
Brian Silverman | d7d6d62 | 2016-03-17 09:53:39 +0000 | [diff] [blame] | 190 | // And then the graph contains a value for the legit target pattern's target "@//foo:foo". |
| 191 | assertTrue(walkableGraph.exists(getKeyForLabel(Label.create("@//foo", "foo")))); |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 192 | } |
| 193 | |
| 194 | // Helpers: |
| 195 | |
Janak Ramakrishnan | 8d239cd | 2016-01-27 21:06:01 +0000 | [diff] [blame] | 196 | private WalkableGraph getGraphFromPatternsEvaluation(ImmutableList<String> patternSequence) |
| 197 | throws InterruptedException { |
| 198 | return getGraphFromPatternsEvaluation(patternSequence, /*keepGoing=*/ true); |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 199 | } |
| 200 | |
| 201 | private WalkableGraph getGraphFromPatternsEvaluation( |
Janak Ramakrishnan | 8d239cd | 2016-01-27 21:06:01 +0000 | [diff] [blame] | 202 | ImmutableList<String> patternSequence, boolean keepGoing) throws InterruptedException { |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 203 | SkyKey independentTarget = PrepareDepsOfPatternsValue.key(patternSequence, ""); |
| 204 | ImmutableList<SkyKey> singletonTargetPattern = ImmutableList.of(independentTarget); |
| 205 | |
| 206 | // When PrepareDepsOfPatternsFunction completes evaluation, |
| 207 | EvaluationResult<SkyValue> evaluationResult = |
| 208 | getSkyframeExecutor() |
| 209 | .getDriverForTesting() |
| 210 | .evaluate(singletonTargetPattern, keepGoing, LOADING_PHASE_THREADS, eventCollector); |
Janak Ramakrishnan | 8d239cd | 2016-01-27 21:06:01 +0000 | [diff] [blame] | 211 | // Currently all callers either expect success or pass keepGoing=true, which implies success, |
| 212 | // since PrepareDepsOfPatternsFunction swallows all errors. Will need to be changed if a test |
| 213 | // that evaluates with keepGoing=false and expects errors is added. |
| 214 | assertThatEvaluationResult(evaluationResult).hasNoError(); |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 215 | |
| 216 | return Preconditions.checkNotNull(evaluationResult.getWalkableGraph()); |
| 217 | } |
| 218 | |
| 219 | private void createFooAndFoo2(boolean dependent) throws IOException { |
| 220 | String dependencyIfAny = dependent ? "srcs = [':foo2']," : ""; |
| 221 | scratch.file( |
| 222 | "foo/BUILD", |
| 223 | "genrule(name = 'foo',", |
| 224 | dependencyIfAny, |
| 225 | " outs = ['out.txt'],", |
| 226 | " cmd = 'touch $@')", |
| 227 | "genrule(name = 'foo2',", |
| 228 | " outs = ['out2.txt'],", |
| 229 | " cmd = 'touch $@')"); |
| 230 | } |
| 231 | |
| 232 | private void createFooWithDependencyOnMissingBarPackage() throws IOException { |
| 233 | scratch.file( |
| 234 | "foo/BUILD", |
| 235 | "genrule(name = 'foo',", |
| 236 | " srcs = ['//bar:bar'],", |
| 237 | " outs = ['out.txt'],", |
| 238 | " cmd = 'touch $@')"); |
| 239 | } |
| 240 | |
| 241 | private void createFooWithDependencyOnBarPackageWithMissingTarget() throws IOException { |
| 242 | scratch.file( |
| 243 | "foo/BUILD", |
| 244 | "genrule(name = 'foo',", |
| 245 | " srcs = ['//bar:bar'],", |
| 246 | " outs = ['out.txt'],", |
| 247 | " cmd = 'touch $@')"); |
| 248 | scratch.file("bar/BUILD"); |
| 249 | } |
| 250 | |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 251 | private static void assertValidValue(WalkableGraph graph, SkyKey key) |
| 252 | throws InterruptedException { |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 253 | assertValidValue(graph, key, /*expectTransitiveException=*/ false); |
| 254 | } |
| 255 | |
| 256 | /** |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 257 | * A node in the walkable graph may have both a value and an exception. This happens when one of a |
| 258 | * node's transitive dependencies throws an exception, but its parent recovers from it. |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 259 | */ |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 260 | private static void assertValidValue( |
| 261 | WalkableGraph graph, SkyKey key, boolean expectTransitiveException) |
| 262 | throws InterruptedException { |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 263 | assertTrue(graph.exists(key)); |
| 264 | assertNotNull(graph.getValue(key)); |
| 265 | if (expectTransitiveException) { |
| 266 | assertNotNull(graph.getException(key)); |
| 267 | } else { |
| 268 | assertNull(graph.getException(key)); |
| 269 | } |
| 270 | } |
| 271 | |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 272 | private static Exception assertException(WalkableGraph graph, SkyKey key) |
| 273 | throws InterruptedException { |
Han-Wen Nienhuys | 81b9083 | 2015-10-26 16:57:27 +0000 | [diff] [blame] | 274 | assertTrue(graph.exists(key)); |
| 275 | assertNull(graph.getValue(key)); |
| 276 | Exception exception = graph.getException(key); |
| 277 | assertNotNull(exception); |
| 278 | return exception; |
| 279 | } |
| 280 | } |