1 /*
2 Copyright (C) 2013 Red Hat
3 
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful, but
10 WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 General Public License for more details.
13 
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17  */
18 
19 package net.sourceforge.jnlp.util.lockingfile;
20 
21 import static org.junit.Assert.assertFalse;
22 import static org.junit.Assert.assertTrue;
23 import org.junit.Before;
24 import java.io.BufferedWriter;
25 import java.io.File;
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.List;
29 import org.junit.Assert;
30 import org.junit.Test;
31 
32 public class LockingReaderWriterTest {
33 
34     private static File storagefile;
35 
newInstance()36     private static TestStringReaderWriter newInstance() {
37         return new TestStringReaderWriter(storagefile);
38     }
39 
40     @Before
setUp()41     public void setUp() throws IOException {
42         storagefile = File.createTempFile("foo", "bar");
43     }
44 
45     @Test
testSimpleActions()46     public void testSimpleActions() throws IOException {
47         TestStringReaderWriter storage = newInstance();
48 
49         storage.add("teststring");
50         assertTrue(storage.contains("teststring"));
51         storage.remove("teststring");
52         assertFalse(storage.contains("teststring"));
53     }
54 
55     @Test
testInterleavedActions()56     public void testInterleavedActions() throws IOException {
57         TestStringReaderWriter storage1 = newInstance();
58         TestStringReaderWriter storage2 = newInstance();
59 
60         storage1.add("teststring");
61         assertTrue(storage2.contains("teststring"));
62         storage2.remove("teststring");
63         assertFalse(storage1.contains("teststring"));
64     }
65 
66     static class TestThread extends Thread {
67         String testString;
68         int iterations;
69         Throwable error = null;
70 
TestThread(String testString, int iterations)71         TestThread(String testString, int iterations) {
72             this.testString = testString;
73             this.iterations = iterations;
74         }
75 
76         @Override
run()77         public void run() {
78             try {
79                 TestStringReaderWriter storage = newInstance();
80                 for (int i = 0; i < iterations; i++) {
81                     assertTrue(storage.contains(this.testString));
82                     storage.add(this.testString);
83                     storage.remove(this.testString);
84                     assertTrue(storage.contains(this.testString));
85                 }
86             } catch (Throwable error) {
87                 error.printStackTrace();
88                 this.error = error;
89             }
90         }
91     }
92 
concurrentReadWrites(int threadAmount, int iterations, String testString)93     private void concurrentReadWrites(int threadAmount, int iterations,
94             String testString) throws InterruptedException {
95         TestStringReaderWriter storage = newInstance();
96 
97         storage.add(testString);
98 
99         List<TestThread> testThreads = new ArrayList<TestThread>();
100 
101         for (int i = 0; i < threadAmount; i++) {
102             TestThread thread = new TestThread(testString, iterations);
103             testThreads.add(thread);
104             thread.start();
105         }
106 
107         for (int i = 0; i < threadAmount; i++) {
108             testThreads.get(i).join();
109         }
110 
111         assertTrue(storage.contains(testString));
112         storage.remove(testString);
113 
114         // So long as number adds == number writes, we should be left with
115         // nothing at end.
116         assertFalse(storage.contains(testString));
117     }
118 
119     // Long testing string, the contents are not important
makeLongTestString()120     private String makeLongTestString() {
121         StringBuilder sb = new StringBuilder();
122         for (int i = 0; i < 1000; i++) {
123             sb.append(Integer.toString(i));
124         }
125         return sb.toString();
126     }
127 
128     @Test
testManyReadWrite()129     public void testManyReadWrite() throws Exception {
130         int oneThread = 1;
131         String shortString = "teststring";
132 
133         // This was causing 'too many open files' because FileUtils#getFileLock
134         // leaks file descriptors. No longer used.
135         concurrentReadWrites(oneThread, 500 /* iterations */, shortString);
136     }
137 
138     @Test
testManyThreads()139     public void testManyThreads() throws Exception {
140         int threadAmount = 25;
141         String shortString = "teststring";
142         String longString = makeLongTestString();
143 
144         concurrentReadWrites(threadAmount, 10 /* per-thread iterations */,
145                 shortString);
146         concurrentReadWrites(threadAmount, 2 /* per-thread iterations */,
147                 longString);
148     }
149 
150     /**
151      * Concrete implementation to aid in testing LockingReaderWriter
152      */
153     public static class TestStringReaderWriter extends LockingReaderWriter {
154 
155         private List<String> cachedContents = new ArrayList<String>();
156 
TestStringReaderWriter(File file)157         public TestStringReaderWriter(File file) {
158             super(file);
159         }
160 
161         @Override
writeContent(BufferedWriter writer)162         public void writeContent(BufferedWriter writer) throws IOException {
163             for (String string : cachedContents) {
164                 writer.write(string);
165                 writer.newLine();
166             }
167         }
168 
169         @Override
readLine(String line)170         protected void readLine(String line) {
171             this.cachedContents.add(line);
172         }
173 
174         @Override
readContents()175         protected void readContents() throws IOException {
176             cachedContents.clear();
177             super.readContents();
178         }
179 
180         /*
181          * Atomic container abstraction methods.
182          */
add(final String line)183         synchronized public void add(final String line) {
184             doLocked(new Runnable() {
185 
186                 public void run() {
187                     try {
188                         readContents();
189                         cachedContents.add(line);
190                         writeContents();
191                     } catch (IOException ex) {
192                         throw new StorageIoException(ex);
193                     }
194                 }
195             });
196         }
197 
contains(final String line)198         synchronized public boolean contains(final String line) {
199             final boolean[] doesContain = { false };
200             doLocked(new Runnable() {
201 
202                 public void run() {
203                     try {
204                         readContents();
205                         doesContain[0] = cachedContents.contains(line);
206                     } catch (IOException e) {
207                         throw new StorageIoException(e);
208                     }
209                 }
210             });
211             return doesContain[0];
212         }
213 
remove(final String line)214         synchronized public boolean remove(final String line) {
215             final boolean[] didRemove = { false };
216 
217             doLocked(new Runnable() {
218                 public void run() {
219                     try {
220                         readContents();
221                         didRemove[0] = cachedContents.remove(line);
222                         writeContents();
223                     } catch (IOException e) {
224                         throw new StorageIoException(e);
225                     }
226                 }
227             });
228 
229             return didRemove[0];
230         }
231     }
232 }