// 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.cmdline;


import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import com.google.devtools.build.lib.cmdline.TargetPattern.Type;

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

/**
 * Tests for {@link TargetPattern}.
 */
@RunWith(JUnit4.class)
public class TargetPatternTest {

  @Test
  public void testPassingValidations() throws TargetParsingException {
    parse("foo:bar");
    parse("foo:all");
    parse("foo/...:all");
    parse("foo:*");

    parse("//foo");
    parse("//foo:bar");
    parse("//foo:all");

    parse("//foo/all");
    parse("java/com/google/foo/Bar.java");
    parse("//foo/...:all");

    parse("//...");
    parse("@repo//foo:bar");
    parse("@repo//foo:all");
    parse("@repo//:bar");
  }

  @Test
  public void testInvalidPatterns() throws TargetParsingException {
    try {
      parse("Bar&&&java");
      fail();
    } catch (TargetParsingException expected) {
    }
  }

  @Test
  public void testNormalize() {
    // Good cases.
    assertEquals("empty", TargetPattern.normalize("empty"));
    assertEquals("a/b", TargetPattern.normalize("a/b"));
    assertEquals("a/b/c", TargetPattern.normalize("a/b/c"));
    assertEquals("a/b/c.d", TargetPattern.normalize("a/b/c.d"));
    assertEquals("a/b/c..", TargetPattern.normalize("a/b/c.."));
    assertEquals("a/b/c...", TargetPattern.normalize("a/b/c..."));

    assertEquals("a/b", TargetPattern.normalize("a/b/")); // Remove trailing empty segments
    assertEquals("a/c", TargetPattern.normalize("a//c")); // Remove empty inner segments
    assertEquals("a/d", TargetPattern.normalize("a/./d")); // Remove inner dot segments
    assertEquals("a", TargetPattern.normalize("a/."));     // Remove trailing dot segments
    // Remove .. segment and its predecessor
    assertEquals("a/e", TargetPattern.normalize("a/b/../e"));
    // Remove trailing .. segment and its predecessor
    assertEquals("a/g", TargetPattern.normalize("a/g/b/.."));
    // Remove double .. segments and two predecessors
    assertEquals("a/h", TargetPattern.normalize("a/b/c/../../h"));
    // Don't remove leading .. segments
    assertEquals("../a", TargetPattern.normalize("../a"));
    assertEquals("../../a", TargetPattern.normalize("../../a"));
    assertEquals("../../../a", TargetPattern.normalize("../../../a"));
    assertEquals("../../b", TargetPattern.normalize("a/../../../b"));
  }

  @Test
  public void testTargetsBelowDirectoryContainsNestedPatterns() throws Exception {
    // Given an outer pattern '//foo/...',
    TargetPattern outerPattern = parseAsExpectedType("//foo/...", Type.TARGETS_BELOW_DIRECTORY);
    // And a nested inner pattern '//foo/bar/...',
    TargetPattern innerPattern = parseAsExpectedType("//foo/bar/...", Type.TARGETS_BELOW_DIRECTORY);
    // Then the outer pattern contains the inner pattern,,
    assertTrue(outerPattern.containsBelowDirectory(innerPattern));
    // And the inner pattern does not contain the outer pattern.
    assertFalse(innerPattern.containsBelowDirectory(outerPattern));
  }

  @Test
  public void testTargetsBelowDirectoryIsExcludableFromForIndependentPatterns() throws Exception {
    // Given a pattern '//foo/...',
    TargetPattern patternFoo = parseAsExpectedType("//foo/...", Type.TARGETS_BELOW_DIRECTORY);
    // And a pattern '//bar/...',
    TargetPattern patternBar = parseAsExpectedType("//bar/...", Type.TARGETS_BELOW_DIRECTORY);
    // Then neither pattern contains the other.
    assertFalse(patternFoo.containsBelowDirectory(patternBar));
    assertFalse(patternBar.containsBelowDirectory(patternFoo));
  }

