001    /*
002    Copyright (c) 1996-2011, 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 java.awt.Point;
039    import java.awt.Rectangle;
040    import java.io.File;
041    import java.util.HashMap;
042    import java.util.Random;
043    
044    import junit.framework.TestCase;
045    
046    import org.hd.d.pg2k.svrCore.ExhibitPropsGlobalImmutable;
047    import org.hd.d.pg2k.svrCore.Name;
048    import org.hd.d.pg2k.svrCore.location.Location;
049    import org.hd.d.pg2k.svrCore.location.LocationMap;
050    import org.hd.d.pg2k.svrCore.props.LocalProps;
051    import org.hd.d.pg2k.webSvr.virtualHosts.AlohaEarth.AEParams;
052    import org.hd.d.pg2k.webSvr.virtualHosts.AlohaEarth.AEUtils;
053    
054    // TODO: test HTTP request path/param parsing/handling.
055    
056    /**Test of various Aloha Earth components.
057     * Includes some tests of Location which could maybe go elsewhere.
058     */
059    public final class AlohaEarthTest extends TestCase
060        {
061        public AlohaEarthTest(final String name)
062            {
063            super(name);
064            }
065    
066        /**Do some basic checks on AEParams object.
067         * Test such as that we can set parameters away from and to their
068         * defaults.
069         */
070        public void testAlohaEarthParams()
071            {
072            // Sanity check on some constants.
073            assertTrue("INITIAL_ZOOM should be in allowed range",
074                       (AEUtils.MIN_ZOOM <=
075                            AEUtils.INITIAL_ZOOM) &&
076                       (AEUtils.INITIAL_ZOOM <=
077                            AEUtils.MAX_ZOOM));
078    
079            // Create a new object and ensure that it starts with default values.
080            final AEParams aep1 = new AEParams();
081            assertTrue("New bean must start off in default state",
082                       aep1.inDefaultState());
083    
084            // We should start with initial zoom.
085            assertTrue("Should start off with initial zoom",
086                       aep1.getZoomFactor() == AEUtils.INITIAL_ZOOM);
087            // We should start with zero initial offsets.
088            assertTrue("Should start off with zero initial offsets",
089                       (aep1.getEastOffset(false) == 0) &&
090                       (aep1.getNorthOffset(false) == 0));
091            // Location should be OE, ON.
092            final Location.Estd l1 = aep1.getLocation(true);
093            assertTrue("Location should have OE ON centre",
094                       (l1.getE().value.intValue() == 0) &&
095                       (l1.getN().value.intValue() == 0));
096            // Target display pixel of own location should be the centre pixel.
097            final Point p = aep1.getDisplayPixelForEstdLocationCentre(l1);
098            // We'll be slightly kind about what the centre pixel is.
099            assertTrue("Target pixel for display of own location should be display centre: " + p,
100                       (Math.abs(p.x - AEUtils.DISPLAY_2D_EARTH_MAP_WIDTH/2) < 2) &&
101                       (Math.abs(p.y - AEUtils.DISPLAY_2D_EARTH_MAP_HEIGHT/2) < 2));
102    
103    
104    
105            // Check that if we set the zoom to something non-initial,
106            // we can retrieve it and the parameters are seen as non-default.
107            // We'll then try resetting it to its default value again.
108            if(AEUtils.INITIAL_ZOOM !=
109                            AEUtils.MAX_ZOOM)
110                {
111                // Do the set via the String-parameter method...
112                aep1.setZoomFactor(
113                    String.valueOf(AEUtils.MAX_ZOOM));
114                assertTrue("Must be able to retrieve new zoomFactor",
115                           aep1.getZoomFactor() == AEUtils.MAX_ZOOM);
116                assertTrue("Must see parameters as non-default",
117                           !aep1.inDefaultState());
118    
119                // Reset to default zoom via the int parameter method...
120                aep1.setZoomFactor(AEUtils.INITIAL_ZOOM);
121                assertTrue("Must be able to reset zoom parameter to default",
122                           aep1.inDefaultState());
123                }
124    
125    
126            // FIXME: Do some brief checking of the east/north offsets...
127    
128    
129    System.err.println("WHOLE_EARTH: " + Location.Estd.WHOLE_EARTH);
130    
131            // Check that no zoomed view is no bigger than WHOLE_EARTH.
132            // FIXME: rounding errors make this fail at MIN_ZOOM for the moment.
133            for(int zf = AEUtils.MIN_ZOOM + 1; zf <= AEUtils.MAX_ZOOM; ++zf)
134                {
135                final AEParams aeps = (new AEParams()).setZoomFactor(zf);
136                assertFalse("No zoomed-out view must be no bigger than WHOLE_EARTH: "+aeps,
137                            aeps.getLocation(true).
138                                isLargerThan(Location.Estd.WHOLE_EARTH));
139                }
140            }
141    
142        /**Ensure that rectangle and other readable parameters are always in bounds where requested.
143         * This is particularly important on edge cases,
144         * and were different sets of bounds interact
145         * so although individual values might be in range,
146         * combinations may not be.
147         * <p>
148         * We systematically run through a full range of zooms
149         * (indeed, nominally beyond the allowed bounds)
150         * and try extracting derived values for a range of input offsets.
151         * <p>
152         * This test is about making sure that things remain within bounds and
153         * is not about making sure that they can be usefully set, for example.
154         */
155        public void testBoundsEnforcement()
156            {
157            final AEParams aep1 = new AEParams();
158    
159            // Pick any old source of random-ish numbers for this.
160            final Random rnd = new Random();
161    
162            // Set of East offsets we try as test cases,
163            // including out-of-bounds and edge cases,
164            // and at least one random "joker" value.
165            final int eo[] =
166                {
167                -AEUtils.BASE_2D_EARTH_MAP_WIDTH,
168                -AEUtils.BASE_2D_EARTH_MAP_WIDTH/2 - 1,
169                -AEUtils.BASE_2D_EARTH_MAP_WIDTH/2,
170                -AEUtils.BASE_2D_EARTH_MAP_WIDTH/2 + 1,
171                -1, 0, 1,
172                AEUtils.BASE_2D_EARTH_MAP_WIDTH/2 - 1,
173                AEUtils.BASE_2D_EARTH_MAP_WIDTH/2,
174                AEUtils.BASE_2D_EARTH_MAP_WIDTH/2 + 1,
175                AEUtils.BASE_2D_EARTH_MAP_WIDTH,
176                (rnd.nextInt(4 * AEUtils.BASE_2D_EARTH_MAP_WIDTH) - // Joker...
177                    2 * AEUtils.BASE_2D_EARTH_MAP_WIDTH)
178                };
179    
180            // Set of North offsets we try as test cases,
181            // including out-of-bounds and edge cases,
182            // and at least one random "joker" value.
183            final int no[] =
184                {
185                -AEUtils.BASE_2D_EARTH_MAP_HEIGHT,
186                -AEUtils.BASE_2D_EARTH_MAP_HEIGHT/2 - 1,
187                -AEUtils.BASE_2D_EARTH_MAP_HEIGHT/2,
188                -AEUtils.BASE_2D_EARTH_MAP_HEIGHT/2 + 1,
189                -1, 0, 1,
190                AEUtils.BASE_2D_EARTH_MAP_HEIGHT/2 - 1,
191                AEUtils.BASE_2D_EARTH_MAP_HEIGHT/2,
192                AEUtils.BASE_2D_EARTH_MAP_HEIGHT/2 + 1,
193                AEUtils.BASE_2D_EARTH_MAP_HEIGHT,
194                (rnd.nextInt(4 * AEUtils.BASE_2D_EARTH_MAP_HEIGHT) - // Joker...
195                    2 * AEUtils.BASE_2D_EARTH_MAP_HEIGHT)
196                };
197    
198            // Run from beyond the valid bounds of zoom...
199            for(int zf = AEUtils.MIN_ZOOM - 1; zf <= AEUtils.MAX_ZOOM + 1; ++zf)
200                {
201                // Set the zoom.
202                aep1.setZoomFactor(zf);
203    
204                // Try full range of East Offset values.
205                for(int eoi = eo.length; --eoi >= 0; )
206                    {
207                    // Set the East Offset.
208                    aep1.setEastOffset(eo[eoi]);
209    
210                    // Try full range of North Offset values.
211                    for(int noi = no.length; --noi >= 0; )
212                        {
213                        // Set the North Offset.
214                        aep1.setNorthOffset(no[noi]);
215    
216    // Dump assembled parameters.
217    //System.out.println("Inputs: zf="+zf+", eo="+eo[eoi]+", no=" + no[noi]);
218    //System.out.println("Parameters computed: " + aep1);
219    
220                        // Check that all extractable parameters are in bounds
221                        // where so requested.
222    
223                        // Check that the zoom behaves correctly.
224                        assertTrue("Zoom factor must be in range",
225                                   (aep1.getZoomFactor() >= AEUtils.MIN_ZOOM) &&
226                                   (aep1.getZoomFactor() <= AEUtils.MAX_ZOOM));
227    
228                        // Location must be in range.
229                        final Location.Estd l1 = aep1.getLocation(true);
230                        assertTrue("Location must not be null",
231                                   l1 != null);
232                        assertTrue("Location East degrees must be within bounds at all times",
233                                   (l1.getE().value.doubleValue() >= -Location.Estd.MAX_E) &&
234                                   (l1.getE().value.doubleValue() <=  Location.Estd.MAX_E));
235                        assertTrue("Location North degrees must be within bounds at all times",
236                                   (l1.getN().value.doubleValue() >= -Location.Estd.MAX_N) &&
237                                   (l1.getN().value.doubleValue() <=  Location.Estd.MAX_N));
238    
239                        assertTrue("East offset must be in bounds when so requested",
240                                   (aep1.getEastOffset(true) >= -AEParams.OFFSET_LIMIT_EAST) &&
241                                   (aep1.getEastOffset(true) <=  AEParams.OFFSET_LIMIT_EAST));
242                        assertTrue("East offset must be in bounds at all times",
243                                   (aep1.getEastOffset(false) >= -AEParams.OFFSET_LIMIT_EAST) &&
244                                   (aep1.getEastOffset(false) <=  AEParams.OFFSET_LIMIT_EAST));
245    
246                        assertTrue("North offset must be in bounds when so requested",
247                                   (aep1.getNorthOffset(true) >= -AEParams.OFFSET_LIMIT_NORTH) &&
248                                   (aep1.getNorthOffset(true) <=  AEParams.OFFSET_LIMIT_NORTH));
249                        assertTrue("North offset must be in bounds at all times",
250                                   (aep1.getNorthOffset(false) >= -AEParams.OFFSET_LIMIT_NORTH) &&
251                                   (aep1.getNorthOffset(false) <=  AEParams.OFFSET_LIMIT_NORTH));
252    
253                        assertTrue("Source pixels width must be in bounds when so requested",
254                                   (aep1.getBaseSrcPixelsWidth(true) > 0) &&
255                                   (aep1.getBaseSrcPixelsWidth(true) <= AEUtils.BASE_2D_EARTH_MAP_WIDTH));
256                        assertTrue("Source pixels height must be in bounds when so requested",
257                                   (aep1.getBaseSrcPixelsHeight(true) > 0) &&
258                                   (aep1.getBaseSrcPixelsHeight(true) <= AEUtils.BASE_2D_EARTH_MAP_HEIGHT));
259    
260                        assertTrue("East degrees must be within bounds when so requested",
261                                   (aep1.getEastDegrees(true) >= -Location.Estd.MAX_E) &&
262                                   (aep1.getEastDegrees(true) <=  Location.Estd.MAX_E));
263                        assertTrue("East degrees must be within bounds at all times",
264                                   (aep1.getEastDegrees(false) >= -Location.Estd.MAX_E) &&
265                                   (aep1.getEastDegrees(false) <=  Location.Estd.MAX_E));
266    
267                        assertTrue("North degrees must be within bounds when so requested",
268                                   (aep1.getNorthDegrees(true) >= -Location.Estd.MAX_N) &&
269                                   (aep1.getNorthDegrees(true) <=  Location.Estd.MAX_N));
270                        assertTrue("North degrees must be within bounds at all times",
271                                   (aep1.getNorthDegrees(false) >= -Location.Estd.MAX_N) &&
272                                   (aep1.getNorthDegrees(false) <=  Location.Estd.MAX_N));
273    
274                        // The source-image Rectangle must always be in bounds...
275                        final Rectangle rect = aep1.getSourceRectangleToDisplay();
276                        assertTrue("Source-pixel rectangle must always be in bounds: " + rect + " from: " + aep1,
277                                   (rect.x >= 0) &&
278                                   (rect.y >= 0) &&
279                                   (rect.width <= AEUtils.BASE_2D_EARTH_MAP_WIDTH) &&
280                                   (rect.height <= AEUtils.BASE_2D_EARTH_MAP_HEIGHT) &&
281                                   (rect.x + rect.width <= AEUtils.BASE_2D_EARTH_MAP_WIDTH) &&
282                                   (rect.y + rect.height <= AEUtils.BASE_2D_EARTH_MAP_HEIGHT));
283    
284                        // Check that when tiling offset is always multiple of tile.
285                        assertTrue("Tile width must be positive",
286                                   aep1.getTileSrcPixelsWidth() > 0);
287                        assertTrue("Tile height must be positive",
288                                   aep1.getTileSrcPixelsHeight() > 0);
289                        if(AEParams.FORCE_TILE_POSITIONING)
290                            {
291                            assertTrue("Constrained East Offset must be multiple of tile width when tiling: " + aep1.getEastOffset(true),
292                                (aep1.getEastOffset(true) % aep1.getTileSrcPixelsWidth()) == 0);
293                            assertTrue("Constrained North Offset must be multiple of tile height when tiling: " + aep1.getNorthOffset(true),
294                                (aep1.getNorthOffset(true) % aep1.getTileSrcPixelsHeight()) == 0);
295                            }
296                        }
297                    }
298                }
299            }
300    
301        /**Test lookup-key generation.
302         * Tests that a key is always generated,
303         * and that hashCode and equals work sufficiently well that a HashSet
304         * of the values correctly distinguishes all different pairs of
305         * AEParams values as seen from their zoom and constrained offsets.
306         * <p>
307         * We also check that these unique keys are one-to-one with unique views
308         * as indicated by the source rectangle a view (map fragment)
309         * is geenrated from.
310         * <p>
311         * To avoid running out of memory an patience this does not attempt to
312         * generate *all* possible valid values, but rather a finite subset
313         * randomly, maybe with a few edge cases thrown in.
314         * <p>
315         * TODO: Generalise this to test unconstrained keys too
316         */
317        public void testLookupKey()
318            {
319            final HashMap<Object, AEParams> keys = new HashMap<Object, AEParams>();   // From key to params...
320            final HashMap<String, AEParams> values = new HashMap<String, AEParams>(); // From slow-but-right key to params.
321            final HashMap<Rectangle, AEParams> views = new HashMap<Rectangle, AEParams>();  // From rect-based-key to params.
322    
323            // Any old randomish source will do...
324            final Random rnd = new Random();
325    
326            // Maximum number of random values to try...
327            // A sample set of 100 only seemed to catch it about
328            // one time in 2 as at 2003/09/23.
329            final int maxRndValues = 1000;
330    
331            // We'll create and insert random values.
332            for(int i = maxRndValues; --i >= 0; )
333                {
334                final AEParams aep1 = new AEParams();
335                // Set random zoom.
336                aep1.setZoomFactor(rnd.nextInt(AEUtils.MAX_ZOOM - AEUtils.MIN_ZOOM + 1) + AEUtils.MIN_ZOOM);
337                // Set random offsets.
338                aep1.setEastOffset(rnd.nextInt(AEUtils.BASE_2D_EARTH_MAP_WIDTH) - AEParams.OFFSET_LIMIT_EAST);
339                aep1.setNorthOffset(rnd.nextInt(AEUtils.BASE_2D_EARTH_MAP_HEIGHT) - AEParams.OFFSET_LIMIT_NORTH);
340    
341                // Make a (constrained) key.
342                final Object key = aep1.makeKey(true);
343                assertTrue("Lookup key must never be null",
344                           key != null);
345                final Object oldKey = keys.put(key, aep1);
346    
347                // Now synthesise a key (String) that should distinguish
348                // all correctly-distinguishable pairs.
349                // This is inefficient but should be clearly correct.
350                final String fakeKey =
351                    aep1.getZoomFactor() + "#" +
352                    aep1.getEastOffset(true) + "#" +
353                    aep1.getNorthOffset(true);
354                final Object oldValue = values.put(fakeKey, aep1);
355    
356                if((oldKey == null) != (oldValue == null))
357                    {
358                    System.err.println("Keys not replaced at same time: about to fail assertion");
359                    System.err.println("New AEParam: " + aep1);
360                    System.err.println("Displaced key from: " + oldKey);
361                    System.err.println("Displaced true duplicate from: "+ oldValue);
362                    }
363    
364                assertTrue("keys must be replaced at the same time (failed after "+(maxRndValues-i)+" goes): " + aep1,
365                           (oldKey == null) == (oldValue == null));
366    
367                // Make a key from the rectangle parameters
368                // to ensure that unique keys map to unique views and vice versa.
369                final Rectangle rect = aep1.getSourceRectangleToDisplay();
370                final Object oldRect = views.put(rect, aep1);
371                assertTrue("views must map one-to-one with keys: " + aep1,
372                           (oldKey == null) == (oldRect == null));
373                }
374    
375            assertTrue("count of distinct values should match",
376                       keys.size() == values.size());
377    
378            assertTrue("counts of distinct keys and views should match",
379                       keys.size() == views.size());
380    
381    //System.out.println("[Unique keys out of set of random values: " + keys.size() + "/" + maxRndValues + "]");
382            }
383    
384        /**Test that title lookup works.
385         * Test generation of titles is correct.
386         */
387        public void testTitleGeneration()
388            throws Exception
389            {
390            if(!Main.isAccessToFilesystem())
391                {
392                System.err.println("No access to filesystem: skipping tests: " + getName());
393                return;
394                }
395    
396            final ExhibitPropsGlobalImmutable epgi = ExhibitPropsGlobalImmutable.loadFromDataDir(new File(LocalProps.getDataDir()));
397            final LocationMap lm = epgi.getLocationMap();
398            assertFalse("Loaded LocationMap must not be empty", lm.isEmpty());
399    
400            // Check that no title ("") is generated at the most-zoomed-out view.
401            final AEParams aeps = new AEParams();
402            aeps.setZoomFactor(AEUtils.MIN_ZOOM);
403            final Name topLevelTitle = AEUtils.getViewLocationMapVirtualPrefix(aeps, lm);
404            assertEquals("Title at top level (most zoomed out) must be empty: \"" + topLevelTitle + "\"",
405                         Name.EMPTY, topLevelTitle);
406            }
407    
408    
409        // FIXME: check that bean results are sorted and correct.
410        }