/* * Copyright 2015-present Facebook, Inc. * * 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.facebook.buck.util.autosparse; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import com.facebook.buck.cli.FakeBuckConfig; import com.facebook.buck.event.BuckEventBus; import com.facebook.buck.event.BuckEventBusFactory; import com.facebook.buck.io.ProjectFilesystemDelegate; import com.facebook.buck.io.ProjectFilesystemDelegateFactory; import com.facebook.buck.testutil.integration.TestDataHelper; import com.facebook.buck.timing.FakeClock; import com.facebook.buck.util.TestProcessExecutorFactory; import com.facebook.buck.util.versioncontrol.HgCmdLineInterface; import com.facebook.buck.util.versioncontrol.SparseSummary; import com.facebook.buck.util.versioncontrol.VersionControlBuckConfig; import com.facebook.buck.util.versioncontrol.VersionControlCommandFailedException; import com.facebook.buck.zip.Unzip; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.eventbus.Subscribe; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import org.junit.After; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; import org.junit.rules.TemporaryFolder; public class AutoSparseIntegrationTest { /* * Tests against a sparse repository containing the tree: * * | file1 x * | file2 * | file3 -> file2 * | not_hidden * * | sparse_profile * * | * + subdir * | | * | + file_in_subdir * | * * not_hidden_subdir * | * + file_in_subdir_not_hidden * * All files are hidden by the sparse profile except for files marked with an asterisk. Files with * an x behind them are executable. * * Test requires the hg sparse extension to be available. */ private static final String HG_REPO_ZIP = "hg_repo.zip"; private static final String REPO_DIR = "sparse_repo"; private static Path repoPath; @SuppressWarnings("javadoc") @ClassRule public static TemporaryFolder tempFolder = new TemporaryFolder(); private static HgCmdLineInterface repoCmdline; @BeforeClass public static void setUpClass() throws IOException, InterruptedException { repoPath = explodeRepoZip(); repoCmdline = new HgCmdLineInterface( new TestProcessExecutorFactory(), repoPath, new VersionControlBuckConfig(FakeBuckConfig.builder().build()).getHgCmd(), ImmutableMap.of()); } @Before public void setUp() throws InterruptedException { assumeHgInstalled(); assumeHgSparseInstalled(); } @After public void tearDown() { AbstractAutoSparseFactory.perSCRoot.clear(); } @Test public void testAutosparseDisabled() throws InterruptedException { ProjectFilesystemDelegate delegate = createDelegate(repoPath, false, ImmutableList.of()); Assume.assumeFalse(delegate instanceof AutoSparseProjectFilesystemDelegate); } @Test public void testAutosparseEnabledHgSubdir() throws InterruptedException { ProjectFilesystemDelegate delegate = createDelegate(repoPath.resolve("not_hidden_subdir"), true, ImmutableList.of()); Assume.assumeTrue(delegate instanceof AutoSparseProjectFilesystemDelegate); } @Test public void testAutosparseEnabledNotHgDir() throws InterruptedException { ProjectFilesystemDelegate delegate = createDelegate(repoPath.getParent(), true, ImmutableList.of()); Assume.assumeFalse(delegate instanceof AutoSparseProjectFilesystemDelegate); } @Test public void testRelativePath() throws InterruptedException { ProjectFilesystemDelegate delegate = createDelegate(); Path path = delegate.getPathForRelativePath(Paths.get("subdir/file_in_subdir")); Assert.assertEquals(path, repoPath.resolve("subdir/file_in_subdir")); } @Test public void testExecutableFile() throws InterruptedException { ProjectFilesystemDelegate delegate = createDelegate(); Assume.assumeTrue(delegate.isExecutable(repoPath.resolve("file1"))); } @Test public void testNotExecutableFile() throws InterruptedException { ProjectFilesystemDelegate delegate = createDelegate(); Assume.assumeFalse(delegate.isExecutable(repoPath.resolve("file2"))); } @Test public void testExecutableNotExisting() throws InterruptedException { ProjectFilesystemDelegate delegate = createDelegate(); Assume.assumeFalse(delegate.isExecutable(repoPath.resolve("nonsuch"))); } @Test public void testSymlink() throws InterruptedException { ProjectFilesystemDelegate delegate = createDelegate(); Assume.assumeTrue(delegate.isSymlink(repoPath.resolve("file3"))); } @Test public void testNotSymLink() throws InterruptedException { ProjectFilesystemDelegate delegate = createDelegate(); Assume.assumeFalse(delegate.isSymlink(repoPath.resolve("file2"))); } @Test public void testSymlinkNotExisting() throws InterruptedException { ProjectFilesystemDelegate delegate = createDelegate(); Assume.assumeFalse(delegate.isSymlink(repoPath.resolve("nonsuch"))); } @Test public void testExists() throws InterruptedException { ProjectFilesystemDelegate delegate = createDelegate(); Assume.assumeTrue(delegate.exists(repoPath.resolve("file1"))); } @Test public void testNotExists() throws InterruptedException { ProjectFilesystemDelegate delegate = createDelegate(); Assume.assumeFalse(delegate.exists(repoPath.resolve("nonsuch"))); } @Test public void testExistsUntracked() throws InterruptedException, IOException { ProjectFilesystemDelegate delegate = createDelegate(); File newFile = repoPath.resolve("newFile").toFile(); new FileOutputStream(newFile).close(); Assume.assumeTrue(delegate.exists(newFile.toPath())); } @Test public void testMaterialize() throws InterruptedException, IOException { ProjectFilesystemDelegate delegate = createDelegate(repoPath, true, ImmutableList.of("subdir")); // Touch various files, these should be part of the profile delegate.exists(repoPath.resolve("file1")); delegate.exists(repoPath.resolve("file2")); // Only directly include the file, not the parent dir delegate.exists(repoPath.resolve("subdir/file_in_subdir")); // Only include the parent directory, not the file delegate.exists(repoPath.resolve("not_hidden_subdir/file_in_subdir_not_hidden")); BuckEventBus eventBus = BuckEventBusFactory.newInstance(new FakeClock(0)); AutoSparseIntegrationTest.CapturingAutoSparseStateEventListener listener = new AutoSparseIntegrationTest.CapturingAutoSparseStateEventListener(); eventBus.register(listener); delegate.ensureConcreteFilesExist(eventBus); List<String> lines = Files.readAllLines( repoPath.resolve(".hg/sparse"), Charset.forName(System.getProperty("file.encoding", "UTF-8"))); List<String> expected = ImmutableList.of( "%include sparse_profile", "[include]", "file1", "file2", "not_hidden_subdir", "subdir/file_in_subdir", "[exclude]", "" // sparse always writes a newline at the end ); Assert.assertEquals(expected, lines); // assert we got events with a matching summary List<AutoSparseStateEvents> events = listener.getLoggedEvents(); Assert.assertEquals(events.size(), 2); Assert.assertTrue(events.get(0) instanceof AutoSparseStateEvents.SparseRefreshStarted); Assert.assertTrue(events.get(1) instanceof AutoSparseStateEvents.SparseRefreshFinished); SparseSummary summary = ((AutoSparseStateEvents.SparseRefreshFinished) events.get(1)).summary; Assert.assertEquals(summary.getProfilesAdded(), 0); Assert.assertEquals(summary.getIncludeRulesAdded(), 4); Assert.assertEquals(summary.getExcludeRulesAdded(), 0); Assert.assertEquals(summary.getFilesAdded(), 3); Assert.assertEquals(summary.getFilesDropped(), 0); Assert.assertEquals(summary.getFilesConflicting(), 0); } private static void assumeHgInstalled() throws InterruptedException { // If Mercurial is not installed on the build box, then skip tests. try { repoCmdline.currentRevisionId(); } catch (VersionControlCommandFailedException ex) { Assume.assumeNoException(ex); } } private static void assumeHgSparseInstalled() { // If hg sparse throws an exception, then skip tests. Throwable exception = null; try { Path exportFile = Files.createTempFile("buck_autosparse_rules", ""); try (Writer writer = new BufferedWriter(new FileWriter(exportFile.toFile()))) { writer.write("[include]\n"); // deliberately mostly empty } repoCmdline.exportHgSparseRules(exportFile); } catch (VersionControlCommandFailedException | InterruptedException | IOException e) { exception = e; } Assume.assumeNoException(exception); } private static ProjectFilesystemDelegate createDelegate() throws InterruptedException { return createDelegate(repoPath, true, ImmutableList.of()); } private static ProjectFilesystemDelegate createDelegate( Path root, boolean enableAutosparse, ImmutableList<String> autosparseIgnore) throws InterruptedException { String hgCmd = new VersionControlBuckConfig(FakeBuckConfig.builder().build()).getHgCmd(); return ProjectFilesystemDelegateFactory.newInstance( root, hgCmd, AutoSparseConfig.of(enableAutosparse, autosparseIgnore)); } private static Path explodeRepoZip() throws InterruptedException, IOException { Path testDataDir = TestDataHelper.getTestDataDirectory(AutoSparseIntegrationTest.class); // Use a real path to resolve funky symlinks (I'm looking at you, OS X). Path destination = tempFolder.getRoot().toPath().toRealPath(); Path hgRepoZipPath = testDataDir.resolve(HG_REPO_ZIP); Path hgRepoZipCopyPath = destination.resolve(HG_REPO_ZIP); Path repoPath = destination.resolve(REPO_DIR); Files.copy(hgRepoZipPath, hgRepoZipCopyPath, REPLACE_EXISTING); Unzip.extractZipFile( hgRepoZipCopyPath, repoPath, Unzip.ExistingFileMode.OVERWRITE_AND_CLEAN_DIRECTORIES); return repoPath; } public static class CapturingAutoSparseStateEventListener { private final List<AutoSparseStateEvents> logEvents = new ArrayList<>(); @Subscribe public void logEvent(AutoSparseStateEvents event) { logEvents.add(event); } public List<AutoSparseStateEvents> getLoggedEvents() { return logEvents; } } }