Here's a fun fact about TestAgainstBrzozowski.
This is the original test which fails every time:

                Automaton a = AutomatonTestUtil.RandomAutomaton(Random());
                AutomatonTestUtil.MinimizeSimple(a);
                Automaton b = (Automaton)a.Clone();
                MinimizationOperations.Minimize(b);
                Assert.IsTrue(BasicOperations.SameLanguage(a, b));
                Assert.AreEqual(a.GetNumberOfStates(), b.GetNumberOfStates());
                Assert.AreEqual(a.GetNumberOfTransitions(), 
b.GetNumberOfTransitions());

If we change the call to AutomatonTestUtil.MinimizeSimple(a) by 
MinimizationOperations.Minimize(a), the test succeeds:

                Automaton a = AutomatonTestUtil.RandomAutomaton(Random());
                MinimizationOperations.Minimize(a);
                Automaton b = (Automaton)a.Clone();
                MinimizationOperations.Minimize(b);
                Assert.IsTrue(BasicOperations.SameLanguage(a, b));
                Assert.AreEqual(a.GetNumberOfStates(), b.GetNumberOfStates());
                Assert.AreEqual(a.GetNumberOfTransitions(), 
b.GetNumberOfTransitions());

Before you say "big deal", if we replace the call to 
MinimizationOperations.Minimize(b) by AutomatonTestUtil.MinimizeSimple(b), the 
test fails every time:

                Automaton a = AutomatonTestUtil.RandomAutomaton(Random());
                AutomatonTestUtil.MinimizeSimple(a);
                Automaton b = (Automaton)a.Clone();
                AutomatonTestUtil.MinimizeSimple(b);
                Assert.IsTrue(BasicOperations.SameLanguage(a, b));
                Assert.AreEqual(a.GetNumberOfStates(), b.GetNumberOfStates());
                Assert.AreEqual(a.GetNumberOfTransitions(), 
b.GetNumberOfTransitions());

Not only that, but if we re-order the clone operation to be executed on the 
un-minimized automaton (to make sure we really have 2 identical automatons) 
like this:

                Automaton a = AutomatonTestUtil.RandomAutomaton(Random());
                Automaton b = (Automaton)a.Clone();
                AutomatonTestUtil.MinimizeSimple(a);
                AutomatonTestUtil.MinimizeSimple(b);
                Assert.IsTrue(BasicOperations.SameLanguage(a, b));
                Assert.AreEqual(a.GetNumberOfStates(), b.GetNumberOfStates());
                Assert.AreEqual(a.GetNumberOfTransitions(), 
b.GetNumberOfTransitions());

... the test fails as well. This is contrary to what is claimed in the big 
giant comment.
To make sure that cloning works, this:

                Automaton a = AutomatonTestUtil.RandomAutomaton(Random());
                Automaton b = (Automaton)a.Clone();
                Assert.IsTrue(BasicOperations.SameLanguage(a, b));
                Assert.AreEqual(a.GetNumberOfStates(), b.GetNumberOfStates());
                Assert.AreEqual(a.GetNumberOfTransitions(), 
b.GetNumberOfTransitions());
                AutomatonTestUtil.MinimizeSimple(a);
                AutomatonTestUtil.MinimizeSimple(b);
                Assert.IsTrue(BasicOperations.SameLanguage(a, b));
                Assert.AreEqual(a.GetNumberOfStates(), b.GetNumberOfStates());
                Assert.AreEqual(a.GetNumberOfTransitions(), 
b.GetNumberOfTransitions());

... never fails in the first 3 assertions. Always fails in one of the last ones.

Preliminary conclusion:  if AutomatonTestUtil.MinimizeSimple can't give the 
same results for identical automatons, it's not a good basis for a test.
As to why... I searched for random factors in the 
AutomatonTestUtil.MinimizeSimple, but found none.  It looks like the algorithm 
is deterministic.
The only strange thing is the GetHashCode() implementation on State: it's bad 
form to define a hash code without an Equals method, especially if you're 
putting States in hash sets or dictionaries. But it seems like reference 
comparison is all that's needed, and since the hashcode is a different one for 
every instance, removing the GetHashCode method or adding the Equals method has 
no effect.

However, I can make the last 3 failing tests cited above work by correcting a 
bug in ValueHashSet<T>.GetHashCode. The current definition is this:


        public override int GetHashCode()
        {
            int h = 0;
            var i = GetEnumerator();
            while (i.MoveNext())
            {
                T obj = i.Current;
                if (!EqualityComparer<T>.Default.Equals(obj, default(T)))
                {
                    h = HashHelpers.CombineHashCodes(h, obj.GetHashCode());
                }
            }
            return h;
        }


This definition is wrong, since it relies on the incorrect assumption that sets 
containing identical elements will enumerate them in identical order. There is 
no order defined in a HashSet<T>, and if you have 2 sets to which you add items 
a after b in one, and b after a in another, the sets are identical but their 
enumerators will not necessarily be. The operation 
HashHelpers.CombineHashCodes(h, obj.GetHashCode()) is noncommutative, causing 
equal set to generate different hashcodes. Using:

        public override int GetHashCode()
        {
            int h = 0;
            foreach(var obj in this)
            {
                if (!EqualityComparer<T>.Default.Equals(obj, default(T)))
                {
                                      h += obj.GetHashCode();
                }
            }
            return h;
        }


Will make the last 3 failing tests work correct. But not the original one!



More weirdness, I suppose.

Vincent

Reply via email to