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     * Created by IntelliJ IDEA.
031     * User: Administrator
032     * Date: 28-Dec-02
033     * Time: 22:24:51
034     * To change template for new class use
035     * Code Style | Class Templates options (Tools | IDE Options).
036     */
037    package org.hd.d.pg2k.test.dev;
038    
039    import java.util.Collections;
040    import java.util.GregorianCalendar;
041    import java.util.HashSet;
042    import java.util.Iterator;
043    import java.util.Properties;
044    import java.util.Set;
045    
046    import junit.framework.TestCase;
047    
048    import org.hd.d.pg2k.ai.scorer.ScorerCacheIF;
049    import org.hd.d.pg2k.svrCore.AllExhibitImmutableData;
050    import org.hd.d.pg2k.svrCore.AllExhibitProperties;
051    import org.hd.d.pg2k.svrCore.ExhibitName;
052    import org.hd.d.pg2k.svrCore.ExhibitPropsComputable;
053    import org.hd.d.pg2k.svrCore.ExhibitPropsComputableMutable;
054    import org.hd.d.pg2k.svrCore.ExhibitPropsGlobalImmutable;
055    import org.hd.d.pg2k.svrCore.ExhibitPropsLoadable;
056    import org.hd.d.pg2k.svrCore.ExhibitStaticAttr;
057    import org.hd.d.pg2k.svrCore.Name;
058    import org.hd.d.pg2k.svrCore.datasource.ExhibitDataFileSource;
059    import org.hd.d.pg2k.svrCore.datasource.SimpleExhibitPipelineIF;
060    import org.hd.d.pg2k.svrCore.props.GenProps;
061    import org.hd.d.pg2k.svrCore.uploader.ExhibitHandlerBeanBase;
062    import org.hd.d.pg2k.svrCore.vars.SimpleVariableValue;
063    import org.hd.d.pg2k.svrCore.vars.SystemVariables;
064    import org.hd.d.pg2k.webSvr.catalogue.SearchPageJavaBean;
065    import org.hd.d.pg2k.webSvr.exhibit.BuiltInFilters;
066    
067    /**Test of filtering of exhibits (eg for search page).
068     * Tests that the filters behave as expected.
069     */
070    public final class ExhibitFilterTest extends TestCase
071        {
072        public ExhibitFilterTest(final String name)
073            {
074            super(name);
075            }
076    
077        // Our test data set; null when not running the tests to save memory...
078        private AllExhibitProperties aep;
079    
080        // Our private empty but non-zero-timestamp GenProps set.
081        private GenProps gp;
082    
083        // Names of some notable members of our test set that we look for specifically...
084        /**A small single-word-name JPEG by DHD with timestamp of now. */
085        private static final String EX_SMALL_DHD_JPEG_NOW = "example/example-DHD.jpg";
086        /**A small single-word GIF by DHD a fortnight old. */
087        private static final String EX_SMALL_DHD_GIF_2WOLD = "example/2wold-DHD.gif";
088        /**A small single-word HTML fragment by ANON from the dawn of timeS (1998). */
089        private static final String EX_SMALL_ANON_HTXT_1998 = "example/1998file-blah-blah-ANON.htxt";
090    
091        /**Time this class was instantiated. */
092        private final static long now = System.currentTimeMillis();
093    
094        /**Time representing 19980101. */
095        private static final long year1998 = (new GregorianCalendar(1998, 1, 1)).getTime().getTime();
096    
097        /**Do any setup needed for the tests. */
098        @Override
099        protected void setUp()
100            {
101            aep = _makeAEP(null);
102            gp = new GenProps(new Properties(), System.currentTimeMillis());
103            }
104    
105        /**Make our standard test AEP value. */
106        private static AllExhibitProperties _makeAEP(final AllExhibitProperties oldAEP)
107            {
108            final Set<ExhibitStaticAttr> esas = new HashSet<ExhibitStaticAttr>();
109            esas.add(new ExhibitStaticAttr(EX_SMALL_DHD_JPEG_NOW, 100, now));
110            esas.add(new ExhibitStaticAttr(EX_SMALL_DHD_GIF_2WOLD, 100, now - 14 * SearchPageJavaBean.DAY_MS));
111            esas.add(new ExhibitStaticAttr(EX_SMALL_ANON_HTXT_1998, 123, year1998));
112    
113            return (new AllExhibitProperties(oldAEP,
114                                             new ExhibitPropsGlobalImmutable(),
115                                             new AllExhibitImmutableData(esas, now),
116                                             Collections.<Name.ExhibitFull, ExhibitPropsLoadable>emptyMap(),
117                                             Collections.<Name.ExhibitFull, ExhibitPropsComputable>emptyMap(),
118                                             0));
119            }
120    
121        /**Do any clearup needed after the tests. */
122        @Override
123        protected void tearDown()
124            {
125            // cleanup code
126            aep = null;
127            gp = null;
128            }
129    
130        /**Do some tests on evaluations of newness, goodness, etc, of exhibits using ExhibitPropsComputableMutable. */
131        public void testSimpleExhibitPropsComputableMutableExhibitInspection()
132            {
133            // Get details of new (right-up-to-date) exhibit.
134            final ExhibitStaticAttr esaNew = aep.aeid.getStaticAttr(EX_SMALL_DHD_JPEG_NOW);
135            final ExhibitPropsComputableMutable epcmNew =
136                    ExhibitPropsComputableMutable.generateFastApproximation(esaNew, new GenProps());
137    //        assertTrue("new exhibit must be seen as new", epcmNew.isExhibitNew());
138            assertTrue("new-exhibit goodness must be in [-1,+1] range",
139                       (epcmNew.getGoodnessAsFloat() >= -1) &&
140                       (epcmNew.getGoodnessAsFloat() <= +1));
141    //        System.out.println("[Example 'new' simple goodness value: "+epcmNew.getGoodnessAsFloat()+" ("+epcmNew.getGoodness()+")]");
142            // Get details of old exhibit.
143            final ExhibitStaticAttr esaOld = aep.aeid.getStaticAttr(EX_SMALL_ANON_HTXT_1998);
144            final ExhibitPropsComputableMutable epcmOld =
145                    ExhibitPropsComputableMutable.generateFastApproximation(esaOld, new GenProps());
146    //        assertFalse("old exhibit must be seen as old", epcmOld.isExhibitNew());
147            assertTrue("old-exhibit goodness must be in [-1,+1] range",
148                       (epcmOld.getGoodnessAsFloat() >= -1) &&
149                       (epcmOld.getGoodnessAsFloat() <= +1));
150    //        System.out.println("[Example 'old' simple goodness value: "+epcmOld.getGoodnessAsFloat()+" ("+epcmOld.getGoodness()+")]");
151            }
152    
153        /**Simple tests of some built-in exhibit filters. */
154        public void testBuiltInFilters()
155            {
156            final BuiltInFilters.filtByCategory filtEXAMPLE = new BuiltInFilters.filtByCategory(new String[]{"example"});
157            assertTrue("should accept exhibit with correct category", filtEXAMPLE.accept(aep, Name.ExhibitFull.create(EX_SMALL_DHD_JPEG_NOW)));
158            assertFalse("should reject name too short to possibly match category", filtEXAMPLE.accept(aep, Name.ExhibitFull.create("e/e-E.e")));
159            final BuiltInFilters.filtByCategory filtEXAXXLE = new BuiltInFilters.filtByCategory(new String[]{"exaxxle"});
160            assertFalse("should reject exhibit with category of correct length but at least one differing char", filtEXAXXLE.accept(aep, Name.ExhibitFull.create(EX_SMALL_DHD_JPEG_NOW)));
161            final BuiltInFilters.filtByCategory filtEXAMPLES = new BuiltInFilters.filtByCategory(new String[]{"examples"});
162            assertFalse("should reject match with longer category whose prefix matches", filtEXAMPLES.accept(aep, Name.ExhibitFull.create(EX_SMALL_DHD_JPEG_NOW)));
163            final BuiltInFilters.filtByCategory filtOTHER = new BuiltInFilters.filtByCategory(new String[]{"other"});
164            assertFalse("should reject different category", filtOTHER.accept(aep, Name.ExhibitFull.create(EX_SMALL_DHD_JPEG_NOW)));
165    
166            // TODO
167    
168    
169            }
170    
171        /**Test differentials in "goodness" induced via GenProps.
172         * Tests that the smallest possible changes in static "goodness" attributes
173         * always show up in the goodness score and overwhelm any "random" factors.
174         * <p>
175         * Tests firstly for author weightings.
176         */
177        public void testSimpleExhibitPropsComputableMutableGoodnessDifferentials()
178            {
179            for(final Iterator<ExhibitStaticAttr> it = aep.aeid.getAllStaticAttrs().iterator(); it.hasNext(); )
180                {
181                final ExhibitStaticAttr esa = it.next();
182                // Calculate "base" goodness with empty GenProps.
183                final ExhibitPropsComputableMutable epcmBase =
184                        ExhibitPropsComputableMutable.generateFastApproximation(esa, new GenProps());
185                final int gBase = epcmBase.getGoodness();
186    
187                final Name.ExhibitFull fullName = esa.getExhibitFullName();
188                final String auth = ExhibitName.getAuthorComponent(fullName).toString();
189    
190                // Compute "down" value with smallest increment down by author.
191                final Properties pDownA = new Properties();
192                pDownA.setProperty(GenProps.PPREFIX_POPWT_DETAILS + GenProps.PCOMP_POPWR_BYAUTH + auth,
193                                   "-1");
194                final ExhibitPropsComputableMutable epcmDownA =
195                        ExhibitPropsComputableMutable.generateFastApproximation(esa, new GenProps(pDownA, 1));
196                final int gDownA = epcmDownA.getGoodness();
197                assertTrue("negative author weighting must reduce goodness from base value",
198                           gDownA < gBase);
199    
200                // Compute "up" value with smallest increment up by author.
201                final Properties pUpA = new Properties();
202                pUpA.setProperty(GenProps.PPREFIX_POPWT_DETAILS + GenProps.PCOMP_POPWR_BYAUTH + auth,
203                                   "1");
204                final ExhibitPropsComputableMutable epcmUpA =
205                        ExhibitPropsComputableMutable.generateFastApproximation(esa, new GenProps(pUpA, 1));
206                final int gUpA = epcmUpA.getGoodness();
207                assertTrue("positive author weighting must increase goodness from base value",
208                           gUpA > gBase);
209                }
210    
211            // TODO: test by attribute...
212    
213            // TODO: test by file type/extension...
214            }
215    
216        /**Test that computable-mutable properties are preserved through serialisation.
217         * Note that (de)serialising the whole AEP may choose
218         * not to preserve this information as a matter of policy
219         * (to save transmission/storage space at the expense of CPU time).
220         */
221        public void testEPCMSer()
222            throws Exception
223            {
224            // We'll make sure that creating new instances of the AEP
225            // produces a new (slightly randomised) goodness each time,
226            // for all exhibits.
227    
228            // Variable "source".
229    //        final BasicVarMgrInterface vars = new BasicVarMgr(true);
230            final ExhibitDataFileSource edfs = new ExhibitDataFileSource(null);
231            final SimpleExhibitPipelineIF vars = edfs;
232    
233            // Put in one vote pro and one con (albeit void) entry.
234            // We need to do this to keep vote/score computations happy!
235            vars.setVariable(new SimpleVariableValue(SystemVariables.VOTE_CON, null));
236            vars.setVariable(new SimpleVariableValue(SystemVariables.VOTE_PRO, null));
237    
238            // Repeat test for all exhibits.
239            for(final Iterator<ExhibitStaticAttr> it = aep.aeid.getAllStaticAttrs().iterator(); it.hasNext(); )
240                {
241                final ExhibitStaticAttr esa = it.next();
242                final Name.ExhibitFull fullName = esa.getExhibitFullName();
243    
244                // Check that when we ask for a full EPCM we don't get a trivially-stale one.
245                assertFalse("Must not get trivially-stale EPCM when told to do full calc",
246                            aep.getExhibitPropsComputableMutable(fullName, false, gp, vars, ScorerCacheIF.TRIVIAL).isTriviallyStale());
247    
248                // Show that retrieving EPCM from same AEP twice gets the same value.
249                // (Providing we don't force recomputation.)
250                assertEquals("EPCM goodness value must be same when fetched twice",
251                             aep.getExhibitPropsComputableMutable(fullName, false, gp, vars, ScorerCacheIF.TRIVIAL).getGoodness(),
252                             aep.getExhibitPropsComputableMutable(fullName, false, gp, vars, ScorerCacheIF.TRIVIAL).getGoodness());
253    
254                // Show that we will get a different EPCM from a new identical AEP.
255                // (Because of random component.)
256                final AllExhibitProperties aep2 = _makeAEP(null);
257                assertNotSame("EPCM goodness values expected to be different from different AEP instances",
258                              aep.getExhibitPropsComputableMutable(fullName, false, gp, vars, ScorerCacheIF.TRIVIAL).getGoodness(),
259                              aep2.getExhibitPropsComputableMutable(fullName, false, gp, vars, ScorerCacheIF.TRIVIAL).getGoodness());
260    
261                // Show that creating new AEP using old one for expensive computable state
262                // preserves that state providing that this was not a quick approximation value...
263                final AllExhibitProperties aepMakeWithOld = _makeAEP(aep);
264                assertEquals("EPCM goodness value must be same when fetched from new instance made using old",
265                             aep.getExhibitPropsComputableMutable(fullName, false, gp, vars, ScorerCacheIF.TRIVIAL).getGoodness(),
266                             aepMakeWithOld.getExhibitPropsComputableMutable(fullName, false, gp, vars, ScorerCacheIF.TRIVIAL).getGoodness());
267    
268    //            // Show that serialising and deserialising AEP preserves expensive computable state.
269    //            // Only valid if the epcmMap field is not transient.
270    //            final AllExhibitProperties aepDeser =
271    //                    (AllExhibitProperties) SerializationTest.checkSerialisationPreservesEquality(aep);
272    //            assertEquals("EPCM goodness value must be same when fetched from (de)serialised copy",
273    //                         aep.getExhibitPropsComputableMutable(fullName, true, gp, vars).getGoodness(),
274    //                         aepDeser.getExhibitPropsComputableMutable(fullName, true, gp, vars).getGoodness());
275                }
276            }
277    
278    
279        /**Test aspects of filtering by the SearchPageJavaBean.
280         * Depends on default data set.
281         */
282        public void testSearchPageJavaBean()
283            {
284            final SearchPageJavaBean spjb1 = new SearchPageJavaBean();
285            spjb1.setAep(aep);
286    
287            // Check that a new search bean returns no filter (null).
288            assertTrue("new search search bean does not not return null filter", spjb1.getSearchFilter() == null);
289    
290            // Check by-author filtering.
291            spjb1.setAuthor("DHD");
292            assertTrue("expected author DHD, got: " + spjb1.getAuthor(), spjb1.getAuthor().equals("DHD"));
293            // Note that the accept() filter takes exhibit short names (without the dir path)
294            // or short persistent keys as of 200908XX.
295            // This will probably not work for exhibits not in our official exhibit set.
296            assertTrue(spjb1.getSearchFilter().accept("example-DHD.jpg"));
297            assertTrue(!spjb1.getSearchFilter().accept("a-ANON.jpg"));
298            assertTrue(spjb1.getSearchFilter().accept(ExhibitName.getFileComponent(EX_SMALL_DHD_JPEG_NOW).toString()));
299            spjb1.setAuthor(ExhibitHandlerBeanBase.SETTER_ALL);
300            assertTrue("could not clear author, got: " + spjb1.getAuthor(), spjb1.getAuthor().equals(""));
301    
302            // Check recent-additions filtering.
303            assertTrue("expected no recent-days filtering, got: " + spjb1.getRecentDaysFilter(), spjb1.getRecentDaysFilter() == 0);
304            spjb1.setRecentDaysFilter(ExhibitHandlerBeanBase.SETTER_ALL);
305            assertTrue("expected no recent-days filtering [2], got: " + spjb1.getRecentDaysFilter(), spjb1.getRecentDaysFilter() == 0);
306            // Check that each symbolic time value sets the correct number of days.
307            for(final Iterator<String> it = SearchPageJavaBean.symFilterDays.keySet().iterator(); it.hasNext(); )
308                {
309                final String symName = it.next();
310                spjb1.setRecentDaysFilter(symName);
311                assertTrue("failed to get right value for symbolic name: " + symName + " got: " + spjb1.getRecentDaysFilter(),
312                           spjb1.getRecentDaysFilter() == (SearchPageJavaBean.symFilterDays.get(symName)).intValue());
313                }
314            // Now test that filtering threshold works correctly,
315            // especially trying to look for overflow of milliseconds in an int.
316            spjb1.setRecentDaysFilter("d"); // Filter on a week first.
317            assertTrue("failed to return new exhibit with d filter", spjb1.getSearchFilter().accept(ExhibitName.getFileComponent(EX_SMALL_DHD_JPEG_NOW).toString()));
318            assertTrue("failed to reject 2w-old exhibit with d filter", !spjb1.getSearchFilter().accept(ExhibitName.getFileComponent(EX_SMALL_DHD_GIF_2WOLD).toString()));
319            assertTrue("failed to reject 1998 exhibit with d filter", !spjb1.getSearchFilter().accept(ExhibitName.getFileComponent(EX_SMALL_ANON_HTXT_1998).toString()));
320            // especially trying to look for overflow of milliseconds in an int.
321            spjb1.setRecentDaysFilter("7"); // Filter on a week first.
322            assertTrue("failed to return new exhibit with 7 filter", spjb1.getSearchFilter().accept(ExhibitName.getFileComponent(EX_SMALL_DHD_JPEG_NOW).toString()));
323            assertTrue("failed to reject 2w-old exhibit with 7 filter", !spjb1.getSearchFilter().accept(ExhibitName.getFileComponent(EX_SMALL_DHD_GIF_2WOLD).toString()));
324            assertTrue("failed to reject 1998 exhibit with 7 filter", !spjb1.getSearchFilter().accept(ExhibitName.getFileComponent(EX_SMALL_ANON_HTXT_1998).toString()));
325            spjb1.setRecentDaysFilter("y"); // Filter on a week first.
326            assertTrue("failed to return new exhibit with y filter", spjb1.getSearchFilter().accept(ExhibitName.getFileComponent(EX_SMALL_DHD_JPEG_NOW).toString()));
327            assertTrue("failed to return 2w-old exhibit with y filter", spjb1.getSearchFilter().accept(ExhibitName.getFileComponent(EX_SMALL_DHD_GIF_2WOLD).toString()));
328            assertTrue("failed to reject 1998 exhibit with y filter", !spjb1.getSearchFilter().accept(ExhibitName.getFileComponent(EX_SMALL_ANON_HTXT_1998).toString()));
329            // Clear recent-days filtering.
330            spjb1.setRecentDaysFilter("0");
331            assertTrue("expected no recent-days filtering [3], got: " + spjb1.getRecentDaysFilter(), spjb1.getRecentDaysFilter() == 0);
332    
333    
334    
335    
336    
337            // Check that a new search bean returns no filter (null).
338            assertTrue("used but cleaned-up search bean does not not return null filter", spjb1.getSearchFilter() == null);
339    
340            }
341        }