package org.apache.lucene.index;

/**
 *  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 java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.store.Directory;
import org.apache.lucene.store.MockDirectoryWrapper;
import org.apache.lucene.util.Constants;
import org.apache.lucene.util._TestUtil;

/**
 * Runs TestNRTThreads in a separate process, crashes the JRE in the middle
 * of execution, then runs checkindex to make sure its not corrupt.
 */
public class TestIndexWriterOnJRECrash extends TestNRTThreads {
  private File tempDir;
  
  @Override
  public void setUp() throws Exception {
    super.setUp();
    tempDir = _TestUtil.getTempDir("jrecrash");
    tempDir.delete();
    tempDir.mkdir();
  }
  
  @Override
  public void testNRTThreads() throws Exception {
    String vendor = Constants.JAVA_VENDOR;
    assumeTrue(vendor + " JRE not supported.", 
        vendor.startsWith("Oracle") || vendor.startsWith("Sun") || vendor.startsWith("Apple"));
    
    // if we are not the fork
    if (System.getProperty("tests.crashmode") == null) {
      // try up to 10 times to create an index
      for (int i = 0; i < 10; i++) {
        forkTest();
        // if we succeeded in finding an index, we are done.
        if (checkIndexes(tempDir))
          return;
      }
    } else {
      // we are the fork, setup a crashing thread
      final int crashTime = _TestUtil.nextInt(random, 3000, 4000);
      Thread t = new Thread() {
        @Override
        public void run() {
          try {
            Thread.sleep(crashTime);
          } catch (InterruptedException e) {}
          crashJRE();
        }
      };
      t.setPriority(Thread.MAX_PRIORITY);
      t.start();
      // run the test until we crash.
      for (int i = 0; i < 1000; i++) {
        super.testNRTThreads();
      }
    }
  }
  
  /** fork ourselves in a new jvm. sets -Dtests.crashmode=true */
  public void forkTest() throws Exception {
    List<String> cmd = new ArrayList<String>();
    cmd.add(System.getProperty("java.home") 
        + System.getProperty("file.separator")
        + "bin"
        + System.getProperty("file.separator")
        + "java");
    cmd.add("-Xmx512m");
    cmd.add("-Dtests.crashmode=true");
    // passing NIGHTLY to this test makes it run for much longer, easier to catch it in the act...
    cmd.add("-Dtests.nightly=true");
    cmd.add("-DtempDir=" + tempDir.getPath());
    cmd.add("-Dtests.seed=" + random.nextLong() + ":" + random.nextLong());
    cmd.add("-ea");
    cmd.add("-cp");
    cmd.add(System.getProperty("java.class.path"));
    cmd.add("org.junit.runner.JUnitCore");
    cmd.add(getClass().getName());
    ProcessBuilder pb = new ProcessBuilder(cmd);
    pb.directory(tempDir);
    pb.redirectErrorStream(true);
    Process p = pb.start();
    InputStream is = p.getInputStream();
    BufferedInputStream isl = new BufferedInputStream(is);
    byte buffer[] = new byte[1024];
    int len = 0;
    if (VERBOSE) System.err.println(">>> Begin subprocess output");
    while ((len = isl.read(buffer)) != -1) {
      if (VERBOSE) {
        System.err.write(buffer, 0, len);
      }
    }
    if (VERBOSE) System.err.println("<<< End subprocess output");
    p.waitFor();
  }
  
  /**
   * Recursively looks for indexes underneath <code>file</code>,
   * and runs checkindex on them. returns true if it found any indexes.
   */
  public boolean checkIndexes(File file) throws IOException {
    if (file.isDirectory()) {
      MockDirectoryWrapper dir = newFSDirectory(file);
      dir.setCheckIndexOnClose(false); // don't double-checkindex
      if (IndexReader.indexExists(dir)) {
        if (VERBOSE) {
          System.err.println("Checking index: " + file);
        }
        _TestUtil.checkIndex(dir);
        dir.close();
        return true;
      }
      dir.close();
      for (File f : file.listFiles())
        if (checkIndexes(f))
          return true;
    }
    return false;
  }
  
  /**
   * currently, this only works/tested on Sun and IBM.
   */
  public void crashJRE() {
    try {
      Class<?> clazz = Class.forName("sun.misc.Unsafe");
      // we should use getUnsafe instead, harmony implements it, etc.
      Field field = clazz.getDeclaredField("theUnsafe");
      field.setAccessible(true);
      Object o = field.get(null);
      Method m = clazz.getMethod("putAddress", long.class, long.class);
      m.invoke(o, 0L, 0L);
    } catch (Exception e) { e.printStackTrace(); }
    fail();
  }
}
