/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

import groovy.json.JsonOutput

plugins { id 'org.apache.beam.module' }

applyJavaNature(
        automaticModuleName: 'org.apache.beam.runners.prism',
)

description = "Apache Beam :: Runners :: Prism :: Java"
ext.summary = "Support for executing a pipeline on Prism."

dependencies {
    implementation project(path: ":sdks:java:core", configuration: "shadow")
    implementation project(":runners:portability:java")

    implementation library.java.joda_time
    implementation library.java.slf4j_api
    implementation library.java.vendored_guava_32_1_2_jre
    compileOnly library.java.hamcrest

    testImplementation library.java.junit
    testImplementation library.java.mockito_core
    testImplementation library.java.truth
}

tasks.test {
    var prismBuildTask = dependsOn(':runners:prism:build')
    systemProperty 'prism.buildTarget', prismBuildTask.project.property('buildTarget').toString()
}

// Below is configuration to support running the Java Validates Runner tests.

configurations {
  validatesRunner
}

dependencies {
  implementation project(path: ":sdks:java:core", configuration: "shadow")
  implementation library.java.hamcrest
  permitUnusedDeclared library.java.hamcrest
  implementation library.java.joda_time
  implementation library.java.slf4j_api
  implementation library.java.vendored_guava_32_1_2_jre

  testImplementation library.java.hamcrest
  testImplementation library.java.junit
  testImplementation library.java.mockito_core
  testImplementation library.java.slf4j_jdk14

  validatesRunner project(path: ":sdks:java:core", configuration: "shadowTest")
  validatesRunner project(path: ":runners:core-java", configuration: "testRuntimeMigration")
  validatesRunner project(path: project.path, configuration: "testRuntimeMigration")
}

project.evaluationDependsOn(":sdks:java:core")
project.evaluationDependsOn(":runners:core-java")

