// Copyright 2018 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.collect.nestedset;

import static com.google.common.truth.Truth.assertThat;

import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.lib.vfs.DigestHashFunction;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;

/** Tests for {@link DigestMap}. */
@RunWith(Parameterized.class)
public class DigestMapTest {

  @Parameters(name = "Hash: {0}")
  public static Iterable<Object[]> hashFunction() {
    return ImmutableList.of(
        new Object[] {DigestHashFunction.SHA1}, new Object[] {DigestHashFunction.SHA256});
  }

  @Parameter public DigestHashFunction digestHashFunction;

  private Fingerprint fingerprint() {
    return new Fingerprint(digestHashFunction);
  }

  @Test
  public void simpleTest() {
    int count = 128; // Must be smaller than byte for this test or we'll overflow
    Object[] keys = new Object[count];
    for (int i = 0; i < count; ++i) {
      keys[i] = new Object();
    }

    DigestMap digestMap = new DigestMap(digestHashFunction, 4);
    for (int i = 0; i < count; ++i) {
      Fingerprint digest = fingerprint().addInt(i);
      Fingerprint fingerprint = fingerprint();
      digestMap.insertAndReadDigest(keys[i], digest, fingerprint);
      Fingerprint reference = fingerprint().addBytes(fingerprint().addInt(i).digestAndReset());
      assertThat(fingerprint.hexDigestAndReset()).isEqualTo(reference.hexDigestAndReset());
    }
    for (int i = 0; i < count; ++i) {
      Fingerprint fingerprint = fingerprint();
      assertThat(digestMap.readDigest(keys[i], fingerprint)).isTrue();
      Fingerprint reference = fingerprint().addBytes(fingerprint().addInt(i).digestAndReset());
      assertThat(fingerprint.hexDigestAndReset()).isEqualTo(reference.hexDigestAndReset());
    }
  }

  @Test
  public void concurrencyTest() throws Exception {
    int count = 128; // Must be smaller than byte for this test or we'll overflow
    Object[] keys = new Object[count];
    for (int i = 0; i < count; ++i) {
      keys[i] = new Object();
    }
    DigestMap digestMap = new DigestMap(digestHashFunction, 4);

    AtomicBoolean done = new AtomicBoolean();
    AtomicReference<Exception> exception = new AtomicReference<>();
    List<Thread> threads = new ArrayList<>();
    int threadCount = 16;
    for (int i = 0; i < threadCount; ++i) {
      Thread thread =
          new Thread(
              () -> {
                Random random = new Random();
                while (!done.get()) {
                  int index = random.nextInt(count);
                  Object key = keys[index];
                  Fingerprint fingerprint = fingerprint();
                  if (!digestMap.readDigest(key, fingerprint)) {
                    Fingerprint digest = fingerprint().addInt(index);
                    digestMap.insertAndReadDigest(key, digest, fingerprint);
                  }
                  Fingerprint reference =
                      fingerprint().addBytes(fingerprint().addInt(index).digestAndReset());
                  String hexDigest = fingerprint.hexDigestAndReset();
                  String referenceDigest = reference.hexDigestAndReset();
                  if (!hexDigest.equals(referenceDigest)) {
                    exception.set(
                        new IllegalStateException(
                            String.format(
                                "Digests are not equal: %s != %s, index %d",
                                hexDigest, referenceDigest, index)));
                    done.set(true);
                  }
                }
              });
      thread.start();
      threads.add(thread);
    }
    Thread.sleep(1000);
    done.set(true);
    for (int i = 0; i < threadCount; ++i) {
      threads.get(i).join(1000);
    }
    if (exception.get() != null) {
      throw exception.get();
    }
  }
}
