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    package org.hd.d.pg2k.test.dev;
031    
032    import java.io.File;
033    import java.util.Arrays;
034    import java.util.Collections;
035    import java.util.HashSet;
036    import java.util.List;
037    import java.util.Properties;
038    import java.util.Set;
039    
040    import junit.framework.TestCase;
041    
042    import org.hd.d.pg2k.svrCore.AllExhibitProperties;
043    import org.hd.d.pg2k.svrCore.CS8Bit;
044    import org.hd.d.pg2k.svrCore.ExhibitName;
045    import org.hd.d.pg2k.svrCore.FileTools;
046    import org.hd.d.pg2k.svrCore.GenUtils;
047    import org.hd.d.pg2k.svrCore.Name;
048    import org.hd.d.pg2k.svrCore.Name.ExhibitFull;
049    import org.hd.d.pg2k.svrCore.TextUtils;
050    import org.hd.d.pg2k.svrCore.props.GenProps;
051    import org.hd.d.pg2k.svrCore.props.GenPropsGenNames;
052    
053    /**
054     * Created by IntelliJ IDEA.
055     * User: Damon Hart-Davis
056     * Date: 27-Jul-2003
057     * Time: 17:37:14
058     */
059    
060    /**Tests various features of exhibit name syntax, parsing, etc.
061     * This should be extended to test all the various conversions and parses
062     * and comparators from ExhibitName and Name.
063     */
064    public final class ExhibitNameTest extends TestCase
065        {
066        /**Test the "valid syntax" methods.
067         * Created after enabling assert()s revealed internal inconsistencies.
068         */
069        public void testValidSyntaxMethods()
070            {
071            final String minFinalNameComponent = "b-C.d";
072            final String minName ="a/" + minFinalNameComponent;
073    
074            // Check minimal valid name is regarded as valid.
075            assertTrue("Minimal valid final name component must be seen as valid",
076                       ExhibitName.validNameFinalComponentSyntax(minFinalNameComponent));
077            assertTrue("Minimal valid name must be seen as valid by ExhibitName.validNameSyntaxBasic()",
078                       ExhibitName.validNameSyntaxBasic(minName));
079            assertTrue("Minimal valid name must be seen as valid by ExhibitName.validNameSyntax()",
080                       ExhibitName.validNameSyntax(minName));
081            }
082    
083        /**Test some extraction/comparison methods.
084         * Initially created after enabling assert()s revealed internal inconsistencies.
085         */
086        public void testExtractionMethods()
087            {
088            final String text = "z/e-W.q";
089            final Name.ExhibitFull full = Name.ExhibitFull.create(text);
090            assertEquals(text, full.toString());
091            final byte[] asBytes = full.toByteArray();
092            final int offset = 3;
093            final byte[] offsetBytes = new byte[39];
094            full.writeToByteArray(offsetBytes, offset);
095            for(int i = text.length(); --i >= 0; )
096                {
097                assertEquals((char) asBytes[i], text.charAt(i));
098                assertEquals((char) offsetBytes[i + offset], text.charAt(i));
099                }
100            assertEquals(0, full.compareTo(full));
101            assertTrue(full.compareTo(Name.ExhibitFull.create("a/e-W.q")) > 0);
102            assertTrue(Name.ExhibitFull.create("a/e-W.q").compareTo(full) < 0);
103            assertTrue(full.compareTo(Name.ExhibitFull.create("z/e-W.qq")) < 0);
104            assertTrue(Name.ExhibitFull.create("z/e-W.qq").compareTo(full) > 0);
105            // Some short name extraction.
106            assertEquals(0, full.getShortName().compareTo(full.getShortName()));
107            assertEquals(0, full.getShortName().compareTo(Name.ExhibitFull.create("somethingelse/e-W.q").getShortName()));
108            // Some virtual name extraction.
109            assertEquals(full, full.getVirtualExhibitName());
110            assertEquals(full, Name.ExhibitFull.create("z/_more2012/_more01/e-W.q").getVirtualExhibitName());
111            }
112    
113        /**Test generation of "virtual" names.
114         * Simple tests that names are "virtualised" correctly.
115         */
116        public void testVirtualNameGeneration()
117            {
118            assertEquals("Already-virtual name must be returned as-is",
119                    Name.ExhibitFull.create("a/b-C.d"),
120                    Name.ExhibitFull.create("a/b-C.d").getVirtualExhibitName());
121            assertEquals("Name must be correctly virtualised",
122                    Name.ExhibitFull.create("a/B-C.def"),
123                    Name.ExhibitFull.create("a/_more1999/_more01/B-C.def").getVirtualExhibitName());
124            // And while we're at it confirm that going via toByteArray() and toString() doesn't break anything.
125            assertEquals("Name must be correctly virtualised",
126                    Name.ExhibitFull.create("a/B-C.def").toString(),
127                    (new CS8Bit(Name.ExhibitFull.create("a/_more1999/_more01/B-C.def").getVirtualExhibitName().toByteArray())).toString());
128            }
129    
130        /**Test extraction of main-words component of a name.
131         * Does some very basic testing of the ability to extract the main words.
132         * <p>
133         * These tests are entirely syntactic,
134         * ie do not require particular exhibit names or meanings.
135         */
136        public void testMainWordExtraction()
137            {
138            // Test set of attribute words, were used.
139            final Set<String> attrWords = new HashSet<String>(Arrays.asList(
140                new String[]{ "mono", "false", "colour", "rotated" }));
141    
142            // Set of values to test:
143            //   * Column 1 is the exhibit names.
144            //   * Column 2 is the main words fragment expected with no attribute words.
145            //   * Column 3 is the main words fragment expected with attrWords.
146            final String testData[][] =
147                {
148                    { "a/b-C.d", "b", "b" },
149                    { "a/b-3-C.d", "b", "b" },
150                    { "a/b-mono-3-C.d", "b-mono", "b" },
151                    { "a/b-false-colour-05-C.d", "b-false-colour", "b" },
152                    { "a/b-dog-Cat-zoo-apple-false-colour-05-C.d", "b-dog-Cat-zoo-apple-false-colour", "b-dog-Cat-zoo-apple" },
153                };
154    
155            // Work through all the test cases.
156            for(int i = testData.length; --i >= 0; )
157                {
158                final String exhibitName = testData[i][0];
159                final String noAWParse = testData[i][1];
160                final String aWParse = testData[i][2];
161                final Set<String> noAttrWords = Collections.emptySet();
162                final String actualNoAWParse = ExhibitName.getMainWordsComponent(exhibitName, noAttrWords).toString();
163                assertTrue("unexpected results without attr words: got `"+actualNoAWParse+"', expected `"+noAWParse+"'",
164                           noAWParse.equals(actualNoAWParse));
165                final String acutalAWParse = ExhibitName.getMainWordsComponent(exhibitName, attrWords).toString();
166                assertTrue("unexpected results with attr words: got `"+acutalAWParse+"', expected `"+aWParse+"'",
167                           aWParse.equals(acutalAWParse));
168                }
169            }
170    
171        /**Test isSensitive() exhibit-flagging mechanism. */
172        public void testIsSensitive()
173            {
174            final String SENSITIVE_PHRASE = "-Monty-Python-";
175    
176            // Synthetic sensitive name.
177            final String sensName = "memes/some-prefix" + SENSITIVE_PHRASE + "some-suffix-ANON.jpg";
178    
179            // Set up minimal GP with a test "sensitive" value.
180            final Properties p = new Properties();
181            p.setProperty(GenPropsGenNames.GEN_PREFIX + GenPropsGenNames.GPGEN_SENSITIVE_NAME_SUBSTRS_KEY, SENSITIVE_PHRASE);
182            final GenProps gp = new GenProps(p, System.currentTimeMillis());
183    
184            // Check sensitive values against default GP.
185            assertTrue("Sensitive exhibit name must be flagged as such: " + sensName, GenUtils.isSensitive(sensName, gp));
186            assertFalse("Non-sensitive exhibit name must not be flagged as sensitive", GenUtils.isSensitive("a/a-A.a", gp));
187            }
188    
189        /**Test sane (internal) behaviour of Name on real AEP data.
190         * This is white-box testing.
191         */
192        public static void testNameSharing()
193            {
194            // To verify some internal parameters, invent a couple of unique names here.
195            final Name.ExhibitFull nufn1 = Name.ExhibitFull.createNoIntern("abcdefghq/a-B.c", null);
196            Main.getOut().println(nufn1.showInternalStructure());
197            assertEquals("must not share any state with any other name", 0, nufn1.getPrevChainLength());
198            assertEquals("must not share any state with any other name", 0, nufn1.getPrefixLen());
199            final Name.ExhibitFull nufn2 = Name.ExhibitFull.createNoIntern("abcdefghq/d-E.f", nufn1);
200            Main.getOut().println(nufn2.showInternalStructure());
201            assertEquals("must link to the one previous name above", 1, nufn2.getPrevChainLength());
202            assertEquals("must share a maximal prefix with the previous name", 10, nufn2.getPrefixLen());
203            final Name.ExhibitFull nufn3 = Name.ExhibitFull.createNoIntern("abcdefghq/de-E.f", nufn2);
204            Main.getOut().println(nufn3.showInternalStructure());
205            assertEquals("must link to the previous names above", 2, nufn3.getPrevChainLength());
206            assertEquals("must share a maximal prefix with the previous name", 11, nufn3.getPrefixLen());
207            assertEquals("should share a reasonable suffix with the previous name", 4, nufn3.getSuffixLen());
208            }
209    
210        /**Test correct behaviour of Name on real AEP data. */
211        public static void testNameOnAEP()
212            throws Exception
213            {
214            // Test performance on a realistic exhibit-name data set.
215            final File lastAEPFile = new File(BackCompatTest.frozenAEPs.get(BackCompatTest.frozenAEPs.size()-1));
216    
217            Main.getOut().println("Using captured AEP " + lastAEPFile);
218            final AllExhibitProperties lastAEP =
219                (AllExhibitProperties) FileTools.deserialiseFromFile(lastAEPFile, true);
220    
221            // Check that short names are extracted correctly with getShortName();
222            assertEquals("cd-E.f", Name.ExhibitFull.create("b/cd-E.f").getShortName().toString());
223            assertEquals("cd-E.f", Name.ExhibitFull.create("ab/cd-E.f").getShortName().toString());
224            assertEquals("cd-E.fg", Name.ExhibitFull.create("ab/cd-E.fg").getShortName().toString());
225            final List<ExhibitFull> allExhibitNamesSorted = lastAEP.aeid.getAllExhibitNamesSorted();
226            for(final Name.ExhibitFull fn : allExhibitNamesSorted)
227                { assertTrue(TextUtils.contentEquals(ExhibitName.getFileComponent(fn), fn.getShortName())); }
228    
229            // Check that virtual names are extracted correctly with getVirtualExhibitName();
230            assertEquals("b/cd-E.f", Name.ExhibitFull.create("b/cd-E.f").getVirtualExhibitName().toString());
231            assertEquals("ab/cd-E.f", Name.ExhibitFull.create("ab/cd-E.f").getVirtualExhibitName().toString());
232            assertEquals("ab/cd-E.fg", Name.ExhibitFull.create("ab/cd-E.fg").getVirtualExhibitName().toString());
233            assertEquals("ab/cd-E.fg", Name.ExhibitFull.create("ab/_more2001/_more01/cd-E.fg").getVirtualExhibitName().toString());
234            for(final Name.ExhibitFull fn : allExhibitNamesSorted)
235                { assertTrue(TextUtils.contentEquals(ExhibitName.getCategoryComponent(fn) + "/" + fn.getShortName(), fn.getVirtualExhibitName())); }
236    
237            // Check that all persistableKey() values are actually unique (as String values),
238            // for this recent AEP instance.
239            final Set<String> pKeys = new HashSet<String>(lastAEP.aeid.length * 2);
240            for(final Name.ExhibitFull fn : allExhibitNamesSorted)
241                {
242                final CharSequence pKey = fn.getShortName().persistableKey();
243                // Check that key is pure-printable-ASCII and of the right length, etc.
244                assertTrue(pKey.length() == Name.ExhibitShort.PERSISTABLE_NAME_LENGTH);
245                assertTrue(pKey.charAt(0) == Name.ExhibitShort.PERSISTABLE_NAME_LEADING_CHAR);
246                for(int i = pKey.length(); --i >= 0; )
247                    {
248                    final char c = pKey.charAt(i);
249                    assertTrue(c > ' ');
250                    assertTrue(c < 127);
251                    }
252                assertTrue("each persistableKey value should be unique", pKeys.add(pKey.toString()));
253                assertEquals("should be able to look up full name from persistable key", fn, lastAEP.aeid.getFullNameFromPersistableKey(pKey));
254                assertEquals("should be able to look up full name from persistable key as-is", fn, lastAEP.aeid.getFullNameFromPersistableKey(pKey));
255                assertEquals("should be able to look up full name from persistable key as String", fn, lastAEP.aeid.getFullNameFromPersistableKey(pKey.toString()));
256                assertEquals("should be able to look up full name from persistable key as Name", fn, lastAEP.aeid.getFullNameFromPersistableKey(Name.create(pKey)));
257                }
258    
259            final int nameCount = allExhibitNamesSorted.size();
260            final String[] allStringExhibitNamesSorted = new String[nameCount];
261            for(int i = nameCount; --i >= 0; )
262                { allStringExhibitNamesSorted[i] = allExhibitNamesSorted.get(i).toString(); }
263            byte[] rawStringsSer = SerializationTest.serialiseToByteArray(allStringExhibitNamesSorted);
264            final int rawStringsSerLen = rawStringsSer.length;
265            final int rawStringsSerCompressedLen = GenUtils.compressData(rawStringsSer, GenUtils.MAX_SUPPORTED_COMPRESSION_LEVEL, false).second.length;
266            Main.getOut().println("Serialised in-order String array bytes raw | compressed: " + rawStringsSerLen + " | " + rawStringsSerCompressedLen);
267            // Find out how small a compressed representation we can make...
268            rawStringsSer = null; // Release resources.
269    
270            final long startTime = System.currentTimeMillis();
271    
272            // Compute best 'from-fresh' set of ExhibitFull values.
273            final Name[] compactExhibitNamesSorted = new Name[nameCount];
274            int maxPrevChainLength = 0;
275            long totalChars = 0;
276            long savedChars = 0;
277            Name prev = null;
278            for(int i = 0; i < nameCount; ++i)
279                {
280                final String s = allStringExhibitNamesSorted[i];
281                final Name out = /* allExhibitNamesSorted.get(i); */ Name.ExhibitFull.createNoIntern(s, prev);
282                totalChars += out.length();
283                compactExhibitNamesSorted[i] = out;
284                final int savedThisTime = out.getPrefixLen() + out.getSuffixLen();
285    //if(savedThisTime == 0) { System.err.println("Unable to save any chars from "+out+" with prev "+prev); }
286                savedChars += savedThisTime;
287                if(out.getPrevChainLength() > maxPrevChainLength) { maxPrevChainLength = out.getPrevChainLength(); }
288    //if(s.length() > 256) { Main.getOut().println("LONG NAME: "+ s); }
289                prev = out;
290                }
291            Main.getOut().println("'Saved' prefix chars (from "+nameCount+" names) vs total = "+savedChars+" vs "+totalChars+" => "+(savedChars/nameCount)+"chars/entry vs average "+(totalChars/nameCount)+"chars/entry");
292            Main.getOut().println("max prevChainLength = "+ maxPrevChainLength);
293    
294            byte[] compactStringsSer = SerializationTest.serialiseToByteArray(compactExhibitNamesSorted);
295            final int compactStringsSerLen = compactStringsSer.length;
296            final int compactStringsSerCompressedLen = GenUtils.compressData(compactStringsSer, GenUtils.MAX_SUPPORTED_COMPRESSION_LEVEL, false).second.length;
297            Main.getOut().println("Serialised in-order compact representation array bytes raw | compressed: " + compactStringsSerLen + " | " + compactStringsSerCompressedLen);
298            compactStringsSer = null; // Release resources.
299    
300            assertTrue("Raw compact form should be no larger on-the-wire than raw simple String form", compactStringsSerLen <= rawStringsSerLen);
301            assertTrue("Compressed compact form should not be larger on-the-wire than raw simple String form", compactStringsSerCompressedLen <= rawStringsSerCompressedLen);
302    
303            final long endTime = System.currentTimeMillis();
304            Main.getOut().println("Run time to rebuild: " + (endTime-startTime) + "ms.");
305            }
306    
307        /**Simple test of comparator to exercise all branches. */
308        public static void testNameCompareTo()
309            {
310            assertTrue(Name.create("bat").compareTo(Name.create("bat")) == 0);
311            assertTrue(Name.create("bat").compareTo(Name.create("cat")) < 0);
312            assertTrue(Name.create("cat").compareTo(Name.create("bat")) > 0);
313            assertTrue(Name.create("battle").compareTo(Name.create("cat")) < 0);
314            assertTrue(Name.create("battle").compareTo(Name.create("cattle")) < 0);
315            assertTrue(Name.create("cat").compareTo(Name.create("cattle")) < 0);
316    
317            assertTrue(Name.ExhibitFull.create("a/bat-A.a").compareTo(Name.ExhibitFull.create("a/bat-A.a")) == 0);
318            assertTrue(Name.ExhibitFull.create("a/bat-A.a").compareTo(Name.ExhibitFull.create("a/cat-A.a")) < 0);
319            assertTrue(Name.ExhibitFull.create("a/cat-A.a").compareTo(Name.ExhibitFull.create("a/bat-A.a")) > 0);
320            final ExhibitFull n1 = Name.ExhibitFull.create("a/bat-A.aa");
321            assertTrue(n1.compareTo(Name.ExhibitFull.create("a/cat-A.a")) < 0);
322            final ExhibitFull n2 = Name.ExhibitFull.create("a/cat-A.aa", n1);
323            assertTrue(n1.compareTo(n2) < 0);
324            assertTrue(Name.ExhibitFull.create("a/cat-A.a").compareTo(n2) < 0);
325            }
326        }