def sickbayTests = [
    // PortableMetrics doesn't implement "getCommitedOrNull" from Metrics
    // Preventing Prism from passing these tests.
    // In particular, it doesn't subclass MetricResult with an override, and
    // it explicilty passes "false" to commited supported in create.
    //
    // There is not currently a category for excluding these _only_ in committed mode
    'org.apache.beam.sdk.metrics.MetricsTest$CommittedMetricTests.testAllCommittedMetrics',
    'org.apache.beam.sdk.metrics.MetricsTest$CommittedMetricTests.testCommittedCounterMetrics',
    'org.apache.beam.sdk.metrics.MetricsTest$CommittedMetricTests.testCommittedDistributionMetrics',
    'org.apache.beam.sdk.metrics.MetricsTest$CommittedMetricTests.testCommittedStringSetMetrics',
    'org.apache.beam.sdk.metrics.MetricsTest$CommittedMetricTests.testCommittedGaugeMetrics',

    // Instead of 42, Prism got 84, which suggests two early panes of 42 are fired.
    'org.apache.beam.sdk.transforms.GroupByKeyTest$BasicTests.testAfterProcessingTimeContinuationTriggerUsingState',

    // A regression introduced when we use number of pending elements rather than watermark to determine
    // the bundle readiness of a stateless stage.
    // Currently, Prism processes a bundle of [100, ..., 1000] when watermark is set to 100,
    // and then a second bundle of [1, ... 99] when the watermark is set to +inf.
    // As a result, it yields an output of [-999, 1, 1...], where -999 comes from the difference between 1000 and 1.
    // According to https://beam.apache.org/releases/javadoc/current/org/apache/beam/sdk/transforms/DoFn.RequiresTimeSortedInput.html,
    // the stateful dofn with `RequiresTimeSortedInput` annotation should buffer an element until the element's timestamp + allowed_lateness.
    // This stateful dofn feature is not yet supported in Prism.
    'org.apache.beam.sdk.transforms.ParDoTest$StateTests.testRequiresTimeSortedInputWithLateDataAndAllowedLateness',

    // Triggered Side Inputs not yet implemented in Prism.
    // https://github.com/apache/beam/issues/31438
    'org.apache.beam.sdk.transforms.ViewTest.testTriggeredLatestSingleton',
    'org.apache.beam.sdk.testing.TestStreamTest.testProcessingTimeTrigger',

    // Prism doesn't support multiple TestStreams.
    'org.apache.beam.sdk.testing.TestStreamTest.testMultipleStreams',

    // GroupIntoBatchesTest tests that fail:
    // Wrong number of elements in windows after GroupIntoBatches.
    'org.apache.beam.sdk.transforms.GroupIntoBatchesTest.testInStreamingMode',
    'org.apache.beam.sdk.transforms.GroupIntoBatchesTest.testBufferingTimerInFixedWindow',
    'org.apache.beam.sdk.transforms.GroupIntoBatchesTest.testBufferingTimerInGlobalWindow',
    // ShardedKey not yet implemented.
    'org.apache.beam.sdk.transforms.GroupIntoBatchesTest.testWithShardedKeyInGlobalWindow',

    // Technically these tests "succeed"
    // the test is just complaining that an AssertionException isn't a RuntimeException
    //
    // java.lang.RuntimeException: test error in finalize
    'org.apache.beam.sdk.transforms.ParDoTest$LifecycleTests.testParDoWithErrorInFinishBatch',
    // java.lang.RuntimeException: test error in process
    'org.apache.beam.sdk.transforms.ParDoTest$LifecycleTests.testParDoWithErrorInProcessElement',
    // java.lang.RuntimeException: test error in initialize
    'org.apache.beam.sdk.transforms.ParDoTest$LifecycleTests.testParDoWithErrorInStartBatch',

    // Only known window fns supported, not general window merging
    // Custom window fns not yet implemented in prism.
    // https://github.com/apache/beam/issues/31921
    'org.apache.beam.sdk.transforms.windowing.WindowTest.testMergingCustomWindows',
    'org.apache.beam.sdk.transforms.windowing.WindowTest.testMergingCustomWindowsKeyedCollection',
    'org.apache.beam.sdk.transforms.windowing.WindowTest.testMergingCustomWindowsWithoutCustomWindowTypes',

    // Possibly a different error being hidden behind the main error.
    // org.apache.beam.sdk.util.WindowedValue$ValueInGlobalWindow cannot be cast to class java.lang.String
    // TODO(https://github.com/apache/beam/issues/29973)
    'org.apache.beam.sdk.transforms.ReshuffleTest.testReshufflePreservesMetadata',
    // TODO(https://github.com/apache/beam/issues/31231)
    'org.apache.beam.sdk.transforms.RedistributeTest.testRedistributePreservesMetadata',

    // Prism isn't handling Java's side input views properly, likely related to triggered side inputs.
    // https://github.com/apache/beam/issues/32932
    // java.lang.IllegalArgumentException: PCollection with more than one element accessed as a singleton view.
    // Consider using Combine.globally().asSingleton() to combine the PCollection into a single value
    // java.util.NoSuchElementException: Empty PCollection accessed as a singleton view.
    'org.apache.beam.sdk.transforms.ViewTest.testDiscardingNonSingletonSideInput',
    // ava.lang.IllegalArgumentException: Duplicate values for a
    'org.apache.beam.sdk.transforms.ViewTest.testMapSideInputWithNullValuesCatchesDuplicates',
    // java.lang.IllegalArgumentException: PCollection with more than one element accessed as a singleton view....
    'org.apache.beam.sdk.transforms.ViewTest.testNonSingletonSideInput',
    // java.util.NoSuchElementException: Empty PCollection accessed as a singleton view.
    'org.apache.beam.sdk.transforms.ViewTest.testEmptySingletonSideInput',
    // Prism side encoding error.
    // java.lang.IllegalStateException: java.io.EOFException
    'org.apache.beam.sdk.transforms.ViewTest.testSideInputWithNestedIterables',

    // Missing output due to processing time timer skew.
    'org.apache.beam.sdk.transforms.ParDoTest$TimestampTests.testProcessElementSkew',

    // Filtered by PortableRunner tests.
    // Teardown not called in exceptions
    // https://github.com/apache/beam/issues/20372
    'org.apache.beam.sdk.transforms.ParDoLifecycleTest.testTeardownCalledAfterExceptionInFinishBundle',
    'org.apache.beam.sdk.transforms.ParDoLifecycleTest.testTeardownCalledAfterExceptionInFinishBundleStateful',
    'org.apache.beam.sdk.transforms.ParDoLifecycleTest.testTeardownCalledAfterExceptionInProcessElement',
    'org.apache.beam.sdk.transforms.ParDoLifecycleTest.testTeardownCalledAfterExceptionInProcessElementStateful',
    'org.apache.beam.sdk.transforms.ParDoLifecycleTest.testTeardownCalledAfterExceptionInSetup',
    'org.apache.beam.sdk.transforms.ParDoLifecycleTest.testTeardownCalledAfterExceptionInSetupStateful',
    'org.apache.beam.sdk.transforms.ParDoLifecycleTest.testTeardownCalledAfterExceptionInStartBundle',
    'org.apache.beam.sdk.transforms.ParDoLifecycleTest.testTeardownCalledAfterExceptionInStartBundleStateful',
]

