Ulf Adams | d186d6a | 2015-12-21 09:43:52 +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.pkgcache; |
| 15 | |
| 16 | import static com.google.common.truth.Truth.assertThat; |
| 17 | import static org.junit.Assert.assertEquals; |
| 18 | import static org.junit.Assert.assertFalse; |
| 19 | import static org.junit.Assert.assertNotNull; |
| 20 | import static org.junit.Assert.assertNotSame; |
| 21 | import static org.junit.Assert.assertSame; |
| 22 | import static org.junit.Assert.assertTrue; |
| 23 | import static org.junit.Assert.fail; |
| 24 | |
| 25 | import com.google.common.base.Predicates; |
| 26 | import com.google.common.collect.ImmutableList; |
Klaus Aehlig | 6f33a1c | 2016-09-13 16:46:10 +0000 | [diff] [blame] | 27 | import com.google.common.collect.ImmutableMap; |
Ulf Adams | d186d6a | 2015-12-21 09:43:52 +0000 | [diff] [blame] | 28 | import com.google.common.collect.ImmutableSet; |
| 29 | import com.google.devtools.build.lib.analysis.BlazeDirectories; |
| 30 | import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; |
| 31 | import com.google.devtools.build.lib.analysis.util.AnalysisMock; |
| 32 | import com.google.devtools.build.lib.cmdline.Label; |
| 33 | import com.google.devtools.build.lib.cmdline.PackageIdentifier; |
| 34 | import com.google.devtools.build.lib.events.Event; |
Luis Fernando Pino Duque | 964712c0 | 2016-03-31 11:05:31 +0000 | [diff] [blame] | 35 | import com.google.devtools.build.lib.flags.InvocationPolicyEnforcer; |
Ulf Adams | d186d6a | 2015-12-21 09:43:52 +0000 | [diff] [blame] | 36 | import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException; |
Ulf Adams | d186d6a | 2015-12-21 09:43:52 +0000 | [diff] [blame] | 37 | import com.google.devtools.build.lib.packages.NoSuchPackageException; |
| 38 | import com.google.devtools.build.lib.packages.NoSuchTargetException; |
| 39 | import com.google.devtools.build.lib.packages.Package; |
Ulf Adams | d186d6a | 2015-12-21 09:43:52 +0000 | [diff] [blame] | 40 | import com.google.devtools.build.lib.packages.Preprocessor; |
| 41 | import com.google.devtools.build.lib.packages.Rule; |
| 42 | import com.google.devtools.build.lib.packages.Target; |
| 43 | import com.google.devtools.build.lib.packages.util.MockToolsConfig; |
| 44 | import com.google.devtools.build.lib.skyframe.DiffAwareness; |
| 45 | import com.google.devtools.build.lib.skyframe.PrecomputedValue; |
| 46 | import com.google.devtools.build.lib.skyframe.SequencedSkyframeExecutor; |
| 47 | import com.google.devtools.build.lib.skyframe.SkyValueDirtinessChecker; |
| 48 | import com.google.devtools.build.lib.skyframe.SkyframeExecutor; |
| 49 | import com.google.devtools.build.lib.syntax.BuildFileAST; |
| 50 | import com.google.devtools.build.lib.testutil.FoundationTestCase; |
| 51 | import com.google.devtools.build.lib.testutil.MoreAsserts; |
Ulf Adams | d186d6a | 2015-12-21 09:43:52 +0000 | [diff] [blame] | 52 | import com.google.devtools.build.lib.util.BlazeClock; |
| 53 | import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor; |
| 54 | import com.google.devtools.build.lib.vfs.ModifiedFileSet; |
| 55 | import com.google.devtools.build.lib.vfs.Path; |
| 56 | import com.google.devtools.build.lib.vfs.PathFragment; |
| 57 | import com.google.devtools.common.options.OptionsParser; |
Luis Fernando Pino Duque | 964712c0 | 2016-03-31 11:05:31 +0000 | [diff] [blame] | 58 | import com.google.devtools.common.options.OptionsParsingException; |
Ulf Adams | d186d6a | 2015-12-21 09:43:52 +0000 | [diff] [blame] | 59 | import java.io.IOException; |
| 60 | import java.util.HashSet; |
| 61 | import java.util.List; |
| 62 | import java.util.Set; |
| 63 | import java.util.UUID; |
Klaus Aehlig | 6f33a1c | 2016-09-13 16:46:10 +0000 | [diff] [blame] | 64 | import org.junit.Before; |
| 65 | import org.junit.Test; |
| 66 | import org.junit.runner.RunWith; |
| 67 | import org.junit.runners.JUnit4; |
Ulf Adams | d186d6a | 2015-12-21 09:43:52 +0000 | [diff] [blame] | 68 | |
| 69 | /** |
| 70 | * Tests for package loading. |
| 71 | */ |
| 72 | @RunWith(JUnit4.class) |
| 73 | public class PackageCacheTest extends FoundationTestCase { |
| 74 | |
Ulf Adams | 015aad9 | 2016-07-13 16:49:40 +0000 | [diff] [blame] | 75 | private AnalysisMock analysisMock; |
Ulf Adams | d186d6a | 2015-12-21 09:43:52 +0000 | [diff] [blame] | 76 | private ConfiguredRuleClassProvider ruleClassProvider; |
| 77 | private SkyframeExecutor skyframeExecutor; |
| 78 | |
| 79 | @Before |
Ulf Adams | c73051c6 | 2016-03-23 09:18:13 +0000 | [diff] [blame] | 80 | public final void initializeSkyframeExecutor() throws Exception { |
Ulf Adams | 015aad9 | 2016-07-13 16:49:40 +0000 | [diff] [blame] | 81 | analysisMock = AnalysisMock.get(); |
| 82 | ruleClassProvider = analysisMock.createRuleClassProvider(); |
| 83 | BlazeDirectories directories = |
| 84 | new BlazeDirectories(outputBase, outputBase, rootDirectory, analysisMock.getProductName()); |
Ulf Adams | d186d6a | 2015-12-21 09:43:52 +0000 | [diff] [blame] | 85 | skyframeExecutor = |
| 86 | SequencedSkyframeExecutor.create( |
Ulf Adams | 015aad9 | 2016-07-13 16:49:40 +0000 | [diff] [blame] | 87 | analysisMock |
| 88 | .getPackageFactoryForTesting() |
| 89 | .create(ruleClassProvider, scratch.getFileSystem()), |
Ulf Adams | d186d6a | 2015-12-21 09:43:52 +0000 | [diff] [blame] | 90 | directories, |
| 91 | null, /* BinTools */ |
| 92 | null, /* workspaceStatusActionFactory */ |
| 93 | ruleClassProvider.getBuildInfoFactories(), |
| 94 | ImmutableList.<DiffAwareness.Factory>of(), |
| 95 | Predicates.<PathFragment>alwaysFalse(), |
| 96 | Preprocessor.Factory.Supplier.NullSupplier.INSTANCE, |
Ulf Adams | a4c1a56 | 2016-04-15 11:12:54 +0000 | [diff] [blame] | 97 | AnalysisMock.get().getSkyFunctions(), |
Ulf Adams | d186d6a | 2015-12-21 09:43:52 +0000 | [diff] [blame] | 98 | ImmutableList.<PrecomputedValue.Injected>of(), |
Luis Fernando Pino Duque | be10218 | 2016-05-23 14:03:55 +0000 | [diff] [blame] | 99 | ImmutableList.<SkyValueDirtinessChecker>of(), |
Ulf Adams | 015aad9 | 2016-07-13 16:49:40 +0000 | [diff] [blame] | 100 | analysisMock.getProductName()); |
Ulf Adams | d186d6a | 2015-12-21 09:43:52 +0000 | [diff] [blame] | 101 | setUpSkyframe(parsePackageCacheOptions()); |
| 102 | } |
| 103 | |
| 104 | private void setUpSkyframe(PackageCacheOptions packageCacheOptions) { |
| 105 | PathPackageLocator pkgLocator = PathPackageLocator.create( |
| 106 | null, packageCacheOptions.packagePath, reporter, rootDirectory, rootDirectory); |
Janak Ramakrishnan | 326c698 | 2016-09-27 14:58:26 +0000 | [diff] [blame^] | 107 | packageCacheOptions.showLoadingProgress = true; |
| 108 | packageCacheOptions.globbingThreads = 7; |
Janak Ramakrishnan | b92c097 | 2016-03-23 16:47:13 +0000 | [diff] [blame] | 109 | skyframeExecutor.preparePackageLoading( |
| 110 | pkgLocator, |
Janak Ramakrishnan | 326c698 | 2016-09-27 14:58:26 +0000 | [diff] [blame^] | 111 | packageCacheOptions, |
Ulf Adams | 015aad9 | 2016-07-13 16:49:40 +0000 | [diff] [blame] | 112 | analysisMock.getDefaultsPackageContent(), |
Janak Ramakrishnan | b92c097 | 2016-03-23 16:47:13 +0000 | [diff] [blame] | 113 | UUID.randomUUID(), |
Klaus Aehlig | 03b9cfd | 2016-09-14 13:14:39 +0000 | [diff] [blame] | 114 | ImmutableMap.<String, String>of(), |
Janak Ramakrishnan | b92c097 | 2016-03-23 16:47:13 +0000 | [diff] [blame] | 115 | new TimestampGranularityMonitor(BlazeClock.instance())); |
Klaus Aehlig | 6f33a1c | 2016-09-13 16:46:10 +0000 | [diff] [blame] | 116 | skyframeExecutor.setDeletedPackages( |
| 117 | ImmutableSet.copyOf(packageCacheOptions.getDeletedPackages())); |
Ulf Adams | d186d6a | 2015-12-21 09:43:52 +0000 | [diff] [blame] | 118 | } |
| 119 | |
| 120 | private PackageCacheOptions parsePackageCacheOptions(String... options) throws Exception { |
| 121 | OptionsParser parser = OptionsParser.newOptionsParser(PackageCacheOptions.class); |
| 122 | parser.parse(new String[] { "--default_visibility=public" }); |
| 123 | parser.parse(options); |
Luis Fernando Pino Duque | 964712c0 | 2016-03-31 11:05:31 +0000 | [diff] [blame] | 124 | |
Ulf Adams | 015aad9 | 2016-07-13 16:49:40 +0000 | [diff] [blame] | 125 | InvocationPolicyEnforcer optionsPolicyEnforcer = analysisMock.getInvocationPolicyEnforcer(); |
Luis Fernando Pino Duque | 964712c0 | 2016-03-31 11:05:31 +0000 | [diff] [blame] | 126 | try { |
| 127 | optionsPolicyEnforcer.enforce(parser); |
| 128 | } catch (OptionsParsingException e) { |
| 129 | throw new IllegalStateException(e); |
| 130 | } |
| 131 | |
Ulf Adams | d186d6a | 2015-12-21 09:43:52 +0000 | [diff] [blame] | 132 | return parser.getOptions(PackageCacheOptions.class); |
| 133 | } |
| 134 | |
| 135 | protected void setOptions(String... options) throws Exception { |
| 136 | setUpSkyframe(parsePackageCacheOptions(options)); |
| 137 | } |
| 138 | |
| 139 | private PackageManager getPackageManager() { |
| 140 | return skyframeExecutor.getPackageManager(); |
| 141 | } |
| 142 | |
| 143 | private void invalidatePackages() throws InterruptedException { |
| 144 | skyframeExecutor.invalidateFilesUnderPathForTesting( |
| 145 | reporter, ModifiedFileSet.EVERYTHING_MODIFIED, rootDirectory); |
| 146 | } |
| 147 | |
| 148 | private Package getPackage(String packageName) |
| 149 | throws NoSuchPackageException, InterruptedException { |
| 150 | return getPackageManager().getPackage(reporter, |
Brian Silverman | d7d6d62 | 2016-03-17 09:53:39 +0000 | [diff] [blame] | 151 | PackageIdentifier.createInMainRepo(packageName)); |
Ulf Adams | d186d6a | 2015-12-21 09:43:52 +0000 | [diff] [blame] | 152 | } |
| 153 | |
| 154 | private Target getTarget(Label label) |
| 155 | throws NoSuchPackageException, NoSuchTargetException, InterruptedException { |
| 156 | return getPackageManager().getTarget(reporter, label); |
| 157 | } |
| 158 | |
| 159 | private Target getTarget(String label) throws Exception { |
| 160 | return getTarget(Label.parseAbsolute(label)); |
| 161 | } |
| 162 | |
| 163 | private void createPkg1() throws IOException { |
| 164 | scratch.file("pkg1/BUILD", "cc_library(name = 'foo') # a BUILD file"); |
| 165 | } |
| 166 | |
| 167 | // Check that a substring is present in an error message. |
| 168 | private void checkGetPackageFails(String packageName, String expectedMessage) throws Exception { |
| 169 | try { |
| 170 | getPackage(packageName); |
| 171 | fail(); |
| 172 | } catch (NoSuchPackageException e) { |
| 173 | assertThat(e.getMessage()).contains(expectedMessage); |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | @Test |
| 178 | public void testGetPackage() throws Exception { |
| 179 | createPkg1(); |
| 180 | Package pkg1 = getPackage("pkg1"); |
| 181 | assertEquals("pkg1", pkg1.getName()); |
| 182 | assertEquals("/workspace/pkg1/BUILD", |
| 183 | pkg1.getFilename().toString()); |
| 184 | assertSame(pkg1, getPackageManager().getPackage(reporter, |
Brian Silverman | d7d6d62 | 2016-03-17 09:53:39 +0000 | [diff] [blame] | 185 | PackageIdentifier.createInMainRepo("pkg1"))); |
Ulf Adams | d186d6a | 2015-12-21 09:43:52 +0000 | [diff] [blame] | 186 | } |
| 187 | |
| 188 | @Test |
| 189 | public void testASTIsNotRetained() throws Exception { |
| 190 | createPkg1(); |
| 191 | Package pkg1 = getPackage("pkg1"); |
| 192 | MoreAsserts.assertInstanceOfNotReachable(pkg1, BuildFileAST.class); |
| 193 | } |
| 194 | |
| 195 | @Test |
| 196 | public void testGetNonexistentPackage() throws Exception { |
| 197 | checkGetPackageFails("not-there", |
| 198 | "no such package 'not-there': " |
| 199 | + "BUILD file not found on package path"); |
| 200 | } |
| 201 | |
| 202 | @Test |
| 203 | public void testGetPackageWithInvalidName() throws Exception { |
| 204 | scratch.file("invalidpackagename&42/BUILD", "cc_library(name = 'foo') # a BUILD file"); |
| 205 | checkGetPackageFails( |
| 206 | "invalidpackagename&42", |
| 207 | "no such package 'invalidpackagename&42': Invalid package name 'invalidpackagename&42'"); |
| 208 | } |
| 209 | |
| 210 | @Test |
| 211 | public void testGetTarget() throws Exception { |
| 212 | createPkg1(); |
| 213 | Label label = Label.parseAbsolute("//pkg1:foo"); |
| 214 | Target target = getTarget(label); |
| 215 | assertEquals(label, target.getLabel()); |
| 216 | } |
| 217 | |
| 218 | @Test |
| 219 | public void testGetNonexistentTarget() throws Exception { |
| 220 | createPkg1(); |
| 221 | try { |
| 222 | getTarget("//pkg1:not-there"); |
| 223 | fail(); |
| 224 | } catch (NoSuchTargetException e) { |
| 225 | assertThat(e).hasMessage("no such target '//pkg1:not-there': target 'not-there' " |
| 226 | + "not declared in package 'pkg1' defined by /workspace/pkg1/BUILD"); |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | /** |
| 231 | * A missing package is one for which no BUILD file can be found. The |
| 232 | * PackageCache caches failures of this kind until the next sync. |
| 233 | */ |
| 234 | @Test |
| 235 | public void testRepeatedAttemptsToParseMissingPackage() throws Exception { |
| 236 | checkGetPackageFails("missing", |
| 237 | "no such package 'missing': " |
| 238 | + "BUILD file not found on package path"); |
| 239 | |
| 240 | // Still missing: |
| 241 | checkGetPackageFails("missing", |
| 242 | "no such package 'missing': " |
| 243 | + "BUILD file not found on package path"); |
| 244 | |
| 245 | // Update the BUILD file on disk so "missing" is no longer missing: |
| 246 | scratch.file("missing/BUILD", |
| 247 | "# an ok build file"); |
| 248 | |
| 249 | // Still missing: |
| 250 | checkGetPackageFails("missing", |
| 251 | "no such package 'missing': " |
| 252 | + "BUILD file not found on package path"); |
| 253 | |
| 254 | invalidatePackages(); |
| 255 | |
| 256 | // Found: |
| 257 | Package missing = getPackage("missing"); |
| 258 | |
| 259 | assertEquals("missing", missing.getName()); |
| 260 | } |
| 261 | |
| 262 | /** |
| 263 | * A broken package is one that exists but contains lexer/parser/evaluator errors. The |
| 264 | * PackageCache only makes one attempt to parse each package once found. |
| 265 | * |
| 266 | * <p>Depending on the strictness of the PackageFactory, parsing a broken package may cause a |
| 267 | * Package object to be returned (possibly missing some rules) or an exception to be thrown. For |
| 268 | * this test we need that strict behavior. |
| 269 | * |
| 270 | * <p>Note: since the PackageCache.setStrictPackageCreation method was deleted (since it wasn't |
| 271 | * used by any significant clients) creating a "broken" build file got trickier--syntax errors are |
| 272 | * not enough. For now, we create an unreadable BUILD file, which will cause an IOException to be |
| 273 | * thrown. This test seems less valuable than it once did. |
| 274 | */ |
| 275 | @Test |
| 276 | public void testParseBrokenPackage() throws Exception { |
| 277 | reporter.removeHandler(failFastHandler); |
| 278 | |
| 279 | Path brokenBuildFile = scratch.file("broken/BUILD"); |
| 280 | brokenBuildFile.setReadable(false); |
| 281 | |
| 282 | try { |
| 283 | getPackage("broken"); |
| 284 | fail(); |
| 285 | } catch (BuildFileContainsErrorsException e) { |
| 286 | assertThat(e.getMessage()).contains("/workspace/broken/BUILD (Permission denied)"); |
| 287 | } |
| 288 | eventCollector.clear(); |
| 289 | |
| 290 | // Update the BUILD file on disk so "broken" is no longer broken: |
| 291 | scratch.overwriteFile("broken/BUILD", |
| 292 | "# an ok build file"); |
| 293 | |
| 294 | invalidatePackages(); // resets cache of failures |
| 295 | |
| 296 | Package broken = getPackage("broken"); |
| 297 | assertEquals("broken", broken.getName()); |
| 298 | assertNoEvents(); |
| 299 | } |
| 300 | |
| 301 | @Test |
| 302 | public void testPackageInErrorReloadedWhenFixed() throws Exception { |
| 303 | reporter.removeHandler(failFastHandler); |
| 304 | Path build = scratch.file("a/BUILD", "cc_library(name='a', feet='stinky')"); |
| 305 | build.setLastModifiedTime(1); |
| 306 | Package a1 = getPackage("a"); |
| 307 | assertTrue(a1.containsErrors()); |
| 308 | assertContainsEvent("//a:a: no such attribute 'feet'"); |
| 309 | |
| 310 | eventCollector.clear(); |
| 311 | build.delete(); |
| 312 | build = scratch.file("a/BUILD", "cc_library(name='a', srcs=['a.cc'])"); |
| 313 | build.setLastModifiedTime(2); |
| 314 | invalidatePackages(); |
| 315 | Package a2 = getPackage("a"); |
| 316 | assertNotSame(a1, a2); |
| 317 | assertFalse(a2.containsErrors()); |
| 318 | assertNoEvents(); |
| 319 | } |
| 320 | |
| 321 | @Test |
| 322 | public void testModifiedBuildFileCausesReloadAfterSync() throws Exception { |
| 323 | Path path = scratch.file("pkg/BUILD", |
| 324 | "cc_library(name = 'foo')"); |
| 325 | path.setLastModifiedTime(1000); |
| 326 | |
| 327 | Package oldPkg = getPackage("pkg"); |
| 328 | // modify BUILD file (and change its timestamp) |
| 329 | path.delete(); |
| 330 | scratch.file("pkg/BUILD", "cc_library(name = 'bar')"); |
| 331 | path.setLastModifiedTime(999); // earlier; mtime doesn't have to advance |
| 332 | assertSame(oldPkg, getPackage("pkg")); // change not yet visible |
| 333 | |
| 334 | invalidatePackages(); |
| 335 | |
| 336 | Package newPkg = getPackage("pkg"); |
| 337 | assertNotSame(oldPkg, newPkg); |
| 338 | assertNotNull(newPkg.getTarget("bar")); |
| 339 | } |
| 340 | |
| 341 | @Test |
| 342 | public void testTouchedBuildFileCausesReloadAfterSync() throws Exception { |
| 343 | Path path = scratch.file("pkg/BUILD", |
| 344 | "cc_library(name = 'foo')"); |
| 345 | path.setLastModifiedTime(1000); |
| 346 | |
| 347 | Package oldPkg = getPackage("pkg"); |
| 348 | path.setLastModifiedTime(1001); |
| 349 | assertSame(oldPkg, getPackage("pkg")); // change not yet visible |
| 350 | |
| 351 | invalidatePackages(); |
| 352 | |
| 353 | Package newPkg = getPackage("pkg"); |
| 354 | assertNotSame(oldPkg, newPkg); |
| 355 | } |
| 356 | |
| 357 | @Test |
| 358 | public void testMovedBuildFileCausesReloadAfterSync() throws Exception { |
| 359 | Path buildFile1 = scratch.file("pkg/BUILD", |
| 360 | "cc_library(name = 'foo')"); |
| 361 | Path buildFile2 = scratch.file("/otherroot/pkg/BUILD", |
| 362 | "cc_library(name = 'bar')"); |
| 363 | setOptions("--package_path=/workspace:/otherroot"); |
| 364 | |
| 365 | Package oldPkg = getPackage("pkg"); |
| 366 | assertSame(oldPkg, getPackage("pkg")); // change not yet visible |
| 367 | assertEquals(buildFile1, oldPkg.getFilename()); |
| 368 | assertEquals(rootDirectory, oldPkg.getSourceRoot()); |
| 369 | |
| 370 | buildFile1.delete(); |
| 371 | invalidatePackages(); |
| 372 | |
| 373 | Package newPkg = getPackage("pkg"); |
| 374 | assertNotSame(oldPkg, newPkg); |
| 375 | assertEquals(buildFile2, newPkg.getFilename()); |
| 376 | assertEquals(scratch.dir("/otherroot"), newPkg.getSourceRoot()); |
| 377 | |
| 378 | // TODO(bazel-team): (2009) test BUILD file moves in the other direction too. |
| 379 | } |
| 380 | |
| 381 | private Path rootDir1; |
| 382 | private Path rootDir2; |
| 383 | |
| 384 | private void setUpCacheWithTwoRootLocator() throws Exception { |
| 385 | // Root 1: |
| 386 | // /a/BUILD |
| 387 | // /b/BUILD |
| 388 | // /c/d |
| 389 | // /c/e |
| 390 | // |
| 391 | // Root 2: |
| 392 | // /b/BUILD |
| 393 | // /c/BUILD |
| 394 | // /c/d/BUILD |
| 395 | // /f/BUILD |
| 396 | // /f/g |
| 397 | // /f/g/h/BUILD |
| 398 | |
| 399 | rootDir1 = scratch.dir("/workspace"); |
| 400 | rootDir2 = scratch.dir("/otherroot"); |
| 401 | |
| 402 | createBuildFile(rootDir1, "a", "foo.txt", "bar/foo.txt"); |
| 403 | createBuildFile(rootDir1, "b", "foo.txt", "bar/foo.txt"); |
| 404 | |
| 405 | rootDir1.getRelative("c").createDirectory(); |
| 406 | rootDir1.getRelative("c/d").createDirectory(); |
| 407 | rootDir1.getRelative("c/e").createDirectory(); |
| 408 | |
| 409 | createBuildFile(rootDir2, "c", "d", "d/foo.txt", "foo.txt", "bar/foo.txt", "e", "e/foo.txt"); |
| 410 | createBuildFile(rootDir2, "c/d", "foo.txt"); |
| 411 | createBuildFile(rootDir2, "f", "g/foo.txt", "g/h", "g/h/foo.txt", "foo.txt"); |
| 412 | createBuildFile(rootDir2, "f/g/h", "foo.txt"); |
| 413 | |
| 414 | setOptions("--package_path=/workspace:/otherroot"); |
| 415 | } |
| 416 | |
| 417 | protected Path createBuildFile(Path workspace, String packageName, |
| 418 | String... targets) throws IOException { |
| 419 | String[] lines = new String[targets.length]; |
| 420 | |
| 421 | for (int i = 0; i < targets.length; i++) { |
| 422 | lines[i] = "sh_library(name='" + targets[i] + "')"; |
| 423 | } |
| 424 | |
| 425 | return scratch.file(workspace + "/" + packageName + "/BUILD", lines); |
| 426 | } |
| 427 | |
| 428 | private void assertLabelValidity(boolean expected, String labelString) |
| 429 | throws Exception { |
| 430 | Label label = Label.parseAbsolute(labelString); |
| 431 | |
| 432 | boolean actual = false; |
| 433 | String error = null; |
| 434 | try { |
| 435 | getTarget(label); |
| 436 | actual = true; |
| 437 | } catch (NoSuchPackageException | NoSuchTargetException e) { |
| 438 | error = e.getMessage(); |
| 439 | } |
| 440 | if (actual != expected) { |
| 441 | fail("assertLabelValidity(" + label + ") " |
| 442 | + actual + ", not equal to expected value " + expected |
| 443 | + " (error=" + error + ")"); |
| 444 | } |
| 445 | } |
| 446 | |
| 447 | private void assertPackageLoadingFails(String pkgName, String expectedError) throws Exception { |
| 448 | Package pkg = getPackage(pkgName); |
| 449 | assertTrue(pkg.containsErrors()); |
| 450 | assertContainsEvent(expectedError); |
| 451 | } |
| 452 | |
| 453 | @Test |
| 454 | public void testLocationForLabelCrossingSubpackage() throws Exception { |
| 455 | scratch.file("e/f/BUILD"); |
| 456 | scratch.file("e/BUILD", |
| 457 | "# Whatever", |
| 458 | "filegroup(name='fg', srcs=['f/g'])"); |
| 459 | reporter.removeHandler(failFastHandler); |
| 460 | List<Event> events = getPackage("e").getEvents(); |
| 461 | assertThat(events).hasSize(1); |
| 462 | assertEquals(2, events.get(0).getLocation().getStartLineAndColumn().getLine()); |
| 463 | } |
| 464 | |
| 465 | /** Static tests (i.e. no changes to filesystem, nor calls to sync). */ |
| 466 | @Test |
| 467 | public void testLabelValidity() throws Exception { |
| 468 | reporter.removeHandler(failFastHandler); |
| 469 | setUpCacheWithTwoRootLocator(); |
| 470 | |
| 471 | scratch.file(rootDir2 + "/c/d/foo.txt"); |
| 472 | |
| 473 | assertLabelValidity(true, "//a:foo.txt"); |
| 474 | assertLabelValidity(true, "//a:bar/foo.txt"); |
| 475 | assertLabelValidity(false, "//a/bar:foo.txt"); // no such package a/bar |
| 476 | |
| 477 | assertLabelValidity(true, "//b:foo.txt"); |
| 478 | assertLabelValidity(true, "//b:bar/foo.txt"); |
| 479 | assertLabelValidity(false, "//b/bar:foo.txt"); // no such package b/bar |
| 480 | |
| 481 | assertLabelValidity(true, "//c:foo.txt"); |
| 482 | assertLabelValidity(true, "//c:bar/foo.txt"); |
| 483 | assertLabelValidity(false, "//c/bar:foo.txt"); // no such package c/bar |
| 484 | |
| 485 | assertLabelValidity(true, "//c:foo.txt"); |
| 486 | |
| 487 | assertLabelValidity(false, "//c:d/foo.txt"); // crosses boundary of c/d |
| 488 | assertLabelValidity(true, "//c/d:foo.txt"); |
| 489 | |
| 490 | assertLabelValidity(true, "//c:foo.txt"); |
| 491 | assertLabelValidity(true, "//c:e"); |
| 492 | assertLabelValidity(true, "//c:e/foo.txt"); |
| 493 | assertLabelValidity(false, "//c/e:foo.txt"); // no such package c/e |
| 494 | |
| 495 | assertLabelValidity(true, "//f:foo.txt"); |
| 496 | assertLabelValidity(true, "//f:g/foo.txt"); |
| 497 | assertLabelValidity(false, "//f/g:foo.txt"); // no such package f/g |
| 498 | assertLabelValidity(false, "//f:g/h/foo.txt"); // crosses boundary of f/g/h |
| 499 | assertLabelValidity(false, "//f/g:h/foo.txt"); // no such package f/g |
| 500 | assertLabelValidity(true, "//f/g/h:foo.txt"); |
| 501 | } |
| 502 | |
| 503 | /** Dynamic tests of label validity. */ |
| 504 | @Test |
| 505 | public void testAddedBuildFileCausesLabelToBecomeInvalid() throws Exception { |
| 506 | reporter.removeHandler(failFastHandler); |
| 507 | scratch.file("pkg/BUILD", |
| 508 | " cc_library(name = 'foo', ", |
| 509 | " srcs = ['x/y.cc'])"); |
| 510 | |
| 511 | assertLabelValidity(true, "//pkg:x/y.cc"); |
| 512 | |
| 513 | // The existence of this file makes 'x/y.cc' an invalid reference. |
| 514 | scratch.file("pkg/x/BUILD"); |
| 515 | |
| 516 | // but not yet... |
| 517 | assertLabelValidity(true, "//pkg:x/y.cc"); |
| 518 | |
| 519 | invalidatePackages(); |
| 520 | |
| 521 | // now: |
| 522 | assertPackageLoadingFails("pkg", |
| 523 | "Label '//pkg:x/y.cc' crosses boundary of subpackage 'pkg/x' " |
| 524 | + "(perhaps you meant to put the colon here: '//pkg/x:y.cc'?)"); |
| 525 | } |
| 526 | |
| 527 | @Test |
| 528 | public void testDeletedPackages() throws Exception { |
| 529 | reporter.removeHandler(failFastHandler); |
| 530 | setUpCacheWithTwoRootLocator(); |
| 531 | createBuildFile(rootDir1, "c", "d/x"); |
| 532 | // Now package c exists in both roots, and c/d exists in only in the second |
| 533 | // root. It's as if we've merged c and c/d in the first root. |
| 534 | |
| 535 | // c/d is still a subpackage--found in the second root: |
| 536 | assertEquals(rootDir2.getRelative("c/d/BUILD"), |
| 537 | getPackage("c/d").getFilename()); |
| 538 | |
| 539 | // Subpackage labels are still valid... |
| 540 | assertLabelValidity(true, "//c/d:foo.txt"); |
| 541 | // ...and this crosses package boundaries: |
| 542 | assertLabelValidity(false, "//c:d/x"); |
| 543 | assertPackageLoadingFails("c", |
| 544 | "Label '//c:d/x' crosses boundary of subpackage 'c/d' (have you deleted c/d/BUILD? " |
| 545 | + "If so, use the --deleted_packages=c/d option)"); |
| 546 | |
| 547 | assertTrue(getPackageManager().isPackage( |
Brian Silverman | d7d6d62 | 2016-03-17 09:53:39 +0000 | [diff] [blame] | 548 | reporter, PackageIdentifier.createInMainRepo("c/d"))); |
Ulf Adams | d186d6a | 2015-12-21 09:43:52 +0000 | [diff] [blame] | 549 | |
| 550 | setOptions("--deleted_packages=c/d"); |
| 551 | invalidatePackages(); |
| 552 | |
| 553 | assertFalse(getPackageManager().isPackage( |
Brian Silverman | d7d6d62 | 2016-03-17 09:53:39 +0000 | [diff] [blame] | 554 | reporter, PackageIdentifier.createInMainRepo("c/d"))); |
Ulf Adams | d186d6a | 2015-12-21 09:43:52 +0000 | [diff] [blame] | 555 | |
| 556 | // c/d is no longer a subpackage--even though there's a BUILD file in the |
| 557 | // second root: |
| 558 | try { |
| 559 | getPackage("c/d"); |
| 560 | fail(); |
| 561 | } catch (NoSuchPackageException e) { |
| 562 | assertThat(e).hasMessage( |
| 563 | "no such package 'c/d': Package is considered deleted due to --deleted_packages"); |
| 564 | } |
| 565 | |
| 566 | // Labels in the subpackage are no longer valid... |
| 567 | assertLabelValidity(false, "//c/d:x"); |
| 568 | // ...and now d is just a subdirectory of c: |
| 569 | assertLabelValidity(true, "//c:d/x"); |
| 570 | } |
| 571 | |
| 572 | @Test |
| 573 | public void testPackageFeatures() throws Exception { |
| 574 | scratch.file("peach/BUILD", |
| 575 | "package(features = ['crosstool_default_false'])", |
| 576 | "cc_library(name = 'cc', srcs = ['cc.cc'])"); |
| 577 | Rule cc = (Rule) getTarget("//peach:cc"); |
| 578 | assertThat(cc.getFeatures()).hasSize(1); |
| 579 | } |
| 580 | |
| 581 | /** Visit label and its dependencies and load all of them. */ |
| 582 | private void visitLabel(String label) throws Exception { |
| 583 | TransitivePackageLoader visitor = getPackageManager().newTransitiveLoader(); |
| 584 | Set<Target> targets = new HashSet<>(); |
| 585 | targets.add(getPackageManager().getTarget(reporter, Label.parseAbsolute(label))); |
| 586 | visitor.sync(reporter, targets, ImmutableSet.<Label>of(), |
| 587 | false, 1, Integer.MAX_VALUE); |
| 588 | } |
| 589 | |
| 590 | @Test |
| 591 | public void testSyntaxErrorInDepPackage() throws Exception { |
| 592 | reporter.removeHandler(failFastHandler); |
| 593 | AnalysisMock.get().setupMockClient(new MockToolsConfig(rootDirectory)); |
| 594 | |
| 595 | scratch.file("a/BUILD", |
| 596 | "genrule(name='x',", |
| 597 | " srcs = ['file.txt'],", |
| 598 | " outs = ['foo'],", |
| 599 | " cmd = 'echo')", |
| 600 | "@"); // syntax error |
| 601 | |
| 602 | scratch.file("b/BUILD", |
| 603 | "genrule(name= 'cc',", |
| 604 | " tools = ['//a:x'],", |
| 605 | " outs = ['bar'],", |
| 606 | " cmd = 'echo')"); |
| 607 | |
| 608 | Package pkgB = getPackage("b"); |
| 609 | |
| 610 | // We should get error message from package a, but package is properly loaded. |
| 611 | visitLabel("//b:cc"); |
| 612 | assertContainsEvent("invalid character: '@'"); |
| 613 | assertFalse(pkgB.containsErrors()); |
| 614 | } |
| 615 | |
| 616 | @Test |
| 617 | public void testBrokenPackageOnMultiplePackagePathEntries() throws Exception { |
| 618 | reporter.removeHandler(failFastHandler); |
| 619 | setOptions("--package_path=.:."); |
| 620 | scratch.file("x/y/BUILD"); |
| 621 | scratch.file("x/BUILD", |
| 622 | "genrule(name = 'x',", |
| 623 | "srcs = [],", |
| 624 | "outs = ['y/z.h'],", |
| 625 | "cmd = '')"); |
| 626 | Package p = getPackage("x"); |
| 627 | assertTrue(p.containsErrors()); |
| 628 | } |
| 629 | } |