content: /*
   * 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.
   */
  package org.apache.cassandra.distributed.impl;
  
  import java.io.IOException;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.function.Consumer;
  
  import org.junit.AssumptionViolatedException;
  import org.junit.Test;
  
  import org.apache.cassandra.distributed.api.IInstanceConfig;
  import org.apache.cassandra.distributed.api.IInvokableInstance;
  import org.apache.cassandra.distributed.api.TokenSupplier;
  import org.apache.cassandra.distributed.impl.AbstractCluster.AbstractBuilder;
  import org.apache.cassandra.distributed.shared.Versions;
  import org.apache.cassandra.utils.FailingRunnable;
  import org.assertj.core.api.Assertions;
  import org.mockito.Mockito;
  
  
  public class AbstractClusterTest
  {
      @Test
      public void allowVnodeWithMultipleTokens()
      {
          AbstractBuilder builder = builder();
          builder.withTokenCount(42);
          unroll(() -> builder.createWithoutStarting());
      }
  
      @Test
      public void allowVnodeWithSingleToken()
      {
          AbstractBuilder builder = builder();
          builder.withTokenCount(1);
          unroll(() -> builder.createWithoutStarting());
      }
  
      @Test
      public void disallowVnodeWithMultipleTokens()
      {
          AbstractBuilder builder = builder();
  
          builder.withoutVNodes();
          builder.withTokenCount(42);
  
          Assertions.assertThatThrownBy(() -> builder.createWithoutStarting())
                    .isInstanceOf(AssumptionViolatedException.class)
                    .hasMessage("vnode is not supported");
      }
  
  
      @Test
      public void disallowVnodeWithSingleToken()
      {
          AbstractBuilder builder = builder();
  
          builder.withoutVNodes();
          builder.withTokenCount(1);
  
          unroll(() -> builder.createWithoutStarting());
      }
  
      @Test
      public void withoutVNodes()
      {
          AbstractBuilder builder = builder();
  
          builder.withoutVNodes();
          //TODO casting is annoying... what can be done to be smarter?
          builder.withTokenSupplier((TokenSupplier) i -> Arrays.asList("a", "b", "c"));
  
          Assertions.assertThatThrownBy(() -> builder.createWithoutStarting())
                    .isInstanceOf(AssumptionViolatedException.class)
                    .hasMessage("vnode is not supported");
      }
  
      @Test
      public void vnodeButTokensDoNotMatch()
      {
          AbstractBuilder builder = builder();
  
          builder.withTokenCount(1);
          //TODO casting is annoying... what can be done to be smarter?
          builder.withTokenSupplier((TokenSupplier) i -> Arrays.asList("a", "b", "c"));
  
          Assertions.assertThatThrownBy(() -> builder.createWithoutStarting())
                    .isInstanceOf(AssumptionViolatedException.class)
                    .hasMessage("no-vnode is requested but not supported");
      }
  
      @Test
      public void noVnodeButTokensDoNotMatch()
      {
          AbstractBuilder builder = builder();
  
          builder.withTokenCount(42);
          //TODO casting is annoying... what can be done to be smarter?
          builder.withTokenSupplier((TokenSupplier) i -> Arrays.asList("a"));
  
          Assertions.assertThatThrownBy(() -> builder.createWithoutStarting())
                    .isInstanceOf(AssumptionViolatedException.class)
                    .hasMessage("vnode is requested but not supported");
      }
  
      @Test
      public void vnodeNotSupported()
      {
          ConfigUpdate config = ConfigUpdate.of("num_tokens", 1);
          AbstractCluster<?> cluster = cluster(4, config);
          config.check = true;
          Assertions.assertThatThrownBy(() -> cluster.createInstanceConfig(1))
                    .isInstanceOf(AssumptionViolatedException.class)
                    .hasMessage("vnode is not supported");
      }
  
      @Test
      public void noVnodeNotSupported()
      {
          ConfigUpdate config = ConfigUpdate.of("num_tokens", 4);
          AbstractCluster<?> cluster = cluster(1, config);
          config.check = true;
          Assertions.assertThatThrownBy(() -> cluster.createInstanceConfig(1))
                    .isInstanceOf(AssumptionViolatedException.class)
                    .hasMessage("no-vnode is requested but not supported");
      }
  
      @Test
      public void vnodeMismatch()
      {
          ConfigUpdate config = ConfigUpdate.of("num_tokens", 4);
          AbstractCluster<?> cluster = cluster(2, config);
          config.check = true;
          Assertions.assertThatThrownBy(() -> cluster.createInstanceConfig(1))
                    .isInstanceOf(AssumptionViolatedException.class)
                    .hasMessage("vnode is enabled and num_tokens is defined in test without GOSSIP or setting initial_token");
      }
  
      @Test
      public void vnodeMismatchDefinesTokens()
      {
          ConfigUpdate config = ConfigUpdate.of("num_tokens", 4, "initial_token", "some values");
          AbstractCluster<?> cluster = cluster(2, config);
          config.check = true;
          unroll(() -> cluster.createInstanceConfig(1));
      }
  
      private static void unroll(FailingRunnable r)
      {
          try
          {
              r.run();
          }
          catch (AssumptionViolatedException e)
          {
              AssertionError e2 = new AssertionError(e.getMessage());
              e2.setStackTrace(e.getStackTrace());
              throw e2;
          }
      }
  
      private static AbstractCluster<?> cluster(int tokenCount, Consumer<IInstanceConfig> fn)
      {
          try
          {
              return (AbstractCluster<?>) builder()
                                          .withTokenCount(tokenCount)
                                          .withConfig(fn)
                                          .createWithoutStarting();
          }
          catch (IOException e)
          {
              throw new AssertionError(e);
          }
      }
  
      private static AbstractBuilder builder()
      {
          return new Builder().withNodes(1);
      }
  
      private static class Builder extends AbstractBuilder<IInvokableInstance, Cluster, Builder>
      {
          public Builder()
          {
              super(Cluster::new);
          }
      }
  
      private static class Cluster extends AbstractCluster<IInvokableInstance>
      {
          protected Cluster(AbstractBuilder builder)
          {
              super(builder);
          }
  
          @Override
          protected IInvokableInstance newInstanceWrapper(Versions.Version version, IInstanceConfig config)
          {
              IInvokableInstance inst = Mockito.mock(IInvokableInstance.class);
              Mockito.when(inst.config()).thenReturn(config);
              return inst;
          }
      }
  
      private static class ConfigUpdate implements Consumer<IInstanceConfig>
      {
          public boolean check = false;
          private final Map<String, Object> override;
  
          private ConfigUpdate(Map<String, Object> override)
          {
              this.override = override;
          }
  
          public static ConfigUpdate of(Object... args)
          {
              Map<String, Object> override = new HashMap<>();
              for (int i = 0; i < args.length; i = i + 2)
                  override.put((String) args[i], args[i + 1]);
              return new ConfigUpdate(override);
          }
  
          @Override
          public void accept(IInstanceConfig config)
          {
              if (!check)
                  return;
              for (Map.Entry<String, Object> e : override.entrySet())
                  config.set(e.getKey(), e.getValue());
          }
      }
  }