using System; using System.Linq; using UnitTestSharp; namespace Clave.UnitTests { public class KeyFormulaTests : TestFixture { public const int HeldBit = 1; public const int PressedBit = 2; public const int ReleasedBit = 4; public const int RepeatedBit = 8; // The first seven are the combinations a real keyboard can produce for one key. The rest can't happen. public static readonly int[] KeyStates = { 0, HeldBit, HeldBit | PressedBit, ReleasedBit, PressedBit | ReleasedBit, HeldBit | PressedBit | ReleasedBit, HeldBit | RepeatedBit, PressedBit, HeldBit | ReleasedBit, RepeatedBit, HeldBit | PressedBit | RepeatedBit, }; public static readonly KeyCode[] Universe = { KeyCode.A, KeyCode.B, KeyCode.C, KeyCode.D }; public static KeySnapshot[] Enumerate(int statesPerKey) { int total = 1; foreach (KeyCode key in Universe) { total *= statesPerKey; } var returnMe = new KeySnapshot[total]; for (int index = 0; index < total; ++index) { KeyState held = default; KeyState pressed = default; KeyState released = default; KeyState repeated = default; int rest = index; foreach (KeyCode key in Universe) { int state = KeyStates[rest % statesPerKey]; rest /= statesPerKey; if ((state & HeldBit) != 0) { held = held.WithKeySet(key); } if ((state & PressedBit) != 0) { pressed = pressed.WithKeySet(key); } if ((state & ReleasedBit) != 0) { released = released.WithKeySet(key); } if ((state & RepeatedBit) != 0) { repeated = repeated.WithKeySet(key); } } returnMe[index] = new KeySnapshot(held, pressed, released, repeated); } return returnMe; } public static readonly KeySnapshot[] ConsistentSnapshots = Enumerate(statesPerKey: 7); public static readonly KeySnapshot[] AllSnapshots = Enumerate(statesPerKey: 11); public static KeyState StateOf(params KeyCode[] keys) { return new KeyState(keys); } public static KeySnapshot Snapshot(KeyState held = default, KeyState pressed = default, KeyState released = default, KeyState repeated = default) { return new KeySnapshot(held, pressed, released, repeated); } public static KeySnapshot SingleKey(bool held, bool pressed, bool released, KeyCode key = KeyCode.A, bool repeated = false) { return Snapshot(held ? StateOf(key) : default, pressed ? StateOf(key) : default, released ? StateOf(key) : default, repeated ? StateOf(key) : default); } public static KeyFormula Held(KeyCode key) { return KeyFormula.Key(key, KeyStateKind.Held); } public static KeyFormula Pressed(KeyCode key) { return KeyFormula.Key(key, KeyStateKind.Pressed); } public static KeyFormula Released(KeyCode key) { return KeyFormula.Key(key, KeyStateKind.Released); } public static KeyFormula Repeated(KeyCode key) { return KeyFormula.Key(key, KeyStateKind.Repeated); } public static KeyState StateOfKind(KeySnapshot snapshot, KeyStateKind kind) { switch (kind) { case KeyStateKind.Held: return snapshot.Held; case KeyStateKind.Pressed: return snapshot.Pressed; case KeyStateKind.Released: return snapshot.Released; default: return snapshot.Repeated; } } public static KeySnapshot WithStateOfKind(KeySnapshot snapshot, KeyStateKind kind, KeyState keys) { switch (kind) { case KeyStateKind.Held: return Snapshot(keys, snapshot.Pressed, snapshot.Released, snapshot.Repeated); case KeyStateKind.Pressed: return Snapshot(snapshot.Held, keys, snapshot.Released, snapshot.Repeated); case KeyStateKind.Released: return Snapshot(snapshot.Held, snapshot.Pressed, keys, snapshot.Repeated); default: return Snapshot(snapshot.Held, snapshot.Pressed, snapshot.Released, keys); } } public static readonly KeyStateKind[] Kinds = { KeyStateKind.Held, KeyStateKind.Pressed, KeyStateKind.Released, KeyStateKind.Repeated, }; // Numbers the variables over Universe key-major, four kinds per key. public static KeySnapshot WithVariableSet(KeySnapshot snapshot, int variable) { KeyStateKind kind = Kinds[variable % Kinds.Length]; KeyCode key = Universe[variable / Kinds.Length]; return WithStateOfKind(snapshot, kind, StateOfKind(snapshot, kind).WithKeySet(key)); } public sealed class Sample { public KeyFormula Formula; public Func Reference; } // A random formula, with its meaning computed independently of KeyFormula. public static Sample RandomSample(Random random, int depth) { if (depth == 0 || random.Next(4) == 0) { KeyCode key = Universe[random.Next(Universe.Length)]; KeyStateKind kind = (KeyStateKind)random.Next(4); return new Sample { Formula = KeyFormula.Key(key, kind), Reference = snapshot => StateOfKind(snapshot, kind).IsKeySet(key), }; } Sample left = RandomSample(random, depth - 1); Sample right = RandomSample(random, depth - 1); switch (random.Next(4)) { case 0: return new Sample { Formula = left.Formula & right.Formula, Reference = snapshot => left.Reference(snapshot) && right.Reference(snapshot), }; case 1: return new Sample { Formula = left.Formula | right.Formula, Reference = snapshot => left.Reference(snapshot) || right.Reference(snapshot), }; case 2: return new Sample { Formula = left.Formula ^ right.Formula, Reference = snapshot => left.Reference(snapshot) != right.Reference(snapshot), }; default: return new Sample { Formula = ~left.Formula, Reference = snapshot => !left.Reference(snapshot), }; } } public static Sample[] RandomSamples(int seed, int count, int depth) { var random = new Random(seed); var returnMe = new Sample[count]; for (int i = 0; i < count; ++i) { returnMe[i] = RandomSample(random, depth); } return returnMe; } public static string FirstDisagreement(KeyFormula formula, Func reference, KeySnapshot[] snapshots) { foreach (KeySnapshot snapshot in snapshots) { if (formula.Matches(snapshot) != reference(snapshot)) { return formula + " on " + snapshot; } } return ""; } public static KeyState Toggle(KeyState keys, KeyCode key) { return keys.IsKeySet(key) ? keys.WithKeyCleared(key) : keys.WithKeySet(key); } // Of the two cases, only the tap can really happen. The impossible one is what a first-found match hits. public static KeyFormula TapOrImpossibleRelease(KeyCode key) { return (Held(key) & Released(key) & ~Pressed(key)) | (~Held(key) & Pressed(key) & Released(key)); } public class NeverTests : TestFixture { public void MatchesNothing() { CheckFalse(KeyFormula.Never.Matches(Snapshot())); CheckFalse(KeyFormula.Never.Matches(Snapshot(held: StateOf(KeyCode.A)))); } public void IsNotAlways() { CheckNotEqual(KeyFormula.Always, KeyFormula.Never); } } public class AlwaysTests : TestFixture { public void MatchesEverything() { Check(KeyFormula.Always.Matches(Snapshot())); Check(KeyFormula.Always.Matches(Snapshot(held: StateOf(KeyCode.A), pressed: StateOf(KeyCode.B)))); } } public class KeyTests : TestFixture { public void MatchesWhenTheKeyIsInTheSet() { Check(Held(KeyCode.A).Matches(Snapshot(held: StateOf(KeyCode.A)))); Check(Pressed(KeyCode.A).Matches(Snapshot(pressed: StateOf(KeyCode.A)))); Check(Released(KeyCode.A).Matches(Snapshot(released: StateOf(KeyCode.A)))); Check(Repeated(KeyCode.A).Matches(Snapshot(repeated: StateOf(KeyCode.A)))); } public void DoesNotMatchWhenTheKeyIsOnlyInAnotherSet() { KeyState justA = StateOf(KeyCode.A); CheckFalse(Held(KeyCode.A).Matches(Snapshot(pressed: justA, released: justA))); CheckFalse(Pressed(KeyCode.A).Matches(Snapshot(held: justA, released: justA))); CheckFalse(Released(KeyCode.A).Matches(Snapshot(held: justA, pressed: justA))); CheckFalse(Repeated(KeyCode.A).Matches(Snapshot(held: justA, pressed: justA, released: justA))); CheckFalse(Held(KeyCode.A).Matches(Snapshot(repeated: justA))); } public void DoesNotMatchWhenOnlyAnotherKeyIsInTheSet() { CheckFalse(Held(KeyCode.A).Matches(Snapshot(held: StateOf(KeyCode.B)))); } public void KeyInTheHighHalf_Matches() { Check(Pressed(KeyCode.Numpad9).Matches(Snapshot(pressed: StateOf(KeyCode.Numpad9)))); CheckFalse(Pressed(KeyCode.Numpad9).Matches(Snapshot(pressed: StateOf(KeyCode.A)))); } public void IsOneNode() { CheckEqual(1, Held(KeyCode.A).NodeCount); } public void NoneKey_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); KeyFormula.Key(KeyCode.None, KeyStateKind.Held); } public void UndefinedKind_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); KeyFormula.Key(KeyCode.A, (KeyStateKind)4); } public void UndefinedKeyThatKeyStateHasRoomFor_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); KeyFormula.Key((KeyCode)120, KeyStateKind.Held); } } public class AnyTests : TestFixture { public void MatchesWhenAnyKeyIsInTheSet() { KeyFormula formula = KeyFormula.Any(StateOf(KeyCode.A, KeyCode.B), KeyStateKind.Held); Check(formula.Matches(Snapshot(held: StateOf(KeyCode.A)))); Check(formula.Matches(Snapshot(held: StateOf(KeyCode.B)))); Check(formula.Matches(Snapshot(held: StateOf(KeyCode.A, KeyCode.B)))); } public void DoesNotMatchWhenNoneIsInTheSet() { KeyFormula formula = KeyFormula.Any(StateOf(KeyCode.A, KeyCode.B), KeyStateKind.Held); CheckFalse(formula.Matches(Snapshot(held: StateOf(KeyCode.C), pressed: StateOf(KeyCode.A)))); } public void KeysInBothHalves_MatchEitherOne() { KeyFormula formula = KeyFormula.Any(StateOf(KeyCode.A, KeyCode.Numpad9), KeyStateKind.Pressed); Check(formula.Matches(Snapshot(pressed: StateOf(KeyCode.A)))); Check(formula.Matches(Snapshot(pressed: StateOf(KeyCode.Numpad9)))); } public void NoKeys_IsNever() { CheckEqual(KeyFormula.Never, KeyFormula.Any(default, KeyStateKind.Held)); } } public class AllTests : TestFixture { public void MatchesWhenEveryKeyIsInTheSet() { KeyFormula formula = KeyFormula.All(StateOf(KeyCode.A, KeyCode.B), KeyStateKind.Held); Check(formula.Matches(Snapshot(held: StateOf(KeyCode.A, KeyCode.B, KeyCode.C)))); } public void DoesNotMatchWhenAKeyIsMissing() { KeyFormula formula = KeyFormula.All(StateOf(KeyCode.A, KeyCode.B), KeyStateKind.Held); CheckFalse(formula.Matches(Snapshot(held: StateOf(KeyCode.A), pressed: StateOf(KeyCode.B)))); } public void KeysInBothHalves_NeedBoth() { KeyFormula formula = KeyFormula.All(StateOf(KeyCode.A, KeyCode.Numpad9), KeyStateKind.Released); Check(formula.Matches(Snapshot(released: StateOf(KeyCode.A, KeyCode.Numpad9)))); CheckFalse(formula.Matches(Snapshot(released: StateOf(KeyCode.Numpad9)))); } public void NoKeys_IsAlways() { CheckEqual(KeyFormula.Always, KeyFormula.All(default, KeyStateKind.Held)); } } public class NoneTests : TestFixture { public void MatchesWhenNoKeyIsInTheSet() { KeyFormula formula = KeyFormula.None(StateOf(KeyCode.A, KeyCode.B), KeyStateKind.Held); Check(formula.Matches(Snapshot(held: StateOf(KeyCode.C), pressed: StateOf(KeyCode.A)))); } public void DoesNotMatchWhenAnyKeyIsInTheSet() { KeyFormula formula = KeyFormula.None(StateOf(KeyCode.A, KeyCode.B), KeyStateKind.Held); CheckFalse(formula.Matches(Snapshot(held: StateOf(KeyCode.B)))); } public void NoKeys_IsAlways() { CheckEqual(KeyFormula.Always, KeyFormula.None(default, KeyStateKind.Held)); } } public class AndTests : TestFixture { public void MatchesWhenBothDo() { KeyFormula formula = Held(KeyCode.A) & Held(KeyCode.B); Check(formula.Matches(Snapshot(held: StateOf(KeyCode.A, KeyCode.B)))); CheckFalse(formula.Matches(Snapshot(held: StateOf(KeyCode.A)))); CheckFalse(formula.Matches(Snapshot(held: StateOf(KeyCode.B)))); } public void NullLeft_Throws() { CheckThrow(typeof(ArgumentNullException)); KeyFormula unused = null & Held(KeyCode.A); } public void NullRight_Throws() { CheckThrow(typeof(ArgumentNullException)); KeyFormula unused = Held(KeyCode.A) & null; } } public class OrTests : TestFixture { public void MatchesWhenEitherDoes() { KeyFormula formula = Held(KeyCode.A) | Held(KeyCode.B); Check(formula.Matches(Snapshot(held: StateOf(KeyCode.A)))); Check(formula.Matches(Snapshot(held: StateOf(KeyCode.B)))); CheckFalse(formula.Matches(Snapshot(held: StateOf(KeyCode.C)))); } public void NullLeft_Throws() { CheckThrow(typeof(ArgumentNullException)); KeyFormula unused = null | Held(KeyCode.A); } public void NullRight_Throws() { CheckThrow(typeof(ArgumentNullException)); KeyFormula unused = Held(KeyCode.A) | null; } } public class XorTests : TestFixture { public void MatchesWhenExactlyOneDoes() { KeyFormula formula = Held(KeyCode.A) ^ Held(KeyCode.B); Check(formula.Matches(Snapshot(held: StateOf(KeyCode.A)))); Check(formula.Matches(Snapshot(held: StateOf(KeyCode.B)))); CheckFalse(formula.Matches(Snapshot(held: StateOf(KeyCode.A, KeyCode.B)))); CheckFalse(formula.Matches(Snapshot())); } public void NullLeft_Throws() { CheckThrow(typeof(ArgumentNullException)); KeyFormula unused = null ^ Held(KeyCode.A); } public void NullRight_Throws() { CheckThrow(typeof(ArgumentNullException)); KeyFormula unused = Held(KeyCode.A) ^ null; } } public class NotTests : TestFixture { public void MatchesWhatTheOperandDoesNot() { KeyFormula formula = ~Held(KeyCode.A); Check(formula.Matches(Snapshot())); CheckFalse(formula.Matches(Snapshot(held: StateOf(KeyCode.A)))); } public void Null_Throws() { CheckThrow(typeof(ArgumentNullException)); KeyFormula unused = ~(KeyFormula)null; } } public class MatchesTests : TestFixture { public void ControlWithoutShiftAndQAndT_MatchesOnlyThatCombination() { KeyFormula formula = KeyFormula.Any(KeyState.Control, KeyStateKind.Held) & ~KeyFormula.Any(KeyState.Shift, KeyStateKind.Held) & Held(KeyCode.Q) & Held(KeyCode.T); Check(formula.Matches(Snapshot(held: StateOf(KeyCode.ControlRight, KeyCode.Q, KeyCode.T)))); CheckFalse(formula.Matches(Snapshot(held: StateOf(KeyCode.Q, KeyCode.T)))); CheckFalse(formula.Matches(Snapshot(held: StateOf(KeyCode.ControlLeft, KeyCode.ShiftRight, KeyCode.Q, KeyCode.T)))); CheckFalse(formula.Matches(Snapshot(held: StateOf(KeyCode.ControlLeft, KeyCode.Q)))); } public void XorOfXWithYAndT_MatchesExactlyOneSide() { KeyFormula formula = Held(KeyCode.X) ^ (Held(KeyCode.Y) & Held(KeyCode.T)); Check(formula.Matches(Snapshot(held: StateOf(KeyCode.X)))); Check(formula.Matches(Snapshot(held: StateOf(KeyCode.X, KeyCode.Y)))); Check(formula.Matches(Snapshot(held: StateOf(KeyCode.Y, KeyCode.T)))); CheckFalse(formula.Matches(Snapshot(held: StateOf(KeyCode.X, KeyCode.Y, KeyCode.T)))); CheckFalse(formula.Matches(Snapshot(held: StateOf(KeyCode.Y)))); } public void KeyPressedWhileAnotherIsHeld_MatchesAcrossSets() { KeyFormula formula = (Pressed(KeyCode.A) & Held(KeyCode.B)) | (Pressed(KeyCode.B) & Held(KeyCode.A)); Check(formula.Matches(Snapshot(held: StateOf(KeyCode.A, KeyCode.B), pressed: StateOf(KeyCode.B)))); Check(formula.Matches(Snapshot(held: StateOf(KeyCode.A, KeyCode.B), pressed: StateOf(KeyCode.A)))); CheckFalse(formula.Matches(Snapshot(held: StateOf(KeyCode.A), pressed: StateOf(KeyCode.A)))); } public void RandomFormulas_AgreeWithTheirReferenceOnEverySnapshot() { foreach (Sample sample in RandomSamples(seed: 1, count: 40, depth: 4)) { CheckEqual("", FirstDisagreement(sample.Formula, sample.Reference, AllSnapshots)); } } } public class KeysTests : TestFixture { public void Never_HasNoKeys() { CheckEqual(default(KeyState), KeyFormula.Never.Keys); } public void Always_HasNoKeys() { CheckEqual(default(KeyState), KeyFormula.Always.Keys); } public void Key_IsThatKey() { CheckEqual(StateOf(KeyCode.A), Pressed(KeyCode.A).Keys); } public void KeyInTheHighHalf_IsThatKey() { CheckEqual(StateOf(KeyCode.Numpad9), Released(KeyCode.Numpad9).Keys); } public void KeysFromEverySet_AreAllListed() { KeyFormula formula = (Held(KeyCode.A) & Pressed(KeyCode.B)) | Released(KeyCode.C); CheckEqual(StateOf(KeyCode.A, KeyCode.B, KeyCode.C), formula.Keys); } public void KeyThatCancelsOut_IsLeftOut() { KeyFormula formula = (Held(KeyCode.A) & Held(KeyCode.B)) | (Held(KeyCode.A) & ~Held(KeyCode.B)); CheckEqual(StateOf(KeyCode.A), formula.Keys); } } public class DependsOnTests : TestFixture { public void Never_DependsOnNothing() { CheckEqual(default(KeySnapshot), KeyFormula.Never.DependsOn); } public void Always_DependsOnNothing() { CheckEqual(default(KeySnapshot), KeyFormula.Always.DependsOn); } public void EachKind_IsListedUnderItsOwnSet() { KeyFormula formula = Held(KeyCode.A) & Pressed(KeyCode.B) & Released(KeyCode.C) & Repeated(KeyCode.Numpad9); CheckEqual(Snapshot(held: StateOf(KeyCode.A), pressed: StateOf(KeyCode.B), released: StateOf(KeyCode.C), repeated: StateOf(KeyCode.Numpad9)), formula.DependsOn); } public void OneKeyInTwoKinds_IsListedUnderBoth() { KeyFormula formula = Held(KeyCode.A) | Pressed(KeyCode.A); CheckEqual(Snapshot(held: StateOf(KeyCode.A), pressed: StateOf(KeyCode.A)), formula.DependsOn); } public void KindThatCancelsOut_IsLeftOut() { KeyFormula formula = (Held(KeyCode.A) & Pressed(KeyCode.A)) | (Held(KeyCode.A) & ~Pressed(KeyCode.A)); CheckEqual(Snapshot(held: StateOf(KeyCode.A)), formula.DependsOn); } public void RandomFormulas_ListExactlyWhatTheirResultDependsOn() { // Bit v of an index is variable v, so flipping one is an xor. int variableCount = Universe.Length * Kinds.Length; var everyAssignment = new KeySnapshot[1 << variableCount]; for (int index = 0; index < everyAssignment.Length; ++index) { for (int variable = 0; variable < variableCount; ++variable) { if ((index >> variable & 1) != 0) { everyAssignment[index] = WithVariableSet(everyAssignment[index], variable); } } } foreach (Sample sample in RandomSamples(seed: 7, count: 10, depth: 3)) { bool[] table = everyAssignment.Select(sample.Reference).ToArray(); KeySnapshot dependsOn = default; for (int variable = 0; variable < variableCount; ++variable) { int flip = 1 << variable; for (int index = 0; index < table.Length; ++index) { if (table[index] != table[index ^ flip]) { dependsOn = WithVariableSet(dependsOn, variable); break; } } } CheckEqual(dependsOn, sample.Formula.DependsOn); } } } public class ImpliesTests : TestFixture { public void ConjunctionImpliesEitherOperand() { KeyFormula conjunction = Held(KeyCode.A) & Held(KeyCode.B); Check(conjunction.Implies(Held(KeyCode.A))); Check(conjunction.Implies(Held(KeyCode.B))); } public void OperandDoesNotImplyConjunction() { CheckFalse(Held(KeyCode.A).Implies(Held(KeyCode.A) & Held(KeyCode.B))); } public void PressedWithoutBeingHeld_DoesNotImplyReleased() { KeyFormula formula = Pressed(KeyCode.A) & ~Held(KeyCode.A); CheckFalse(formula.Implies(Released(KeyCode.A))); } public void Null_Throws() { CheckThrow(typeof(ArgumentNullException)); Held(KeyCode.A).Implies(null); } } public class ImpliesWhenConsistentTests : TestFixture { public void PressedWithoutBeingHeld_ImpliesReleased() { KeyFormula formula = Pressed(KeyCode.A) & ~Held(KeyCode.A); Check(formula.ImpliesWhenConsistent(Released(KeyCode.A))); } public void ReleasedWhileHeld_ImpliesPressed() { KeyFormula formula = Released(KeyCode.A) & Held(KeyCode.A); Check(formula.ImpliesWhenConsistent(Pressed(KeyCode.A))); } public void Repeated_ImpliesHeldButNotPressed() { Check(Repeated(KeyCode.A).ImpliesWhenConsistent(Held(KeyCode.A))); Check(Repeated(KeyCode.A).ImpliesWhenConsistent(~Pressed(KeyCode.A))); CheckFalse(Repeated(KeyCode.A).Implies(Held(KeyCode.A))); } public void CounterexampleNoKeyboardCouldProduce_DoesNotCount() { KeyFormula formula = TapOrImpossibleRelease(KeyCode.A); Check(formula.ImpliesWhenConsistent(~Held(KeyCode.A))); CheckFalse(formula.Implies(~Held(KeyCode.A))); } public void ConsistentCounterexample_DoesNotImply() { CheckFalse(Held(KeyCode.A).ImpliesWhenConsistent(Held(KeyCode.B))); } public void EitherOfTwoKeys_DoesNotImplyOne() { CheckFalse((Held(KeyCode.A) | Held(KeyCode.B)).ImpliesWhenConsistent(Held(KeyCode.A))); } public void Itself_IsImplied() { KeyFormula formula = Held(KeyCode.A) | Pressed(KeyCode.B); Check(formula.ImpliesWhenConsistent(formula)); } public void Never_ImpliesAnything() { Check(KeyFormula.Never.ImpliesWhenConsistent(Held(KeyCode.A))); } public void RandomPairs_AgreeWithBruteForceOverEveryConsistentSnapshot() { Sample[] samples = RandomSamples(seed: 4, count: 30, depth: 4); bool[][] tables = samples.Select(sample => ConsistentSnapshots.Select(sample.Reference).ToArray()) .ToArray(); for (int i = 0; i < samples.Length; ++i) { for (int j = 0; j < samples.Length; ++j) { bool expected = Enumerable.Range(0, ConsistentSnapshots.Length) .All(index => !tables[i][index] || tables[j][index]); CheckEqual(expected, samples[i].Formula.ImpliesWhenConsistent(samples[j].Formula)); } } } public void Null_Throws() { CheckThrow(typeof(ArgumentNullException)); Held(KeyCode.A).ImpliesWhenConsistent(null); } } public class ImpliesWhenConsistentBeyondWitnessTests : TestFixture { public void ImplicationOverEverySnapshot_Implies() { Check((Held(KeyCode.A) & Held(KeyCode.B)).ImpliesWhenConsistentBeyondWitness(Held(KeyCode.A))); } public void ImplicationOnlyOverConsistentSnapshots_Implies() { KeyFormula formula = Pressed(KeyCode.A) & ~Held(KeyCode.A); Check(formula.ImpliesWhenConsistentBeyondWitness(Released(KeyCode.A))); CheckFalse(formula.Implies(Released(KeyCode.A))); } public void ConsistentCounterexample_DoesNotImply() { CheckFalse(Held(KeyCode.A).ImpliesWhenConsistentBeyondWitness(Held(KeyCode.B))); } public void CounterexampleNoKeyboardCouldProduce_DoesNotCount() { Check(TapOrImpossibleRelease(KeyCode.A).ImpliesWhenConsistentBeyondWitness(~Held(KeyCode.A))); } public void FirstAndLastKey_UseTheirOwnConstraints() { Check(Repeated(KeyCode.A).ImpliesWhenConsistentBeyondWitness(Held(KeyCode.A))); Check(Repeated(KeyCode.NumpadEnter).ImpliesWhenConsistentBeyondWitness(Held(KeyCode.NumpadEnter))); CheckFalse(Repeated(KeyCode.NumpadEnter).ImpliesWhenConsistentBeyondWitness(Held(KeyCode.A))); } public void Null_Throws() { CheckThrow(typeof(ArgumentNullException)); Held(KeyCode.A).ImpliesWhenConsistentBeyondWitness(null); } } public class TryFindConsistentWitnessTests : TestFixture { public void Never_FindsNone() { CheckFalse(KeyFormula.Never.TryFindConsistentWitness(out _)); } public void Key_FindsASnapshotItMatches() { KeyFormula formula = Pressed(KeyCode.A); Check(formula.TryFindConsistentWitness(out KeySnapshot witness)); Check(formula.Matches(witness)); } public void Witness_IsConsistent() { Check((Pressed(KeyCode.A) | Repeated(KeyCode.B)).TryFindConsistentWitness(out KeySnapshot witness)); Check(witness.IsConsistent()); } public void FormulaNoKeyboardCouldMatch_FindsNone() { CheckFalse((Repeated(KeyCode.A) & Pressed(KeyCode.A)).TryFindConsistentWitness(out _)); } } public class IsSatisfiableTests : TestFixture { public void RepeatedWithPressedOrReleased_IsNotSatisfiable() { CheckFalse((Repeated(KeyCode.A) & Pressed(KeyCode.A)).IsSatisfiable()); CheckFalse((Repeated(KeyCode.A) & Released(KeyCode.A)).IsSatisfiable()); CheckFalse((Repeated(KeyCode.A) & ~Held(KeyCode.A)).IsSatisfiable()); Check((Repeated(KeyCode.A) & Held(KeyCode.A)).IsSatisfiable()); } public void Never_IsNotSatisfiable() { CheckFalse(KeyFormula.Never.IsSatisfiable()); } public void Always_IsSatisfiable() { Check(KeyFormula.Always.IsSatisfiable()); } public void Key_IsSatisfiable() { Check(Pressed(KeyCode.A).IsSatisfiable()); } public void KeyAndItsNegation_IsNotSatisfiable() { CheckFalse((Held(KeyCode.A) & ~Held(KeyCode.A)).IsSatisfiable()); } public void PressedWithoutBeingHeldIsStillPossible_AsATap() { Check((Pressed(KeyCode.A) & ~Held(KeyCode.A)).IsSatisfiable()); } public void PressedWithoutHeldOrReleased_IsNotSatisfiable() { CheckFalse((Pressed(KeyCode.A) & ~Held(KeyCode.A) & ~Released(KeyCode.A)).IsSatisfiable()); } public void ReleasedWhileHeldWithoutPress_IsNotSatisfiable() { CheckFalse((Released(KeyCode.A) & Held(KeyCode.A) & ~Pressed(KeyCode.A)).IsSatisfiable()); } public void OnlyAnImpossibleFirstMatch_IsStillSatisfiableByAnotherOne() { Check(TapOrImpossibleRelease(KeyCode.A).IsSatisfiable()); } public void RandomFormulas_AgreeWithBruteForceOverEveryConsistentSnapshot() { foreach (Sample sample in RandomSamples(seed: 5, count: 100, depth: 4)) { CheckEqual(ConsistentSnapshots.Any(snapshot => sample.Reference(snapshot)), sample.Formula.IsSatisfiable()); } } } public class RequiresEdgeTests : TestFixture { public void Pressed_RequiresOne() { Check(Pressed(KeyCode.A).RequiresEdge()); } public void Released_RequiresOne() { Check(Released(KeyCode.A).RequiresEdge()); } public void Repeated_RequiresOne() { Check(Repeated(KeyCode.A).RequiresEdge()); } public void Held_DoesNot() { CheckFalse(Held(KeyCode.A).RequiresEdge()); } public void NotHeld_DoesNot() { CheckFalse((~Held(KeyCode.A)).RequiresEdge()); } public void PressedWhileAnotherIsHeld_RequiresOne() { Check((Pressed(KeyCode.A) & Held(KeyCode.B)).RequiresEdge()); } public void PressedOrHeld_DoesNot() { CheckFalse((Pressed(KeyCode.A) | Held(KeyCode.B)).RequiresEdge()); } public void NotPressed_DoesNot() { CheckFalse((~Pressed(KeyCode.A)).RequiresEdge()); } public void Always_DoesNot() { CheckFalse(KeyFormula.Always.RequiresEdge()); } public void Never_Does() { Check(KeyFormula.Never.RequiresEdge()); } public void RandomFormulas_AgreeWithBruteForceOverEveryIdleSnapshot() { foreach (Sample sample in RandomSamples(seed: 6, count: 100, depth: 4)) { bool matchesAnIdleSnapshot = AllSnapshots.Any(snapshot => snapshot.Pressed == default && snapshot.Released == default && snapshot.Repeated == default && sample.Reference(snapshot)); CheckEqual(!matchesAnIdleSnapshot, sample.Formula.RequiresEdge()); } } } public class EqualsTests : TestFixture { public void FormulasBuiltDifferentlyButMeaningTheSame_AreEqual() { KeyFormula first = Held(KeyCode.A) & Held(KeyCode.B); KeyFormula second = ~(~Held(KeyCode.A) | ~Held(KeyCode.B)); CheckEqual(first, second); Check(first.Equals(second)); Check(first.Equals((object)second)); } public void DifferentMeanings_AreNotEqual() { CheckNotEqual(Held(KeyCode.A), Held(KeyCode.B)); CheckNotEqual(Held(KeyCode.A), Pressed(KeyCode.A)); CheckNotEqual(Held(KeyCode.A) & Held(KeyCode.B), Held(KeyCode.A) | Held(KeyCode.B)); } public void Null_IsNotEqual() { CheckFalse(Held(KeyCode.A).Equals((KeyFormula)null)); CheckFalse(Held(KeyCode.A).Equals((object)null)); } public void AnotherType_IsNotEqual() { CheckFalse(Held(KeyCode.A).Equals("Held(A)")); } } public class GetHashCodeTests : TestFixture { public void EqualFormulas_HaveEqualHashCodes() { KeyFormula first = Held(KeyCode.A) & Held(KeyCode.B); KeyFormula second = ~(~Held(KeyCode.A) | ~Held(KeyCode.B)); CheckEqual(first.GetHashCode(), second.GetHashCode()); } public void DifferentFormulas_HaveDifferentHashCodes() { CheckNotEqual(Held(KeyCode.A).GetHashCode(), Held(KeyCode.B).GetHashCode()); } } public class ToStringTests : TestFixture { public void Never_SaysSo() { CheckEqual("Never", KeyFormula.Never.ToString()); } public void Always_SaysSo() { CheckEqual("Always", KeyFormula.Always.ToString()); } public void Key_NamesTheSetAndTheKey() { CheckEqual("Held(A)", Held(KeyCode.A).ToString()); CheckEqual("Pressed(Z)", Pressed(KeyCode.Z).ToString()); CheckEqual("Released(Numpad9)", Released(KeyCode.Numpad9).ToString()); CheckEqual("Repeated(Backspace)", Repeated(KeyCode.Backspace).ToString()); } public void Negation_PutsABangBeforeIt() { CheckEqual("!Held(A)", (~Held(KeyCode.A)).ToString()); } public void Conjunction_JoinsWithAnd() { CheckEqual("Held(A) & Pressed(B)", (Held(KeyCode.A) & Pressed(KeyCode.B)).ToString()); } public void Disjunction_JoinsDisjointCasesWithOr() { CheckEqual("Held(A) | !Held(A) & Pressed(B)", (Held(KeyCode.A) | Pressed(KeyCode.B)).ToString()); } } } }