// Copyright 2019 The Nomulus 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.

apply plugin: 'java'
apply plugin: 'nebula.lint'
apply plugin: 'net.ltgt.errorprone'
apply plugin: 'checkstyle'
apply plugin: 'jacoco'

// Checkstyle should run as part of the testing task
tasks.test.dependsOn tasks.checkstyleMain
tasks.test.dependsOn tasks.checkstyleTest

// Generate per-subproject reports in build/reports/jacoco directory.
// TODO(weiminyu): publish periodical reports to well known location.
// TODO(weiminyu): investigate incremental coverage change calculation and alert.
tasks.test.finalizedBy jacocoTestReport

// Exclude test-only dependencies from release artifacts.
// TLDR: Project dependency should be on runtimeClasspath. A custom
// configuration 'deploy_jar' is used as a proxy to expose runtimeClasspath
// to other projects. It is also convenient to exclude test-only jars from
// runtimeClasspath.
//
// Here is the context:
// - Some dependencies have test-only jars on their 'compile' classpaths.
// - As a result, they appear in our  'compile', 'runtimeClasspath' and
//   'default' configurations, among others, and end up in our release war
//   files.
// - Since these jars are needed for our own tests, we can only exclude them
//   from runtimeClasspath. Excluding them from 'compile' or 'runtime' would
//   also exclude them from testCompileClasspath and testRuntimeClasspath,
//   resulting in test failures.
// - If configuration is not specified, project dependency uses the 'default'
//   configuration, which always has the same content as 'runtime'.
// - When release is involved, 'runtimeClasspath' is actually the right
//   configuration to use. The 'runtime' configuration does not include
//   'runtimeOnly' dependencies, while 'runtimeClasspath' does.
// - 'runtimeClasspath' cannot be referenced directly by another project.
//   We use a custom configuration 'deploy_jar' as a proxy.
// TODO(weiminyu): Fix all project dependencies to use deploy_jar
configurations {
  deploy_jar.extendsFrom runtimeClasspath

  all.findAll {
    it.name in ['runtimeClasspath', 'compileClasspath']
  }.each {
    // JUnit is from org.apache.beam:beam-runners-google-cloud-dataflow-java,
    // and json-simple.
    it.exclude group: 'junit'
    // Mockito is from org.apache.beam:beam-runners-google-cloud-dataflow-java
    // See https://issues.apache.org/jira/browse/BEAM-8862
    it.exclude group: 'org.mockito', module: 'mockito-core'
  }
}

dependencies {
    // compatibility with Java 8
    errorprone("com.google.errorprone:error_prone_core:2.3.4")
}

test {
  useJUnitPlatform()
}

// Sets up integration test with a registry environment. The target environment
// is passed by the 'test.gcp_integration.env' property. Test runner must have
// been authorized to access the corresponding GCP project, e.g., by running
// 'gcloud auth application-default login' or by downloading a credential file
// and assign the path to it to the GOOGLE_APPLICATION_CREDENTIALS environment
// variable.
//
// A typical use case is to run tests from desktop that accesses Cloud resources. See
// core/src/test/java/google/registry/beam/initsql/BeamJpaModuleTest.java for an example.
tasks.withType(Test).configureEach {
  def gcp_integration_env_property = 'test.gcp_integration.env'

  if (project.hasProperty(gcp_integration_env_property)) {
    String targetEnv = project.property(gcp_integration_env_property)

    if (targetEnv in ['sandbox', 'production']) {
      throw new RuntimeException("Integration test with production or sandbox not allowed.")
    }
    systemProperty gcp_integration_env_property, targetEnv
  }

  // This environment variable along with testcontainers 1.15.2 works around
  // a race condition introduced in 1.15.0. This can be removed once httpclient5
  // becomes the default transport type in testcontainers, which may happen
  // in 1.16.x.
  // See https://github.com/testcontainers/testcontainers-java/issues/3531
  // for more information.
  environment('TESTCONTAINERS_TRANSPORT_TYPE', 'httpclient5')
}

