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.io.ByteArrayInputStream;
039 import java.io.ByteArrayOutputStream;
040 import java.io.DataInputStream;
041 import java.io.EOFException;
042 import java.io.IOException;
043 import java.io.InputStream;
044 import java.util.ArrayList;
045 import java.util.Arrays;
046 import java.util.HashMap;
047 import java.util.Map;
048 import java.util.Random;
049 import java.util.SortedMap;
050 import java.util.zip.ZipEntry;
051 import java.util.zip.ZipInputStream;
052 import java.util.zip.ZipOutputStream;
053
054 import junit.framework.TestCase;
055
056 import org.hd.d.pg2k.svrCore.AllExhibitProperties;
057 import org.hd.d.pg2k.svrCore.CoreConsts;
058 import org.hd.d.pg2k.svrCore.ExhibitAttrUtils;
059 import org.hd.d.pg2k.svrCore.FileTools;
060 import org.hd.d.pg2k.svrCore.FileTools.ZE;
061 import org.hd.d.pg2k.svrCore.GenUtils;
062 import org.hd.d.pg2k.svrCore.HostUtils;
063 import org.hd.d.pg2k.svrCore.ROByteArray;
064 import org.hd.d.pg2k.svrCore.Tuple.Pair;
065 import org.hd.d.pg2k.svrCore.datasource.ExhibitDataFileSource;
066 import org.hd.d.pg2k.svrCore.location.GeoUtils.CCTLD;
067 import org.hd.d.pg2k.svrCore.props.GenProps;
068 import org.hd.d.pg2k.svrCore.props.LocalProps;
069 import org.hd.d.pg2k.webSvr.ads.AdUtils;
070 import org.hd.d.pg2k.webSvr.catalogue.TrailData;
071
072 /**Simple tests of access/handling of local and other configuration data.
073 */
074 public final class ConfigFilesTest extends TestCase
075 {
076 public ConfigFilesTest(final String name)
077 {
078 super(name);
079 }
080
081 /**Test that we can find some of the principal files.
082 * We may also check that we can parse/use them.
083 * <p>
084 * We skip these tests if we do not have full/normal access
085 * to the filesystem.
086 */
087 public void testConfigFileAccess()
088 {
089 if(!Main.isAccessToFilesystem())
090 {
091 System.err.println("No access to filesystem: skipping tests: " + getName());
092 return;
093 }
094
095 // Check that local properties are loaded.
096 assertTrue("Must have access to LocalProps",
097 LocalProps.getTimestamp() > 0);
098
099 // Ensure that we can find key global properties
100 // such as the set of exhibit attribute/discardable words....
101 assertTrue("Must be able to load exhibit attribute words from global properties",
102 ExhibitAttrUtils.getAttrWords().getAttrWords().length > 0);
103 // // ...and the built-in location database.
104 // assertTrue("Must be able to load the built-in location DB",
105 // !LocationMap.getBuiltInLocationMap().isEmpty());
106
107 // Check that we've correctly loaded the LocalProps
108 // by validating a specific value or two.
109 assertEquals("Could not retrieve correct server-slowdown factor from LocalProps",
110 2, LocalProps.getServerSlowdownFactor());
111 assertTrue("Could not retrieve correct low-CPU threshold factor from LocalProps",
112 Math.abs(0.5f - LocalProps.getLightLoadMax()) < 0.0001f);
113 assertTrue("Could not retrieve correct high-CPU threshold factor from LocalProps",
114 Math.abs(3.0f - LocalProps.getHeavyLoadMin()) < 0.0001f);
115 }
116
117 /**Test access to hostname matching in GenProps.
118 * This checks for some data that we know to be there.
119 */
120 public void testGPHostMatching()
121 throws Exception
122 {
123 if(!Main.isAccessToFilesystem())
124 {
125 System.err.println("No access to filesystem: skipping tests: " + getName());
126 return;
127 }
128
129 // Check that we can load test data and that it is non-empty.
130 final ExhibitDataFileSource efds = new ExhibitDataFileSource();
131 final GenProps gp = efds.getGenProps(-1);
132 assertNotNull("Must not retrieve null GenProps", gp);
133 assertTrue("Must not retrieve empty GenProps",
134 gp.timestamp != 0);
135
136 // Check that we have hotlink-allow host lists working...
137 assertNotNull("We expect the allow-hotlink hosts list to be non-null", gp.getHotLinkAllowHosts());
138 assertFalse("We expect the allow-hotlink hosts list to be non-empty", gp.getHotLinkAllowHosts().isEmpty());
139 assertEquals("We expect the allow-hotlink hosts list to have right length after conflating normalised entries", 3, gp.getHotLinkAllowHosts().size());
140 assertNull("We expect the allow-hotlink regex pattern to be null", gp.getHotLinkAllowHostsRegex());
141
142 // Check that we have hotlink-disallow host lists working...
143 assertNotNull("We expect the disallow-hotlink hosts list to be non-null", gp.getHotLinkDisallowHosts());
144 assertFalse("We expect the disallow-hotlink hosts list to be non-empty", gp.getHotLinkDisallowHosts().isEmpty());
145 assertEquals("We expect the disallow-hotlink hosts list to have right length after conflating normalised entries", 4, gp.getHotLinkDisallowHosts().size());
146 assertNotNull("We expect the disallow-hotlink regex pattern to be non-null", gp.getHotLinkDisallowHostsRegex());
147
148 // Check that one paticular set of cases is covered by the regex.
149 assertTrue("Must be able to match simple disallow case", gp.getHotLinkDisallowHostsRegex().matcher("profile.myspace.com").matches());
150 assertTrue("Must be able to match arbitrary disallow case", gp.getHotLinkDisallowHostsRegex().matcher("new.subDOMAIN-MixEd-caSE9.myspace.COM").matches());
151
152
153 //TODO
154
155 }
156
157 /**Test access to author database in GenProps.
158 * This checks for some data that we know to be there.
159 */
160 public void testAuthDB()
161 throws Exception
162 {
163 if(!Main.isAccessToFilesystem())
164 {
165 System.err.println("No access to filesystem: skipping tests: " + getName());
166 return;
167 }
168
169 // Check that we have a standard regex match for disallow hosts working.
170
171 // Check that we can load test data and that it is non-empty.
172 final ExhibitDataFileSource efds = new ExhibitDataFileSource();
173 final GenProps gp = efds.getGenProps(-1);
174 assertNotNull("Must not retrieve null GenProps", gp);
175 assertTrue("Must not retrieve empty GenProps",
176 gp.timestamp != 0);
177
178 final GenProps.AuthData adDHD = gp.getAuthData("DHD");
179 assertNotNull("Cannot retrieve author data for DHD", adDHD);
180 assertEquals("Author initials wrong for DHD",
181 "DHD", adDHD.auth);
182 assertEquals("Author name is wrong for DHD",
183 "Damon Hart-Davis", adDHD.name);
184 assertEquals("Author home-page URL is wrong for DHD",
185 "http://d.hd.org/", adDHD.www);
186 assertEquals("Author email is wrong for DHD",
187 "d@hd.org", adDHD.email);
188 assertEquals("Author description is wrong for DHD",
189 "Gallery maintainer.", adDHD.desc);
190
191 // Check that this seems to survive (de)serialisation...
192 final GenProps sgp = (GenProps)
193 SerializationTest.checkObjectCanBeSerialisedAndDeserialised(gp);
194 final GenProps.AuthData adDHD2 = sgp.getAuthData("DHD");
195 assertNotNull("Cannot retrieve author data for DHD after (de)serialisation", adDHD2);
196 assertEquals("Author initials wrong for DHD after (de)serialisation",
197 "DHD", adDHD2.auth);
198 assertEquals("Author name is wrong for DHD after (de)serialisation",
199 "Damon Hart-Davis", adDHD2.name);
200 assertEquals("Author home-page URL is wrong for DHD after (de)serialisation",
201 "http://d.hd.org/", adDHD2.www);
202 assertEquals("Author email is wrong for DHD after (de)serialisation",
203 "d@hd.org", adDHD2.email);
204 assertEquals("Author description is wrong for DHD after (de)serialisation",
205 "Gallery maintainer.", adDHD2.desc);
206
207 assertTrue("Must contain expected hotlink-allowed host",
208 gp.getHotLinkAllowHosts().contains(HostUtils.normaliseVirtualHostName("www.hd.org")));
209 assertTrue("Must contain expected hotlink-disallowed host",
210 gp.getHotLinkDisallowHosts().contains(HostUtils.normaliseVirtualHostName("bad.host1")));
211
212 // Check that equality works too.
213 assertEquals("AuthData should compare equals after (de)serialisation",
214 adDHD, adDHD2);
215 }
216
217 /**Test access to generic key/value pairs in GenProps.
218 * This checks for some data that we know to be there.
219 */
220 public void testGen()
221 throws Exception
222 {
223 if(!Main.isAccessToFilesystem())
224 {
225 System.err.println("No access to filesystem: skipping tests: " + getName());
226 return;
227 }
228
229 // Check that we can load test data and that it is non-empty.
230 final ExhibitDataFileSource efds = new ExhibitDataFileSource();
231 final GenProps gp = efds.getGenProps(-1);
232 assertNotNull("Must not retrieve null GenProps", gp);
233 assertTrue("Must not retrieve empty GenProps",
234 gp.timestamp != 0);
235
236 // Check that generic map is not null.
237 final Map<String,String> gen = gp.getGen();
238 assertNotNull("Gen map must not be null", gen);
239
240 if(gen.isEmpty())
241 {
242 System.err.println("gen = " + (new ArrayList<Map.Entry<String,String>>(gen.entrySet())));
243 fail("Gen map must not be empty (should contain test value(s))");
244 }
245
246 // Check that the generic map is not (trivially) mutable.
247 try
248 {
249 gen.put("a", "b");
250 fail("Gen map is apparently mutable");
251 }
252 catch(final UnsupportedOperationException e) { /* Good; rejected change. */ }
253
254 // Get generic test value...
255 final String testKey = "testGen.key";
256 final String testValue = "testGenValue";
257 assertEquals("Must be able to retrieve expected test value", testValue, gen.get(testKey));
258
259 // Look for ECPM value...
260 assertEquals("Must be able to look up ECPM value", 10, AdUtils.computeECPM(gp, "kPMPILV=MRTF"));
261
262 // Check for "sensitive" names.
263 assertTrue("Must be able to detect sensitive names correctly [1]", GenUtils.isSensitive("a/an-obituary-ANON.tif", gp));
264 assertTrue("Must be able to detect sensitive names correctly [2]", GenUtils.isSensitive("a/some-death-notice-mono-ANON.gif", gp));
265 assertFalse("Must be able to detect sensitive names correctly [3]", GenUtils.isSensitive("a/my-cat-ANON.gif", gp));
266
267 // Check that this seems to survive (de)serialisation...
268 final GenProps sgp = (GenProps)
269 SerializationTest.checkObjectCanBeSerialisedAndDeserialised(gp);
270 final Map<String,String> gen2 = sgp.getGen();
271 assertEquals("Gen maps must be equal after (de)serialisation", gen, gen2);
272 assertEquals("Must be able to retrieve expected test value after (de)serialisation", testValue, gen2.get(testKey));
273 assertEquals("Must be able to look up ECPM value after (de)serialisation", 10, AdUtils.computeECPM(sgp, "kPMPILV=MRTF"));
274 }
275
276 /**Ensure that we can load the exhibits and meta data from the filesystem.
277 * This uses the ExhibitDataFileSource.
278 * <p>
279 * We skip these tests if we do not have full/normal access
280 * to the filesystem.
281 */
282 public void testLoadOfExhibitData()
283 throws Exception
284 {
285 if(!Main.isAccessToFilesystem())
286 {
287 System.err.println("No access to filesystem: skipping tests: " + getName());
288 return;
289 }
290
291 // Check that we can load test data and that it is non-empty.
292 final ExhibitDataFileSource efds = new ExhibitDataFileSource();
293 final GenProps gp = efds.getGenProps(-1);
294 assertNotNull("Must not retrieve null GenProps", gp);
295 assertTrue("Must not retrieve empty GenProps",
296 gp.timestamp != 0);
297 final AllExhibitProperties aep =
298 efds.getAllExhibitProperties(-1);
299 assertNotNull("Must not retrieve null AllExhibitProperties", aep);
300 assertTrue("Must not retrieve empty AllExhibitProperties",
301 aep.aeid.size() > 0);
302 assertTrue("Must retrieve non-empty global-immutable properties",
303 aep.epgi.longHash != 0);
304 // assertTrue("Must retrieve non-empty LocationMap",
305 // aep.lm.hashCode() != 0);
306 }
307
308 /**Test classified ad filtering. */
309 public static void testClassifiedAdFiltering()
310 {
311 // Test country filtering...
312 final GenProps.ClassifiedAd cladNoCountries = new GenProps.ClassifiedAd(
313 "hello", ".", 0, 0, null, null);
314 assertFalse("With no countries specified, anything (even null) must be acceptable",
315 cladNoCountries.wrongCountry(null));
316 assertFalse("With no countries specified, any valid ccTLD must be acceptable",
317 cladNoCountries.wrongCountry(new CCTLD("uk")));
318 final GenProps.ClassifiedAd cladUK = new GenProps.ClassifiedAd(
319 "hello", ".", 0, 0, null, "uk");
320 assertTrue("With one-or-more countries specified, null is not acceptable",
321 cladUK.wrongCountry(null));
322 assertFalse("With one-or-more countries specified, a matching ccTLD must be acceptable",
323 cladUK.wrongCountry(new CCTLD("uk")));
324 }
325
326 /**Test trail data parsing. */
327 public static void testTrailDataParsing()
328 throws Exception
329 {
330 // Unusable trail data texts that should be rejected.
331 final String badData[] =
332 {
333 "" /* Bad empty file. */ ,
334 "blah" /* Missing title. */ ,
335 "@ \n" /* Missing title. */ ,
336 "@ Title" /* Missing body. */ ,
337 "@ Title\n" /* Missing body. */ ,
338 "@ Title\n\n" /* Missing body. */ ,
339 };
340 // Check that seriously bad trail texts are rejected outright.
341 for(final String b : badData)
342 {
343 try
344 {
345 _parseTrailTestAsString(b);
346 fail("TrailData.parseFromByteStream() failed to reject bad input text");
347 }
348 catch(final IllegalArgumentException e) { /* Good, correctly rejected bad text. */ }
349 catch(final EOFException e) { /* Good, correctly rejected bad text. */ }
350 }
351
352 // Minimal trail text for simple parsing.
353 final String trailText1 =
354 "@ Test title 1 \n" +
355 " body text ";
356 final TrailData td1 = _parseTrailTestAsString(trailText1);
357 assertEquals("title should have ends trimmed of whitespace but be otherwise unmolested",
358 "Test title 1", td1.title.toString());
359 assertEquals("there should be one body entry",
360 1, td1.body.size());
361 assertEquals("body text should have ends trimmed of whitespace but be otherwise unmolested",
362 "body text", td1.body.get(0).second.toString());
363
364 // More complex trail text for simple parsing.
365 final String trailText2 =
366 "@ Title 2\r\n" +
367 "Intro text\nwaffles on a little...\n" +
368 "* example-ANON.jpg Comment\r\nText for first exhibit.\n\n\n\n\n" +
369 "* cat-ANON.gif Catty comment\nText for cat exhibit.\n\n" +
370 "* -2- Another catty comment\nText for next cat exhibit.\nMore text...\n" +
371 "\n\nFinal trailing\r\npara...";
372 final TrailData td2 = _parseTrailTestAsString(trailText2);
373 assertEquals("title should have ends trimmed of whitespace but be otherwise unmolested",
374 "Title 2", td2.title.toString());
375 assertEquals("there should be five body entries",
376 5, td2.body.size());
377 // Intro para...
378 assertEquals("body text should have ends trimmed of whitespace but be otherwise unmolested",
379 "Intro text waffles on a little...", td2.body.get(0).second.toString());
380 // First exhibit...
381 assertEquals("exhibit name should be correctly captured",
382 "example-ANON.jpg", td2.body.get(1).first.toString());
383 assertEquals("body text should have ends trimmed of whitespace but be otherwise unmolested",
384 "Text for first exhibit.", td2.body.get(1).second.toString());
385 // Second exhibit...
386 assertEquals("exhibit name should be correctly captured",
387 "cat-ANON.gif", td2.body.get(2).first.toString());
388 assertEquals("body text should have ends trimmed of whitespace but be otherwise unmolested",
389 "Text for cat exhibit.", td2.body.get(2).second.toString());
390 // Third exhibit...
391 assertEquals("exhibit name should be correctly synthesised",
392 "cat-2-ANON.gif", td2.body.get(3).first.toString());
393 assertEquals("body text should have ends trimmed of whitespace but be otherwise unmolested",
394 "Text for next cat exhibit. More text...", td2.body.get(3).second.toString());
395 // Final para...
396 assertNull("exhibit name should be correctly synthesised",
397 td2.body.get(4).first);
398 assertEquals("body text should have ends trimmed of whitespace but be otherwise unmolested",
399 "Final trailing para...", td2.body.get(4).second.toString());
400 }
401
402 /**Parse trail text supplied as (7-bit ASCII) String; never null.
403 * @param trailText 7-bit-ASCII trail text; never null
404 */
405 private static TrailData _parseTrailTestAsString(final String trailText)
406 throws IOException
407 {
408 return TrailData.parseFromByteStream(new ByteArrayInputStream(trailText.getBytes(CoreConsts.FILE_ENCODING_ASCII7)));
409 }
410
411 /**Test handling of ZIP-file data in/for exhibits.
412 */
413 public static void testZIPHandling()
414 throws Exception
415 {
416 // Create a simple ZIP with a few random files in it. */
417 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
418 final ZipOutputStream zos = new ZipOutputStream(baos);
419 final int nEntries = 1 + rnd.nextInt(5); // Cannot be empty...
420 final Map<String,ROByteArray> entryContent = new HashMap<String, ROByteArray>(nEntries);
421 for(int i = 0; i < nEntries; ++i)
422 {
423 final String name = "file" + (i+1);
424 final byte[] data = new byte[rnd.nextInt(1024)];
425 rnd.nextBytes(data);
426 final ROByteArray content = new ROByteArray(data);
427 entryContent.put(name, content);
428 // Randomly set level and method to stress things...
429 zos.setLevel(rnd.nextInt(10));
430 final ZipEntry ze = new ZipEntry(name);
431 if(0 == (i&1)) { ze.setSize(data.length); } // Set size on alternate entries only.
432 zos.putNextEntry(ze);
433 zos.write(data);
434 zos.closeEntry();
435 }
436 zos.finish();
437 zos.close();
438
439 // Now make sure that we can extract the file listing and content...
440 final FileTools.RandomAccessData rad = FileTools.wrapBytesAsRandomAccessData(baos.toByteArray());
441 // Check that all entries are visible...
442 final SortedMap<CharSequence, ZE> centralDir = FileTools.getZIPEntriesLengthAndOffset(rad);
443 assertNotNull("Must be able to find central directory", centralDir);
444 assertEquals("Same file count must exist in central directory as stored", entryContent.size(), centralDir.size());
445 // Check individual entries...
446 for(final String e : entryContent.keySet())
447 {
448 assertTrue("Each file must exist in central directory as recovered", centralDir.containsKey(e));
449
450 // Test the stream-based extractor...
451 // It needs to be set up afresh each time (and is otherwise inefficient).
452 final Pair<ZipEntry, ROByteArray> entryS = FileTools.getZIPEntry(new ByteArrayInputStream(baos.toByteArray()), e);
453 assertNotNull("Each entry must be visible with the stream-based extractor", entryS);
454 assertEquals("Each entry's name should be preserved with the stream-based extractor", e, entryS.first.getName());
455 assertEquals("Each entry's data should be preserved with the stream-based extractor", entryContent.get(e), entryS.second);
456
457 // Check that the offset in the directory is correct.
458 final int offset = centralDir.get(e).offset;
459 assertTrue("offset cannot be negative", offset >= 0);
460 assertTrue("offset cannot be beyond EOF", offset < baos.size());
461 assertTrue("offset cannot be beyond start of end header", offset < baos.size() - ZipEntry.ENDHDR);
462 final InputStream is = new ByteArrayInputStream(baos.toByteArray());
463 is.skip(offset);
464 final ZipInputStream zis = new ZipInputStream(is);
465 final DataInputStream dis = new DataInputStream(zis);
466 final ZipEntry ze = zis.getNextEntry();
467 assertEquals("Must locate correct ZIP entry with offset", e, ze.getName());
468 // Verify that data/content was preserved...
469 final ROByteArray ro = entryContent.get(ze.getName());
470 final byte[] tmp = new byte[ro.length()];
471 dis.readFully(tmp);
472 assertTrue(Arrays.equals(tmp, ro.toByteArray()));
473 zis.close(); // Release resources.
474 }
475 }
476
477 /**Test checking of power-state.
478 * Mainly to ensure that nothing crashes nor hangs.
479 */
480 public static void testPowerStateChecking()
481 {
482 GenUtils.mustConservePower();
483 }
484
485
486 /**OK pseudo-random source. */
487 private static final Random rnd = new Random();
488 }