1 /* CacheLRUWrapperTest.java 2 Copyright (C) 2012 Thomas Meyer 3 4 This file is part of IcedTea. 5 6 IcedTea is free software; you can redistribute it and/or 7 modify it under the terms of the GNU General Public License as published by 8 the Free Software Foundation, version 2. 9 10 IcedTea is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with IcedTea; see the file COPYING. If not, write to 17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 18 02110-1301 USA. 19 20 Linking this library statically or dynamically with other modules is 21 making a combined work based on this library. Thus, the terms and 22 conditions of the GNU General Public License cover the whole 23 combination. 24 25 As a special exception, the copyright holders of this library give you 26 permission to link this library with independent modules to produce an 27 executable, regardless of the license terms of these independent 28 modules, and to copy and distribute the resulting executable under 29 terms of your choice, provided that you also meet, for each linked 30 independent module, the terms and conditions of the license of that 31 module. An independent module is a module which is not derived from 32 or based on this library. If you modify this library, you may extend 33 this exception to your version of the library, but you are not 34 obligated to do so. If you do not wish to do so, delete this 35 exception statement from your version. 36 */ 37 38 package net.sourceforge.jnlp.cache; 39 40 import static org.junit.Assert.assertFalse; 41 import static org.junit.Assert.assertTrue; 42 43 import java.io.ByteArrayOutputStream; 44 import java.io.File; 45 import java.io.IOException; 46 import java.io.PrintStream; 47 import java.util.concurrent.CountDownLatch; 48 49 import org.junit.Before; 50 import org.junit.Test; 51 52 import net.sourceforge.jnlp.ServerAccess; 53 import net.sourceforge.jnlp.config.InfrastructureFileDescriptor; 54 import net.sourceforge.jnlp.config.PathsAndFiles; 55 import net.sourceforge.jnlp.util.CacheTestUtils; 56 57 public class CacheLRUWrapperTest { 58 59 // does no DeploymentConfiguration exist for this file name? 60 private static final String cacheIndexFileName = PathsAndFiles.CACHE_INDEX_FILE_NAME + "_testing"; 61 private static final File javaTmp = new File(System.getProperty("java.io.tmpdir")); 62 private static final File tmpCache; 63 private static final File tmpIndexFile; 64 65 static { 66 try { 67 tmpCache = File.createTempFile("itw", "CacheLRUWrapperTest", javaTmp); tmpCache.delete()68 tmpCache.delete(); tmpCache.mkdir()69 tmpCache.mkdir(); tmpCache.deleteOnExit()70 tmpCache.deleteOnExit(); 71 if (!tmpCache.isDirectory()) { 72 throw new IOException("Unsuccess to create tmpfile, remove it and createsame directory"); 73 } 74 tmpIndexFile = new File(tmpCache, cacheIndexFileName); 75 } catch (IOException ex) { 76 throw new RuntimeException(ex); 77 } 78 79 } 80 81 private static class DummyInfrastructureFileDescriptor extends InfrastructureFileDescriptor{ 82 private final File backend; 83 84 DummyInfrastructureFileDescriptor(File backend)85 private DummyInfrastructureFileDescriptor(File backend) { 86 super(); 87 this.backend=backend; 88 } 89 90 @Override getFile()91 public File getFile() { 92 return backend; 93 } 94 95 @Override getFullPath()96 public String getFullPath() { 97 return backend.getAbsolutePath(); 98 } 99 100 } 101 102 private static final CacheLRUWrapper clw = new CacheLRUWrapper( 103 new DummyInfrastructureFileDescriptor(tmpIndexFile), 104 new DummyInfrastructureFileDescriptor(tmpCache)); 105 106 private final int noEntriesCacheFile = 1000; 107 108 private ByteArrayOutputStream baos; 109 private PrintStream out; 110 111 112 113 @Before setup()114 public void setup() { 115 baos = new ByteArrayOutputStream(); 116 out = new PrintStream(baos); 117 } 118 119 @Test testLoadStoreTiming()120 public void testLoadStoreTiming() throws InterruptedException { 121 122 final File cacheIndexFile = clw.getRecentlyUsedFile().getFile(); 123 cacheIndexFile.delete(); 124 try { 125 int noLoops = 1000; 126 127 long time[] = new long[noLoops]; 128 129 clw.lock(); 130 clearCacheIndexFile(); 131 132 fillCacheIndexFile(noEntriesCacheFile); 133 clw.store(); 134 135 // FIXME: wait a second, because of file modification timestamp only provides accuracy on seconds. 136 Thread.sleep(1000); 137 138 long sum = 0; 139 for(int i=0; i < noLoops - 1; i++) { 140 time[i]= System.nanoTime(); 141 clw.load(); 142 time[i+1]= System.nanoTime(); 143 if(i==0) 144 continue; 145 sum = sum + time[i] - time[i-1]; 146 } 147 148 double avg = sum / time.length; 149 ServerAccess.logErrorReprint("Average = " + avg + "ns"); 150 151 // wait more than 100 microseconds for noLoops = 1000 and noEntries=1000 is bad 152 assertTrue("load() must not take longer than 100 µs, but took in avg " + avg/1000 + "µs", avg < 100 * 1000); 153 } finally { 154 clw.unlock(); 155 cacheIndexFile.delete(); 156 } 157 } 158 fillCacheIndexFile(int noEntries)159 private void fillCacheIndexFile(int noEntries) { 160 161 // fill cache index file 162 for(int i = 0; i < noEntries; i++) { 163 String path = clw.getRecentlyUsedFile().getFullPath() + File.separatorChar + i + File.separatorChar + "test" + i + ".jar"; 164 String key = clw.generateKey(path); 165 clw.addEntry(key, path); 166 } 167 } 168 169 @Test testModTimestampAfterStore()170 public void testModTimestampAfterStore() throws InterruptedException { 171 172 final File cacheIndexFile = clw.getRecentlyUsedFile().getFile(); 173 cacheIndexFile.delete(); 174 try{ 175 clw.lock(); 176 177 // 1. clear cache entries + store 178 clw.addEntry("aa", "bb"); 179 clw.store(); 180 long lmBefore = cacheIndexFile.lastModified(); 181 Thread.sleep(1010); 182 clearCacheIndexFile(); 183 long lmAfter = cacheIndexFile.lastModified(); 184 assertTrue("modification timestamp hasn't changed! Before = " + lmBefore + " After = " + lmAfter, lmBefore < lmAfter); 185 186 // FIXME: wait a second, because of file modification timestamp only provides accuracy on seconds. 187 Thread.sleep(1010); 188 189 // 2. load cache file 190 lmBefore = cacheIndexFile.lastModified(); 191 clw.load(); 192 lmAfter = cacheIndexFile.lastModified(); 193 assertTrue("modification timestamp has changed!", lmBefore == lmAfter); 194 195 // 3. add some cache entries and store 196 lmBefore = cacheIndexFile.lastModified(); 197 fillCacheIndexFile(noEntriesCacheFile); 198 clw.store(); 199 lmAfter = cacheIndexFile.lastModified(); 200 assertTrue("modification timestamp hasn't changed! Before = " + lmBefore + " After = " + lmAfter, lmBefore < lmAfter); 201 202 } finally { 203 cacheIndexFile.delete(); 204 clw.unlock(); 205 } 206 } 207 clearCacheIndexFile()208 private void clearCacheIndexFile() { 209 210 clw.lock(); 211 212 try { 213 // clear cache + store file 214 clw.clearLRUSortedEntries(); 215 clw.store(); 216 } finally { 217 clw.unlock(); 218 } 219 } 220 221 @Test testAddEntry()222 public void testAddEntry() { 223 String key = "key"; 224 String value = "value"; 225 226 clw.addEntry(key, value); 227 assertTrue(clw.containsKey(key) && clw.containsValue(value)); 228 } 229 230 @Test testRemoveEntry()231 public void testRemoveEntry() { 232 String key = "key"; 233 String value = "value"; 234 235 clw.addEntry(key, value); 236 clw.removeEntry(key); 237 assertFalse(clw.containsKey(key) && clw.containsValue(value)); 238 } 239 240 @Test(timeout = 2000l) testLock()241 public void testLock() throws IOException { 242 try { 243 clw.lock(); 244 assertTrue(clw.getRecentlyUsedPropertiesFile().isHeldByCurrentThread()); 245 } finally { 246 clw.unlock(); 247 } 248 } 249 250 @Test(timeout = 2000l) testUnlock()251 public void testUnlock() throws IOException { 252 try { 253 clw.lock(); 254 } finally { 255 clw.unlock(); 256 } 257 assertTrue(!clw.getRecentlyUsedPropertiesFile().isHeldByCurrentThread()); 258 } 259 260 @Test(timeout = 2000l) testStoreFailsWithoutLock()261 public void testStoreFailsWithoutLock() throws IOException { 262 assertTrue(!clw.store()); 263 } 264 265 @Test(timeout = 2000l) testStoreWorksWithLocK()266 public void testStoreWorksWithLocK() throws IOException { 267 try { 268 clw.lock(); 269 assertTrue(clw.store()); 270 } finally { 271 clw.unlock(); 272 } 273 } 274 275 @Test(timeout = 2000l) testMultithreadLockPreventsStore()276 public void testMultithreadLockPreventsStore() throws IOException, InterruptedException { 277 int numThreads = 100; 278 CountDownLatch doneSignal = new CountDownLatch(numThreads); 279 280 Thread[] list = new Thread[numThreads]; 281 282 for (int i = 0; i < numThreads; i++) { 283 list[i] = new Thread(new StoreWorker(doneSignal)); 284 } 285 286 for (int i = 0; i < numThreads; i++) { 287 list[i].start(); 288 } 289 290 //Wait for all children to finish 291 for (int i = 0; i < numThreads; i++) { 292 list[i].join(); 293 } 294 295 String out = baos.toString(); 296 297 assertTrue(CacheTestUtils.stringContainsOnlySingleInstance(out, "true") && out.contains("false")); 298 } 299 300 private class StoreWorker implements Runnable { 301 302 private final CountDownLatch doneSignal; 303 StoreWorker(CountDownLatch doneSignal)304 public StoreWorker(CountDownLatch doneSignal) { 305 this.doneSignal = doneSignal; 306 } 307 @Override run()308 public void run() { 309 try { 310 clw.getRecentlyUsedPropertiesFile().tryLock(); 311 boolean result = clw.store(); 312 synchronized (out) { 313 out.println(String.valueOf(result)); 314 out.flush(); 315 } 316 //Let parent know outputting is done 317 doneSignal.countDown(); 318 //Wait until able to continue to clw.unlock() 319 doneSignal.await(); 320 } catch (Exception e) { 321 e.printStackTrace(); 322 } finally { 323 clw.unlock(); 324 } 325 } 326 } 327 } 328