/* * Copyright (c) 2013-2017 Cinchapi Inc. * * 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.cinchapi.concourse.util; import java.io.IOException; import java.lang.Thread.State; import java.nio.file.Files; import java.nio.file.Paths; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Assert; import org.junit.Test; import com.cinchapi.concourse.test.ConcourseBaseTest; import com.google.common.base.Throwables; /** * Unit tests for {@link FileOps}. * * @author Jeff Nelson */ public class FileOpsTest extends ConcourseBaseTest { @Test(timeout = 10000) public void testAwaitChange() throws InterruptedException { String file = FileOps.tempFile(FileOps.tempDir("con"), "foo", ".test"); CountDownLatch latch = new CountDownLatch(1); Thread t = new Thread(() -> { FileOps.awaitChange(file); latch.countDown(); }); t.start(); AtomicBoolean done = new AtomicBoolean(false); Thread t2 = new Thread(() -> { while (!done.get()) { // There is an arbitrary and uncontrollable delay between the // time that the path Thread t starting and the // FileOps#awaitChange method actually registering the path so // we just keep writing to the file so that, eventually, one of // the changes will be caught FileOps.write(Random.getSimpleString(), file); } }); t2.start(); latch.await(); Assert.assertTrue(true); done.set(true); } @Test(timeout = 5000) public void testAwaitChangeDoesNotRegisterPathMoreThanOnce() throws InterruptedException { // NOTE: Do not change this test to use a CountDownLatch or some other // precise way of measuring when the threads have completed because // change notifications are subject to arbitrary delays of the // underlying file system. Furthermore, this test is designed to test // registration and not the accuracy of the change notifications. String file = FileOps.tempFile(); Thread t1 = new Thread(() -> { FileOps.awaitChange(file); }); t1.setDaemon(true); Thread t2 = new Thread(() -> { FileOps.awaitChange(file); }); t2.setDaemon(true); Assert.assertEquals(0, FileOps.REGISTERED_WATCHER_PATHS.size()); t1.start(); long start = System.nanoTime(); while (FileOps.REGISTERED_WATCHER_PATHS.size() < 1) { continue; } long elapsed = System.nanoTime() - start; t2.start(); TimeUnit.NANOSECONDS.sleep(elapsed * 10); // sleep for 10x how long it // took to register the path // so that we have a high // degree of confidence that // the second thread has // finished FileOps.write("a", file); Assert.assertEquals(1, FileOps.REGISTERED_WATCHER_PATHS.size()); } @Test(timeout = 5000) public void testRegisterDifferentPaths() { String file1 = FileOps.tempFile(FileOps.tempDir("con"), null, null); String file2 = FileOps.tempFile(FileOps.tempDir("con"), null, null); Thread t1 = new Thread(() -> { FileOps.awaitChange(file1); }); t1.setDaemon(true); Thread t2 = new Thread(() -> { FileOps.awaitChange(file2); }); t2.setDaemon(true); t1.start(); t2.start(); while (FileOps.REGISTERED_WATCHER_PATHS.size() < 2) { continue; } Assert.assertTrue(true); } @Test public void testAwaitChangeNeverMissesUpdate() throws InterruptedException, IOException { String file = FileOps.tempFile(); CountDownLatch testSignaler = new CountDownLatch(1); CountDownLatch writeSignaler = new CountDownLatch(1); Thread t1 = new Thread(() -> { // ensure the parent path gets registered Thread parentThread = Thread.currentThread(); Thread raceConditionDetector = new Thread(() -> { while (parentThread.getState() == State.RUNNABLE) { continue; } try { if(Files.size(Paths.get(file)) > 0) { FileOps.touch(file); } } catch (IOException e) { throw Throwables.propagate(e); } }); raceConditionDetector.setDaemon(true); raceConditionDetector.start(); writeSignaler.countDown(); FileOps.awaitChange(file); testSignaler.countDown(); }); t1.start(); Thread t2 = new Thread(() -> { try { writeSignaler.await(); FileOps.write("foo", file); } catch (InterruptedException e) { e.printStackTrace(); } }); t2.start(); testSignaler.await(); CountDownLatch startable = new CountDownLatch(2); Thread t3 = new Thread(() -> { startable.countDown(); try { startable.await(); FileOps.awaitChange(file); } catch (InterruptedException e) { e.printStackTrace(); } }); Thread t4 = new Thread(() -> { startable.countDown(); try { startable.await(); FileOps.write("foo", file); } catch (InterruptedException e) { e.printStackTrace(); } }); t3.start(); t4.start(); t3.join(); t4.join(); } }