001 /*
002 Copyright (c) 1996-2012, Damon Hart-Davis
003 All rights reserved.
004
005 Redistribution and use in source and binary forms, with or without
006 modification, are permitted provided that the following conditions are
007 met:
008
009 * Redistributions of source code must retain the above copyright
010 notice, this list of conditions and the following disclaimer.
011
012 * Redistributions in binary form must reproduce the above copyright
013 notice, this list of conditions and the following disclaimer in the
014 documentation and/or other materials provided with the
015 distribution.
016
017 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028 */
029
030 /*
031 * Created by IntelliJ IDEA.
032 * User: Administrator
033 * Date: 28-Dec-02
034 * Time: 22:24:51
035 */
036 package org.hd.d.pg2k.test.dev;
037
038 import static org.mutabilitydetector.unittesting.MutabilityAssert.assertImmutable;
039
040 import java.io.ByteArrayInputStream;
041 import java.io.DataInputStream;
042 import java.io.InputStream;
043 import java.util.Arrays;
044 import java.util.Collection;
045 import java.util.Collections;
046 import java.util.Random;
047 import java.util.SortedSet;
048
049 import junit.framework.Assert;
050 import junit.framework.TestCase;
051
052 import org.hd.d.pg2k.svrCore.AllExhibitProperties;
053 import org.hd.d.pg2k.svrCore.Compact7BitString;
054 import org.hd.d.pg2k.svrCore.GenUtils;
055 import org.hd.d.pg2k.svrCore.Name;
056 import org.hd.d.pg2k.svrCore.TextUtils;
057 import org.hd.d.pg2k.webSvr.util.WebConsts;
058
059 /**Miscellaneous tests not filed elsewhere.
060 * This should be kept as small as possible with tests being moved to better homes ASAP.
061 */
062 public final class MiscTest extends TestCase
063 {
064 public MiscTest(final String name)
065 {
066 super(name);
067 }
068
069 // /**Do any setup needed for the tests. */
070 // protected void setUp()
071 // {
072 // }
073
074 // /**Do any clearup needed after the tests. */
075 // protected void tearDown()
076 // {
077 // // cleanup code
078 // }
079
080 /**Test that we can retrieve the application version. */
081 public static final void testAppVersion()
082 {
083 assertTrue(GenUtils.appVersion().startsWith("1."));
084 }
085
086 /**Place-holder to verify that the immutability checker is available. */
087 public static void testImmutabilityTest()
088 {
089 assertImmutable(java.lang.Character.class);
090 }
091
092 /**Test our quick log-2 computation. */
093 public static final void testLog2Approx()
094 {
095 // Test some important fixed/boundary cases.
096 assertEquals(-1, GenUtils.log2Approx(0));
097 assertEquals(0, GenUtils.log2Approx(1));
098 assertEquals(1, GenUtils.log2Approx(2));
099 assertEquals(1, GenUtils.log2Approx(3));
100 assertEquals(2, GenUtils.log2Approx(4));
101 assertEquals(2, GenUtils.log2Approx(5));
102 assertEquals(2, GenUtils.log2Approx(6));
103 assertEquals(2, GenUtils.log2Approx(7));
104 assertEquals(3, GenUtils.log2Approx(8));
105 assertEquals(30, GenUtils.log2Approx(Integer.MAX_VALUE));
106 assertEquals(31, GenUtils.log2Approx(1L + Integer.MAX_VALUE));
107 assertEquals(31, GenUtils.log2Approx(2L + Integer.MAX_VALUE));
108 assertEquals(62, GenUtils.log2Approx(Long.MAX_VALUE));
109
110 // Try on a variety of random non-negative values.
111 for(int i = 100; --i >= 0; )
112 {
113 final long v = (rnd.nextLong() >>> 1);
114 final long l = GenUtils.log2Approx(v);
115 assertTrue("GenUtils.log2Approx(v) answer out of range", (l >= -1) && (l < 64));
116 }
117
118 // Try another real-life larger example.
119 assertTrue(1 << WebConsts.MAX_NORMAL_PAGE_CACHE_MS_SHIFT <= WebConsts.MAX_NORMAL_PAGE_CACHE_MS);
120 assertTrue(2 << WebConsts.MAX_NORMAL_PAGE_CACHE_MS_SHIFT > WebConsts.MAX_NORMAL_PAGE_CACHE_MS);
121 }
122
123 /**Test if the data pump.
124 * Makes sure that at the very least it does not corrupt data
125 * passing through it.
126 * <p>
127 * We set up and test a number of pump instances
128 * to try to ensure that we not
129 * eating up (and failing to release) vast system resources
130 * even if the caller fails to explicitly close() all the stream.
131 */
132 public static void testDataPump()
133 throws Exception
134 {
135 final long startTime = System.currentTimeMillis();
136 final long stopBy = startTime + 1000; // 1s max on this test.
137 for(int i = 1000; --i >= 0; )
138 {
139 // Random data size up to several megabytes...
140 final byte[] data = new byte[rnd.nextInt(8123123)];
141 rnd.nextBytes(data);
142 //System.out.println("[dataPump() test on length "+data.length);
143 // Wrap the input data, hiding the available() value (sometimes)
144 // to prevent over-optimisation by dataPump() undermining this test,
145 // ie we "dumb down" the InputStream here a little.
146 final ByteArrayInputStream bais = new ByteArrayInputStream(data.clone()){
147 @Override public int available()
148 { return(rnd.nextBoolean() ? 0 : super.available()); }
149 @Override public boolean markSupported()
150 { return(false); }
151 };
152 // Try data pumps with varying buffering settings.
153 final InputStream is = GenUtils.dataPump(GenUtils.dataPump(bais, 1 + rnd.nextInt(1123123), rnd.nextBoolean()), 1 + rnd.nextInt(1123123), rnd.nextBoolean());
154 final DataInputStream dis = new DataInputStream(is);
155
156 final byte buf[] = new byte[data.length];
157 dis.readFully(buf); // All data must be available through the pump.
158 assertTrue("Data must not be corrupted on pumped stream", Arrays.equals(data, buf));
159 assertEquals("Must be no spurious trailing data on pumped stream", -1, dis.read());
160
161 // Only sometimes remember to close() the stream.
162 // This simulates some programming errors of omission
163 // that we'd like to protect against.
164 if(rnd.nextBoolean()) { dis.close(); }
165
166 // Stop if we've taken a long time on this test.
167 if(System.currentTimeMillis() > stopBy) { break; }
168 }
169 }
170
171 /**Test encoding to Base64 (as used in HTTP headers) and decoding thereof.
172 */
173 public static void test8To6()
174 {
175 // Standard simple test cases.
176 // Trivial empty case.
177 assertEquals("Must be able to encode empty value correctly", "", TextUtils.encode8To6(new byte[0]));
178 assertTrue("Must be able to decode empty value correctly", TextUtils.decode8To6("").length == 0);
179 // Aligned (multiple of 3 bytes, thus no partial last block).
180 final byte[] test0 = { 'h', 'e', 'l' };
181 final String encoded0 = "aGVs";
182 assertEquals("Must be able to encode simple test0 correctly", encoded0, TextUtils.encode8To6(test0));
183 assertTrue("Must be able to decode simple test0 correctly", Arrays.equals(test0, TextUtils.decode8To6(encoded0)));
184 // Not aligned, so partial last block.
185 final byte[] test1 = { 'h', 'e', 'l', 'l', 'o' };
186 final String encoded1 = "aGVsbG8=";
187 assertEquals("Must be able to encode simple test1 correctly", encoded1, TextUtils.encode8To6(test1));
188 assertTrue("Must be able to decode simple test1 correctly", Arrays.equals(test1, TextUtils.decode8To6(encoded1)));
189
190 // Now try encode/decode on some lumps of random data...
191 for(int i = 100; --i >= 0; )
192 {
193 final byte[] data = new byte[rnd.nextInt(1000)];
194 rnd.nextBytes(data);
195 assertTrue("Must be able to encode/decode arbitrary binary data",
196 Arrays.equals(data, TextUtils.decode8To6(TextUtils.encode8To6(data.clone()))));
197 }
198 }
199
200 private static final String test7BitStrings[] = {
201 "",
202 "One man and his \t dog!",
203 "http://gallery.hd.org/_c/places-and-sights/_more2009/_more06/England-Dorset-Portland-Bill-lighthouse-flora-and-somewhat-parched-and-barren-surrounds-on-sunny-June-day-out-from-Weymouth-42-DHD.jpg.html",
204 };
205
206 /**Test correct behaviour of Compact7BitString.
207 * Basic tests.
208 */
209 public static void testCompact7BitString()
210 {
211 for(final String s : test7BitStrings)
212 {
213 // Simplest possible stand-alone operation.
214 final Compact7BitString converted = Compact7BitString.convertToCompact7BitString(s, null);
215 assertNotSame(s, converted);
216 assertEquals(s, converted.toString());
217 }
218 }
219
220 /**Test correct behaviour of Name.
221 * Very basic tests.
222 */
223 public static void testName()
224 throws Exception
225 {
226 // Test behaviour when we try to construct a shared-prefix variant
227 // when the unshared variant already exists.
228 // By default we want the new sharing (more compact) variant
229 // to replace the unshared one even though they test equals() by MemoryTools.intern().
230 // We use two unique values here, with significant shared termini.
231 // We have a further 'bogus' value with no shared termini to deflect some internal Name cleverness...
232 final Name.ExhibitFull decoy = Name.ExhibitFull.create("w/x-Y.z");
233 final String ln1 = "uniquestring/_more2006/_more10/trek-4-days-from-Ganden-monastery-4500m-via-Shuga-La-pass-Chitu-La-pass-5100m-trekkers-local-guides-grassy-slopes-yaks-snow-blizzards-lakes-sand-dunes-magical-scenery-roof-of-the-world-views-bizarre-transport-near-Lhasa-Tibet-7-CKB.jpg";
234 final String ln2 = "uniquestring/_more2006/_more10/trek-4-days-from-Ganden-monastery-4500m-via-Shuga-La-pass-Chitu-La-pass-5100m-trekkers-local-guides-grassy-slopes-yaks-snow-blizzards-lakes-sand-dunes-magical-scenery-roof-of-the-world-views-bizarre-transport-near-Lhasa-Tibet-8-CKB.jpg";
235 final String ln3 = "uniquestring/_more2006/_more10/trek-4-days-from-Ganden-monastery-4500m-via-Shuga-La-pass-Chitu-La-pass-5100m-trekkers-local-guides-grassy-slopes-yaks-snow-blizzards-lakes-sand-dunes-magical-scenery-roof-of-the-world-views-bizarre-transport-near-Lhasa-Tibet-9-CKB.jpg";
236 final Name.ExhibitFull nu1 = Name.ExhibitFull.create(ln1, decoy);
237 assertEquals("Expected to share no component yet", 0, nu1.getPrefixLen());
238 final Name.ExhibitFull nu2 = Name.ExhibitFull.create(ln2, decoy);
239 assertEquals("Expected to share no component yet", 0, nu2.getPrefixLen());
240 // Now attempt to create version of second string with shared prefix.
241 final Name.ExhibitFull ns3 = Name.ExhibitFull.create(ln3, nu1);
242 assertNotSame("Expected to share some prefix component at least", 0, ns3.getPrefixLen());
243
244 // If we construct another very similar new value even without using an explicit 'prev'
245 // then we'd expect create() to find a prefix match from the earlier still-live values
246 // unless we're suffering horrible memory stress at this very moment...
247 final Name.ExhibitFull ns4 = Name.ExhibitFull.create(ln3); // Let Name go hunting for cached ad-hoc 'prev'...
248 assertNotSame("Expected to have found a shared prefix in the ad-hoc prefix cache", 0, ns4.getPrefixLen());
249 }
250
251 /**Test correct behaviour of Name.
252 * Tests on slightly longer/harder values.
253 */
254 public static void testNameLonger()
255 throws Exception
256 {
257 for(final String s : test7BitStrings)
258 {
259 // Simplest possible stand-alone operation.
260 final Name converted = Name.create(s, null);
261 assertNotSame(s, converted);
262 assertEquals(s, converted.toString());
263
264 // Try repeatedly converting a value that is an extension of the previous.
265 String sPrev = s;
266 Name prev = converted;
267 for(int i = 50; --i >= 0; )
268 {
269 final String s2 = sPrev + " suffix " + rnd.nextLong();
270 final Name c2 = Name.create(s2, prev);
271 assertNotSame(s2, prev);
272 assertEquals(s2, c2.toString());
273 assertTrue("chain length should now be at least 1, or 0 for empty prev", (prev.length()==0) ? (0==c2.getPrevChainLength()) : (c2.getPrevChainLength()>0));
274 // Check that basic (de)serialisation works.
275 SerializationTest.checkSerialisationPreservesEquality(c2);
276 // Extend these values...
277 sPrev = s2;
278 prev = c2;
279 }
280 }
281 }
282
283 /**Simple test for problems from memory-saving lazy evaluation, etc. */
284 public static final void testAEPMemoryTweaks()
285 {
286 final AllExhibitProperties aep = new AllExhibitProperties();
287 Assert.assertNotNull(aep.getExhibitsByAttribute()); // Must not throw an exception nor return null..
288 Assert.assertNotNull(aep.getExhibitCountsByAttribute()); // Must not throw an exception nor return null..
289 Assert.assertNotNull(aep.getMainWords()); // Must not throw an exception nor return null..
290 Assert.assertNotNull(aep.getHashMD5ToName()); // Must not throw an exception nor return null..
291 Assert.assertNotNull(aep.getHashMD5Err()); // Must not throw an exception nor return null..
292 }
293
294 /**Test that long-term low-power mode always starts out as 'off' and stays that way for short-running tests! */
295 public static void testMustConservePowerLongTerm()
296 {
297 assertFalse("must always start false and take at least 24h to change", GenUtils.mustConservePowerLongTerm());
298 }
299
300 /**Check the word-counting algorithm. */
301 public static void testQuickWordCount()
302 {
303 assertEquals(0, TextUtils.quickWordCount(""));
304 assertEquals(1, TextUtils.quickWordCount("a"));
305 assertEquals(1, TextUtils.quickWordCount(" a "));
306 assertEquals(1, TextUtils.quickWordCount("a.b"));
307 assertEquals(2, TextUtils.quickWordCount("a b"));
308 assertEquals(2, TextUtils.quickWordCount("a\r\nb"));
309 assertEquals(2, TextUtils.quickWordCount("\t a\r\nb "));
310 }
311
312 /**Test getIntHashedSimple() basics. */
313 public static void testGetIntHashedSimple()
314 {
315 for(int i = 10; --i >= 0; )
316 {
317 final int n = 1 + rnd.nextInt(900);
318 final long seed = rnd.nextLong();
319 final int result = GenUtils.getIntHashedSimple(seed, n);
320 assertTrue(result >= 0);
321 assertTrue(result < n);
322 }
323 }
324
325 /**Test leastN() basics. */
326 public static void testLeastN()
327 {
328 // Simple case of empty (non-null) result from requesting least 0 from empty collection/iterator.
329 assertEquals(0, GenUtils.leastN(0, Collections.<String>emptyList().iterator(), String.CASE_INSENSITIVE_ORDER).size());
330 assertEquals(0, GenUtils.leastN(1, Collections.<String>emptyList().iterator(), String.CASE_INSENSITIVE_ORDER).size());
331 assertEquals(0, GenUtils.leastN(9, Collections.<String>emptyList().iterator(), String.CASE_INSENSITIVE_ORDER).size());
332 // Various tests on non-empty source to exercise most code paths.
333 final Collection<String> items1 = Arrays.asList(new String[]{"cat","sat","on","mat"});
334 for(int i = 2+items1.size(); --i >= 0; )
335 {
336 final SortedSet<String> leastN = GenUtils.leastN(i, items1.iterator(), String.CASE_INSENSITIVE_ORDER);
337 assertEquals(Math.min(i, items1.size()), leastN.size());
338 if(i > 0) { assertEquals("cat", leastN.first()); }
339 }
340 }
341
342
343 /**Dummy test to keep jUnit framework happy when there are no other tests in this class. */
344 public static final void testDummy()
345 { }
346
347 /**Private source of OK pseudo-random numbers. */
348 private static final Random rnd = new Random();
349 }