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        }