  @Test
  public void testTargetsBelowDirectoryContainsForOtherPatternTypes() throws Exception {
    // Given a TargetsBelowDirectory pattern, tbdFoo of '//foo/...',
    TargetPattern tbdFoo = parseAsExpectedType("//foo/...", Type.TARGETS_BELOW_DIRECTORY);

    // And target patterns of each type other than TargetsBelowDirectory, e.g. 'foo/bar',
    // '//foo:bar', and 'foo:all',
    TargetPattern pathAsTargetPattern = parseAsExpectedType("foo/bar", Type.PATH_AS_TARGET);
    TargetPattern singleTargetPattern = parseAsExpectedType("//foo:bar", Type.SINGLE_TARGET);
    TargetPattern targetsInPackagePattern = parseAsExpectedType("foo:all", Type.TARGETS_IN_PACKAGE);

    // Then the non-TargetsBelowDirectory patterns are contained by tbdFoo, and do not contain
    // tbdFoo.
    assertTrue(tbdFoo.containsBelowDirectory(pathAsTargetPattern));
    assertFalse(pathAsTargetPattern.containsBelowDirectory(tbdFoo));

    assertTrue(tbdFoo.containsBelowDirectory(singleTargetPattern));
    assertFalse(singleTargetPattern.containsBelowDirectory(tbdFoo));

    assertTrue(tbdFoo.containsBelowDirectory(targetsInPackagePattern));
    assertFalse(targetsInPackagePattern.containsBelowDirectory(tbdFoo));
  }

  @Test
  public void testTargetsBelowDirectoryDoesNotContainCoincidentPrefixPatterns() throws Exception {
    // Given a TargetsBelowDirectory pattern, tbdFoo of '//foo/...',
    TargetPattern tbdFoo = parseAsExpectedType("//foo/...", Type.TARGETS_BELOW_DIRECTORY);

    // And target patterns with prefixes equal to the directory of the TBD pattern, but not below
    // it,
    TargetPattern targetsBelowDirectoryPattern =
        parseAsExpectedType("//food/...", Type.TARGETS_BELOW_DIRECTORY);
    TargetPattern pathAsTargetPattern = parseAsExpectedType("food/bar", Type.PATH_AS_TARGET);
    TargetPattern singleTargetPattern = parseAsExpectedType("//food:bar", Type.SINGLE_TARGET);
    TargetPattern targetsInPackagePattern =
        parseAsExpectedType("food:all", Type.TARGETS_IN_PACKAGE);

    // Then the non-TargetsBelowDirectory patterns are not contained by tbdFoo.
    assertFalse(tbdFoo.containsBelowDirectory(targetsBelowDirectoryPattern));
    assertFalse(tbdFoo.containsBelowDirectory(pathAsTargetPattern));
    assertFalse(tbdFoo.containsBelowDirectory(singleTargetPattern));
    assertFalse(tbdFoo.containsBelowDirectory(targetsInPackagePattern));
  }

  @Test
  public void testDepotRootTargetsBelowDirectoryContainsPatterns() throws Exception {
    // Given a TargetsBelowDirectory pattern, tbdDepot of '//...',
    TargetPattern tbdDepot = parseAsExpectedType("//...", Type.TARGETS_BELOW_DIRECTORY);

    // And target patterns of each type other than TargetsBelowDirectory, e.g. 'foo/bar',
    // '//foo:bar', and 'foo:all',
    TargetPattern tbdFoo = parseAsExpectedType("//foo/...", Type.TARGETS_BELOW_DIRECTORY);
    TargetPattern pathAsTargetPattern = parseAsExpectedType("foo/bar", Type.PATH_AS_TARGET);
    TargetPattern singleTargetPattern = parseAsExpectedType("//foo:bar", Type.SINGLE_TARGET);
    TargetPattern targetsInPackagePattern = parseAsExpectedType("foo:all", Type.TARGETS_IN_PACKAGE);

    // Then the patterns are contained by tbdDepot, and do not contain tbdDepot.
    assertTrue(tbdDepot.containsBelowDirectory(tbdFoo));
    assertFalse(tbdFoo.containsBelowDirectory(tbdDepot));

    assertTrue(tbdDepot.containsBelowDirectory(pathAsTargetPattern));
    assertFalse(pathAsTargetPattern.containsBelowDirectory(tbdDepot));

    assertTrue(tbdDepot.containsBelowDirectory(singleTargetPattern));
    assertFalse(singleTargetPattern.containsBelowDirectory(tbdDepot));

    assertTrue(tbdDepot.containsBelowDirectory(targetsInPackagePattern));
    assertFalse(targetsInPackagePattern.containsBelowDirectory(tbdDepot));
  }

  private static TargetPattern parse(String pattern) throws TargetParsingException {
    return TargetPattern.defaultParser().parse(pattern);
  }

  private static TargetPattern parseAsExpectedType(String pattern, Type expectedType)
      throws TargetParsingException {
    TargetPattern parsedPattern = parse(pattern);
    assertThat(parsedPattern.getType()).isEqualTo(expectedType);
    assertThat(parsedPattern.getOriginalPattern()).isEqualTo(pattern);
    return parsedPattern;
  }
}
