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&#46;hd&#46;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        }