// Copyright 2015 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.build.lib.actions;

import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.NULL_ACTION_OWNER;
import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.NULL_ARTIFACT_OWNER;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.fail;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException;
import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.testutil.Scratch;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.Nullable;

/**
 * Tests {@link ArtifactFactory}. Also see {@link ArtifactTest} for a test
 * of individual artifacts.
 */
@RunWith(JUnit4.class)
public class ArtifactFactoryTest {

  private static final RepositoryName MAIN = RepositoryName.MAIN;

  private Scratch scratch = new Scratch();

  private Path execRoot;
  private Root clientRoot;
  private Root clientRoRoot;
  private Root alienRoot;
  private Root outRoot;

  private PathFragment fooPath;
  private PackageIdentifier fooPackage;
  private PathFragment fooRelative;

  private PathFragment barPath;
  private PackageIdentifier barPackage;
  private PathFragment barRelative;

  private PathFragment alienPath;
  private PackageIdentifier alienPackage;
  private PathFragment alienRelative;

  private ArtifactFactory artifactFactory;

  @Before
  public final void createFiles() throws Exception  {
    execRoot = scratch.dir("/output/workspace");
    clientRoot = Root.asSourceRoot(scratch.dir("/client/workspace"));
    clientRoRoot = Root.asSourceRoot(scratch.dir("/client/RO/workspace"));
    alienRoot = Root.asSourceRoot(scratch.dir("/client/workspace"));
    outRoot = Root.asDerivedRoot(execRoot, execRoot.getRelative("out-root/x/bin"));

    fooPath = new PathFragment("foo");
    fooPackage = PackageIdentifier.createInMainRepo(fooPath);
    fooRelative = fooPath.getRelative("foosource.txt");

    barPath = new PathFragment("foo/bar");
    barPackage = PackageIdentifier.createInMainRepo(barPath);
    barRelative = barPath.getRelative("barsource.txt");

    alienPath = new PathFragment("external/alien");
    alienPackage = PackageIdentifier.create("@alien", alienPath);
    alienRelative = alienPath.getRelative("alien.txt");

    artifactFactory = new ArtifactFactory(execRoot, "bazel-out");
    setupRoots();
  }

  private void setupRoots() {
    Map<PackageIdentifier, Root> packageRootMap = new HashMap<>();
    packageRootMap.put(fooPackage, clientRoot);
    packageRootMap.put(barPackage, clientRoRoot);
    packageRootMap.put(alienPackage, alienRoot);
    artifactFactory.setPackageRoots(packageRootMap);
  }

  @Test
  public void testGetSourceArtifactYieldsSameArtifact() throws Exception {
    assertSame(artifactFactory.getSourceArtifact(fooRelative, clientRoot),
               artifactFactory.getSourceArtifact(fooRelative, clientRoot));
  }

  @Test
  public void testGetSourceArtifactUnnormalized() throws Exception {
    assertSame(artifactFactory.getSourceArtifact(fooRelative, clientRoot),
               artifactFactory.getSourceArtifact(new PathFragment("foo/./foosource.txt"),
                   clientRoot));
  }

  @Test
  public void testResolveArtifact_noDerived_simpleSource() throws Exception {
    assertSame(artifactFactory.getSourceArtifact(fooRelative, clientRoot),
        artifactFactory.resolveSourceArtifact(fooRelative, MAIN));
    assertSame(artifactFactory.getSourceArtifact(barRelative, clientRoRoot),
        artifactFactory.resolveSourceArtifact(barRelative, MAIN));
  }

  @Test
  public void testResolveArtifact_inExternalRepo() throws Exception {
    assertSame(
        artifactFactory.getSourceArtifact(alienRelative, alienRoot),
        artifactFactory.resolveSourceArtifact(alienRelative, MAIN));
  }

  @Test
  public void testResolveArtifact_noDerived_derivedRoot() throws Exception {
    assertNull(artifactFactory.resolveSourceArtifact(
            outRoot.getPath().getRelative(fooRelative).relativeTo(execRoot), MAIN));
    assertNull(artifactFactory.resolveSourceArtifact(
            outRoot.getPath().getRelative(barRelative).relativeTo(execRoot), MAIN));
  }

  @Test
  public void testResolveArtifact_noDerived_simpleSource_other() throws Exception {
    Artifact actual = artifactFactory.resolveSourceArtifact(fooRelative, MAIN);
    assertSame(artifactFactory.getSourceArtifact(fooRelative, clientRoot), actual);
    actual = artifactFactory.resolveSourceArtifact(barRelative, MAIN);
    assertSame(artifactFactory.getSourceArtifact(barRelative, clientRoRoot), actual);
  }