tasks.withType(JavaCompile).configureEach {
    // The -Werror flag causes Intellij to fail on deprecated api use.
    // Allow IDE user to turn off this flag by specifying a Gradle VM
    // option from inside the IDE.
    if (System.getProperty('no_werror') != 'true' &&
        // The core project throws an warning about Gradle annotation processor
        // not compatible with Java source version > 8. Re-assess once the
        // warning is fixed.
        getProject().name != 'core') {
      options.compilerArgs << "-Werror"
    }
    options.errorprone.disableWarningsInGeneratedCode = true
    options.errorprone.errorproneArgumentProviders.add([
            asArguments: {
                return ['-XepExcludedPaths:.*/build/generated/.*']
            }] as CommandLineArgumentProvider)
    // Disable features currently incompatible with Java 12
    if ((JavaVersion.current().majorVersion as Integer) > 11) {
      // This check is broken in Java 12.
      // See https://github.com/google/error-prone/issues/1257
      options.errorprone.errorproneArgs=['-Xep:Finally:OFF']
    }
}

// TODO: Change source version to 11 after target version is changed to 11 and
// once we figure out what's wrong with Dagger compilation with source version
// >8.
sourceCompatibility = '8'
// TODO: Change target version to 11. Source version can stay at 8.
targetCompatibility = '8'

compileJava { options.encoding = "UTF-8" }
compileTestJava { options.encoding = "UTF-8" }

gradleLint.rules = [
        // Checks if Gradle wrapper is up-to-date
        'archaic-wrapper',
        // Checks for indirect dependencies with dynamic version spec. Best
        // practice calls for declaring them with specific versions.
        'undeclared-dependency',
        'unused-dependency'
        // TODO(weiminyu): enable more dependency checks
]

// To check or fix file formats, run the following commands from this directory:
// - Check: ./gradlew spotlessCheck
// - Format in place: ./gradlew spotlessApply
spotless {
  format 'misc', {
    clearSteps()
    target '**/*.gradle'
    targetExclude '**/cloudbuild-caches/**'
    trimTrailingWhitespace()
    indentWithSpaces(2)
    endWithNewline()
  }
}

jacocoTestReport {
    // Use coverage data from all tests tasks, not just the one named 'test'.
    getExecutionData().setFrom(fileTree(buildDir).include("/jacoco/*.exec"))
}

// Initialize for coverage minimum determination for each sub project.
task initCoverageMinimums {
  // Use current coverage ratio of each subproject as placeholder value.
  // Long-term plan is to calculate incremental coverage on the fly.
  rootProject.ext.coverageMinimums = [
     'core'    : 0.6,
     'proxy'   : 0.52,
     'util'    : 0.57
  ].asImmutable()

  rootProject.ext.getMinCoverage = { key ->
    if (rootProject.coverageMinimums.containsKey(key)) {
      return rootProject.coverageMinimums.get(key)
    }
    return 0.0
  }
}

// Alerts for coverage violation. Note that,
// - This task is FYI only and needs to be invoked explicitly.
// - This task does not address incremental coverage.
jacocoTestCoverageVerification {

  dependsOn initCoverageMinimums

  violationRules {
    rule {
      // Each limit consists of the following properties:
      // - An 'element' type: BUNDLE (default), PACKAGE, CLASS, SOURCEFILE, or METHOD.
      // - A 'counter' type: INSTRUCTION (default), LINE, BRANCH, COMPLEXITY, METHOD, or CLASS
      // - A 'value' type: TOTALCOUNT, COVEREDCOUNT, MISSEDCOUNT, COVEREDRATIO (default),
      //   or MISSEDRATIO
      // - The 'minimum' threshold, given as a fraction or a percentage (including '%')
      limit {
        minimum = rootProject.getMinCoverage(project.getName())
      }
    }
  }
}
