// Path: src/test/java/mirror/TestUtils.java
// public static void move(final String from, final String to) {
//   new Execute(new String[] { "mv", from, to }).toSystemOut();
// }
// 
// Path: src/test/java/mirror/TestUtils.java
// public static String readFile(final File file) throws IOException {
//   return FileUtils.readFileToString(file, UTF_8);
// }
// 
// Path: src/test/java/mirror/TestUtils.java
// public static void writeFile(final File file, final String data) throws IOException {
//   FileUtils.writeStringToFile(file, data, UTF_8);
// }
// 
// Path: src/main/java/mirror/tasks/TaskFactory.java
// public interface TaskFactory {
// 
//   default TaskHandle runTask(TaskLogic logic) {
//     return runTask(logic, null);
//   }
// 
//   TaskHandle runTask(TaskLogic logic, Runnable onFailure);
// 
//   void stopTask(TaskLogic logic);
// 
//   default TaskPool newTaskPool() {
//     return new TaskPool(this);
//   }
// 
// }
// 
// Path: src/main/java/mirror/tasks/ThreadBasedTaskFactory.java
// public class ThreadBasedTaskFactory implements TaskFactory {
// 
//   private final ConcurrentHashMap<TaskLogic, ThreadBasedTask> tasks = new ConcurrentHashMap<>();
// 
//   @Override
//   public TaskHandle runTask(TaskLogic logic, Runnable onFailure) {
//     ThreadBasedTask task = new ThreadBasedTask(logic, onFailure, () -> {
//       // we don't need to call stopTask(logic) or task.stop because the task is already stopping,
//       // we just to ensure we remove this entry from our map to avoid memory leaks
//       tasks.remove(logic);
//     });
//     tasks.put(logic, task);
//     task.start();
//     return () -> stopTask(logic);
//   }
// 
//   // Note that this method isn't synchronized, as while shutting down one task,
//   // while we block on task.stop completing, the task might ask us to shut down
//   // one of it's child tasks, but from it's own thread, which would block.
//   @Override
//   public void stopTask(TaskLogic logic) {
//     ThreadBasedTask task = tasks.get(logic);
//     if (task != null) {
//       tasks.remove(logic);
//       task.stop();
//     }
//   }
// 
// }


import static mirror.TestUtils.move;
import static mirror.TestUtils.readFile;
import static mirror.TestUtils.writeFile;
import static org.apache.commons.io.FileUtils.deleteDirectory;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.grpc.Server;
import io.grpc.inprocess.InProcessChannelBuilder;
import io.grpc.inprocess.InProcessServerBuilder;
import mirror.tasks.TaskFactory;
import mirror.tasks.ThreadBasedTaskFactory;
  }

  @Test
  public void testUpdateFileThatWasMarkedReadOnlyByCodeGenerator() throws Exception {
    // given two files exist
    writeFile(new File(root1, "foo.txt"), "abc1");
    writeFile(new File(root2, "foo.txt"), "abc2");
    // and root1's file is newer
    new File(root1, "foo.txt").setLastModified(2000);
    new File(root2, "foo.txt").setLastModified(1000);
    // but root2's file is read only (e.g. due to overzealous code generators marking it read-only)
    NativeFileAccessUtils.setReadOnly(new File(root2, "foo.txt").toPath());
    // when mirror is started
    startMirror();
    sleep();
    // then we can successfully update root2
    assertThat(readFile(new File(root2, "foo.txt")), is("abc1"));
  }

  @Test
  public void testSimpleFileThatIsEmpty() throws Exception {
    startMirror();
    writeFile(new File(root1, "foo.txt"), "");
    sleep();
    assertThat(readFile(new File(root2, "foo.txt")), is(""));
  }

  private void startMirror() throws Exception {
    // server
    int port = nextPort++;
// Path: src/test/java/mirror/TestUtils.java
// public static void move(final String from, final String to) {
//   new Execute(new String[] { "mv", from, to }).toSystemOut();
// }
// 
// Path: src/test/java/mirror/TestUtils.java
// public static String readFile(final File file) throws IOException {
//   return FileUtils.readFileToString(file, UTF_8);
// }
// 
// Path: src/test/java/mirror/TestUtils.java
// public static void writeFile(final File file, final String data) throws IOException {
//   FileUtils.writeStringToFile(file, data, UTF_8);
// }
// 
// Path: src/main/java/mirror/tasks/TaskFactory.java
// public interface TaskFactory {
// 
//   default TaskHandle runTask(TaskLogic logic) {
//     return runTask(logic, null);
//   }
// 
//   TaskHandle runTask(TaskLogic logic, Runnable onFailure);
// 
//   void stopTask(TaskLogic logic);
// 
//   default TaskPool newTaskPool() {
//     return new TaskPool(this);
//   }
// 
// }
// 
// Path: src/main/java/mirror/tasks/ThreadBasedTaskFactory.java
// public class ThreadBasedTaskFactory implements TaskFactory {
// 
//   private final ConcurrentHashMap<TaskLogic, ThreadBasedTask> tasks = new ConcurrentHashMap<>();
// 
//   @Override
//   public TaskHandle runTask(TaskLogic logic, Runnable onFailure) {
//     ThreadBasedTask task = new ThreadBasedTask(logic, onFailure, () -> {
//       // we don't need to call stopTask(logic) or task.stop because the task is already stopping,
//       // we just to ensure we remove this entry from our map to avoid memory leaks
//       tasks.remove(logic);
//     });
//     tasks.put(logic, task);
//     task.start();
//     return () -> stopTask(logic);
//   }
// 
//   // Note that this method isn't synchronized, as while shutting down one task,
//   // while we block on task.stop completing, the task might ask us to shut down
//   // one of it's child tasks, but from it's own thread, which would block.
//   @Override
//   public void stopTask(TaskLogic logic) {
//     ThreadBasedTask task = tasks.get(logic);
//     if (task != null) {
//       tasks.remove(logic);
//       task.stop();
//     }
//   }
// 
// }


// Path: src/test/java/mirror/IntegrationTest.java
import static mirror.TestUtils.move;
import static mirror.TestUtils.readFile;
import static mirror.TestUtils.writeFile;
import static org.apache.commons.io.FileUtils.deleteDirectory;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.grpc.Server;
import io.grpc.inprocess.InProcessChannelBuilder;
import io.grpc.inprocess.InProcessServerBuilder;
import mirror.tasks.TaskFactory;
import mirror.tasks.ThreadBasedTaskFactory;

  }

  @Test
  public void testUpdateFileThatWasMarkedReadOnlyByCodeGenerator() throws Exception {
    // given two files exist
    writeFile(new File(root1, "foo.txt"), "abc1");
    writeFile(new File(root2, "foo.txt"), "abc2");
    // and root1's file is newer
    new File(root1, "foo.txt").setLastModified(2000);
    new File(root2, "foo.txt").setLastModified(1000);
    // but root2's file is read only (e.g. due to overzealous code generators marking it read-only)
    NativeFileAccessUtils.setReadOnly(new File(root2, "foo.txt").toPath());
    // when mirror is started
    startMirror();
    sleep();
    // then we can successfully update root2
    assertThat(readFile(new File(root2, "foo.txt")), is("abc1"));
  }

  @Test
  public void testSimpleFileThatIsEmpty() throws Exception {
    startMirror();
    writeFile(new File(root1, "foo.txt"), "");
    sleep();
    assertThat(readFile(new File(root2, "foo.txt")), is(""));
  }

  private void startMirror() throws Exception {
    // server
    int port = nextPort++;
    TaskFactory serverTaskFactory = new ThreadBasedTaskFactory();