  @Test
  public void testResolveArtifactWithUpLevelFailsCleanly() throws Exception {
    // We need a package in the root directory to make every exec path (even one with up-level
    // references) be in a package.
    Map<PackageIdentifier, Root> packageRoots = ImmutableMap.of(
        PackageIdentifier.createInMainRepo(new PathFragment("")), clientRoot);
    artifactFactory.setPackageRoots(packageRoots);
    PathFragment outsideWorkspace = new PathFragment("../foo");
    PathFragment insideWorkspace =
        new PathFragment("../" + clientRoot.getPath().getBaseName() + "/foo");
    assertNull(artifactFactory.resolveSourceArtifact(outsideWorkspace, MAIN));
    assertNull("Up-level-containing paths that descend into the right workspace aren't allowed",
            artifactFactory.resolveSourceArtifact(insideWorkspace, MAIN));
    MockPackageRootResolver packageRootResolver = new MockPackageRootResolver();
    packageRootResolver.setPackageRoots(packageRoots);
    Map<PathFragment, Artifact> result = new HashMap<>();
    result.put(insideWorkspace, null);
    result.put(outsideWorkspace, null);
    assertThat(
        artifactFactory.resolveSourceArtifacts(ImmutableList.of(insideWorkspace, outsideWorkspace),
            packageRootResolver).entrySet()).containsExactlyElementsIn(result.entrySet());
  }

  @Test
  public void testClearResetsFactory() {
    Artifact fooArtifact = artifactFactory.getSourceArtifact(fooRelative, clientRoot);
    artifactFactory.clear();
    setupRoots();
    assertNotSame(fooArtifact, artifactFactory.getSourceArtifact(fooRelative, clientRoot));
  }

  @Test
  public void testFindDerivedRoot() throws Exception {
    assertThat(artifactFactory.isDerivedArtifact(fooRelative)).isFalse();
    assertThat(artifactFactory.isDerivedArtifact(
        new PathFragment("bazel-out/local-fastbuild/bin/foo"))).isTrue();
  }

  @Test
  public void testSetGeneratingActionIdempotenceNewActionGraph() throws Exception {
    Artifact a = artifactFactory.getDerivedArtifact(fooRelative, outRoot, NULL_ARTIFACT_OWNER);
    Artifact b = artifactFactory.getDerivedArtifact(barRelative, outRoot, NULL_ARTIFACT_OWNER);
    MutableActionGraph actionGraph = new MapBasedActionGraph();
    Action originalAction = new ActionsTestUtil.NullAction(NULL_ACTION_OWNER, a);
    actionGraph.registerAction(originalAction);

    // Creating a second Action referring to the Artifact should create a conflict.
    try {
      Action action = new ActionsTestUtil.NullAction(NULL_ACTION_OWNER, a, b);
      actionGraph.registerAction(action);
      fail();
    } catch (ActionConflictException e) {
      assertSame(a, e.getArtifact());
      assertSame(originalAction, actionGraph.getGeneratingAction(a));
    }
  }

  @Test
  public void testGetDerivedArtifact() throws Exception {
    PathFragment toolPath = new PathFragment("_bin/tool");
    Artifact artifact = artifactFactory.getDerivedArtifact(toolPath, execRoot);
    assertEquals(toolPath, artifact.getExecPath());
    assertEquals(Root.asDerivedRoot(execRoot), artifact.getRoot());
    assertEquals(execRoot.getRelative(toolPath), artifact.getPath());
    assertNull(artifact.getOwner());
  }

  @Test
  public void testGetDerivedArtifactFailsForAbsolutePath() throws Exception {
    try {
      artifactFactory.getDerivedArtifact(new PathFragment("/_bin/b"), execRoot);
      fail();
    } catch (IllegalArgumentException e) {
      // Expected exception
    }
  }

  private static class MockPackageRootResolver implements PackageRootResolver {
    private Map<PathFragment, Root> packageRoots = Maps.newHashMap();

    public void setPackageRoots(Map<PackageIdentifier, Root> packageRoots) {
      for (Entry<PackageIdentifier, Root> packageRoot : packageRoots.entrySet()) {
        this.packageRoots.put(packageRoot.getKey().getPackageFragment(), packageRoot.getValue());
      }
    }

    @Override
    public Map<PathFragment, Root> findPackageRootsForFiles(Iterable<PathFragment> execPaths) {
      Map<PathFragment, Root> result = new HashMap<>();
      for (PathFragment execPath : execPaths) {
        for (PathFragment dir = execPath.getParentDirectory(); dir != null;
            dir = dir.getParentDirectory()) {
          if (packageRoots.get(dir) != null) {
            result.put(execPath, packageRoots.get(dir));
          }
        }
        if (result.get(execPath) == null) {
          result.put(execPath, null);
        }
      }
      return result;
    }
    
    @Override
    @Nullable
    public Map<PathFragment, Root> findPackageRoots(Iterable<PathFragment> execPaths)
        throws PackageRootResolutionException {
      return null; // unused
    }
  }
}
