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 package org.hd.d.pg2k.test.dev;
031
032 import java.io.ByteArrayInputStream;
033 import java.io.ByteArrayOutputStream;
034 import java.io.File;
035 import java.io.IOException;
036 import java.nio.ByteBuffer;
037 import java.util.ArrayList;
038 import java.util.Arrays;
039 import java.util.BitSet;
040 import java.util.Collections;
041 import java.util.HashMap;
042 import java.util.HashSet;
043 import java.util.Iterator;
044 import java.util.List;
045 import java.util.Map;
046 import java.util.Properties;
047 import java.util.Random;
048 import java.util.Set;
049 import java.util.Timer;
050 import java.util.TimerTask;
051
052 import junit.framework.TestCase;
053
054 import org.hd.d.pg2k.ai.scorer.ScorerCacheIF;
055 import org.hd.d.pg2k.svrCore.AllExhibitImmutableData;
056 import org.hd.d.pg2k.svrCore.AllExhibitProperties;
057 import org.hd.d.pg2k.svrCore.ExhibitPropsComputableMutable;
058 import org.hd.d.pg2k.svrCore.ExhibitPropsComputableMutableVoteCacheIF;
059 import org.hd.d.pg2k.svrCore.ExhibitStaticAttr;
060 import org.hd.d.pg2k.svrCore.ExhibitThumbnails;
061 import org.hd.d.pg2k.svrCore.FileTools;
062 import org.hd.d.pg2k.svrCore.Name;
063 import org.hd.d.pg2k.svrCore.Name.ExhibitFull;
064 import org.hd.d.pg2k.svrCore.Rnd;
065 import org.hd.d.pg2k.svrCore.SimpleLoggerIF;
066 import org.hd.d.pg2k.svrCore.Stratum;
067 import org.hd.d.pg2k.svrCore.VarTools;
068 import org.hd.d.pg2k.svrCore.datasource.ExhibitDataFileSource;
069 import org.hd.d.pg2k.svrCore.datasource.ExhibitDataTunnelSource;
070 import org.hd.d.pg2k.svrCore.datasource.SimpleExhibitPipelineFilter;
071 import org.hd.d.pg2k.svrCore.datasource.SimpleExhibitPipelineIF;
072 import org.hd.d.pg2k.svrCore.props.GenProps;
073 import org.hd.d.pg2k.svrCore.vars.BasicVarMgr;
074 import org.hd.d.pg2k.svrCore.vars.BasicVarMgrInterface;
075 import org.hd.d.pg2k.svrCore.vars.EventPeriod;
076 import org.hd.d.pg2k.svrCore.vars.EventVariableValue;
077 import org.hd.d.pg2k.svrCore.vars.EventVariableValueBuffer;
078 import org.hd.d.pg2k.svrCore.vars.EventVariableValuePeriodRow;
079 import org.hd.d.pg2k.svrCore.vars.EventVariableValueSet;
080 import org.hd.d.pg2k.svrCore.vars.InstanceID;
081 import org.hd.d.pg2k.svrCore.vars.PipelineVarMgr;
082 import org.hd.d.pg2k.svrCore.vars.SimpleNumberStats;
083 import org.hd.d.pg2k.svrCore.vars.SimpleVarStats;
084 import org.hd.d.pg2k.svrCore.vars.SimpleVariableDefinition;
085 import org.hd.d.pg2k.svrCore.vars.SimpleVariablePipelineIF;
086 import org.hd.d.pg2k.svrCore.vars.SimpleVariableValue;
087 import org.hd.d.pg2k.svrCore.vars.SystemVariables;
088
089 /**Test the behaviour of system variables.
090 * This includes as stand-alone objects,
091 * in variable-manager caches,
092 * in pipelines and tunnels, etc.
093 */
094 public final class SystemVariablesTest extends TestCase
095 {
096 public SystemVariablesTest(final String name)
097 {
098 super(name);
099 }
100
101 private static final SimpleLoggerIF logger = new SimpleLoggerIF(){
102 public void log(final String message) { Main.getOut().println(message); }
103 };
104
105 /**Timer that we can use to run pipelines; never null during tests. */
106 private Timer t;
107
108 /**Do any setup needed for the tests. */
109 @Override
110 protected void setUp()
111 {
112 t = new Timer();
113 }
114
115 /**Do any clearup needed after the tests. */
116 @Override
117 protected void tearDown()
118 {
119 t.cancel();
120 t = null;
121 }
122
123
124 /**Check some simple (de)serialistion works as expected.
125 * In particular, deserialising one of the canonical system variable definition
126 * should yield the canonical instance, ie that we our providing instance control.
127 * <p>
128 * However, it should still be possible to (de)serialise definitions
129 * (and compound values containing them)
130 * that are no longer legitimate system variable (and do not conflict with them)
131 * so that we can examine historical data for example.
132 */
133 public void testSimpleSerialisation()
134 throws Exception
135 {
136 // Test on all system definitions...
137 for(final SimpleVariableDefinition def : SystemVariables.nameToDef.values())
138 {
139 assertTrue("SimpleVariableDefinition.equals() should work for itself!", def.equals(def));
140 final SimpleVariableDefinition defDeser = (SimpleVariableDefinition)
141 SerializationTest.checkSerialisationPreservesEquality(def);
142 assertTrue("Deserialised canonical object should == original",
143 defDeser == def);
144 }
145
146 // Check that we can (de)serialise arbitrary (non-conflicting) definitions.
147 final SimpleVariableDefinition newDefs[] =
148 {
149 new SimpleVariableDefinition("newvar." + (rnd.nextLong() >>> 1),
150 SimpleVariableDefinition.TYPE_NUMBER,
151 rnd.nextBoolean(),
152 rnd.nextBoolean(),
153 rnd.nextBoolean(),
154 rnd.nextBoolean(), 0, null),
155 new SimpleVariableDefinition("newvar." + (rnd.nextLong() >>> 1),
156 SimpleVariableDefinition.TYPE_STRING,
157 rnd.nextBoolean(),
158 rnd.nextBoolean(),
159 rnd.nextBoolean(),
160 rnd.nextBoolean(), 0, null),
161 new SimpleVariableDefinition("newvar." + (rnd.nextLong() >>> 1),
162 SimpleVariableDefinition.TYPE_NONE,
163 rnd.nextBoolean(),
164 rnd.nextBoolean(),
165 rnd.nextBoolean(),
166 rnd.nextBoolean(), 0, null),
167 new SimpleVariableDefinition("newvar." + (rnd.nextLong() >>> 1),
168 SimpleVariableDefinition.TYPE_IID,
169 rnd.nextBoolean(),
170 rnd.nextBoolean(),
171 rnd.nextBoolean(),
172 rnd.nextBoolean(), 0, null),
173 };
174 for(final SimpleVariableDefinition def : newDefs)
175 {
176 assertTrue("SimpleVariableDefinition.equals() should work for itself!", def.equals(def));
177 final SimpleVariableDefinition defDeser = (SimpleVariableDefinition)
178 SerializationTest.checkSerialisationPreservesEquality(def);
179 assertTrue("Deserialised non-canonical object should != original",
180 defDeser != def);
181
182 if(def.isEvent())
183 {
184 // Check that we can (de)serialise event collection objects with non-std defs.
185 final EventVariableValueBuffer evvb =
186 new EventVariableValueBuffer(def,
187 EventPeriod.values()[rnd.nextInt(EventPeriod.values().length)],
188 1);
189 assertTrue("EventVariableValueBuffer.equals() should work for itself!", evvb.equals(evvb));
190 SerializationTest.checkSerialisationPreservesEquality(evvb);
191
192 final EventVariableValue evv = evvb.toEventVariableValue();
193 assertTrue("EventVariableValue.equals() should work for itself!", evv.equals(evv));
194 SerializationTest.checkSerialisationPreservesEquality(evv);
195 }
196 }
197 }
198
199
200 /**Do simple tests on InstanceID type...
201 * Such as making sure that all instance are unique, sort correctly, etc.
202 */
203 public void testInstanceID()
204 throws Exception
205 {
206 // Check equality works for equal values.
207 final InstanceID iid = InstanceID.createInstanceID();
208 assertTrue("InstanceID.equals() must work correctly for equal/same value",
209 iid.equals(iid));
210 assertTrue("InstanceID.compareTo() must work correctly for equal/same value",
211 iid.compareTo(iid) == 0);
212
213 // Just FYI...
214 Main.getOut().println("[Sample InstanceID: " + iid + ".]");
215
216 // Check equals() returns false for worng object tyupe.
217 assertFalse("equals() must fail for non-InstanceID arg",
218 iid.equals(rnd));
219
220 final int NUM_TO_TEST = 100;
221 final List<InstanceID> allValues = new ArrayList<InstanceID>(NUM_TO_TEST);
222 for(int i = NUM_TO_TEST; --i >= 0; )
223 { allValues.add(InstanceID.createInstanceID()); }
224 // Check that they are all distinct
225 // as far as Set is concerned using equals()/hashCode().
226 assertEquals("All InstanceID values generated must in practice be distinct for equals()/hashCode()",
227 NUM_TO_TEST, (new HashSet<InstanceID>(allValues)).size());
228
229 // Now sort our values to see if Comparable implementation OK...
230 Collections.sort(allValues);
231 for(int i = NUM_TO_TEST; --i > 0; )
232 {
233 final InstanceID vn = allValues.get(i-1);
234 final InstanceID vm = allValues.get(i);
235 assertTrue("Values must be correctly totally ordered",
236 vn.longValue() < vm.longValue());
237 assertTrue("longValue()s must be non-negative",
238 vn.longValue() >= 0);
239 assertTrue("String representation must be reasonable size",
240 (vn.toString().length() > 0) && (vn.toString().length() < 80));
241 }
242
243 // Check that we can (de)serialise...
244 SerializationTest.checkSerialisationPreservesEquality(
245 InstanceID.createInstanceID());
246
247 // Check that we don't make extra copies in memory when deserialising.
248 assertTrue("Deserialisation must not make extra copies of InstanceID",
249 iid == SerializationTest.checkSerialisationPreservesEquality(iid));
250 }
251
252 /**Test that variables of fairly arbitrary names/types can be constructed.
253 * This means, for example, that we can ship any variables between systems
254 * across HTTP tunnels, etc, providing that any stored values type-check,
255 * (even if we are then not allowed to set the values in a local variable
256 * store).
257 */
258 public void testArbitraryVarConstruction()
259 throws Exception
260 {
261 // Run a few test constructions.
262 for(int i = 100; --i >= 0; )
263 {
264 final int type = SimpleVariableDefinition.TYPE__MIN +
265 rnd.nextInt(SimpleVariableDefinition.TYPE__MAX + 1 -
266 SimpleVariableDefinition.TYPE__MIN);
267
268 final StringBuilder name = new StringBuilder(99);
269 do // Make name of random length...
270 {
271 if(name.length() > 0) { name.append('.'); }
272 name.append(Long.toHexString(rnd.nextLong() >>> 1));
273 } while((rnd.nextInt(3) > 0) &&
274 (name.length() < SimpleVariableDefinition.MAX_NAME_LEN / 2));
275 final boolean isLocal = rnd.nextBoolean();
276 final boolean isPersistent = rnd.nextBoolean();
277 final boolean isReadOnly = rnd.nextBoolean();
278 final boolean isEvent = rnd.nextBoolean();
279 // Ensure that it is possible to construct arbitrary definition.
280 final SimpleVariableDefinition def = new SimpleVariableDefinition(
281 name.toString(),
282 type,
283 isLocal,
284 isPersistent,
285 isReadOnly,
286 isEvent, 0, null);
287 // ...and check that we can (de)serialise it.
288 SerializationTest.checkSerialisationPreservesEquality(def);
289 // Check that all the getters work.
290 assertEquals("Broken isLocal()", isLocal, def.isLocal());
291 assertEquals("Broken isPersistent()", isPersistent, def.isPersistent());
292 assertEquals("Broken isReadOnly()", isReadOnly, def.isReadOnly());
293 assertEquals("Broken isEvent()", isEvent, def.isEvent());
294
295 // Select a legal value for the type;
296 // including null (which is valid for all types).
297 Object value = null;
298 if(rnd.nextBoolean())
299 {
300 switch(type)
301 {
302 case SimpleVariableDefinition.TYPE_NUMBER:
303 {
304 value = new Double(rnd.nextGaussian());
305 break;
306 }
307
308 case SimpleVariableDefinition.TYPE_IID:
309 {
310 value = InstanceID.createInstanceID();
311 break;
312 }
313
314 case SimpleVariableDefinition.TYPE_STRING:
315 {
316 value = "silly text " + rnd.nextLong();
317 break;
318 }
319 }
320 }
321 // Ensure that we can construct an instance...
322 final SimpleVariableValue svv = new SimpleVariableValue(def, value);
323 // Check that the type getter method works OK.
324 assertEquals("Must be able to getType() correctly",
325 type, def.getType());
326 // ...and check that we can (de)serialise it.
327 SerializationTest.checkSerialisationPreservesEquality(svv);
328
329 // Now try building a value of each type
330 // with totally bogus values that should fail to type-check.
331 final Object badValues[] = { rnd, new Object(), Void.class };
332 for(int b = badValues.length; --b >= 0; )
333 {
334 try
335 {
336 new SimpleVariableValue(def, badValues[b]);
337 fail("Type-checking failed with bogus construction, def=" + def);
338 }
339 catch(final IllegalArgumentException e) { } // Should reject.
340 }
341 }
342 }
343
344 /**Test basic variable manager behaviour for the endpoint.
345 * Instances not marked as an endpoint should not have a system ID defined,
346 * whereas an endpoint should have one defined.
347 */
348 public void testBasicVarMgrAsEndPoint()
349 {
350 final BasicVarMgr bvm1 = new BasicVarMgr();
351 assertFalse("BasicVarMgr should not be end-point unless marked as such",
352 bvm1.isEndPoint());
353 assertNull("BasicVarMgr should not contain an IID unless an endpoint",
354 bvm1.getVariable(SystemVariables.LOCAL_SYS_ID));
355
356 final BasicVarMgr bvm2 = new BasicVarMgr(true);
357 assertTrue("BasicVarMgr should be end-point when marked as such",
358 bvm2.isEndPoint());
359 assertNotNull("BasicVarMgr should contain an IID when an end-point",
360 bvm2.getVariable(SystemVariables.LOCAL_SYS_ID));
361
362 // Make sure that we cannot override system ID in any BasicVarMgr...
363 final SimpleVariableValue svvBogusSysID = new SimpleVariableValue(
364 SystemVariables.LOCAL_SYS_ID, InstanceID.createInstanceID());
365 // Nor set variable not listed in SystemVariables.defs.
366 final SimpleVariableDefinition bogusDef = new SimpleVariableDefinition(
367 "bogus.random.name." + (rnd.nextInt() >>> 1),
368 SimpleVariableDefinition.TYPE__MIN +
369 rnd.nextInt(1 + SimpleVariableDefinition.TYPE__MAX -
370 SimpleVariableDefinition.TYPE__MIN)
371 );
372 final SimpleVariableValue svvBogus = new SimpleVariableValue(
373 bogusDef, null);
374 final BasicVarMgr bvms[] = { bvm1, bvm2 };
375 for(int i = bvms.length; --i >= 0; )
376 {
377 try
378 {
379 bvms[i].setVariable(svvBogusSysID);
380 fail("Apparently allowed system ID to be set with setVariable() (from downstream)");
381 }
382 catch(final IOException e) { }
383 catch(final UnsupportedOperationException e) { }
384 catch(final IllegalArgumentException e) { }
385
386 try
387 {
388 if(bvms[i].setVariables(new SimpleVariableValue[]{svvBogusSysID}) != 0)
389 { fail("Apparently allowed system ID to be set with setVariables() (from downstream)"); }
390 }
391 catch(final IOException e) { }
392 catch(final UnsupportedOperationException e) { }
393 catch(final IllegalArgumentException e) { }
394
395 try
396 {
397 bvms[i].setVariable(svvBogus);
398 fail("Apparently allowed non-extant variable to be set with setVariable() (from downstream)");
399 }
400 catch(final IOException e) { }
401 catch(final UnsupportedOperationException e) { }
402 catch(final IllegalArgumentException e) { }
403
404 try
405 {
406 if(bvms[i].setVariables(new SimpleVariableValue[]{svvBogus}) != 0)
407 { fail("Apparently allowed non-extant variable to be set with setVariables() (from downstream)"); }
408 }
409 catch(final IOException e) { }
410 catch(final UnsupportedOperationException e) { }
411 catch(final IllegalArgumentException e) { }
412 }
413 }
414
415 /**Test basic setting/getting of local variables.
416 * It is assumed that the endPoint setting should be irrelevant.
417 */
418 public void testLocalVariableOps()
419 throws Exception
420 {
421 final BasicVarMgr bvm = new BasicVarMgr(rnd.nextBoolean());
422
423 // Use a local, TYPE_NUMBER, read/write variable...
424 final SimpleVariableDefinition def =
425 SystemVariables.ExhibitDataSimpleCache_CACHE_AVAIL_SPACE_PERCENT_USED;
426
427 // Initial attempt to retrieve this should return null.
428 assertNull("getVariable(X) for local X should return null until X has been set",
429 bvm.getVariable(def));
430
431 // Now try setting it to a few values at random.
432 // It should always be possible to retrieve the value set.
433 final Number testValues[] =
434 {
435 new Integer(rnd.nextInt()),
436 new Long(rnd.nextLong()),
437 new Float(rnd.nextFloat()),
438 new Double(rnd.nextDouble()),
439 };
440 for(int i = testValues.length; --i >= 0; )
441 {
442 final Number n = testValues[i];
443 final SimpleVariableValue svv = new SimpleVariableValue(def, n);
444 bvm.setVariable(svv);
445 assertEquals("Must be able to retrieve the local value that was set",
446 n, bvm.getVariable(def).getValue());
447 assertTrue("Must be able to retrieve the local value with getVariables()",
448 checkValuePresent(bvm.getVariables(-1), svv));
449 }
450 }
451
452 /**Test basic setting/getting of global variables.
453 * This will have to be an end-point to set global variables.
454 */
455 public void testGlobalVariableOps()
456 throws Exception
457 {
458 final BasicVarMgr bvm = new BasicVarMgr(true);
459
460 // Use a global, TYPE_NUMBER, read/write variable...
461 final SimpleVariableDefinition def =
462 SystemVariables.ThroughputMonitorFilter_CLIENT_COUNT;
463
464 // Just FYI...
465 Main.getOut().println("[Sample SimpleVariableDefinition: " + def + ".]");
466
467 // Initial attempt to retrieve this should return null.
468 assertNull("getVariable(X) for global X should return null until X has been set",
469 bvm.getVariable(def));
470
471 // Create two system IDs.
472 final InstanceID system1 = InstanceID.createInstanceID();
473 final InstanceID system2 = InstanceID.createInstanceID();
474
475 // Now try setting it to a few values at random.
476 // It should always be possible to retrieve the value set.
477 final Number testValues[] =
478 {
479 new Integer(rnd.nextInt()),
480 new Long(rnd.nextLong()),
481 new Float(rnd.nextFloat()),
482 new Double(rnd.nextDouble()),
483 new Integer(rnd.nextInt()),
484 };
485 for(int i = testValues.length; --i >= 0; )
486 {
487 final Number n = testValues[i];
488 final SimpleVariableValue svvL = new SimpleVariableValue(def, n);
489 final SimpleVariableValue svvG = svvL.put(
490 (rnd.nextBoolean() ? system1 : system2), svvL);
491 bvm.setVariable(svvG);
492 assertEquals("Must be able to retrieve the global value that was set",
493 n, bvm.getVariable(def).getValue());
494 assertTrue("Must be able to retrieve the global value with getVariables()",
495 checkValuePresent(bvm.getVariables(-1), svvL));
496 }
497
498 // Now explicitly set one value for each system,
499 // and check that we can see both in the globalMap.
500 final Integer v1 = new Integer(1);
501 final SimpleVariableValue svvL1 = new SimpleVariableValue(def, v1);
502 final SimpleVariableValue svvG1 = svvL1.put(system1, svvL1);
503 final Integer v2 = new Integer(2);
504 final SimpleVariableValue svvL2 = new SimpleVariableValue(def, v2);
505 final SimpleVariableValue svvG2 = svvL2.put(system2, svvL2);
506 bvm.setVariable(svvG1);
507 bvm.setVariable(svvG2);
508 final SimpleVariableValue svv12 = bvm.getVariable(def);
509 assertEquals("globalMap must contain exactly two entries",
510 2, svv12.getGlobalMap().size());
511 assertEquals("system1 entry must appear in global map and be right",
512 v1, svv12.getGlobalMap().get(system1).getValue());
513 assertEquals("system2 entry must appear in global map and be right",
514 v2, svv12.getGlobalMap().get(system2).getValue());
515
516 // Just FYI...
517 Main.getOut().println("[Sample SimpleVariableValue: " + svvG1 + ".]");
518 Main.getOut().println("[Sample SimpleVariableValue full description: " + svvG2.getFullDescription() + ".]");
519 }
520
521 /**Test handling of TYPE_NUMBER values.
522 * We want to ensure that all java.lang.XXX Number extensions are allowed,
523 * but not any user-defined ones.
524 */
525 public void testTypeNumber()
526 throws Exception
527 {
528 final BasicVarMgr bvm = new BasicVarMgr(rnd.nextBoolean());
529
530 // Use a local, TYPE_NUMBER, read/write variable...
531 final SimpleVariableDefinition def =
532 SystemVariables.ExhibitDataSimpleCache_CACHE_AVAIL_SPACE_PERCENT_USED;
533
534 // Good values which should all be settable...
535
536 // Now try setting it to a few values at random.
537 // It should always be possible to retrieve the value set.
538 final Number goodValues[] =
539 {
540 new Integer(rnd.nextInt()),
541 new Long(rnd.nextLong()),
542 new Float(rnd.nextFloat()),
543 new Double(rnd.nextDouble()),
544 Byte.valueOf((byte) rnd.nextInt()),
545 new Short((short) rnd.nextInt()),
546 };
547 for(int i = goodValues.length; --i >= 0; )
548 {
549 final Number n = goodValues[i];
550 final SimpleVariableValue svv = new SimpleVariableValue(def, n);
551 bvm.setVariable(svv);
552 assertEquals("Must be able to set and retrieve JVM Number impl",
553 n, bvm.getVariable(def).getValue());
554 }
555
556 // Make sure that we can set it to null too.
557 final SimpleVariableValue svvNull = new SimpleVariableValue(def, null);
558 bvm.setVariable(svvNull);
559 assertNull("Must be able to set NUMBER value to null",
560 bvm.getVariable(def).getValue());
561
562 // Invent a bad implementation of Number,
563 // which should be rejected.
564 final Number bad1 = new Number(){
565 @Override
566 public int intValue()
567 {
568 return 0;
569 }
570
571 @Override
572 public long longValue()
573 {
574 return 0;
575 }
576
577 @Override
578 public float floatValue()
579 {
580 return 0;
581 }
582
583 @Override
584 public double doubleValue()
585 {
586 return 0;
587 }
588
589 /**Unique Serialisation class ID generated by http://random.hd.org/. */
590 private static final long serialVersionUID = -8016942945220968907L;
591 };
592 // Take several values which TYPE_NUMBER variable should not allow
593 // even to be constructed.
594 final Object badVals[] = { bad1, "hello", new Object(), rnd };
595 for(int i = badVals.length; --i >= 0; )
596 {
597 try
598 {
599 new SimpleVariableValue(def, badVals[i]);
600 fail("Should not allow user-defined Number instance as value");
601 }
602 catch(final IllegalArgumentException e)
603 {
604 // Good: rejected dodgy value...
605 }
606 }
607 }
608
609
610
611 /**Simple final pipeline stage (a master endpoint) variable values.
612 * Note that all data operations return null;
613 * this class is for var testing purposes only (thus is not public).
614 */
615 static final class BasicVarMgrEndpoint implements SimpleExhibitPipelineIF
616 {
617 public ExhibitStaticAttr getStaticAttr(final ExhibitFull name)
618 // throws IOException
619 {
620 return null;
621 }
622
623 public void getRawFile(final ByteBuffer buf,
624 final Name.ExhibitFull exhibitName, final int position, final boolean dontCache)
625 // throws IOException
626 {
627 }
628
629 public AllExhibitImmutableData getAllExhibitImmutableData(final long oldStamp)
630 // throws IOException
631 {
632 return null;
633 }
634
635 public AllExhibitProperties getAllExhibitProperties(final long oldHash)
636 // throws IOException
637 {
638 return null;
639 }
640
641 public GenProps getGenProps(final long oldStamp)
642 // throws IOException
643 {
644 return null;
645 }
646
647 public Properties getGenSecProps(final long oldStamp)
648 // throws IOException
649 {
650 return null;
651 }
652
653 public ExhibitThumbnails getThumbnails(final ExhibitFull name, final boolean create)
654 // throws IOException
655 {
656 return null;
657 }
658
659 public void setVariable(final SimpleVariableValue newValue)
660 throws IOException,
661 UnsupportedOperationException
662 {
663 vars.setVariable(newValue);
664 }
665
666 public int setVariables(final SimpleVariableValue[] newValues)
667 throws IOException
668 {
669 return(vars.setVariables(newValues));
670 }
671
672 public SimpleVariableValue getVariable(final SimpleVariableDefinition var)
673 // throws IOException
674 {
675 return(vars.getVariable(var));
676 }
677
678 public SimpleVariableValue[] getVariables(final long changedSince)
679 // throws IOException
680 {
681 return(vars.getVariables(changedSince));
682 }
683
684 public EventVariableValue getEventValue(final SimpleVariableDefinition def,
685 final EventPeriod intervalSelector,
686 final boolean current)
687 {
688 return(vars.getEventValue(def, intervalSelector, current));
689 }
690
691 public EventVariableValue[] getEventValues(final SimpleVariableDefinition def,
692 final EventPeriod intervalSelector,
693 final long intervalNumber,
694 final BitSet whichValues)
695 {
696 return(vars.getEventValues(def, intervalSelector, intervalNumber, whichValues));
697 }
698
699 public void syncVariables(final boolean force)
700 // throws IOException
701 {
702 }
703
704 /**Get requested Properties selected by key and versionID.
705 * Fetches a Properties set unconditionally (versionID == -1)
706 * else if the versionID presented is not current.
707 *
708 * @param key selector (with possible embedded sub-key)
709 * for desired properties set; never null
710 * @param versionID if -1 then map is always returned if available,
711 * else must be non-negative and null is returned if the versionID
712 * presented matches that of the current version
713 * (ie if the caller has presumably got the up-to-date version);
714 * may be a timestamp or a hash or other value,
715 * and by convention is zero only for an empty properties set
716 *
717 * @return null, or Properties map guaranteed to contain only
718 * String keys and values
719 */
720 public java.util.Properties getProperties(final PropsKey key,
721 final long versionID)
722 throws IOException
723 {
724 throw new IOException("NOT IMPLEMENTED");
725 }
726
727 public void poll(final GenProps gp)
728 // throws IOException
729 {
730 }
731
732 /**Always pretend to be a master/root. */
733 public Stratum getStratum()
734 // throws IOException
735 {
736 return(Stratum.ROOT);
737 }
738
739 /**Does nothing in this implementation. */
740 public void destroy()
741 {
742 }
743
744 /**Variable store; must be an end-point */
745 private final BasicVarMgr vars = new BasicVarMgr(true);
746 }
747
748
749 /**Simple pipeline stage that caches variable values.
750 * This can be optionally write-through.
751 * <p>
752 * Note that all data operations return null;
753 * this class is for var testing purposes only (thus is not public).
754 */
755 static final class TestVarCacheStage extends SimpleExhibitPipelineFilter
756 {
757 /**Construct test variable-caching stage.
758 *
759 * @param upstream upstream stage of pipeline; non-null
760 * @param writeThrough if true, all set operations "write-through"
761 * this cache stage immediately...
762 */
763 TestVarCacheStage(final SimpleExhibitPipelineIF upstream,
764 final boolean writeThrough)
765 {
766 super(upstream);
767 vars = new PipelineVarMgr(upstream, writeThrough);
768 }
769
770 @Override
771 public void setVariable(final SimpleVariableValue newValue)
772 throws IOException,
773 UnsupportedOperationException
774 {
775 vars.setVariable(newValue);
776 }
777
778 @Override
779 public int setVariables(final SimpleVariableValue[] newValues)
780 throws IOException
781 {
782 return(vars.setVariables(newValues));
783 }
784
785 @Override
786 public SimpleVariableValue getVariable(final SimpleVariableDefinition var)
787 // throws IOException
788 {
789 return(vars.getVariable(var));
790 }
791
792 @Override
793 public SimpleVariableValue[] getVariables(final long changedSince)
794 // throws IOException
795 {
796 return(vars.getVariables(changedSince));
797 }
798
799 @Override
800 public void syncVariables(final boolean force)
801 throws IOException
802 {
803 vars.syncVariables(force);
804 }
805
806 @Override
807 public void poll(final GenProps gp)
808 throws IOException
809 {
810 // Never explicitly force a full refresh...
811 try { vars.syncVariables(false); }
812 finally { source.poll(gp); }
813 }
814
815 /**Variable manager. */
816 private final PipelineVarMgr vars;
817 };
818
819 /**Check BasicVarMgr embedded at the end of a pipeline.
820 * We're prepared to spend some time running this
821 * (though in fact it should be fast)
822 * since it is so basic to correct behaviour.
823 */
824 public void testBasicVarMgrAsPipelineEndpoint()
825 throws Exception
826 {
827 // Tunnel endpoint (wraps a BasicVarMar).
828 final SimpleExhibitPipelineIF ep = new BasicVarMgrEndpoint();
829
830 // Now run our battery of pipeline tests...
831 final SimpleVariablePipelineIF[] pipeline = new SimpleVariablePipelineIF[]{ep};
832 Main.getOut().println("Starting tests on simple pipeline...");
833 singlePipelineTest(pipeline,
834 System.currentTimeMillis() + 120000L); // Test for 2m max.
835 }
836
837 /**Check vanilla SimpleExhibitPipelineFilter embedded in a pipeline.
838 * This is only checking the variable-handling aspects.
839 */
840 public void testSimpleExhibitPipelineFilterVariableHandling()
841 throws Exception
842 {
843 // Tunnel endpoint (wraps a BasicVarMar).
844 final SimpleExhibitPipelineIF ep = new BasicVarMgrEndpoint();
845
846 // Simple vanilla SimpleExhibitPipelineFilter instance.
847 final SimpleExhibitPipelineFilter sepf =
848 new SimpleExhibitPipelineFilter(ep);
849
850 // Now run our battery of pipeline tests...
851 final SimpleVariablePipelineIF[] pipeline =
852 new SimpleVariablePipelineIF[]{sepf, ep};
853 singlePipelineTest(pipeline,
854 System.currentTimeMillis() + 5000L); // Test for 5s max.
855 }
856
857 /**Test PipelineVarMgr in pipeline in write-through mode.
858 * Checks that we can build a simple pipeline with:
859 * <ul>
860 * <li>a BasicVarMgr endpoint.
861 * <li>a PipelineVarMgr (itself an extended BasicVarMgr) upstream.
862 * <ul>
863 * <p>
864 * Write-though mode is faster and "safer"
865 * but may incur higher costs, eg sending updates over a tunnel.
866 */
867 public void testPipelineVarMgrSimpleWriteThrough()
868 throws Exception
869 {
870 checkShortPipelineVarMgr(true);
871 }
872
873 /**Test PipelineVarMgr in pipeline in non-write-through mode.
874 * Checks that we can build a simple pipeline with:
875 * <ul>
876 * <li>a BasicVarMgr endpoint.
877 * <li>a PipelineVarMgr (itself an extended BasicVarMgr) upstream.
878 * <ul>
879 * <p>
880 * Non-write-though mode groups writes together
881 * and eliminates redundant writes for efficiency,
882 * but delays writes and thus information propagation.
883 */
884 public void testPipelineVarMgrSimpleNonWriteThrough()
885 throws Exception
886 {
887 checkShortPipelineVarMgr(false);
888 }
889
890 /**Check a short pipeline with a single PipelineVarMgr stage.
891 * This can be run in write-through or non-write-through mode.
892 * <p>
893 * We run this stand-alonw because as of 20040808
894 * the non-write-though implementation was buggy/lossy.
895 *
896 * @param writeThrough
897 * @throws InterruptedException
898 * @throws IOException
899 */
900 private void checkShortPipelineVarMgr(final boolean writeThrough)
901 throws InterruptedException, IOException
902 {
903 // Tunnel endpoint (wraps a BasicVarMar).
904 final SimpleExhibitPipelineIF ep = new BasicVarMgrEndpoint();
905
906 // Make several cacheing stages...
907 // Randomly make at least one stage write-through and another other not.
908 final TestVarCacheStage c1 = new TestVarCacheStage(ep, writeThrough);
909
910 // Get run poll() for cacheStage so it can sync() its vars.
911 t.scheduleAtFixedRate(new TimerTask(){
912 @Override
913 public void run()
914 {
915 try { c1.poll(new GenProps()); }
916 catch(final Exception e) { e.printStackTrace(Main.getErr()); }
917 }
918 },
919 rnd.nextInt(2000), 100L); // Run poll() every 0.1s, after a delay.
920
921 // Now run our battery of pipeline tests...
922 final SimpleExhibitPipelineIF[] pipeline =
923 new SimpleExhibitPipelineIF[]{c1, ep};
924
925 // Do extra tests of variable set/get (covered in main tests too)
926 // looking for race condition we have seen in past.
927 Main.getOut().println("Starting pre-tests on short cache pipeline, writeThrough="+writeThrough+"...");
928 checkSimpleSetGet(pipeline,
929 System.currentTimeMillis() + 10000L); // Test for 10s max.
930
931 Main.getOut().println("Starting tests on short cache pipeline, writeThrough="+writeThrough+"...");
932 singlePipelineTest(pipeline,
933 System.currentTimeMillis() + 60000L); // Test for 1m max.
934 }
935
936 /**Test several PipelineVarMgr stages in a long pipeline.
937 * Checks that we can build a simple pipeline with:
938 * <ul>
939 * <li>a BasicVarMgr endpoint.
940 * <li>Several PipelineVarMgr stages upstream,
941 * randomly write-through or not.
942 * <ul>
943 * <p>
944 * Normally it would not be sensible to stack several cache stages
945 * like this because of latency problems, so this is somewhat harsh.
946 */
947 public void testPipelineVarMgrLong()
948 throws Exception
949 {
950 // Tunnel endpoint (wraps a BasicVarMar).
951 final SimpleExhibitPipelineIF ep = new BasicVarMgrEndpoint();
952
953 final boolean wt = rnd.nextBoolean();
954
955 // Make several cacheing stages...
956 // Randomly make at least one stage write-through and another other not.
957 final TestVarCacheStage c1 = new TestVarCacheStage(ep, wt);
958 final TestVarCacheStage c2 = new TestVarCacheStage(c1, !wt);
959 final TestVarCacheStage c3 = new TestVarCacheStage(c2, rnd.nextBoolean());
960 final TestVarCacheStage c4 = new TestVarCacheStage(c3, rnd.nextBoolean());
961 final TestVarCacheStage c5 = new TestVarCacheStage(c4, rnd.nextBoolean());
962
963 // Get run poll() for cacheStage so it can sync() its vars.
964 t.scheduleAtFixedRate(new TimerTask(){
965 @Override
966 public void run()
967 {
968 try { c5.poll(new GenProps()); }
969 catch(final Exception e) { e.printStackTrace(Main.getErr()); }
970 }
971 },
972 rnd.nextInt(2000), 100L); // Run poll() every 0.1s, after a delay.
973
974 // Now run our battery of pipeline tests...
975 final SimpleExhibitPipelineIF[] pipeline =
976 new SimpleExhibitPipelineIF[]{c5, c4, c3, c2, c1, ep};
977 Main.getOut().println("Starting tests on long pipeline...");
978 singlePipelineTest(pipeline,
979 System.currentTimeMillis() + 120000L); // Test for 2m max.
980 }
981
982
983 /**Returns true iff the specified variable is present in the array with the specified value.
984 * Checks name, type and value.
985 *
986 * @param svv variable/value to check for; must not be null
987 */
988 public static boolean checkValuePresent(final SimpleVariableValue[] vars,
989 final SimpleVariableValue svv)
990 {
991 if(svv == null)
992 { throw new IllegalArgumentException(); }
993
994 for(int i = vars.length; --i >= 0; )
995 {
996 if(svv.equals(vars[i]))
997 { return(true); }
998 }
999 return(false);
1000 }
1001
1002 /**Driver routine to test variable handling in pipelines.
1003 * This makes sure that values are correctly propagated,
1004 * globals correctly merged, etc.
1005 * <p>
1006 * The pipeline order is downstream-first, end-point-last,
1007 * eg the persistent store or tunnel for a pipeline would
1008 * be at the highest-numbered index,
1009 * and the normal access point for the tunnel
1010 * (eg as exposed by the DataSourceBean) is at index 0.
1011 * <p>
1012 * Takes in-order lists of taps/entrypoints on a data pipeline,
1013 * the first (two) being separate slaves (before a tunnel)
1014 * and the last being in a master (after a tunnel).
1015 * All but one of the lists can be null (but never empty).
1016 * <p>
1017 * Internal correctness tests will be run on each of the constituent
1018 * slave/master pipelines.
1019 * <p>
1020 * The parameter arrays must not be altered while the tests are running.
1021 * <p>
1022 * This checks that:
1023 * <ul>
1024 * <li>System IDs are unique to each side of each tunnel.
1025 * <li>System IDs are available everywhere in the tunnel.
1026 * <li>System IDs are not writable in the tunnel.
1027 * <li>Non-existent variables cannot be set in the tunnel.
1028 * <li>Cache stages propagate values within allowed latencies.
1029 * <li>Locals never a have a (non-null) globalMap.
1030 * <li>Locals are not propagated across a tunnel.
1031 * <li>Globals are propagated both ways across a tunnel.
1032 * <li>Globals are propagated between slaves.
1033 * <li>Globals are correctly merged.
1034 * <li>Multiple settings of the same variable happen in order.
1035 * <li>TODO: That at least the top of each pipeline is thread-safe
1036 * </ul>
1037 *
1038 * @param slave1 pipeline for slave 1; null or non-empty
1039 * @param slave2 pipeline for slave 2; null or non-empty
1040 * @param master pipeline for master; null or non-empty
1041 *
1042 * @throws IOException if an inappropriate IOException is thrown by
1043 * the pipeline under test
1044 */
1045 public static void pipelineVariableTest(final SimpleVariablePipelineIF slave1[],
1046 final SimpleVariablePipelineIF slave2[],
1047 final SimpleVariablePipelineIF master[],
1048 final long stopBy)
1049 throws IOException,
1050 InterruptedException
1051 {
1052 // Make a list of just the non-null pipelines...
1053 final List<BasicVarMgrInterface[]> livePipes = new ArrayList<BasicVarMgrInterface[]>(3);
1054 if(slave1 != null) { livePipes.add(slave1); }
1055 if(slave2 != null) { livePipes.add(slave2); }
1056 if(master != null) { livePipes.add(master); }
1057
1058 // Start with independent tests on each pipeline.
1059 final SimpleVariablePipelineIF pipelines[][] =
1060 { slave1, slave2, master };
1061 final Map<InstanceID,SimpleVariablePipelineIF[]> systemIDs = new HashMap<InstanceID, SimpleVariablePipelineIF[]>(); // From InstanceID --> pipeline.
1062 final int nPipes = pipelines.length;
1063 // for(int i = nPipes; --i >= 0; )
1064 // {
1065 // if(pipelines[i] != null)
1066 // {
1067 // Main.getOut().println("[pipelineVariableTest(): testing pipeline "+i+"...]");
1068 // if(null != systemIDs.put(singlePipelineTest(pipelines[i], stopBy), pipelines[i]))
1069 // {
1070 // fail("pipelines had duplicate system IDs");
1071 // }
1072 // }
1073 // }
1074
1075 // We can explicitly test propagation of globals between systems
1076 // as long as we have the master pipeline and at least one slave.
1077 // TODO: all tests to do with propagation of globals, etc...
1078 // TODO: demonstrate that locals are never propagated...
1079 if((master != null) &&
1080 ((slave1 != null) || (slave2 != null)))
1081 {
1082 Main.getOut().println("[pipelineVariableTest(): testing global propagation and merge...]");
1083
1084 // Take our set of distinct (writable) local variables,
1085 // for each, pick one non-null pipeline
1086 // and set it at the top of the pipeline,
1087 // then check that none of those local values
1088 // appears in any of the other pipelines.
1089 // We wait for up to the maximum propagation latency
1090 // to make sure that none of the values have propagated.
1091 final SimpleVariableDefinition localWritableTestVars[] =
1092 {
1093 SystemVariables.TEST_NUMBER_LOCAL,
1094 SystemVariables.TEST_NUMBER_LOCAL2,
1095 SystemVariables.TEST_STRING_LOCAL,
1096 };
1097 // The values that we have set for each local variable.
1098 final SimpleVariableValue values[] =
1099 new SimpleVariableValue[localWritableTestVars.length];
1100 // Record which test variable(s) we wrote where...
1101 // We only set values for non-null pipeline stages.
1102 final List<SimpleVariableDefinition> what[] =
1103 new List[nPipes];
1104 // Now set up the local variables before we do our other tests...
1105 // This gives values time to (erroneously) propagate in error.
1106 _setUpLocalVars(localWritableTestVars, pipelines, nPipes, what, values);
1107
1108 // TEST GLOBALS!
1109 // Check that if I set a test global in each pipeline,
1110 // then in all pipelines I can see a merged globalMap
1111 // within the allowed latency.
1112 _checkGlobalMerge(livePipes, stopBy);
1113
1114 // Now make sure that the locals did not propagate...
1115 _testLocalNonPropagation(values, nPipes, pipelines, what);
1116 }
1117
1118 // Now re-run most of the per-pipeline tests
1119 // to make sure that nothing has broken,
1120 // TODO: but maybe use a random ordering this time.
1121 systemIDs.clear();
1122 for(int i = nPipes; --i >= 0; )
1123 {
1124 if(pipelines[i] != null)
1125 {
1126 Main.getOut().println("[pipelineVariableTest(): retesting pipeline "+i+"...]");
1127 if(null != systemIDs.put(singlePipelineTest(pipelines[i], stopBy), pipelines[i]))
1128 {
1129 fail("pipelines had duplicate system IDs");
1130 }
1131 }
1132 }
1133 }
1134
1135 /**Check global variable propagation and merge.
1136 * Check that if I set a test global in each pipeline,
1137 * then in all pipelines I can see a merged globalMap
1138 * within the allowed latency.
1139 * <p>
1140 * This does not alter its parameters, though sets/gets values in them.
1141 *
1142 * @param livePipes list of pipelines of participating systems;
1143 * no pipelines are null or empty
1144 * @param stopBy time to attempt to stop by
1145 */
1146 private static void _checkGlobalMerge(final List<BasicVarMgrInterface[]>/*<SimpleExhibitPipelineIF[]>*/ livePipes,
1147 final long stopBy)
1148 throws InterruptedException
1149 {
1150 // Test set of global variables to use.
1151 final SimpleVariableDefinition testGlobals[] =
1152 {
1153 SystemVariables.TEST_NUMBER_GLOBAL,
1154 SystemVariables.TEST_NUMBER_GLOBAL2,
1155 SystemVariables.TEST_STRING_GLOBAL,
1156 };
1157 if(RANDOMISE_VARIABLE_TEST_ORDER)
1158 {
1159 // Randomise the ordering...
1160 Collections.shuffle(Arrays.asList(testGlobals));
1161 }
1162
1163 for(int g = testGlobals.length; --g >= 0; )
1164 {
1165 final SimpleVariableDefinition def = testGlobals[g];
1166
1167 // Values that we successfully assigned for this variable,
1168 // at most one per pipeline.
1169 // All of these should appear in each globalMap...
1170 final List<SimpleVariableValue> values = new ArrayList<SimpleVariableValue>();
1171
1172 // For each live pipeline, try to set a value for this variable.
1173 for(final Iterator<BasicVarMgrInterface[]> it = livePipes.iterator(); it.hasNext(); )
1174 {
1175 final BasicVarMgrInterface[] pipeline =
1176 it.next();
1177 final BasicVarMgrInterface pipelineTop = pipeline[0];
1178
1179 // Chose a unique random value for each variable
1180 // (of an appropriate type),
1181 // record it,
1182 // and set it,
1183 // (and check that it got set here).
1184 // It is acceptable for the set/get to be vetoed with
1185 // IOException or UnsupportedOperationException.
1186 SimpleVariableValue svv;
1187 do { svv = makeRandomSimpleVariableValue(def); } while(values.contains(svv));
1188 try
1189 {
1190 // Set the value at the top of the pipeline...
1191 pipelineTop.setVariable(svv);
1192 // Once the set is allowed, note what we set...
1193 values.add(svv);
1194 // Verify that it really did get set...
1195 assertEquals("Global variable set accepted but did not work",
1196 svv, pipelineTop.getVariable(def));
1197 }
1198 catch(final IOException e)
1199 {
1200 // Not capable of local set/get right now...
1201 Main.getErr().println(e.getMessage());
1202 }
1203 catch(final UnsupportedOperationException e)
1204 {
1205 // Not capable of local set/get...
1206 Main.getErr().println(e.getMessage());
1207 }
1208 }
1209
1210 // Now try and retrieve the merged globalMap for this variable,
1211 // from all live pipelines,
1212 // waiting up to the maximum allowed latency.
1213 final long now = System.currentTimeMillis();
1214 final long warnAfter = now + 10 * 1000L;
1215 final long retrieveBy = now +
1216 SystemVariables.MAX_VALUE_DISTRIBUTION_LATENCY_MS;
1217
1218 // For each live pipeline, try to set a value for this variable.
1219 for(final Iterator<BasicVarMgrInterface[]> it = livePipes.iterator(); it.hasNext(); )
1220 {
1221 final BasicVarMgrInterface[] pipeline =
1222 it.next();
1223 final BasicVarMgrInterface pipelineTop = pipeline[0];
1224
1225 try
1226 {
1227 // Loop until we get the globalMap we expect,
1228 // or we run out of time...
1229 int waitTime = 10 + rnd.nextInt(100);
1230 for( ; ; )
1231 {
1232 final SimpleVariableValue svv =
1233 pipelineTop.getVariable(def);
1234
1235 //Main.getErr().println(" For def="+def+", svv=" + svv);
1236 //Main.getErr().println(" Values being waited for: " + new ArrayList(values));
1237
1238 // We can get some sort of value...
1239 if(svv != null)
1240 {
1241 // And it has a globalMap...
1242 final Map<InstanceID,SimpleVariableValue> globalMap = svv.getGlobalMap();
1243 if(globalMap != null)
1244 {
1245 // Main.getErr().println(" Value retrieved="+svv.getFullDescription());
1246
1247 // Then make sure that they are all there...
1248 if(globalMap.values().containsAll(values))
1249 {
1250 // Yes! We found all our values!
1251 break;
1252 }
1253 }
1254 }
1255
1256 final long n = System.currentTimeMillis();
1257 if(n > retrieveBy)
1258 { fail("Could not see global propagated to remote pipeline for " + def); }
1259 if(n > warnAfter)
1260 {
1261 Main.getOut().println("[Waiting to see global propagated to remote pipeline for "+def+"...]");
1262 Main.getOut().println(" [Values being waited for: " + new ArrayList<SimpleVariableValue>(values)+"]");
1263 if(svv != null) { Main.getOut().println(" [Value retrieved="+svv.getFullDescription()+"]"); }
1264 }
1265 Thread.sleep(waitTime *= 2); // Wait a while...
1266 }
1267 }
1268 catch(final IOException e)
1269 {
1270 // Cannot get this value right now...
1271 }
1272 catch(final UnsupportedOperationException e)
1273 {
1274 // Cannot get this value...
1275 }
1276
1277 // IFF we are randomising variable-test order
1278 // then we can bucket out when we run out of time
1279 // since we will eventually test all possible values.
1280 if(RANDOMISE_VARIABLE_TEST_ORDER)
1281 {
1282 if(!notAfter(stopBy))
1283 { return; }
1284 }
1285 }
1286 }
1287 }
1288
1289 /**Given a variable definition, make a random value for it; never null.
1290 * This attempts to return values distributed across the entire
1291 * legitimate range for a given type;
1292 * where that type has a large range then the values generated should
1293 * be unique within a given test run
1294 * (unless the random number source behaves poorly).
1295 * <p>
1296 * Conversely, for restricted types such as TYPE_NONE,
1297 * little or no practical variation is possible.
1298 * <p>
1299 * Non-local values are generated without a globalMap;
1300 * the results of several calls to this function can be merged
1301 * with put() to generate a non-empty globalMap if desired.
1302 * <p>
1303 * Package-visible so as to be available to other test routines.
1304 *
1305 * @param def definition for which value is wanted; never null
1306 * @return random value for that definition.
1307 */
1308 static SimpleVariableValue makeRandomSimpleVariableValue(
1309 final SimpleVariableDefinition def)
1310 {
1311 if(def == null)
1312 { throw new IllegalArgumentException(); }
1313
1314 // Some of the time just return a null value...
1315 if(rnd.nextInt(3) == 0)
1316 { return(new SimpleVariableValue(def, null)); }
1317
1318 // Try to generate a type-specific, non-null value if possible.
1319 switch(def.getType())
1320 {
1321 case SimpleVariableDefinition.TYPE_NONE:
1322 return(new SimpleVariableValue(def, null));
1323
1324 case SimpleVariableDefinition.TYPE_IID:
1325 return(new SimpleVariableValue(def, InstanceID.createInstanceID()));
1326
1327 case SimpleVariableDefinition.TYPE_NUMBER:
1328 // Randomly pick a Number data type.
1329 // Fall back to a good/large/simple Number type.
1330 switch(rnd.nextInt(6))
1331 {
1332 case 1:
1333 return(new SimpleVariableValue(def,
1334 new Integer(rnd.nextInt())));
1335 case 2:
1336 return(new SimpleVariableValue(def,
1337 new Short((short) rnd.nextInt())));
1338 case 3:
1339 return(new SimpleVariableValue(def,
1340 new Byte((byte) rnd.nextInt())));
1341 case 4:
1342 return(new SimpleVariableValue(def,
1343 new Float((float) rnd.nextDouble())));
1344 case 5:
1345 return(new SimpleVariableValue(def,
1346 new Double((short) rnd.nextGaussian())));
1347 }
1348 // Default is to return a Long.
1349 return(new SimpleVariableValue(def,
1350 new Long(rnd.nextLong())));
1351
1352 case SimpleVariableDefinition.TYPE_STRING:
1353 return(new SimpleVariableValue(def,
1354 "random test string ... " + String.valueOf(rnd.nextLong())));
1355
1356 default: // Should not happen: we need to fix the code!
1357 throw new IllegalStateException("Cannot generate variable of this type: " + def);
1358 }
1359 }
1360
1361 /**Run after _setUpLocalVars() to ensure that locals did NOT propagate between pipelines.
1362 *
1363 * @param values
1364 * @param nPipes
1365 * @param pipelines
1366 * @param what
1367 */
1368 private static void _testLocalNonPropagation(final SimpleVariableValue[] values,
1369 final int nPipes,
1370 final BasicVarMgrInterface[][] pipelines,
1371 final List[] what)
1372 {
1373 // Now, after we know that variables have had time to propagate,
1374 // we make sure that local variables have not done so,
1375 // but that any original values we set are still intact.
1376 // We'll check the state of each of the variables
1377 // on each of the pipelines...
1378 for(int i = values.length; --i >= 0; )
1379 {
1380 final SimpleVariableValue val = values[i];
1381 final SimpleVariableDefinition def = val.getDef();
1382 for(int j = nPipes; --j >= 0; )
1383 {
1384 // Skip over null (absent) pipelines...
1385 if(pipelines[j] == null)
1386 { continue; }
1387
1388 final BasicVarMgrInterface pipelineTop =
1389 pipelines[j][0];
1390
1391 // The given variable should be set to our value
1392 // ONLY on the pipeline that we set it on...
1393 final boolean shouldBeSetHere =
1394 what[j].contains(def);
1395 try
1396 {
1397 final SimpleVariableValue svv = pipelineTop.getVariable(def);
1398 if(shouldBeSetHere)
1399 {
1400 assertEquals("Local variable should still be set on the pipeline we set it",
1401 val, svv);
1402 }
1403 else
1404 {
1405 assertFalse("Local variable should NOT have propagated to another pipeline",
1406 val.equals(svv));
1407 }
1408 }
1409 catch(final IOException e)
1410 {
1411 // Not capable of local get right now...
1412 }
1413 catch(final UnsupportedOperationException e)
1414 {
1415 // Not capable of local get...
1416 }
1417 }
1418 }
1419 }
1420
1421 /**Set random variables on random pipes for testing later.
1422 * This records which variables and values were set on each pipeline
1423 * for later testing by _testLocalNonPropagation() later.
1424 * <p>
1425 * There must be at least a master and one slave for this to be
1426 * meaningful.
1427 *
1428 * @param localWritableTestVars
1429 * @param pipelines
1430 * @param nPipes
1431 * @param what
1432 * @param values
1433 */
1434 private static void _setUpLocalVars(final SimpleVariableDefinition[] localWritableTestVars,
1435 final BasicVarMgrInterface[][] pipelines,
1436 final int nPipes,
1437 final List<SimpleVariableDefinition>[] what,
1438 final SimpleVariableValue[] values)
1439 {
1440 // Set empty lists for each pipeline of what variables were set for it.
1441 for(int i = nPipes; --i >= 0; )
1442 { what[i] = new ArrayList<SimpleVariableDefinition>(localWritableTestVars.length); }
1443
1444 // Now randomly choose one of the pipelines
1445 // to set each test variable on...
1446 for(int i = localWritableTestVars.length; --i >= 0; )
1447 {
1448 final SimpleVariableDefinition def =
1449 localWritableTestVars[i];
1450
1451 // Chose a pipeline to set the variable in.
1452 int which;
1453 // Spin until we find a non-null pipeline...
1454 while(pipelines[which = rnd.nextInt(nPipes)] == null)
1455 { }
1456 // Note which variable is set in which pipeline.
1457 what[which].add(def);
1458
1459 // Chose a random value for each variable
1460 // (of an appropriate type),
1461 // record it,
1462 // and set it,
1463 // (and check that it got set here).
1464 // It is acceptable for the set/get to be vetoed with
1465 // IOException or UnsupportedOperationException.
1466 switch(def.getType())
1467 {
1468 case SimpleVariableDefinition.TYPE_NUMBER:
1469 values[i] = new SimpleVariableValue(def,
1470 new Long(rnd.nextLong()));
1471 break;
1472 case SimpleVariableDefinition.TYPE_STRING:
1473 values[i] = new SimpleVariableValue(def,
1474 String.valueOf(rnd.nextGaussian()));
1475 break;
1476 default:
1477 throw new Error("Cannot support local variable of this type in this test: " + def);
1478 }
1479 try
1480 {
1481 final BasicVarMgrInterface pipelineTop = pipelines[which][0];
1482 pipelineTop.setVariable(values[i]);
1483 assertEquals("Local variable set accepted but did not work",
1484 values[i], pipelineTop.getVariable(def));
1485 }
1486 catch(final IOException e)
1487 {
1488 // Not capable of local set/get right now...
1489 }
1490 catch(final UnsupportedOperationException e)
1491 {
1492 // Not capable of local set/get...
1493 }
1494 }
1495 }
1496
1497 /**Run variable tests on one pipeline ans returns the InstanceID; never null.
1498 * Runs a whole suite of tests on one individual pipeline within one system.
1499 * <p>
1500 * Used by pipelineVariableTest().
1501 * <p>
1502 * Throws an exception, assertion or fail()s upon discovering a problem.
1503 * <p>
1504 * Tests that:
1505 * <ul>
1506 * <li>The system ID can be fetched from any tap on the pipeline,
1507 * at any time, in any order (a random sampling will be done),
1508 * and that that ID is consistent. No IOException can be thrown.
1509 * <li>Local and global variables can be set at any stage of a pipeline
1510 * with the value immediately retrievable there or the set rejected with
1511 * UnsupportedOperationException;
1512 * check with setVariable() and setVariables().
1513 * <li>Non-existent variables cannot be set in the tunnel.
1514 * <li>Values successfully written anywhere in the pipeline are available
1515 * everywhere else within the specified maximum latency.
1516 * <li>Variable set operations are not visibly reordered,
1517 * eg that cacheing and tunnel (etc) stages do not cause re-ordering.
1518 * </ul>
1519 * <p>
1520 * The pipeline order is downstream-first, end-point-last,
1521 * eg the persistent store or tunnel for a pipeline would
1522 * be at the highest-numbered index,
1523 * and the normal access point for the tunnel
1524 * (eg as exposed by the DataSourceBean) is at index 0.
1525 *
1526 * @return the system ID for the pipeline.
1527 *
1528 * @param pipeline one or more stages/taps on a pipeline in order,
1529 * downstream first,
1530 * never null nor empty nor containing null or duplicate entries
1531 * @param stopBy stop testing by or soon after this time,
1532 * having done at least one of each type of test
1533 *
1534 * @throws IOException if an inappropriate IOException is thrown by
1535 * the pipeline under test
1536 */
1537 public static InstanceID singlePipelineTest(
1538 final SimpleVariablePipelineIF pipeline[],
1539 final long stopBy)
1540 throws IOException,
1541 InterruptedException
1542 {
1543 if((pipeline == null) || (pipeline.length == 0))
1544 { throw new IllegalArgumentException(); }
1545
1546 //Main.getOut().println("[singlePipelineTest(): starting...]");
1547
1548 // Force the pipeline to be quiet.
1549 try{ pipeline[0].syncVariables(true); }
1550 catch(final IOException e) { /* Ignore legitimate I/O issues. */ }
1551
1552 //Main.getOut().println("[singlePipelineTest(): pipeline quietened...]");
1553
1554 // Check handling of system ID in pipeline
1555 // (and get the value for this pipeline).
1556 final InstanceID iid = checkSystemIDHandling(pipeline, stopBy);
1557
1558 //Main.getOut().println("[singlePipelineTest(): SystemID handling checked...]");
1559
1560 // Test simple get and set operations on locals and globals.
1561 checkSimpleSetGet(pipeline, stopBy);
1562
1563 //Main.getOut().println("[singlePipelineTest(): simple set/get checked...]");
1564
1565 // Ensure that illegal ops are vetoed, and in the right way.
1566 checkIllegalGetSetOpsVetoedCorrectly(pipeline, stopBy);
1567
1568 //Main.getOut().println("[singlePipelineTest(): vetoing of illegal ops checked...]");
1569
1570 // Simply check that we can retrieve the "all" bucket
1571 // (which should not be authoritative)
1572 // or at least not blow up when trying to do so.
1573 final BitSet whichValues = new BitSet(1);
1574 whichValues.set(0);
1575 for(int i = pipeline.length; --i >= 0; )
1576 {
1577 // Try getting first slot with real/null BitSet,
1578 // which should be equivalent.
1579 final EventVariableValue evvs[] =
1580 pipeline[0].getEventValues(SystemVariables.TEST_STRING_GLOBAL_EVENT,
1581 EventPeriod.VLONG,
1582 0,
1583 rnd.nextBoolean() ? null : whichValues);
1584 assert(evvs != null);
1585 if(evvs.length > 0)
1586 {
1587 if(evvs[0] != null)
1588 { assert(!evvs[0].isAuthoritative()); }
1589 }
1590 }
1591
1592 // Force the pipeline to be quiet.
1593 try { pipeline[0].syncVariables(true); }
1594 catch(final IOException e) { /* Ignore legitimate I/O issues. */ }
1595
1596 //Main.getOut().println("[singlePipelineTest(): done; pipeline quietened.]");
1597
1598 return(iid);
1599 }
1600
1601 /**If true, randomise order of variable tests (and skip some if we are short of time).
1602 * This can only be done if we quieten the pipelines
1603 * between tests so that tests don't conflict with one another
1604 * by having updates from one test still bouncing around during the next.
1605 * <p>
1606 * This allows us to do as few as one test each time,
1607 * on the grounds that we will test the entire range over time.
1608 */
1609 private static final boolean RANDOMISE_VARIABLE_TEST_ORDER = true;
1610
1611 /**Test set and get of globals and locals.
1612 * Tests simple set/get operations work and preserve timestamps,
1613 * and that values do not get "lost" due to races, cache errors, etc.
1614 * <p>
1615 * Also tests that variable set operations are not visibly reordered,
1616 * eg that cacheing and tunnel (etc) staged do not cause re-ordering.
1617 * <p>
1618 * Tests that locals (that we set) never get a (non-null) globalMap.
1619 *
1620 * @param pipeline pipeline; non-null, non-zero-length
1621 * @param stopBy time to stop running tests by, if possible
1622 */
1623 private static void checkSimpleSetGet(final SimpleVariablePipelineIF[] pipeline,
1624 final long stopBy)
1625 throws InterruptedException
1626 {
1627 if((pipeline == null) || (pipeline.length < 1))
1628 { throw new IllegalArgumentException(); }
1629 final int plen = pipeline.length;
1630
1631 // Test that we can set and get globals and locals anywhere,
1632 // or else have the updates rejected with an
1633 // UnsupportedOperationException (or possibly an IOException).
1634 //
1635 // Also test that we can read back any values set from everywhere
1636 // else in the pipeline within the agreed maximum latency...
1637 //
1638 // Unless we force the pipeline to go quiet between tests,
1639 // we cannot test the same variable twice in succession,
1640 // since old updates may still be richoceting around the system,
1641 // so we must interleave tests of different variables.
1642 // Further, we cannot test one global variable twice in succession
1643 // in case we are not operating on the master's pipeline
1644 // and thus interleaving a local-variable test
1645 // does not guarantee that updates from a previous global
1646 // have died down.
1647 //
1648 // So, we can only randomise the test order
1649 // if we wait for the pipeline to be quiet before and after...
1650 //
1651 // TODO: add non-Number test variables
1652 final SimpleVariableDefinition[] writeTestVars =
1653 {
1654 SystemVariables.TEST_NUMBER_GLOBAL,
1655 SystemVariables.TEST_NUMBER_GLOBAL2,
1656 SystemVariables.TEST_NUMBER_LOCAL,
1657 SystemVariables.TEST_NUMBER_LOCAL2,
1658 };
1659 if(RANDOMISE_VARIABLE_TEST_ORDER)
1660 { Collections.shuffle(Arrays.asList(writeTestVars)); }
1661 try
1662 {
1663 // Try a few times for each stage...
1664 // Run quite a few tests to catch intermittent problems.
1665 final int maxTests = 5 + plen/5;
1666 for(int j = 0; j < maxTests; ++j)
1667 {
1668 // Make sure we never try to use the same variable twice
1669 // in succession else previous changes may still be
1670 // reverberating around the system...
1671 for(int i = 0; i < writeTestVars.length; ++i)
1672 {
1673 // Once we've tested all the variables at least once,
1674 // we can force syncVariables(true) to speed things up...
1675 // If we are randomising the variable test order,
1676 // then we can get away with syncing all but the first test.
1677 final boolean doSync = (j > 0) ||
1678 (RANDOMISE_VARIABLE_TEST_ORDER && (i > 0));
1679
1680 final SimpleVariableDefinition def = writeTestVars[i];
1681 // Pick a pipeline stage at random;
1682 // all should behave reasonably
1683 // even if not all functionality is available.
1684 final int stageN = rnd.nextInt(plen);
1685 final BasicVarMgrInterface whichStage = pipeline[stageN];
1686
1687 // Use setVariable()...
1688 // Create new value...
1689 final SimpleVariableValue svv1 = new SimpleVariableValue(
1690 def, new Integer(rnd.nextInt()));
1691 try
1692 {
1693 // Check that we can do zero-or-more
1694 // previous and distinct sets of the variable,
1695 // and that these values "lose"
1696 // and that the final set operation prevails.
1697 final int preSets = rnd.nextInt(3);
1698 for(int p = preSets; --p >= 0; )
1699 {
1700 whichStage.setVariable(new SimpleVariableValue(def,
1701 new Integer(rnd.nextInt())));
1702 }
1703
1704 // Now do the set that we care about...
1705 // Randomly check that setVariable() or setVariables()
1706 // work equivalently.
1707 if(rnd.nextBoolean())
1708 { whichStage.setVariable(svv1); }
1709 else
1710 {
1711 final List<SimpleVariableValue> vars = new ArrayList<SimpleVariableValue>();
1712 for(int p = preSets; --p >= 0; )
1713 {
1714 vars.add(new SimpleVariableValue(def,
1715 new Integer(rnd.nextInt())));
1716 }
1717 // Put our wanted value last so that it should win...
1718 vars.add(svv1);
1719 final SimpleVariableValue vs[] = new SimpleVariableValue[vars.size()];
1720 vars.toArray(vs);
1721 if(0 == whichStage.setVariables(vs))
1722 { throw new UnsupportedOperationException("no variable were set; probably OK"); }
1723 }
1724
1725 // Make sure that we can get our value back straight away.
1726 assertEquals("Must be able to immediately retrieve from same stage ("+stageN+") value set with setVariable() for " + def,
1727 svv1, whichStage.getVariable(def));
1728 if(def.isLocal())
1729 {
1730 assertEquals("Timestamp must be preserved (at least for locals) at same stage value set with setVariable() for " + def,
1731 svv1.getTimestamp(), whichStage.getVariable(def).getTimestamp());
1732 }
1733
1734 // Check that local variable never has non-null globalMap...
1735 if(def.isLocal())
1736 {
1737 assertNull("Local variable should always have null globalMap",
1738 whichStage.getVariable(def).getGlobalMap());
1739 }
1740
1741 // Now test that value can be seen at all stages
1742 // within the allowed maximum latency...
1743 // Sometimes allow a syncVariables(true)
1744 // at the TOP of the pipeline
1745 // to stochastically speed up the tests
1746 // and to check that the semantics are correct.
1747 final long waitStart = System.currentTimeMillis();
1748 final long warnAfter = waitStart + 10 * 1000;
1749 final long retrieveBy = waitStart +
1750 SystemVariables.MAX_VALUE_DISTRIBUTION_LATENCY_MS;
1751 for(int r = plen; --r >= 0; )
1752 {
1753 try
1754 {
1755 // Keep trying to retrieve the variable's value
1756 // until we see the value we expect,
1757 // or we exceed the maximum allowed latency.
1758 // If it is a global, we allow the value
1759 // to be in the globalMap instead.
1760 int waitTime = 10 + rnd.nextInt(100);
1761 if(doSync) { pipeline[0].syncVariables(true); }
1762 while(!svv1.equals(pipeline[r].getVariable(def)))
1763 {
1764 final SimpleVariableValue svvActual =
1765 pipeline[r].getVariable(def);
1766 if(svvActual != null)
1767 {
1768 final Map<InstanceID,SimpleVariableValue> sAGM = svvActual.getGlobalMap();
1769 if(sAGM != null)
1770 {
1771 if(sAGM.values().contains(svv1))
1772 {
1773 // OK, found it in the map instead!
1774 break;
1775 }
1776 }
1777 }
1778
1779 // If we did a syncVariables(true),
1780 // we should have seen the value immediately.
1781 if(doSync)
1782 { fail("Could not see value propagated to stage "+r+" immediately after syncVariables(true) (written at stage "+stageN+") for " + def); }
1783
1784 final long now = System.currentTimeMillis();
1785 if(now > retrieveBy)
1786 { fail("Could not see value propagated to stage "+r+" in time (written at stage "+stageN+") for " + def); }
1787 if(now > warnAfter)
1788 { Main.getOut().println("[Waiting at stage "+r+" to see value written at stage "+stageN+" for "+def+" ("+svv1.getFullDescription()+", got "+((svvActual==null)?"null":svvActual.getFullDescription())+")"+"...]"); }
1789 Thread.sleep(waitTime *= 2); // Wait a while...
1790 }
1791
1792 // Check timestamp is preserved at least for
1793 // at least for all locals.
1794 if(def.isLocal())
1795 {
1796 assertEquals("Timestamp must be preserved at same stage value set with setVariable() for " + def,
1797 svv1.getTimestamp(), pipeline[r].getVariable(def).getTimestamp());
1798 }
1799
1800 // Check that local variable never has non-null globalMap...
1801 if(def.isLocal())
1802 {
1803 assertNull("Local variable should always have null globalMap",
1804 pipeline[r].getVariable(def).getGlobalMap());
1805 }
1806 }
1807 catch(final IOException e)
1808 {
1809 // A stage that throws an IOException
1810 // is excused from this test...
1811 Main.getOut().println("[IOException at stage "+r+" fetching value written at stage "+stageN+", so skipping.]");
1812 continue;
1813 }
1814 catch(final UnsupportedOperationException e)
1815 {
1816 // A stage that throws an UnsupportedOperationException
1817 // is excused from this test...
1818 Main.getOut().println("[UnsupportedOperationException at stage "+r+" fetching value written at stage "+stageN+", so skipping.]");
1819 continue;
1820 }
1821 }
1822 }
1823 // It is acceptable for the operation to be vetoed.
1824 catch(final IOException e)
1825 {
1826 Main.getOut().println("[setVariable() was rejected with IOException for " + def + ".]");
1827 }
1828 catch(final UnsupportedOperationException e)
1829 {
1830 Main.getOut().println("[setVariable() was rejected with UnsupportedOperationException for " + def + ".]");
1831 // But if the operation was vetoed should we have done it?
1832 // assertNotSame("Value seems to have been set with setVariable() even though apparently vetoed " + def,
1833 // svv1, whichStage.getVariable(def));
1834 }
1835
1836 // Use setVariables()...
1837 // Create new value...
1838 final SimpleVariableValue svv2 = new SimpleVariableValue(
1839 def, new Short((short) rnd.nextInt()));
1840 try
1841 {
1842 final int nSet =
1843 whichStage.setVariables(new SimpleVariableValue[]{svv2});
1844 assertTrue("setVariables() result must not be negative",
1845 nSet >= 0);
1846 assertTrue("setVariables() result must not exceed number of values passed",
1847 nSet <= 1);
1848 if(nSet > 0)
1849 {
1850 assertEquals("Must be able to retrieve value set with setVariables() for " + def,
1851 svv2, whichStage.getVariable(def));
1852 }
1853 }
1854 // It is acceptable for the operation to be vetoed.
1855 catch (final IOException e)
1856 {
1857 Main.getOut().println("[setVariables() was rejected with IOException for " + def + ".]");
1858 }
1859 catch (final UnsupportedOperationException e)
1860 {
1861 Main.getOut().println("[setVariables() was rejected with UnsupportedOperationException for " + def + ".]");
1862 // But if the operation was vetoed should we have done it?
1863 // assertNotSame("Value seems to have been set with setVariables() even though apparently vetoed " + def,
1864 // svv2, whichStage.getVariable(def));
1865 }
1866
1867 // Stop once we've done at least one test
1868 // if doing tests in random order
1869 // so that over time we do test all the variables.
1870 if(RANDOMISE_VARIABLE_TEST_ORDER)
1871 { if(!notAfter(stopBy)) { return; } } // Outta time...
1872 }
1873
1874 // We've tested all the variables at least once,
1875 // so if we're out of time we quit now.
1876 if(!notAfter(stopBy)) { return; } // Outta time...
1877 }
1878 }
1879 catch(final IllegalArgumentException e)
1880 {
1881 e.printStackTrace(Main.getErr());
1882 fail("Operation failed inappropriately with IllegalArgumentException");
1883 }
1884 // catch(IOException e)
1885 // {
1886 // e.printStackTrace(Main.getErr());
1887 // fail("Operation failed inappropriately with IOException");
1888 // }
1889 }
1890
1891 /**Check that the pipeline handles system ID correctly.
1892 * Mainly that the value can be fetched from any stage at any time,
1893 * and that it is always the same for a given pipeline.
1894 *
1895 * @param pipeline pipeline; non-null, non-zero-length
1896 * @param stopBy time to stop running tests by, if possible
1897 */
1898 private static InstanceID checkSystemIDHandling(
1899 final BasicVarMgrInterface[] pipeline,
1900 final long stopBy)
1901 throws IOException
1902 {
1903 if((pipeline == null) || (pipeline.length < 1))
1904 { throw new IllegalArgumentException(); }
1905 final int plen = pipeline.length;
1906
1907 try
1908 {
1909 // Check that ID can be fetched from any stage at any time,
1910 // and is consistent.
1911 final SimpleVariableValue iidVar = pipeline[rnd.nextInt(plen)].
1912 getVariable(SystemVariables.LOCAL_SYS_ID);
1913 assertNotNull("Must always be able to fetch IID variable", iidVar);
1914 final InstanceID iid = (InstanceID) iidVar.getValue();
1915 assertNotNull("IID variable value must always be non null", iid);
1916 // Test a reasonable number of samples.
1917 for(int i = 2 + plen / 10; --i >= 0; )
1918 {
1919 final InstanceID iid2 = (InstanceID) pipeline[rnd.nextInt(plen)].
1920 getVariable(SystemVariables.LOCAL_SYS_ID).getValue();
1921 assertEquals("System ID must be the same on each fetch", iid, iid2);
1922 if(!notAfter(stopBy)) { break; } // Outta time...
1923 }
1924
1925 // Check that we cannot overwrite the ID value...
1926 // Try at every stage of the pipeline,
1927 // and make sure that we can read it back.
1928 final InstanceID bogusIID = InstanceID.createInstanceID();
1929 assertFalse("Got duplicate InstanceID", iid.equals(bogusIID));
1930 for(int j = 3; --j >= 0; ) // Try several times to overwrite.
1931 {
1932 for(int i = plen; --i >= 0; )
1933 {
1934 try
1935 {
1936 pipeline[i].setVariable(new SimpleVariableValue(
1937 SystemVariables.LOCAL_SYS_ID, bogusIID));
1938 }
1939 catch(final IllegalArgumentException e) { } // Absorb attempts to veto.
1940 catch(final UnsupportedOperationException e) { } // Absorb attempts to veto.
1941 final InstanceID iid2 = (InstanceID) pipeline[i].
1942 getVariable(SystemVariables.LOCAL_SYS_ID).getValue();
1943 assertEquals("System ID must not be overwritable", iid, iid2);
1944 }
1945 if(!notAfter(stopBy)) { break; } // Outta time...
1946 }
1947
1948 return(iid);
1949 }
1950 catch(final IOException e)
1951 {
1952 e.printStackTrace(Main.getErr());
1953 fail("IOException inappropriately thrown while testing system ID access");
1954 throw e; // Should not actually get here...
1955 }
1956 }
1957
1958 /**This checks that operations are vetoed when they should be.
1959 * This also checks that an appriopiate mechanism is used,
1960 * eg the correct exception type so that the caller can understand
1961 * the propblem.
1962 *
1963 * @param pipeline pipeline; non-null, non-zero-length
1964 * @param stopBy time to stop running tests by, if possible
1965 */
1966 private static void checkIllegalGetSetOpsVetoedCorrectly(final BasicVarMgrInterface[] pipeline,
1967 final long stopBy)
1968 {
1969 if((pipeline == null) || (pipeline.length < 1))
1970 { throw new IllegalArgumentException(); }
1971 final int plen = pipeline.length;
1972
1973 // Try a few times for each stage...
1974 for(int j = 3 + plen/10; --j >= 0; )
1975 {
1976 final SimpleVariableDefinition bogusDef = makeRandomSimpleVariableDefinition();
1977 final SimpleVariableValue svvBogus = new SimpleVariableValue(
1978 bogusDef, null);
1979 for(int i = plen; --i >= 0; )
1980 {
1981 final BasicVarMgrInterface stage = pipeline[i];
1982
1983 try
1984 {
1985 // Ensure that attempt to set null variable is disallowed.
1986 try
1987 {
1988 stage.setVariable(null);
1989 fail("Apparently allowed null variable to be set with setVariable() (from downstream)");
1990 }
1991 catch(final IllegalArgumentException e) { }
1992
1993 // Ensure that attempt to get null variable is disallowed.
1994 try
1995 {
1996 stage.getVariable(null);
1997 fail("Apparently allowed null variable fetch with getVariable() (from downstream)");
1998 }
1999 catch(final IllegalArgumentException e) { }
2000
2001 // Ensure that attempt to set null variable list is disallowed.
2002 try
2003 {
2004 stage.setVariables(null);
2005 fail("Apparently allowed null variable array to be set with setVariables() (from downstream)");
2006 }
2007 catch(final IllegalArgumentException e) { }
2008
2009 // Ensure that attempt to set null variable in non-empty list is disallowed.
2010 try
2011 {
2012 stage.setVariables(new SimpleVariableValue[]{null});
2013 fail("Apparently allowed null variable to be set with setVariables() (from downstream)");
2014 }
2015 catch(final IllegalArgumentException e) { }
2016
2017 // But ensure that a legal set of zero veriuables is allowed.
2018 assertEquals("Harmless set of empty array of variables should be allowed",
2019 0, stage.setVariables(new SimpleVariableValue[0]));
2020 }
2021 catch(final IOException e)
2022 {
2023 e.printStackTrace(Main.getErr());
2024 fail("Illegal operation inappropriately vetoed with IOException");
2025 }
2026 catch(final UnsupportedOperationException e)
2027 {
2028 e.printStackTrace(Main.getErr());
2029 fail("Illegal operation inappropriately vetoed with UnsupportedOperationException");
2030 }
2031
2032 try
2033 {
2034 stage.setVariable(svvBogus);
2035 fail("Apparently allowed non-extant variable to be set with setVariable() (from downstream)");
2036 }
2037 catch(final IOException e) { }
2038 catch(final UnsupportedOperationException e) { }
2039 catch(final IllegalArgumentException e) { }
2040
2041 try
2042 {
2043 if(stage.setVariables(new SimpleVariableValue[]{svvBogus}) != 0)
2044 { fail("Apparently allowed non-extant variable to be set with setVariables() (from downstream)"); }
2045 }
2046 catch(final IOException e) { }
2047 catch(final UnsupportedOperationException e) { }
2048 catch(final IllegalArgumentException e) { }
2049 }
2050
2051 if(!notAfter(stopBy)) { break; } // Outta time...
2052 }
2053 }
2054
2055 /**Makes a random definition; never returns null.
2056 * This randomly generates a variable definition with:
2057 * <ul>
2058 * <li>a unique random name
2059 * <li>a random type (out of all the possible variable types)
2060 * <li>random properties such as local/global, read-only/read-write
2061 * </ul>
2062 * <p>
2063 * Package-visible so as to be available to other test routines.
2064 */
2065 static SimpleVariableDefinition makeRandomSimpleVariableDefinition()
2066 {
2067 // Check that we cannot set null variable
2068 // or variable not listed in SystemVariables.defs.
2069 // We try this with a random type and flags...
2070 final SimpleVariableDefinition bogusDef = new SimpleVariableDefinition(
2071 "bogus.random.name." + (rnd.nextLong() >>> 1),
2072 SimpleVariableDefinition.TYPE__MIN +
2073 rnd.nextInt(1 + SimpleVariableDefinition.TYPE__MAX -
2074 SimpleVariableDefinition.TYPE__MIN),
2075 rnd.nextBoolean(),
2076 rnd.nextBoolean(),
2077 rnd.nextBoolean(),
2078 rnd.nextBoolean(), 0, null);
2079 return(bogusDef);
2080 }
2081
2082 /**Class to encapsulate "local" tunnel "server".
2083 * Attaches to top of data pipeline and allows
2084 * raw RPC to interact with that pipeline.
2085 */
2086 static class LocalTunnelServer
2087 {
2088 /**Construct this with the data pipeline.
2089 * @param source data source; never null.
2090 */
2091 LocalTunnelServer(final SimpleExhibitPipelineIF source)
2092 {
2093 this(source, false);
2094 }
2095
2096 /**Construct this with the data pipeline.
2097 * @param source data source; never null.
2098 * @param alwaysUseCache if true then always enable the RPC cache,
2099 * else switch it off and on at random
2100 */
2101 LocalTunnelServer(final SimpleExhibitPipelineIF source,
2102 final boolean alwaysUseCache)
2103 {
2104 if(source == null) { throw new IllegalArgumentException(); }
2105 this.source = source;
2106 this.alwaysUseCache = alwaysUseCache;
2107 }
2108
2109 /**Upstream data source; never null. */
2110 private final SimpleExhibitPipelineIF source;
2111
2112 /**If true then always enable the RPC cache, else switch it off and on at random. */
2113 private final boolean alwaysUseCache;
2114
2115 /**An RPC cache-handler instance. */
2116 private final ExhibitDataTunnelSource.HIRPCCache _RPC_cache = new ExhibitDataTunnelSource.HIRPCCache();
2117
2118 /**Perform RPC calls on the wrapped data source.
2119 *
2120 * @param request request packet; never null
2121 * @return response packet; never null
2122 * @throws IOException in case of I/O difficulties
2123 */
2124 ExhibitDataTunnelSource.RawPacket doRPCRaw(
2125 final ExhibitDataTunnelSource.RawPacket request)
2126 throws IOException
2127 {
2128 // Randomly use the cache or not unless alwaysUseCache.
2129 // Overall behaviour should be correct either way.
2130 final ExhibitDataTunnelSource.HIRPCCache cache =
2131 (alwaysUseCache || rnd.nextBoolean()) ? _RPC_cache : null;
2132 return(ExhibitDataTunnelSource.handleInboundRPC(source, request, null, cache, logger));
2133 }
2134 }
2135
2136 /**Local tunnel client/source, for connecting to LocalTunnelServer.
2137 * This simulates a real tunnel, but all done locally.
2138 * <p>
2139 * Can be configured to force (de)serialisation of the RPC data
2140 * as if sent on the wire, which will test the serialisation code
2141 * and ensure no accidental data sharing "by reference" is going on.
2142 */
2143 static class LocalTunnelSource extends ExhibitDataTunnelSource
2144 {
2145 /**Construct local tunnel source.
2146 * Sequesters reference to the LocalTunnelServer (the master),
2147 * and notes whether all RPC packets should be (de)serialised.
2148 *
2149 */
2150 LocalTunnelSource(final LocalTunnelServer master,
2151 final boolean forceSerialization,
2152 final SimpleLoggerIF logger)
2153 {
2154 super(logger);
2155 if(master == null) { throw new IllegalArgumentException(); }
2156 this.master = master;
2157 this.forceSerialization = forceSerialization;
2158 }
2159
2160 /**Master server connection; never null. */
2161 private final LocalTunnelServer master;
2162
2163 /**If true, force (de)serialisation as if on the wire. */
2164 private final boolean forceSerialization;
2165
2166 /**Provide the implementation of the data transport.
2167 */
2168 @Override
2169 protected ExhibitDataTunnelSource.RawPacket doRPCRaw(
2170 final ExhibitDataTunnelSource.RawPacket request)
2171 throws IOException
2172 {
2173 // If not forcing serialisation,
2174 // do the RPC call directly.
2175 if(!forceSerialization)
2176 {
2177 return(master.doRPCRaw(request));
2178 }
2179
2180 // Do it the hard way, serialised via byte streams.
2181 // We allow caller-selected attempts to compress the payload.
2182 final ByteArrayOutputStream baos1 =
2183 new ByteArrayOutputStream(32 + request.plLength);
2184 request.writePacket(baos1);
2185 final ByteArrayInputStream bais1 =
2186 new ByteArrayInputStream(baos1.toByteArray());
2187 final ExhibitDataTunnelSource.RawPacket response =
2188 master.doRPCRaw(ExhibitDataTunnelSource.RawPacket.readPacket(bais1));
2189 final ByteArrayOutputStream baos2 =
2190 new ByteArrayOutputStream(32 + response.plLength);
2191 response.writePacket(baos2);
2192 final ByteArrayInputStream bais2 =
2193 new ByteArrayInputStream(baos2.toByteArray());
2194 return(ExhibitDataTunnelSource.RawPacket.readPacket(bais2));
2195 }
2196
2197 /**Does nothing in this implementation. */
2198 @Override
2199 public void destroy()
2200 {
2201 }
2202 }
2203
2204 /**Do simple test of tunnel connectivity.
2205 * We do this with and without real serialisation,
2206 * so in passing we are testing the on-the-wire protocol too.
2207 * <p>
2208 * We explicitly check that getting/setting a novel variable on the slave
2209 * (ie not known to the master)
2210 * is quietly ignored and does not cause any exception/failure.
2211 * <p>
2212 * This is a <em>very</em> simple test;
2213 * look elsewhere for comprehensive tests!
2214 */
2215 public void testLocalTunnelSimple()
2216 throws Exception
2217 {
2218 // Do alternate tests with/out forced (de)serialisation...
2219 for(int i = 2; --i >= 0; )
2220 {
2221 // Chose whether to force serialisation...
2222 final boolean forceSerialisation = (i & 1) == 0;
2223
2224 // Tunnel endpoint at master (wraps a BasicVarMar).
2225 final SimpleExhibitPipelineIF ep = new BasicVarMgrEndpoint();
2226
2227 // Tunnel client end on slave.
2228 final SimpleExhibitPipelineIF ts = new LocalTunnelSource(
2229 new LocalTunnelServer(ep), forceSerialisation, logger);
2230
2231 // Check that we can set a value on the slave
2232 // and see it on the master,
2233 // and vice versa.
2234 // This should be immediate,
2235 // and show up as the main value of the variable,
2236 // because there is no cacheing involved.
2237 final SimpleVariableDefinition def =
2238 SystemVariables.TEST_NUMBER_GLOBAL;
2239 final Number v1 = new Long(rnd.nextLong());
2240 final SimpleVariableValue svv1 = new SimpleVariableValue(def, v1);
2241 ts.setVariable(svv1);
2242 assertEquals("Must be able to see on the master the value set on the slave",
2243 svv1, ep.getVariable(def));
2244 final Number v2 = new Integer(rnd.nextInt());
2245 final SimpleVariableValue svv2 = new SimpleVariableValue(def, v2);
2246 ep.setVariable(svv2);
2247 assertEquals("Must be able to see on the slave the value set on the master",
2248 svv2, ts.getVariable(def));
2249
2250
2251 // Check that get of variable unknown to master does no harm.
2252 for( ; ; )
2253 {
2254 final SimpleVariableDefinition novelDef = makeRandomSimpleVariableDefinition();
2255 // final SimpleVariableValue rndSvv = makeRandomSimpleVariableValue(novelDef);
2256 if(!novelDef.isLocal())
2257 {
2258 assertNull("Getting novel (non-local) value with getVariable() must return null", ts.getVariable(novelDef));
2259 if(novelDef.isEvent())
2260 {
2261 assertNotNull(ts.getEventValues(novelDef, EventPeriod.VLONG, 0, null)); // Must not throw exception...
2262 }
2263 }
2264
2265 // Stop when we've tested at least one global event value...
2266 if(!novelDef.isLocal() && novelDef.isEvent()) { break; }
2267 }
2268
2269
2270 // Now try normal pipeline tests in the very simple setup.
2271 final SimpleExhibitPipelineIF slave1[] = { ts };
2272 final SimpleExhibitPipelineIF master[] = { ep };
2273 pipelineVariableTest(slave1, null, master,
2274 System.currentTimeMillis() + 30000L); // Run test for <= 30s.
2275 }
2276 }
2277
2278 /**Run full tunnel variable test.
2279 * Emulates a reasonably-realistic setup with a master and two slaves,
2280 * all of which hold a caching stage.
2281 * <p>
2282 * One of the tunnels forces full (de)serialisation of its RPC traffic,
2283 * thus exercising much of the HTTP tunnel code in passing.
2284 * <p>
2285 * We poll() all the pipelines at different speeds to ensure that
2286 * rate synchronisation is not critical to correct functioning.
2287 */
2288 public void testLocalTunnelFull()
2289 throws Exception
2290 {
2291 // Tunnel endpoint at master (wraps a BasicVarMar).
2292 final SimpleExhibitPipelineIF epm = new BasicVarMgrEndpoint();
2293 // Caching stage in master (no write-through).
2294 final SimpleExhibitPipelineIF csm = new TestVarCacheStage(epm, false);
2295 // Master pipeline.
2296 final SimpleExhibitPipelineIF master[] = { csm, epm };
2297 // Run poll() so pipeline can sync() its vars.
2298 final SimpleExhibitPipelineIF masterTop = master[0];
2299 t.scheduleAtFixedRate(new TimerTask(){
2300 @Override
2301 public void run()
2302 {
2303 try { masterTop.poll(new GenProps()); }
2304 catch(final Exception e) { e.printStackTrace(Main.getErr()); }
2305 }
2306 },
2307 0L, 1000L); // Run master poll() every 1s.
2308
2309 // Master stage to which tunnel attaches on master.
2310 final SimpleExhibitPipelineIF masterTunnelServer = csm;
2311
2312 // Tunnel client end on slave1; serialisation is forced.
2313 final SimpleExhibitPipelineIF ts1 = new LocalTunnelSource(
2314 new LocalTunnelServer(masterTunnelServer), true, logger);
2315 // Tunnel caching stage on slave1.
2316 final SimpleExhibitPipelineIF cs1 = new TestVarCacheStage(ts1, false);
2317 // Slave1 pipeline.
2318 final SimpleExhibitPipelineIF slave1[] = { cs1, ts1 };
2319 // Run poll() so pipeline can sync() its vars.
2320 t.scheduleAtFixedRate(new TimerTask(){
2321 @Override
2322 public void run()
2323 {
2324 try { slave1[0].poll(new GenProps()); }
2325 catch(final Exception e) { e.printStackTrace(Main.getErr()); }
2326 }
2327 },
2328 0L, 203L); // Run master poll() every 0.2s.
2329
2330 // Tunnel client end on slave2; serialisation is not done.
2331 final SimpleExhibitPipelineIF ts2 = new LocalTunnelSource(
2332 new LocalTunnelServer(masterTunnelServer), false, logger);
2333 // Tunnel caching stage on slave2.
2334 final SimpleExhibitPipelineIF cs2 = new TestVarCacheStage(ts2, false);
2335 // Slave2 pipeline.
2336 final SimpleExhibitPipelineIF slave2[] = { cs2, ts2 };
2337 // Run poll() so pipeline can sync() its vars.
2338 t.scheduleAtFixedRate(new TimerTask(){
2339 @Override
2340 public void run()
2341 {
2342 try { slave2[0].poll(new GenProps()); }
2343 catch(final Exception e) { e.printStackTrace(Main.getErr()); }
2344 }
2345 },
2346 1000L, 151L); // Run master poll() every 0.15s after a delay.
2347
2348 pipelineVariableTest(slave1, slave2, master,
2349 System.currentTimeMillis() + 120000L); // Run test for <= 2m.
2350
2351 // Check that the slaves' IDs appear in a map by address...
2352 // We could check this more or less anywhere,
2353 // but we'll use the top of the master pipeline for simplicity...
2354 final SimpleVariableValue slaves =
2355 masterTop.getVariable(SystemVariables.TunnelServlet_SLAVE_ADDRS);
2356 assertNotNull("Must have String globalMap value of participating slaves", slaves);
2357 final Map<InstanceID,SimpleVariableValue> slavMap = slaves.getGlobalMap();
2358 assertEquals("Must see exactly two slaves in globalMap",
2359 2, slavMap.size());
2360 assertTrue("Must find our two slaves as keys in globalMap",
2361 slavMap.keySet().contains(ts1.getVariable(SystemVariables.LOCAL_SYS_ID).getValue()) &&
2362 slavMap.keySet().contains(ts2.getVariable(SystemVariables.LOCAL_SYS_ID).getValue()));
2363 Main.getOut().println("[Sample slave list: " +VarTools.generateSimpleStats(masterTop, SystemVariables.TunnelServlet_SLAVE_ADDRS, 0)+ ".]");
2364 }
2365
2366 /**Test that we can correctly generate stats safely.
2367 * We test that is possible to try to generate a SimpleNumberStats instance,
2368 * even with weird inputs.
2369 * <p>
2370 * TODO: Test with local value
2371 */
2372 public void testSimpleNumberStats()
2373 {
2374 // Check that we correctly and politely handle null variables.
2375 assertNull("A null arg to generateSimpleNumberStats() should result in null",
2376 VarTools.generateSimpleNumberStats(null, -1, "", null));
2377
2378 // Check that we correctly and politely reject wrongly-typed variables.
2379 assertNull("A TYPE_STRING arg to generateSimpleNumberStats() should result in null",
2380 VarTools.generateSimpleNumberStats(new SimpleVariableValue(
2381 SystemVariables.TEST_STRING_GLOBAL, "oh happy day!"),
2382 -1, "", null));
2383 assertNull("A TYPE_NONE arg to generateSimpleNumberStats() should result in null",
2384 VarTools.generateSimpleNumberStats(new SimpleVariableValue(
2385 SystemVariables.KEEP_ALIVE, null),
2386 -1, "", null));
2387
2388 // Check that we correctly and politely reject null-values variables.
2389 assertNull("A null TYPE_NUMBER arg to generateSimpleNumberStats() should result in null",
2390 VarTools.generateSimpleNumberStats(new SimpleVariableValue(
2391 SystemVariables.TEST_NUMBER_LOCAL, null),
2392 -1, "", null));
2393
2394
2395 // Create three system IDs.
2396 final InstanceID system1 = InstanceID.createInstanceID();
2397 final InstanceID system2 = InstanceID.createInstanceID();
2398 final InstanceID system3 = InstanceID.createInstanceID();
2399
2400 // Create two values, with different sub-types of Number.
2401 // Pick values that should be computable without loss of accuracy
2402 // for all the operators in SimpleNumberStats.
2403 final Number v1 = new Integer(rnd.nextInt());
2404 final Number v2 = new Float((float) rnd.nextGaussian());
2405
2406 // Pick yet a different "main" value.
2407 final Number vMain = new Short((short) rnd.nextInt());
2408
2409 // Our variable definition.
2410 final SimpleVariableDefinition def = SystemVariables.TEST_NUMBER_GLOBAL;
2411
2412 // Now synthesise our variable with a globalMap...
2413 final SimpleVariableValue svvGlobal =
2414 (new SimpleVariableValue(def, vMain)).
2415 put(system1, new SimpleVariableValue(def, v1), false).
2416 put(system2, new SimpleVariableValue(def, v2), false).
2417 put(system3, new SimpleVariableValue(def, null), false);
2418
2419 // Now make the stats object...
2420 // We claim to be system1...
2421 final SimpleNumberStats statsGlobal =
2422 new SimpleNumberStats(svvGlobal, -1, system1, "#.##");
2423
2424 // FYI...
2425 Main.getOut().println("[Sample SimpleNumberStats: " + statsGlobal + ".]");
2426
2427 // Check the accessor methods.
2428 assertEquals("Number of systems should be 3", 3, statsGlobal.getSystemCount());
2429 assertEquals("Number of non-null values should be 2", 2, statsGlobal.getNonNullValueCount());
2430 assertEquals("Max computed incorrectly",
2431 new Double(Math.max(v1.doubleValue(), v2.doubleValue())),
2432 new Double(statsGlobal.getMax().doubleValue()));
2433 assertEquals("Min computed incorrectly",
2434 new Double(Math.min(v1.doubleValue(), v2.doubleValue())),
2435 new Double(statsGlobal.getMin().doubleValue()));
2436 assertEquals("Sum computed incorrectly",
2437 new Double(v1.doubleValue() + v2.doubleValue()),
2438 new Double(statsGlobal.getSum().doubleValue()));
2439 }
2440
2441
2442 /**Test simple variable stats.
2443 * Check that the basic, generic, SimpleVarStats works correctly
2444 * over a wide range of inputs...
2445 */
2446 public void testSimpleVarStats()
2447 {
2448 // Try this on a few random variable definitions
2449 // plus all extant system variable values.
2450 final List<SimpleVariableDefinition>/*<SimpleVariableDefinition>*/ testDefSet = new ArrayList<SimpleVariableDefinition>();
2451 testDefSet.addAll(SystemVariables.defs);
2452 for(int i = 37; --i >= 0; )
2453 { testDefSet.add(makeRandomSimpleVariableDefinition()); }
2454
2455 // Try out one possible value for each test definition.
2456 for(final Iterator<SimpleVariableDefinition> it = testDefSet.iterator(); it.hasNext(); )
2457 {
2458 final SimpleVariableDefinition def =
2459 it.next();
2460 final SimpleVariableValue svv = makeRandomSimpleVariableValue(def);
2461
2462 // Wrap this up in a simple stats object.
2463 // TODO: check the globalMap filtering behaviour.
2464 // TODO: check the selection by sysID.
2465 final SimpleVarStats svs = new SimpleVarStats(svv, -1, null);
2466
2467 // Check features of this basic value,
2468 // and its handling by the stats value.
2469 assertNull("Basic value must always have empty globalMap",
2470 svv.getGlobalMap());
2471 assertEquals("Stats must correctly report non-null entries as zero for basic value",
2472 0, svs.getNonNullValueCount());
2473 assertEquals("Stats must correctly report system count as zero for basic value",
2474 0, svs.getSystemCount());
2475 assertSame("Stats must return main value correctly for basic value",
2476 svv.getValue(), svs.getMainValue());
2477
2478 // Simply make sure that we can convert it to a String
2479 // without the roof crashing on our heads...
2480 final String readableString = svs.toString();
2481 assertNotNull("SimpleVarStats.toString() must not return null", readableString);
2482 // And the result can only possibly be an empty String
2483 // if there is no globalMap
2484 // AND the String representation of the main value is "".
2485 if(!"".equals(String.valueOf(svv.getValue())) ||
2486 (svv.getGlobalMap() != null))
2487 {
2488 assertTrue("SimpleVarStats.toString() can only be empty if there is no global map and the main value renders as an empty string",
2489 !"".equals(svs.toString()));
2490 }
2491 }
2492
2493 // TODO: implement tests for non-empty globalMap...
2494 }
2495
2496
2497 /**Do very basic event-handling tests.
2498 * This works on a stand-alone BasicVarMgr end-point for simplicity.
2499 */
2500 public void testBasicEventHandling()
2501 throws Exception
2502 {
2503 // Set of test event names.
2504 // All String types for now.
2505 final SimpleVariableDefinition[] testEvents =
2506 {
2507 SystemVariables.TEST_STRING_GLOBAL_EVENT,
2508 };
2509
2510 // Check that behaviour is correct for each event name above.
2511 for(final SimpleVariableDefinition def : testEvents)
2512 {
2513 // Create a new (end-point) var manager.
2514 final BasicVarMgr vars = new BasicVarMgr(true);
2515
2516 final String sval1 = "test value " + rnd.nextInt();
2517
2518 // Set/send the value a variable number of times...
2519 final int nEvents = rnd.nextInt(100);
2520 for(int i = nEvents; --i >= 0; )
2521 { vars.setVariable(new SimpleVariableValue(def, sval1)); }
2522
2523 // Check that the total count is correct.
2524 // (This may be split between current and previous slots if we crossed a boundary.)
2525 // This should be true at all event scales,
2526 // though this may be fragile on the shortest (VSHORT) timescale.
2527 for(final EventPeriod ep : EventPeriod.values())
2528 {
2529 final EventVariableValue currentEventSet = vars.getEventValue(def, ep, false);
2530 final EventVariableValue prevEventSet = vars.getEventValue(def, ep, true);
2531 assertNotNull("current event set returned by getEventValue() must not be null", currentEventSet);
2532 assertNotNull("previous event set returned by getEventValue() must not be null", prevEventSet);
2533 final int countedEvents = currentEventSet.getTotalEventCount() +
2534 prevEventSet.getTotalEventCount();
2535 assertEquals("Total number of events must be correctly recorded",
2536 nEvents, countedEvents);
2537 assertEquals("Total number of events reported by getCount() must be correct",
2538 nEvents, currentEventSet.getCount(sval1) + prevEventSet.getCount(sval1));
2539 assertTrue("Rank for prev must be 0 (or MAX_VALUE if count is 0)",
2540 (prevEventSet.getRank(sval1) == 0) ||
2541 ((prevEventSet.getRank(sval1) == Integer.MAX_VALUE) && (prevEventSet.getCount(sval1) == 0)));
2542 assertTrue("Rank for current must be 0 (or MAX_VALUE if count is 0)",
2543 (currentEventSet.getRank(sval1) == 0) ||
2544 ((currentEventSet.getRank(sval1) == Integer.MAX_VALUE) && (currentEventSet.getCount(sval1) == 0)));
2545 assertFalse("All event histories returned from bare BasicVarMgr must be non-authoritative",
2546 currentEventSet.isAuthoritative() || prevEventSet.isAuthoritative());
2547 }
2548 }
2549 }
2550
2551 /**Check that events are correctly split into multiple periods.
2552 * We test this on just the very shortest period/interval here
2553 * in order to keep test times reasonable,
2554 * on the assumption that processing is uniform for all periods/intervals.
2555 */
2556 public void testEventBucketting()
2557 throws Exception
2558 {
2559 // Create a new (end-point) var manager.
2560 final BasicVarMgr vars = new BasicVarMgr(true);
2561
2562 // The test event that we wish to use.
2563 final SimpleVariableDefinition def = SystemVariables.TEST_STRING_GLOBAL_EVENT;
2564
2565 // The test value that we use.
2566 final String value = "MyLittleTestValue(MT)" + Rnd.fastRnd.nextLong();
2567
2568 // Total event count we believe that we have delivered so far.
2569 int totalCountSoFar = 0;
2570
2571 // Now that we have done our set-up, start timing...
2572 final long startTime = System.currentTimeMillis();
2573 // Run this test up to long enough to span more than two of the shortest buckets.
2574 final long stopTime = startTime + Rnd.fastRnd.nextInt(1+(5*SystemVariables.EVENT_INTERVAL_VSHORT_TERM_MS)/2);
2575
2576 do
2577 {
2578 final long now = System.currentTimeMillis();
2579 // What is our current interval number?
2580 final long currentIntervalNumber = EventPeriod.VSHORT.getIntervalNumber(now);
2581
2582 // The maximum number of intervals that can have passed since we started.
2583 // We allow some leeway here.
2584 final int maxIntervals = 3 + (int) ((now - startTime) / SystemVariables.EVENT_INTERVAL_VSHORT_TERM_MS);
2585
2586 // Record an event.
2587 final SimpleVariableValue svv = new SimpleVariableValue(def, value);
2588 vars.setVariable(svv);
2589 ++totalCountSoFar;
2590
2591 // Set up a bit vector of values to fetch.
2592 // We always include enough to catch enough recent slots
2593 // to be able to see all our events,
2594 // but sometimes we throw in other requests
2595 // to make sure that that does not itself break anything.
2596 final BitSet whichValues = new BitSet(maxIntervals);
2597 // Enough bits to cover any slots that we may have events in.
2598 whichValues.set(0, maxIntervals);
2599 // Sometimes add other bogus requests.
2600 if(Rnd.fastRnd.nextBoolean())
2601 { for(int i = 10; --i >= 0; ) { whichValues.set(Rnd.fastRnd.nextInt(SystemVariables.EVENT_SAMPLES_RETAINED)); } }
2602
2603 // Retrieve results, and check that we find the right count across all of the entries...
2604 // Allow for a little clock skew and look forward one extra slot...
2605 final EventVariableValue evvs[] =
2606 vars.getEventValues(def, EventPeriod.VSHORT, currentIntervalNumber+1, whichValues);
2607 assertNotNull("Result of getEventValues() must never be null", evvs);
2608 assertTrue("Result of getEventValues() must never be zero-length once we have recorded an event",
2609 evvs.length > 0);
2610
2611 // Tot up total count found in results.
2612 // And make sure that we only get non-null results at most where we asked for them.
2613 int totalCountFound = 0;
2614 int resultsReturned = 0;
2615 for(int i = evvs.length; --i >= 0; )
2616 {
2617 final EventVariableValue evv = evvs[i];
2618 if(evv == null) { continue; }
2619 assertTrue("Must get a result only where one was asked for", whichValues.get(i));
2620 ++resultsReturned;
2621 totalCountFound += evv.getTotalEventCount();
2622 }
2623 assertEquals("Total count across all results must be correct (evvs.length="+evvs.length+", resultsReturned="+resultsReturned+")", totalCountSoFar, totalCountFound);
2624
2625 // Sleep so as to aim for at least 100-or-so events in total.
2626 Thread.sleep(1 + Rnd.fastRnd.nextInt(1 | SystemVariables.EVENT_INTERVAL_VSHORT_TERM_MS/101));
2627 } while(System.currentTimeMillis() < stopTime);
2628 }
2629
2630
2631
2632 /**Do simple test of tunnel connectivity for set()ting and get()ting events.
2633 * We do this with and without real serialisation,
2634 * so in passing we are testing the on-the-wire protocol too.
2635 * <p>
2636 * This is a <em>very</em> simple test;
2637 * look elsewhere for comprehensive tests!
2638 */
2639 public void testLocalTunnelEventSimple()
2640 throws Exception
2641 {
2642 // Do alternate tests with/out forced (de)serialisation...
2643 for(int s = 2; --s >= 0; )
2644 {
2645 // Chose whether to force serialisation...
2646 final boolean forceSerialisation = (s & 1) == 0;
2647
2648 // Set of test event names.
2649 // All String types for now.
2650 final SimpleVariableDefinition[] testEvents =
2651 {
2652 SystemVariables.TEST_STRING_GLOBAL_EVENT,
2653 };
2654
2655 // Check that behaviour is correct for each event name above.
2656 for(final SimpleVariableDefinition def : testEvents)
2657 {
2658 // Tunnel endpoint at master (wraps a BasicVarMar).
2659 final SimpleExhibitPipelineIF ep = new BasicVarMgrEndpoint();
2660
2661 // Tunnel client end on slave.
2662 final SimpleExhibitPipelineIF ts = new LocalTunnelSource(
2663 new LocalTunnelServer(ep), forceSerialisation, logger);
2664
2665 final String sval1 = "test value " + rnd.nextInt();
2666
2667 // Set the value a variable number of times...
2668 final int nEvents = rnd.nextInt(100);
2669 for(int i = nEvents; --i >= 0; ) { ts.setVariable(new SimpleVariableValue(def, sval1)); }
2670
2671 // Check that the total count is correct.
2672 // (This may be split between current and previous slots if we crossed a boundary.)
2673 // This should be true at all event scales,
2674 // though this may be fragile on the shortest (VSHORT) timescale.
2675 for(final EventPeriod p : EventPeriod.values())
2676 {
2677 final EventVariableValue currentEventSet = ts.getEventValue(def, p, false);
2678 final EventVariableValue prevEventSet = ts.getEventValue(def, p, true);
2679 assertNotNull("current event set returned by getEventValue() must not be null", currentEventSet);
2680 assertNotNull("previous event set returned by getEventValue() must not be null", prevEventSet);
2681 final int countedEvents = currentEventSet.getTotalEventCount() +
2682 prevEventSet.getTotalEventCount();
2683 assertEquals("Total number of events must be correctly recorded",
2684 nEvents, countedEvents);
2685 assertEquals("Total number of events reported by getCount() must be correct",
2686 nEvents, currentEventSet.getCount(sval1) + prevEventSet.getCount(sval1));
2687 assertTrue("Rank for prev must be 0 (or MAX_VALUE if count is 0)",
2688 (prevEventSet.getRank(sval1) == 0) ||
2689 ((prevEventSet.getRank(sval1) == Integer.MAX_VALUE) && (prevEventSet.getCount(sval1) == 0)));
2690 assertTrue("Rank for current must be 0 (or MAX_VALUE if count is 0)",
2691 (currentEventSet.getRank(sval1) == 0) ||
2692 ((currentEventSet.getRank(sval1) == Integer.MAX_VALUE) && (currentEventSet.getCount(sval1) == 0)));
2693
2694 // Get a value as far in the past as we might expect to be able to,
2695 // just to make sure that it all works, or at least does not blow up.
2696 // Base it on the previous interval to exacerbate "wastage".
2697 final BitSet wastefulReq = new BitSet();
2698 wastefulReq.set(SystemVariables.EVENT_SAMPLES_RETAINED-1);
2699 final long wastefulReqIN = p.getIntervalNumber(System.currentTimeMillis());
2700 final EventVariableValue evvs[] =
2701 ts.getEventValues(def, p, wastefulReqIN, wastefulReq);
2702 assertNotNull("Result of getEventRequests() must not be null", evvs);
2703 // Check that at most the request slot is not null.
2704 for(int i = evvs.length; --i >= 0; )
2705 {
2706 final EventVariableValue evv = evvs[i];
2707 assertFalse("Must not get data not requested", (evv != null) && !wastefulReq.get(i));
2708 // If I get a result then it must be what I asked for.
2709 if(evv != null)
2710 {
2711 assertEquals("Def of result must be right", def, evv.getDef());
2712 assertEquals("Period of result must be right", p, evv.getPeriod());
2713 assertEquals("Interval number of result must be right", wastefulReqIN, evv.getIntervalNumber());
2714 }
2715 }
2716 }
2717 }
2718 }
2719 }
2720
2721
2722
2723 /**Check that the various event objects/collections can be (de)serialised.
2724 * Basic sanity checks; nothing very clever or extensive.
2725 */
2726 public void testEventCollectionSerialisation()
2727 throws Exception
2728 {
2729 final SimpleVariableDefinition def = SystemVariables.TEST_STRING_GLOBAL_EVENT;
2730 SerializationTest.checkObjectCanBeSerialisedAndDeserialised(
2731 new EventVariableValueSet(def));
2732 for(final EventPeriod ep : EventPeriod.values())
2733 {
2734 SerializationTest.checkObjectCanBeSerialisedAndDeserialised(
2735 new EventVariableValuePeriodRow(def, ep));
2736
2737 final long intervalNumber = ep.getIntervalNumber(System.currentTimeMillis());
2738 SerializationTest.checkSerialisationPreservesEquality(
2739 new EventVariableValue(false, def, ep, intervalNumber, (Rnd.fastRnd.nextInt() >>> 1), null, null));
2740 SerializationTest.checkSerialisationPreservesEquality(
2741 new EventVariableValue(true, def, ep, intervalNumber, (Rnd.fastRnd.nextInt() >>> 1), null, null));
2742
2743 SerializationTest.checkSerialisationPreservesEquality(
2744 new EventVariableValueBuffer(def, ep, intervalNumber));
2745 }
2746 }
2747
2748 /**Test that we can persist the event histories.
2749 */
2750 public void testEventHistoryPeristence()
2751 throws Exception
2752 {
2753 // Work in a temporary subdirectory of the current directory.
2754 final File tempDir = (new File("CacheTst.tmp")).getAbsoluteFile();
2755
2756 try { FileTools.rmRecursively(tempDir); }
2757 catch(final IOException e) { e.printStackTrace(Main.getErr()); }
2758 if(tempDir.isDirectory())
2759 { fail("unable to remove extant temp test directory"); }
2760
2761 if(!tempDir.mkdirs() || !tempDir.isDirectory())
2762 { fail("unable to create temp test directory"); }
2763 try
2764 {
2765 final SimpleVariableDefinition def = SystemVariables.TEST_STRING_GLOBAL_EVENT;
2766 assertTrue("test event must be persistent", def.isPersistent());
2767
2768 // Create our main variable manager...
2769 final BasicVarMgr vars = new BasicVarMgr(true);
2770
2771 // Check that we cannot see events in the new var manager.
2772 for(final EventPeriod period : EventPeriod.values())
2773 { assertEquals("must not yet be able to see any events", 0, vars.getEventValue(def, period, rnd.nextBoolean()).getTotalEventCount()); }
2774
2775 // Check that loading from our new empty directory still shows zero events.
2776 vars.loadEventHistories(tempDir, true);
2777 for(final EventPeriod period : EventPeriod.values())
2778 { assertEquals("must not yet be able to see any events", 0, vars.getEventValue(def, period, rnd.nextBoolean()).getTotalEventCount()); }
2779
2780 // Insert a random number of events and check that we can see them.
2781 final String value = "dummyValue" + Rnd.fastRnd.nextInt();
2782 final int eventCount = Rnd.fastRnd.nextInt(101);
2783 for(int i = eventCount; --i >= 0; )
2784 { vars.setVariable(new SimpleVariableValue(def, value)); }
2785
2786 // Check that we can see the new events in the variable manager.
2787 for(final EventPeriod period : EventPeriod.values())
2788 {
2789 assertEquals("must be able to see events", eventCount,
2790 vars.getEventValue(def, period, false).getTotalEventCount() +
2791 vars.getEventValue(def, period, true).getTotalEventCount());
2792 }
2793
2794 // Save the history data...
2795 // For the options that should not matter, try them at random.
2796 vars.saveEventHistories(tempDir, Rnd.fastRnd.nextBoolean(), Rnd.fastRnd.nextBoolean(), false);
2797
2798 // Check that we can still see the new events in the variable manager.
2799 for(final EventPeriod period : EventPeriod.values())
2800 {
2801 assertEquals("must be able to see events after save", eventCount,
2802 vars.getEventValue(def, period, false).getTotalEventCount() +
2803 vars.getEventValue(def, period, true).getTotalEventCount());
2804 }
2805
2806
2807 // Create a new variable manager...
2808 final BasicVarMgr vars2 = new BasicVarMgr(true);
2809
2810 // Check that we cannot see events in a new var manager.
2811 for(final EventPeriod period : EventPeriod.values())
2812 { assertEquals("must not yet be able to see any events in new vars", 0, vars2.getEventValue(def, period, rnd.nextBoolean()).getTotalEventCount()); }
2813
2814 // Now make sure that we can load the state into a new manager,
2815 // and see the correct state in there.
2816 final BasicVarMgr vars3 = new BasicVarMgr(true);
2817 vars3.loadEventHistories(tempDir, rnd.nextBoolean());
2818 // Check that we can still see the new events in the variable manager.
2819 for(final EventPeriod period : EventPeriod.values())
2820 {
2821 assertEquals("must be able to see events after load", eventCount,
2822 vars3.getEventValue(def, period, false).getTotalEventCount() +
2823 vars3.getEventValue(def, period, true).getTotalEventCount());
2824 }
2825 }
2826 finally
2827 {
2828 // Zap any files that we wrote...
2829 FileTools.rmRecursively(tempDir);
2830 }
2831 }
2832
2833
2834 /**Observe upstream accesses to event values.
2835 * This can optionally veto redundant reads that indicate that a cacheing stage
2836 * is not working properly.
2837 * <p>
2838 * The EventVariableValue.equals() and hashCode() methods allow
2839 * us to simply keep a Set of authorised values returned;
2840 * any duplicate is an error.
2841 */
2842 static final class EventValueRequestMonitor extends SimpleExhibitPipelineFilter
2843 {
2844 /**Construct instance wrapped round upstream source.
2845 * @param upstream datasource; never null
2846 * @param vetoRedundantReads if true, veto attempts to read authoritative values
2847 * more than once, ie redunantly;
2848 * should be safe to use only behind a cache stage
2849 */
2850 EventValueRequestMonitor(final SimpleExhibitPipelineIF upstream,
2851 final boolean vetoRedundantReads)
2852 {
2853 super(upstream);
2854 this.vetoRedundantReads = vetoRedundantReads;
2855 }
2856 /**If true, veto attempts to read authoritative values more than once, ie redunantly. */
2857 private final boolean vetoRedundantReads;
2858
2859 /**Set of authoritative responses so far passed downstream; never null. */
2860 private final Set<EventVariableValue> authValueSeen = new HashSet<EventVariableValue>();
2861
2862 /**Intercept event requests and look for incorrect redundancy. */
2863 @Override
2864 public EventVariableValue getEventValue(final SimpleVariableDefinition def,
2865 final EventPeriod intervalSelector,
2866 final boolean current)
2867 {
2868 if((def == null) || !def.isEvent() || (intervalSelector == null))
2869 { throw new IllegalArgumentException(); }
2870
2871 final long now = System.currentTimeMillis();
2872 final long currentIntervalNumber = intervalSelector.getIntervalNumber(now);
2873 final long intervalNumber = (current ? currentIntervalNumber : (currentIntervalNumber-1));
2874
2875 final BitSet justTheFirst = new BitSet(1);
2876 justTheFirst.set(0); // Get just the first interval's data.
2877 final EventVariableValue evv[] = getEventValues(def,
2878 intervalSelector,
2879 intervalNumber,
2880 null);
2881
2882 // If we got some real (non-null) data back, then return it...
2883 if((evv.length > 0) && (evv[0] != null))
2884 { return(evv[0]); }
2885
2886 // Else invent empty non-authoritative return value (with no events)...
2887 return(BasicVarMgr.getEmptyNonAuthEVV(def, intervalSelector, intervalNumber));
2888 }
2889
2890 /**Intercept event requests and look for incorrect redundancy. */
2891 @Override
2892 public EventVariableValue[] getEventValues(final SimpleVariableDefinition def,
2893 final EventPeriod intervalSelector,
2894 final long intervalNumber,
2895 final BitSet whichValues)
2896 {
2897 // System.out.println("***Upstream request: " + whichValues);
2898
2899 final EventVariableValue[] evvs = source.getEventValues(def, intervalSelector, intervalNumber, whichValues);
2900
2901 // Look out for duplicate reads if they are not to be allowed...
2902 if(vetoRedundantReads)
2903 {
2904 final long now = System.currentTimeMillis();
2905 final long currentIntervalNumber = intervalSelector.getIntervalNumber(now);
2906 // A cacheing stage need not hold values further in the past
2907 // than our holding window.
2908 final long oldestCacheable = currentIntervalNumber - SystemVariables.EVENT_SAMPLES_RETAINED;
2909
2910 for(int i = whichValues.nextSetBit(0); i >= 0; i = whichValues.nextSetBit(i+1))
2911 {
2912 // Stop when we've gone too far back to be cacheable...
2913 if(intervalNumber - i < oldestCacheable) { break; }
2914
2915 final EventVariableValue evv = evvs[i];
2916 if(evv == null) { continue; }
2917 if(!evv.isAuthoritative()) { continue; }
2918
2919 if(!authValueSeen.add(evv))
2920 {
2921 // This is a forbidden duplicate...
2922 fail("Duplicate auth EVV retrieved: " + evv.toString());
2923 }
2924 }
2925 }
2926
2927 return(evvs);
2928 }
2929 }
2930
2931
2932
2933 /**Test correct generation and handling of authoritative event values.
2934 * Check that values (other than the current interval) retrieved from the master store
2935 * are correctly marked as authoritative.
2936 * Ensure that absent values are returned as authoritative empty values
2937 * to help upstream caches avoid re-requesting them.
2938 * <p>
2939 * Check that these are cached correctly by a pipeline cache stage
2940 * and not redundantly requested from the source.
2941 * This is important to ensure that slaves do not waste time and bandwidth
2942 * in redundantly re-requesting past values from the master.
2943 */
2944 public void testAuthEventValueGenerationAndCacheing()
2945 throws IOException
2946 {
2947 // Set up a simple instance of a file data source.
2948 final ExhibitDataFileSource edfs = new ExhibitDataFileSource();
2949
2950 // Test that we get appropriate data from the ExhibitDataFileSource
2951 // which will be used in the master to be a persistent data repository.
2952 //
2953 // All requests for valid events/periods should be responded to
2954 // as authoritative or not depending on the slot time as follows:
2955 // * In the future: null are returned because no data *can* exist yet.
2956 // * For the current interval: a non-authoritative, non-null response.
2957 // * For any past interval: an authoritative response.
2958 // Where the data for responses does not yet exist,
2959 // it must be invented with no events to indicate that
2960 // it definitely does not exist
2961 // so as to allow negative cacheing by upstream users.
2962
2963 // Test event; must support all periods.
2964 final SimpleVariableDefinition def = SystemVariables.TEST_STRING_GLOBAL_EVENT;
2965
2966 // Run the tests a few times for different values.
2967 for(int testNumber = 1000 + rnd.nextInt(10); --testNumber >= 0; )
2968 {
2969 // On alternate tests, introduce a cache stage.
2970 final boolean insertCache = ((testNumber & 1) != 0);
2971
2972 // Wrap the raw source in such a way as to veto cacheing errors,
2973 // ie where the cache wrongly passes redundant reads upstream.
2974 final SimpleExhibitPipelineFilter dataSource =
2975 new EventValueRequestMonitor(edfs, insertCache);
2976
2977 // Now wrap the data source as needed.
2978 final SimpleVariablePipelineIF source = (!insertCache) ? (SimpleVariablePipelineIF) dataSource :
2979 new PipelineVarMgr(dataSource, rnd.nextBoolean());
2980
2981 // Compute an offset of the start of the request interval
2982 // from the current interval,
2983 // since this must work correctly whatever the interval is.
2984 final int offset = rnd.nextInt(2*SystemVariables.EVENT_SAMPLES_RETAINED) -
2985 (SystemVariables.EVENT_SAMPLES_RETAINED);
2986
2987 // Pick a period at random;
2988 // the test event is assumed to support all periods.
2989 final EventPeriod p = (EventPeriod.values())[rnd.nextInt(EventPeriod.values().length)];
2990
2991 // Get "current" interval for the given period.
2992 final long currentInterval = p.getIntervalNumber(System.currentTimeMillis());
2993
2994 // Compute start interval for our test.
2995 final long startInterval = currentInterval + offset;
2996
2997 // Compute random (small, possibly zero) set of random test interval slots.
2998 // We pick a range unlikey to upset bounds checking anywhere.
2999 final BitSet whichValues = new BitSet();
3000 for(int sn = rnd.nextInt(31); --sn >= 0; )
3001 { whichValues.set(rnd.nextInt(SystemVariables.EVENT_SAMPLES_RETAINED)); }
3002
3003 // Show that repeated requests are idempotent,
3004 // and that past values are correctly handled by any cache stage.
3005 for(int requestNumber = 2 + rnd.nextInt(3); --requestNumber >= 0; )
3006 {
3007 //System.out.println("[Requesting (@offset="+offset+"): " + whichValues + ".]");
3008 // Make the request...
3009 final EventVariableValue result[] = source.getEventValues(def,
3010 p,
3011 startInterval,
3012 whichValues);
3013
3014 assertNotNull("Result of getEventValues() must not be null", result);
3015 assertTrue("Result of getEventValues() must be at least long enough to cover all requests",
3016 result.length >= whichValues.length());
3017
3018 // We examine every slot in the returned array...
3019 for(int i = result.length; --i >= 0; )
3020 {
3021 final long thisInterval = startInterval-i;
3022
3023 final EventVariableValue evv = result[i];
3024 assertEquals("Slot must be full iff request bit set and vice versa, but all null in the future",
3025 whichValues.get(i) && (thisInterval <= currentInterval),
3026 (evv != null));
3027
3028 // Verify correct null/auth/nonauth based on slot number.
3029 // Future slots MUST be null...
3030 if(thisInterval > currentInterval)
3031 { assertNull("future interval slots must be null", evv); }
3032 else if(thisInterval == currentInterval)
3033 {
3034 if(evv == null)
3035 { assertFalse("Must have non-null current slot value if requested", whichValues.get(i)); }
3036 else
3037 { assertFalse("Current value returned must not be authoritative", evv.isAuthoritative()); }
3038 }
3039 else
3040 {
3041 if(evv == null)
3042 { assertFalse("Must have non-null past slot value if requested", whichValues.get(i)); }
3043 else
3044 { assertTrue("Past value returned must be authoritative", evv.isAuthoritative()); }
3045 }
3046
3047 if(evv == null) { continue; }
3048
3049 // For non-null values check def, etc, is as requested.
3050 //System.out.println("[evv="+evv+".]");
3051 assertEquals("Returned value must be for correct def", def, evv.getDef());
3052 assertEquals("Returned value must be for period", p, evv.getPeriod());
3053 assertEquals("Returned value must be for correct interval", thisInterval, evv.getIntervalNumber());
3054 }
3055
3056 // TODO: check that rerequesting the results through a cache stage does not fetch them from the source again.
3057 }
3058 }
3059 }
3060
3061 /**Test that (re)computing ExhibitPropsComputableMutable data does not cause redundant event data fetches.
3062 * We care about this because event data is quite bulky
3063 * and therefore quite expensive and slow to fetch,
3064 * so we want to make sure that we fetch each event history at most once.
3065 */
3066 public void testEPCMComputationDoesNotRefetchEventHistory()
3067 throws Exception
3068 {
3069 // Set up a simple instance of a file data source,
3070 // wrapped in a filter to discover redundant event-data fetches,
3071 // and wrapped in a simple pipeline cache stage.
3072 final ExhibitDataFileSource edfs = new ExhibitDataFileSource();
3073 final SimpleVariablePipelineIF dataSource = new PipelineVarMgr(
3074 new EventValueRequestMonitor(edfs, true),
3075 true); // Make this a write-through variable cache for simplicity...
3076
3077 final AllExhibitProperties aep = edfs.getAllExhibitProperties(-1);
3078 assertNotSame("Must be real exhibit data for us to work on", 0, aep.aeid.length);
3079
3080 final GenProps gp = edfs.getGenProps(-1);
3081 assertNotSame("Must be real GenProps for us to work with", 0, gp.timestamp);
3082
3083 // Force explicit full computation of all EPCM several times...
3084 for(int i = 10; --i >= 0; )
3085 {
3086 for(final ExhibitStaticAttr esa : aep.aeid.getAllStaticAttrs())
3087 {
3088 // Force the computation...
3089 ExhibitPropsComputableMutable.compute(esa, gp, aep, dataSource,
3090 ExhibitPropsComputableMutableVoteCacheIF.TRIVIAL,
3091 ScorerCacheIF.TRIVIAL);
3092 }
3093 }
3094 }
3095
3096
3097
3098
3099 /**Returns true if time is not after that stated. */
3100 private static boolean notAfter(final long stopBy)
3101 { return(System.currentTimeMillis() <= stopBy); }
3102
3103 /**Private source of OK pseudo-random numbers. */
3104 private static final Random rnd = new Random();
3105 }