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 java.io.File;
039 import java.net.InetAddress;
040 import java.net.UnknownHostException;
041 import java.util.ArrayList;
042 import java.util.Arrays;
043 import java.util.BitSet;
044 import java.util.HashMap;
045 import java.util.HashSet;
046 import java.util.Iterator;
047 import java.util.List;
048 import java.util.Properties;
049 import java.util.Random;
050 import java.util.Set;
051 import java.util.SortedSet;
052 import java.util.TreeSet;
053
054 import junit.framework.TestCase;
055
056 import org.hd.d.pg2k.svrCore.AddrTools;
057 import org.hd.d.pg2k.svrCore.ExhibitPropsGlobalImmutable;
058 import org.hd.d.pg2k.svrCore.Name;
059 import org.hd.d.pg2k.svrCore.location.GeoProximity;
060 import org.hd.d.pg2k.svrCore.location.GeoUtils;
061 import org.hd.d.pg2k.svrCore.location.Location;
062 import org.hd.d.pg2k.svrCore.location.LocationMap;
063 import org.hd.d.pg2k.svrCore.props.LocalProps;
064 import org.hd.d.pg2k.webSvr.virtualHosts.AlohaEarth.AEParams;
065 import org.mutabilitydetector.unittesting.AllowedReason;
066 import org.mutabilitydetector.unittesting.MutabilityAssert;
067 import org.mutabilitydetector.unittesting.MutabilityMatchers;
068
069 /**Test of various aspects of Location and GeoUtils, etc.
070 * This includes geographical lookup by client IP address,
071 * though maybe these should be moved to a separate GeoUtilsTests class.
072 * <p>
073 *
074 * TODO: test that exhibits correctly get specific/generic flag
075 * depending on details being with exhibit or looked up in generic map
076 */
077 public final class LocationTest extends TestCase
078 {
079 public LocationTest(final String name)
080 {
081 super(name);
082 }
083
084 /**Basic tests of LocationMap behaviour.
085 * These tests are not dependent on external data.
086 */
087 public static void testLocationMapBasics()
088 {
089 // Default constructor should yield empty map with zero hashcode.
090 final LocationMap elm = new LocationMap();
091 //assertImmutable(LocationMap.class);
092 assertTrue("Default LocationMap must be empty", elm.isEmpty());
093 assertTrue("Default LocationMap must have zero timestamp",
094 elm.hashCode() == 0);
095
096 // TODO: some real tests!
097 }
098
099 /**Tests use of contains() and related operations on Location.Estd.
100 * We use some randomness to stochastically test the entire space
101 * of parameters that should be unimportant (ie the "specific" flag).
102 */
103 public static void testLocationEstdContains()
104 throws Exception
105 {
106 final Properties propsAllZeros = new Properties();
107 propsAllZeros.setProperty("type", "Estd");
108 propsAllZeros.setProperty("E", "0");
109 propsAllZeros.setProperty("EErr", "0");
110 propsAllZeros.setProperty("N", "0");
111 propsAllZeros.setProperty("NErr", "0");
112 final Location.Estd allZeros =
113 new Location.Estd(rnd.nextBoolean(), "", propsAllZeros);
114
115 final Properties propsZeroCentre = new Properties();
116 propsZeroCentre.setProperty("type", "Estd");
117 propsZeroCentre.setProperty("E", "0");
118 propsZeroCentre.setProperty("EErr", "1");
119 propsZeroCentre.setProperty("N", "0");
120 propsZeroCentre.setProperty("NErr", "1");
121 final Location.Estd zeroCentre =
122 new Location.Estd(rnd.nextBoolean(), "", propsZeroCentre);
123
124 final Properties propsAllOnes = new Properties();
125 propsAllOnes.setProperty("type", "Estd");
126 propsAllOnes.setProperty("E", "1");
127 propsAllOnes.setProperty("EErr", "1");
128 propsAllOnes.setProperty("N", "1");
129 propsAllOnes.setProperty("NErr", "1");
130 final Location.Estd allOnes =
131 new Location.Estd(rnd.nextBoolean(), "", propsAllOnes);
132
133
134 // All items should contain their own centre.
135 // All items should be contained by WHOLE_EARTH and not contain it.
136 final Location.Estd allEstdItems[] =
137 {
138 allZeros, zeroCentre, allOnes
139 };
140 for(int i = allEstdItems.length; --i >= 0; )
141 {
142 final Location.Estd l = allEstdItems[i];
143 assertTrue("Location.Estd item must always contain its own centre",
144 l.containsCentre(l));
145 assertTrue("Location.Estd item must always contain its own area",
146 l.containsArea(l));
147 assertFalse("Location.Estd item must not strictly contain its own area",
148 l.strictlyContainsArea(l));
149 assertTrue("All item centres must be contained by WHOLE_EARTH",
150 Location.Estd.WHOLE_EARTH.containsCentre(l));
151 assertTrue("All item areas must be contained by WHOLE_EARTH",
152 Location.Estd.WHOLE_EARTH.containsArea(l));
153 assertFalse("No item area may strictly contain WHOLE_EARTH",
154 l.strictlyContainsArea(Location.Estd.WHOLE_EARTH));
155 assertFalse("No item area may be larger than WHOLE_EARTH",
156 l.isLargerThan(Location.Estd.WHOLE_EARTH));
157 }
158
159 // Test (some) expected pair-wise relationships.
160 assertTrue("0E0N,+/-0 centre should be contained by bigger zero-centred item",
161 zeroCentre.containsCentre(allZeros));
162 assertTrue("0E0N,+/-0 area should be contained by bigger zero-centred item",
163 zeroCentre.containsArea(allZeros));
164 assertTrue("0E0N,+/-0 area should be strictly contained by bigger zero-centred item",
165 zeroCentre.strictlyContainsArea(allZeros));
166 assertTrue("0E0N,+/-0 should contain centre of (bigger) zero-centred item",
167 allZeros.containsCentre(zeroCentre));
168 assertFalse("0E0N,+/-0 should not contain area of (bigger) zero-centred item",
169 allZeros.containsArea(zeroCentre));
170 assertFalse("0E0N,+/-0 should not strictly contain area of (bigger) zero-centred item",
171 allZeros.strictlyContainsArea(zeroCentre));
172 assertFalse("0E0N,+/-0 should not contain centre of non-zero-centred item",
173 allZeros.containsCentre(allOnes));
174 assertFalse("0E0N,+/-0 should not contain area of non-zero-centred item",
175 allZeros.containsArea(allOnes));
176 assertTrue("1E1N,+/-1 should contain centre of (big) zero-centred item",
177 allOnes.containsCentre(zeroCentre));
178 assertFalse("1E1N,+/-1 should not contain area of (big) zero-centred item",
179 allOnes.containsArea(zeroCentre));
180 assertTrue("1E1N,+/-1 should contain centre of (point) zero-centred item",
181 allOnes.containsCentre(allZeros));
182 assertTrue("1E1N,+/-1 should contain area of (point) zero-centred item",
183 allOnes.containsArea(allZeros));
184 assertTrue("1E1N,+/-1 should strictly contain area of (point) zero-centred item",
185 allOnes.strictlyContainsArea(allZeros));
186 }
187
188
189
190
191 /**Test that we can look up exhibits by name in the sample auxInfo file.
192 * FIXME: Also needs stand-alone tests that do not depend on the
193 * vast config file.
194 * <p>
195 * We skip these tests if we do not have full/normal access
196 * to the filesystem.
197 */
198 public void testLocationLookup()
199 throws Exception
200 {
201 if(!Main.isAccessToFilesystem())
202 {
203 System.err.println("No access to filesystem: skipping tests: " + getName());
204 return;
205 }
206
207 final ExhibitPropsGlobalImmutable epgi = ExhibitPropsGlobalImmutable.loadFromDataDir(new File(LocalProps.getDataDir()));
208 final LocationMap lm = epgi.getLocationMap();
209 assertFalse("Loaded LocationMap must not be empty", lm.isEmpty());
210
211
212 // Looking up this data item...
213 // location.places-and-sights/New-Zealand-.type=Estd
214 // location.places-and-sights/New-Zealand-.E=172.5
215 // location.places-and-sights/New-Zealand-.EErr=6.5
216 // location.places-and-sights/New-Zealand-.N=-41
217 // location.places-and-sights/New-Zealand-.NErr=6.5
218 // List of exhibits all of which we ought to be able to
219 // get general (non-specific) location info for,
220 // either forwards or reverse.
221 // (We assume no attribute words for this test.)
222 final String[] testExhibits =
223 {
224 "places-and-sights/New-Zealand-random-DHD.gif",
225 "places-and-sights/_more2003/_more04/New-Zealand-South-Island-west-coast-Milford-Sound-fjord-taken-20001213-waterfall-tumbling-down-steep-vegetated-bank-rotated-MB.jpg",
226 "places-and-sights/Earth-DHD.gif",
227 // "places-and-sights/Earth-whole-planet-DHD.gif", // Is specific!
228 "places-and-sights/England-London-made-up-name-Z.wav",
229 "natural-science/_more2005/_more07/bee-bumblebee-bumble-bee-pollinating-pollination-of-lavender-purple-flowers-from-bush-flowering-in-July-in-London-England-8-DHD.jpg",
230 "natural-science/_more2005/_more07/bee-bumblebee-bumble-bee-pollinating-pollination-of-lavender-purple-flowers-from-bush-flowering-in-July-in-London-England-closeup-9-DHD.jpg",
231 };
232 // Create enough attribute words to cover our examples.
233 final Set<String> attrWords = new HashSet<String>(Arrays.asList(new String[]{
234 "closeup", "rotated"
235 }));
236 for(int i = testExhibits.length; --i >= 0; )
237 {
238 final Name.ExhibitFull name = Name.ExhibitFull.create(testExhibits[i]);
239 final Location.Base te1 = lm.locLookup(name, attrWords);
240 assertNotNull("Result of LocationMap lookup (te1) must not be null: " + name,
241 te1);
242 assertFalse("Result of te1 lookup should not be specific for " + name + ": " + te1,
243 te1.specific);
244 assertTrue("Result of te1 lookup should be Estd for " + name + ", got: " + te1 + " class "+ te1.getClass(),
245 te1 instanceof Location.Estd);
246 final Location.Estd te1E = (Location.Estd) te1;
247 assertTrue("Result of (te1) lookup should be on full Aloha Earth map",
248 (new AEParams()).getLocation(true).containsCentre(te1E));
249 assertFalse("No looked-up item should be larger than WHOLE_EARTH",
250 te1E.isLargerThan(Location.Estd.WHOLE_EARTH));
251 }
252 }
253
254
255
256 /**Test class immutability/safety. */
257 public static void testCCTLDImmutability()
258 {
259 //assertImmutable(GeoUtils.CCTLD.class);
260 MutabilityAssert.assertInstancesOf(GeoUtils.CCTLD.class, MutabilityMatchers.areImmutable(), AllowedReason.provided(String.class).isAlsoImmutable());
261 }
262
263 /**Test that we can use the geographical-proximity-by-ccTLD data.
264 * This tests that we can find close-by countries (in connectivity terms).
265 */
266 public static void testGeoProximity()
267 {
268 // Look for "gb"/"uk" to be in the same group.
269 // This also establishes that we can load at least the default set.
270 final GeoUtils.CCTLD uk = new GeoUtils.CCTLD("uk");
271 final GeoUtils.CCTLD us = new GeoUtils.CCTLD("us");
272 final List<GeoUtils.CCTLD> group = Arrays.asList(new GeoUtils.CCTLD[]{
273 uk, new GeoUtils.CCTLD("gb")});
274 final Set<GeoUtils.CCTLD> groupS = new HashSet<GeoUtils.CCTLD>(group);
275
276 for(final Iterator<GeoUtils.CCTLD> it = groupS.iterator(); it.hasNext(); )
277 {
278 final GeoUtils.CCTLD cc = it.next();
279 assertTrue("Must be able to find all of our group "+group+" when we look up " + cc,
280 GeoUtils.getCloseCCTLDs(cc).containsAll(groupS));
281 }
282
283 // Check that "uk" is in the "RIPE" region.
284 assertTrue("Expect to find uk in the RIPE region",
285 GeoUtils.getCountriesInRegion("RIPE").contains(uk));
286
287 // Check a few expected proximities.
288 assertEquals("The same country should have COUNTRY proximity to itself",
289 GeoProximity.COUNTRY, GeoUtils.computeProximity("uk", uk));
290 assertEquals("The same country should have COUNTRY proximity to itself",
291 GeoProximity.COUNTRY, GeoUtils.computeProximity("us", us));
292 assertEquals("Well-connected countries should have COUNTRY proximity",
293 GeoProximity.COUNTRYGROUP, GeoUtils.computeProximity("uk", new GeoUtils.CCTLD("fr")));
294 assertEquals("Well-connected countries should have COUNTRY proximity",
295 GeoProximity.COUNTRYGROUP, GeoUtils.computeProximity("nl", new GeoUtils.CCTLD("de")));
296 assertEquals("Well-connected countries should have COUNTRY proximity",
297 GeoProximity.COUNTRYGROUP, GeoUtils.computeProximity("ca", us));
298 assertEquals("A region/registry should have CONTINENT proximity to a country in it",
299 GeoProximity.CONTINENT, GeoUtils.computeProximity("RIPE", uk));
300 assertEquals("A region/registry should have CONTINENT proximity to a country in it",
301 GeoProximity.CONTINENT, GeoUtils.computeProximity("ARIN", us));
302 assertEquals("A region/registry should have NONE proximity to a country not in it",
303 GeoProximity.NONE, GeoUtils.computeProximity("ARIN", uk));
304 assertEquals("A region/registry should have NONE proximity to a country not in it",
305 GeoProximity.NONE, GeoUtils.computeProximity("RIPE", us));
306 assertEquals("A region/registry should have NONE proximity to a (non-existent) country not in it",
307 GeoProximity.NONE, GeoUtils.computeProximity("RIPE", new GeoUtils.CCTLD("xx")));
308 assertEquals("A (non-existent) region/registry should have NONE proximity to a country not in it",
309 GeoProximity.NONE, GeoUtils.computeProximity("NONESUCH", us));
310 assertEquals("A (non-existent) region/registry should have NONE proximity to a (non-existent) country not in it",
311 GeoProximity.NONE, GeoUtils.computeProximity("NONESUCH", new GeoUtils.CCTLD("xx")));
312 }
313
314 /**Check lookup of geographic location of IP address.
315 * This checks that basic lookup of location by IP address
316 * (ie in which country is the machine/interface with that address)
317 * works for some simple basic cases.
318 * <p>
319 * TODO: whitebox check that mappings in source data are actually visible.
320 */
321 public static void testRegionLookup()
322 throws Exception
323 {
324 final long t0 = System.currentTimeMillis();
325
326 // Do quick region/ccTLD lookups for a couple of addresses whose location is known
327 // and should be pretty stable...
328 final String reg1 = GeoUtils.getRegionByAddress(InetAddress.getByName("195.137.27.135"), true);
329 assertTrue("Must be able to look up known-UK address quickly",
330 ("RIPE".equals(reg1) || ("uk".equals(reg1))));
331 final String reg2 = GeoUtils.getRegionByAddress(InetAddress.getByName("72.9.239.218"), true);
332 assertTrue("Must be able to look up known-US address quickly",
333 ("ARIN".equals(reg2) || ("us".equals(reg2))));
334 // final String reg3 = GeoUtils.getRegionByAddress(InetAddress.getByName("193.178.223.10"), true);
335 // assertTrue("Must be able to look up known-UK address quickly",
336 // ("RIPE".equals(reg3) || ("uk".equals(reg3))));
337
338 final long t1 = System.currentTimeMillis();
339 System.out.println("[Initial lookups: "+(t1-t0)+"ms.]"); // Can help with performance tuning.
340
341
342 // Do region/ccTLD lookups for the
343 // loop-back and other "private" addresses
344 // which should never have a geographical association.
345
346 // Handle the loop-back on its own for now.
347 final InetAddress loopback = InetAddress.getByName("127.0.0.1");
348
349 // Try to look up the region.
350 // This should simply return some numeric prefix of the address
351 // since the whole of the 127/8 block is non-geographic private space.
352 // We try the quick flag randomly either way as it should not matter.
353 final boolean quickLoopbackLookup = rnd.nextBoolean();
354 final String region = GeoUtils.getRegionByAddress(loopback, quickLoopbackLookup);
355 assertTrue("Region lookup should be 127 or start 127.",
356 "127".equals(region) || region.startsWith("127."));
357
358 // Try to look up the ccTLD; the result should be null.
359 // We try the quick flag randomly either way as it should not matter.
360 final GeoUtils.CCTLD ccTLD = GeoUtils.getCCTLDByAddress(loopback, !quickLoopbackLookup);
361 assertNull("We should not find a country code associated with the loopback address",
362 ccTLD);
363
364 // Handle generic private/non-routable/non-geographic addresses.
365 // These should in principle include all the RFC1597 addresses,
366 // though in practice we may not be precise enough to handle
367 // all of them correctly...
368 final String nonGeoAddrs[] =
369 { "127.0.0.1", "10.0.0.0", "172.16.0.0", "192.168.0.0" };
370 final int indexOfFullLookup = rnd.nextInt(nonGeoAddrs.length);
371 for(int i = nonGeoAddrs.length; --i >= 0; )
372 {
373 final InetAddress addr = InetAddress.getByName(nonGeoAddrs[i]);
374 assertNull("ccTLD (quick) lookup for private address ("+addr+") should be null",
375 GeoUtils.getCCTLDByAddress(addr, true));
376 // Do one of these at random as a full lookup to make sure it does not matter.
377 final String regionByAddress = GeoUtils.getRegionByAddress(addr,
378 (i != indexOfFullLookup));
379 assertTrue("Region lookup for private address ("+addr+") should not be ccTLD nor registry/region name: " + regionByAddress,
380 !GeoUtils.CCTLD.isSyntaticallyValidCcTLD(regionByAddress) &&
381 !GeoUtils.isSyntaticallyValidRegistryName(regionByAddress));
382 }
383
384 // Check that at least one single-octet IPv4 prefix
385 // returns a non-numeric region and/or a non-null ccTLD
386 // even when doing a "quick" lookup with internal data only
387 // (or at least not going to the Net for further data).
388 boolean foundCcTLD = false;
389 boolean foundRegion = false;
390 for(int i = 256; --i >= 0; )
391 {
392 final String dottedQuad = String.valueOf(i) + ".0.0.1";
393 final InetAddress ia = InetAddress.getByName(dottedQuad);
394
395 // We do a quick lookup of ccTLD, ie by internal data only.
396 final GeoUtils.CCTLD cc = GeoUtils.getCCTLDByAddress(ia, true);
397 if(null != cc)
398 { foundCcTLD = true; }
399
400 // We do a quick lookup of region, ie by internal data only.
401 final String reg = GeoUtils.getRegionByAddress(ia, true);
402 assertNotNull("region must not be null", reg);
403 assertTrue("region must not be empty String", reg.length() > 0);
404 for(int r = reg.length(); --r >= 0; )
405 {
406 final char rc = reg.charAt(r);
407 assertTrue("all region characters must be printable ASCII",
408 (rc > 32) && (rc < 127));
409 }
410 final char regFirstChar = reg.charAt(0);
411 assertTrue("region must start with a digit or letter",
412 Character.isDigit(regFirstChar) ||
413 Character.isLetter(regFirstChar));
414 if(!Character.isDigit(regFirstChar))
415 { foundRegion = true; }
416 //Main.getOut().println("[cc/reg="+cc+"/"+reg+"]");
417 }
418 assertTrue("Must find at least one non-null ccTLD by IP address", foundCcTLD);
419 assertTrue("Must find at least one non-numeric region by IP address", foundRegion);
420
421 // // Try doing sample lookups on the "local" address...
422 // // This is no more of a test than to ensure we don't blow up!
423 // Main.getOut().println("Quick lookup on local address gives: "
424 // + GeoUtils.getRegionByAddress(InetAddress.getLocalHost(), true));
425 // Main.getOut().println("Full lookup on local address gives: "
426 // + GeoUtils.getRegionByAddress(InetAddress.getLocalHost(), false));
427 }
428
429 /**Check DNS lookup.
430 * If DNS lookup is working at all,
431 * check that forward and reverse lookup is working as expected.
432 */
433 public static void testDNSLookup()
434 {
435 // Set of names to look up addresses for,
436 // then lookup in reverse to get back the names for.
437 // We pick addresses that are pretty likely to work correctly for this case!
438 final String testNames[] = {
439 //"www.ibm.com.", // Failed 20111115.
440 "mirror-uk-rb1.gallery.hd.org.",
441 //"www.parliament.uk.", // Failed 20090705!
442 };
443 try
444 {
445 for(final String n : testNames)
446 {
447 // final InetAddress addrs[] = InetAddress.getAllByName(n);
448 final InetAddress addr = InetAddress.getByName(n);
449 final String rlName = AddrTools.doReverseLookup(addr, false);
450 if(rlName == null)
451 {
452 Main.getOut().println("Reverse lookup currently not possible...");
453 continue;
454 }
455 assertEquals("Reverse lookup should return orginal name", n, rlName);
456 final String rlName2 = AddrTools.doReverseLookup(addr, true);
457 if(rlName2 == null)
458 {
459 Main.getOut().println("Reverse lookup currently not possible (2)...");
460 continue;
461 }
462 assertEquals("Verifying reverse lookup should return orginal name", n, rlName2);
463 }
464 }
465 catch(final UnknownHostException e)
466 {
467 e.printStackTrace(Main.getErr());
468 Main.getErr().println("WARNING: lookup failed: aborting all DNS lookup tests since DNS may be unavailable...");
469 }
470 }
471
472 /**Test AddrPrefix class briefly.
473 */
474 public static void testAddrPrefix()
475 {
476 assertEquals("Should create correct padded dotted form", "001", new AddrTools.AddrPrefix(new byte[]{1}).toPaddedDottedPrefix());
477 assertEquals("Should create correct padded dotted form", "213.092", new AddrTools.AddrPrefix(new byte[]{(byte)213, 92}).toPaddedDottedPrefix());
478 assertEquals("Should create correct padded dotted form", "004.000.255", new AddrTools.AddrPrefix(new byte[]{4, 0, (byte)255}).toPaddedDottedPrefix());
479 }
480
481 /**Test some simple cases for AddrPrefix dotted-quad parsing. */
482 public static void testAddrPrefixParsing()
483 {
484 assertTrue("Should be able to parse prefix correctly", Arrays.equals((new AddrTools.AddrPrefix("127")).toByteArray(), new byte[]{127}));
485 assertTrue("Should be able to parse prefix correctly", Arrays.equals((new AddrTools.AddrPrefix("127.000")).toByteArray(), new byte[]{127,0}));
486 assertTrue("Should be able to parse prefix correctly", Arrays.equals((new AddrTools.AddrPrefix("127.00")).toByteArray(), new byte[]{127,0}));
487 assertTrue("Should be able to parse prefix correctly", Arrays.equals((new AddrTools.AddrPrefix("127.0")).toByteArray(), new byte[]{127,0}));
488 assertTrue("Should be able to parse prefix correctly", Arrays.equals((new AddrTools.AddrPrefix("127.0.0")).toByteArray(), new byte[]{127,0,0}));
489 assertTrue("Should be able to parse prefix correctly", Arrays.equals((new AddrTools.AddrPrefix("127.0.0.1")).toByteArray(), new byte[]{127,0,0,1}));
490
491 // Test a few random prefixes/addresses of various length,
492 // with sub-3-digit octets randomly padded or not.
493 final StringBuilder sb = new StringBuilder();
494 final StringBuilder o = new StringBuilder();
495 for(int i = 20000; --i >= 0; )
496 {
497 final int length = 1 + rnd.nextInt(4);
498 final byte[] data = new byte[length];
499 rnd.nextBytes(data); // Fill in random bytes.
500
501 sb.setLength(0);
502 for(int j = 0; j < length; ++j)
503 {
504 o.setLength(0);
505 o.append(data[j] & 0xff);
506 while((o.length() < 3) && rnd.nextBoolean())
507 { o.insert(0, '0'); }
508 if(sb.length() != 0) { sb.append('.'); }
509 sb.append(o);
510 }
511
512 final String s = sb.toString();
513 assertTrue("Should be able to parse prefix correctly: "+s, Arrays.equals((new AddrTools.AddrPrefix(s)).toByteArray(), data));
514 }
515 }
516
517 /**Check AddrPrefix.hashCode() effectiveness, as this may be important for performance.
518 * We test this against any real data we have to hand
519 * plus a few choice manually-selected values.
520 * <p>
521 * The aim is to ensure that there are not excessive collisions
522 * for real data values, as that would make HashSet and related containers
523 * behave badly.
524 */
525 public static void testAddrPrefixHashCode()
526 {
527 // Maximum collisions that we will permit on any one hash value.
528 final int MAX_COLLISIONS = 3;
529
530 // Get addresses from built-in IP-->ccTLD map first.
531 final SortedSet<AddrTools.AddrPrefix> testPrefixes =
532 new TreeSet<AddrTools.AddrPrefix>(GeoUtils.getCCTLDFromIPPrefix().keySet());
533 // Add a few more potentially-important values by hand.
534 testPrefixes.add(new AddrTools.AddrPrefix("127.0.0.1"));
535 testPrefixes.add(new AddrTools.AddrPrefix("127.0.0"));
536 testPrefixes.add(new AddrTools.AddrPrefix("127.0"));
537 testPrefixes.add(new AddrTools.AddrPrefix("127"));
538 testPrefixes.add(new AddrTools.AddrPrefix("0.0.0.0"));
539 testPrefixes.add(new AddrTools.AddrPrefix("0.0.0"));
540 testPrefixes.add(new AddrTools.AddrPrefix("0.0"));
541 testPrefixes.add(new AddrTools.AddrPrefix("0"));
542 testPrefixes.add(new AddrTools.AddrPrefix("10.0.0.0"));
543 testPrefixes.add(new AddrTools.AddrPrefix("10.0.0"));
544 testPrefixes.add(new AddrTools.AddrPrefix("10.0"));
545 testPrefixes.add(new AddrTools.AddrPrefix("10"));
546 testPrefixes.add(new AddrTools.AddrPrefix("192.168"));
547 testPrefixes.add(new AddrTools.AddrPrefix("192.168.0"));
548 testPrefixes.add(new AddrTools.AddrPrefix("192.168.1"));
549 testPrefixes.add(new AddrTools.AddrPrefix("192.168.100"));
550 testPrefixes.add(new AddrTools.AddrPrefix("192.168.101"));
551 testPrefixes.add(new AddrTools.AddrPrefix("172.16"));
552 final int tps = testPrefixes.size();
553 Main.getOut().println("[AddrPrefix sample size: " + tps + ".]");
554
555 // All hash values seen so far, and what they are for.
556 // Test hash quality across full Integer range.
557 final HashMap<Integer,List<AddrTools.AddrPrefix>> hashes = new HashMap<Integer, List<AddrTools.AddrPrefix>>(2*tps);
558 int collisionCount = 0;
559 // Test hash quality across minimum-possible range (ie the set size).
560 final BitSet constrainedHashes = new BitSet(tps);
561 int collisionCountConstrained = 0;
562 for(final AddrTools.AddrPrefix ap : testPrefixes)
563 {
564 final Integer h = new Integer(ap.hashCode());
565
566 List<AddrTools.AddrPrefix> collisions = hashes.get(h);
567 if(collisions == null)
568 {
569 collisions = new ArrayList<AddrTools.AddrPrefix>(1);
570 hashes.put(h, collisions);
571 }
572 else
573 {
574 ++collisionCount;
575 Main.getOut().println("Hash collision between "+ap+" and "+collisions);
576 if(collisions.size() >= MAX_COLLISIONS-1)
577 { fail("TOO MANY COLLISIONS"); }
578 }
579 collisions.add(ap);
580
581 // Test very harsh constrained conditions.
582 final int hc = Math.abs(ap.hashCode() % tps);
583 if(constrainedHashes.get(hc)) { ++collisionCountConstrained; }
584 else { constrainedHashes.set(hc); }
585 }
586
587 Main.getOut().println("[Hash collisions: " + collisionCount + " = " + ((100.0f * collisionCount) / tps) + "%.]");
588 Main.getOut().println("[Maximally constrained hash collisions: " + collisionCountConstrained + " = " + ((100.0f * collisionCountConstrained) / tps) + "%.]");
589 }
590
591
592 /**Private source of OK pseudo-random numbers. */
593 private static final Random rnd = new Random();
594 }