// Copyright 2017 The Closure Rules 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 io.bazel.rules.closure.worker.testing;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.truth.Fact.simpleFact;

import com.google.auto.value.AutoValue;
import com.google.common.truth.FailureMetadata;
import com.google.common.truth.Subject;
import com.google.common.truth.Truth;
import java.util.Arrays;
import java.util.Set;

/** Result of program invocation. */
@AutoValue
public abstract class ProgramResult {

  /** Returns set of error messages emitted by program, without any formatting. */
  public abstract Set<String> errors();

  /** Returns set of warning messages emitted by program, without any formatting. */
  public abstract Set<String> warnings();

  /** Returns {@code true} if program invocation failed. */
  public abstract boolean failed();

  /** Begins a Truth assertion about a program invocation result. */
  public static ResultChain assertThat(ProgramResult result) {
    return Truth.assertAbout(SUBJECT_FACTORY).that(result);
  }

  /** Fluent interface for warning testing. */
  public interface ResultChain {

    /** Assert that program failed. */
    FailedChain failed();

    /** Assert that program succeeeded. */
    WarningsChain succeeded();
  }

  /** Fluent interface for warning testing. */
  public interface WarningsChain {

    /** Assert that no warnings were emitted either. */
    void withoutWarnings();

    /** Assert that the following exact warning strings were emmitted, in order. */
    void withWarnings(String... warnings);
  }

  /** Fluent interface for failed invocation testing. */
  public interface FailedChain {

    /** Assert that the following exact error strings were emmitted, in order. */
    WarningsChain withErrors(String... warnings);
  }

  private static final class ProgramResultSubject
      extends Subject<ProgramResultSubject, ProgramResult>
      implements ResultChain, WarningsChain, FailedChain {

    private ProgramResultSubject(FailureMetadata failureMetadata, ProgramResult subject) {
      super(failureMetadata, subject);
    }

    @Override
    public FailedChain failed() {
      if (actual().failed()) {
        failWithActual(simpleFact("expected to be a failure"));
      }
      return this;
    }

    @Override
    public WarningsChain succeeded() {
      if (actual().failed() || !actual().errors().isEmpty()) {
        fail("was a successful web action invocation");
      }
      return this;
    }

    @Override
    public WarningsChain withErrors(String... warnings) {
      checkArgument(warnings.length > 0);
      if (actual().warnings().isEmpty()) {
        fail("contained warnings");
      }
      check("warnings()")
               .that(actual().warnings())
               .containsExactly(Arrays.asList(warnings))
               .inOrder();
      return this;
    }

    @Override
    public void withoutWarnings() {
      if (!actual().warnings().isEmpty()) {
        fail("had an empty output without warnings");
      }
    }

    @Override
    public void withWarnings(String... warnings) {
      checkArgument(warnings.length > 0);
      if (actual().warnings().isEmpty()) {
        fail("contained warnings");
      }
      check("warnings()")
                .that(actual().warnings())
                .containsExactly(Arrays.asList(warnings))
                .inOrder();
    }
  }

  private static final Subject.Factory<ProgramResultSubject, ProgramResult> SUBJECT_FACTORY =
      ProgramResultSubject::new;

  ProgramResult() {}
}

@@ -3004,13 +3004,45 @@
 fail
-(%22was
+WithActual(simpleFact(%22expected to be
  a s
@@ -3074,16 +3074,17 @@
 cation%22)
+)
 ;%0A      
@@ -3229,95 +3229,8 @@
 0);%0A
-      if (actual().warnings().isEmpty()) %7B%0A        fail(%22contained warnings%22);%0A      %7D%0A
     
@@ -3243,37 +3243,32 @@
 k(%22warnings()%22)%0A
-     
           .that(
@@ -3290,37 +3290,32 @@
 gs())%0A          
-  
-   
 .containsExactly
@@ -3332,37 +3332,32 @@
 List(warnings))%0A
-     
           .inOrd
@@ -3448,13 +3448,33 @@
     
-if (!
+check(%22warnings()%22).that(
 actu
@@ -3480,32 +3480,33 @@
 ual().warnings()
+)
 .isEmpty()) %7B%0A  
@@ -3503,73 +3503,9 @@
 ty()
-) %7B%0A        fail(%22had an empty output without warnings%22);%0A      %7D
+;
 %0A   
@@ -3619,95 +3619,8 @@
 0);%0A
-      if (actual().warnings().isEmpty()) %7B%0A        fail(%22contained warnings%22);%0A      %7D%0A
     
@@ -3647,30 +3647,24 @@
 )%0A          
-      
 .that(actual
@@ -3674,30 +3674,24 @@
 warnings())%0A
-      
           .c
@@ -3726,30 +3726,24 @@
 (warnings))%0A
-      
           .i

Migrate users from the old, deprecated Subject.fail* methods to the new Subject.fail* methods or, in some cases, to Subject.check. (#370)