/**
 * Runs Java ValidatesRunner tests against the Prism Runner
 * with the specified environment type.
 */
def createPrismValidatesRunnerTask = { name, environmentType ->
  Task vrTask = tasks.create(name: name, type: Test, group: "Verification") {
    description "PrismRunner Java $environmentType ValidatesRunner suite"
    classpath = configurations.validatesRunner

    var prismBuildTask = dependsOn(':runners:prism:build')
    systemProperty "beamTestPipelineOptions", JsonOutput.toJson([
        "--runner=TestPrismRunner",
        "--experiments=beam_fn_api",
        "--defaultEnvironmentType=${environmentType}",
        "--prismLogLevel=warn",
        "--prismLocation=${prismBuildTask.project.property('buildTarget').toString()}",
        "--enableWebUI=false",
    ])
    testClassesDirs = files(project(":sdks:java:core").sourceSets.test.output.classesDirs)
    useJUnit {
      includeCategories 'org.apache.beam.sdk.testing.ValidatesRunner'
      // Should be run only in a properly configured SDK harness environment
      excludeCategories 'org.apache.beam.sdk.testing.UsesExternalService'
      excludeCategories 'org.apache.beam.sdk.testing.UsesSdkHarnessEnvironment'

      // Not supported in Portable Java SDK yet.
      // https://github.com/apache/beam/issues?q=is%3Aissue+is%3Aopen+MultimapState
      excludeCategories 'org.apache.beam.sdk.testing.UsesMultimapState'

      // Not yet supported in Prism.
      excludeCategories 'org.apache.beam.sdk.testing.UsesBoundedTrieMetrics'
    }
    filter {
      for (String test : sickbayTests) {
          excludeTestsMatching test
      }
    }
  }
  return vrTask
}

tasks.register("validatesRunnerSickbay", Test) {
    group = "Verification"
    description "Validates Prism local runner (Sickbay Tests)"

    var prismBuildTask = dependsOn(':runners:prism:build')
    systemProperty "beamTestPipelineOptions", JsonOutput.toJson([
        "--runner=TestPrismRunner",
        "--experiments=beam_fn_api",
        "--enableWebUI=false",
        "--prismLogLevel=warn",
        "--prismLocation=${prismBuildTask.project.property('buildTarget').toString()}"
    ])

  classpath = configurations.validatesRunner
  testClassesDirs = files(project(":sdks:java:core").sourceSets.test.output.classesDirs)

  filter {
    for (String test : sickbayTests) {
      includeTestsMatching test
    }
  }
}

task prismDockerValidatesRunner {
  Task vrTask = createPrismValidatesRunnerTask("prismDockerValidatesRunnerTests", "DOCKER")
  vrTask.dependsOn ":sdks:java:container:${project.ext.currentJavaVersion}:docker"
}

task prismLoopbackValidatesRunner {
  dependsOn createPrismValidatesRunnerTask("prismLoopbackValidatesRunnerTests", "LOOPBACK")
}
