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 }