public IEnumerable<Violation> Apply(TestCase testCase) { var calledAssertingMethods = testCase.GetCalledAssertingMethods(); var tracker = new MethodValueTracker(testCase.TestMethod); // For each asserting method with >= 1 parameters: foreach (var cm in calledAssertingMethods) { var methodRef = cm.MethodReference; var parameterPurposes = testCase.Framework.GetParameterPurposes(methodRef); if (!IsSingleTruthCheckingMethod(methodRef, parameterPurposes)) continue; foreach (var valueGraph in tracker.ValueGraphs) { IList<MethodValueTracker.Value> consumedValues = tracker.GetConsumedValues(valueGraph, cm.Instruction).ToList(); if (consumedValues.Count == 0) continue; // not part of value graph var interestingValue = consumedValues[0]; var producers = UltimateProducers(interestingValue); if (producers.Count > 1) { yield return new Violation(this, testCase, cm.Instruction, string.Format("{0}.{1} performs a boolean test on a composite boolean value", cm.MethodReference.DeclaringType.Name, cm.MethodReference.Name)); } } } }
public void TestThatValueConsumptionForInstructionMatchesPath() { var method = GetType().FindMethod("MethodWithBranch()"); var tracker = new MethodValueTracker(method); var graph = tracker.ValueGraphs[1]; // second path // Find the last instruction that stores to y (first local) var instr = method.Body.Instructions.Where(i => i.OpCode == OpCodes.Stloc_0).Last(); var producers = tracker.GetConsumedValues(graph, instr).Select(v => v.Producer.OpCode).ToList(); // Expect the producer of the conssumed value to load from u (third local) CollectionAssert.AreEqual(new[] { OpCodes.Ldloc_2 }, producers); }
public void TestThatValueConsumptionForInstructionOutsidePathIsEmpty() { var method = GetType().FindMethod("MethodWithBranch()"); var tracker = new MethodValueTracker(method); var graph = tracker.ValueGraphs[0]; // first path // Find the last instruction that stores to y, part of second path var instr = method.Body.Instructions.Where(i => i.OpCode == OpCodes.Stloc_0).Last(); var consumed = tracker.GetConsumedValues(graph, instr); Assert.AreEqual(0, consumed.Count()); }
public IEnumerable<Violation> Apply(TestCase testCase) { // Store the framework so that we don't have to pass it around everywhere. _framework = testCase.Framework; var calledAssertingMethods = testCase.GetCalledAssertingMethods(); var tracker = new MethodValueTracker(testCase.TestMethod); var whitelistedFields = FindWhitelistedFields(testCase.TestMethod.DeclaringType); // For each asserting method with >= 1 parameters: foreach (var cm in calledAssertingMethods.Where(cm => cm.MethodDefinition.HasParameters)) { var method = cm.MethodDefinition; //TODO: if the method is a helper, we need to "unfold" the helper // to get to the real asserting methods, and this will require us // to join value-generation graphs across method calls... var paramPurposes = _framework.GetParameterPurposes(method); if (paramPurposes == null) continue; // unknown method, rule does not apply foreach (var valueGraph in tracker.ValueGraphs) { IList<MethodValueTracker.Value> consumedValues = tracker.GetConsumedValues(valueGraph, cm.Instruction).ToList(); if (consumedValues.Count == 0) continue; // not part of value graph // Build a list of arguments with the details we need to know if the rule applies. var arguments = method.Parameters .Select((p, index) => new ArgumentDetails { Method = method, Index = index, Purpose = paramPurposes[index], ConsumedValue = consumedValues[index] }).ToList(); // Handle cases like Assert.IsTrue(x == 5) by expanding arguments ExpandIfSingleTruthCheckingMethod(method, ref arguments); // We're only interested in arguments that represent expectations! var interestingArguments = arguments.Where(a => IsPerhapsExpectation(a.Purpose)).ToList(); // This might happen with for example Assert.Fail("some reason"). if (interestingArguments.Count == 0) continue; // Add in the "forbidden producer", if any, for each argument. A forbidden producer is an // instruction that generates a value externally, such as a call. interestingArguments = interestingArguments.Select( a => { a.ForbiddenProducer = FirstForbiddenProducer(valueGraph, a.ConsumedValue, whitelistedFields); return a; }).ToList(); // If there is at least one locally produced argument, the rule doesn't apply. if (interestingArguments.Any(IsLocallyProduced)) continue; if (interestingArguments.All(a => a.Purpose == ParameterPurpose.ExpectedOrActual)) { // Since we don't know exactly which parameter that represents the expectation, we // just generate a single violation. yield return new Violation(this, testCase, interestingArguments[0].ConsumedValue.Consumer, CreateViolationMessageForUncertainCase(interestingArguments[0])); continue; } foreach (var a in interestingArguments.Where(IsExternallyProduced)) { // Generate a violation at the location of the forbidden producer! yield return new Violation(this, testCase, a.ForbiddenProducer, CreateViolationMessage(a)); } } } }
public void TestThatConstantConsumedViaBoxedValueIsFound() { var method = GetType().FindMethod("BoxedConstantUseByStaticMethod()"); var tracker = new MethodValueTracker(method); var consumedValue = tracker.GetConsumedValues(tracker.ValueGraphs[0], LastCall(method)).First(); var roots = tracker.FindSourceValues(consumedValue); Assert.AreEqual(OpCodes.Ldc_I4_S, roots.First().Producer.OpCode); }
private IEnumerable<OpCode> FindAllSourceValueOpCodes(MethodValueTracker tracker, Instruction instruction) { var consumedValues = tracker.GetConsumedValues(tracker.ValueGraphs[0], instruction); var sourceValues = consumedValues.SelectMany(tracker.FindSourceValues); return sourceValues.Select(v => v.Producer.OpCode).Distinct(); }
public void TestThatQueryInstructionCannotBeNull() { var method = GetType().FindMethod("DirectConstantUseByStaticMethod()"); var tracker = new MethodValueTracker(method); tracker.GetConsumedValues(tracker.ValueGraphs[0], null); }
public void TestThatGraphCannotBeNullWhenQueryingConsumedValues() { var method = GetType().FindMethod("DirectConstantUseByStaticMethod()"); var tracker = new MethodValueTracker(method); tracker.GetConsumedValues(null, LastCall(method)); }
public void TestThatConsumedValuesIncludesFirstArgOfStaticCallInStatic() { var method = GetType().FindMethod("StaticMethodWithStaticCall(System.Int32)"); var tracker = new MethodValueTracker(method); var consumedValues = tracker.GetConsumedValues(tracker.ValueGraphs[0], LastCall(method)); var opCodes = consumedValues.Select(v => v.Producer.OpCode); // Ldarg_0 should be in the list! CollectionAssert.AreEqual(new[] { OpCodes.Ldarg_0 }, opCodes); }
public void TestThatConsumedValuesExcludesThisObjectForVirtualCall() { var method = GetType().FindMethod("DirectConstantUseByVirtualMethod()"); var tracker = new MethodValueTracker(method); var consumedValues = tracker.GetConsumedValues(tracker.ValueGraphs[0], LastCall(method)); var opCodes = consumedValues.Select(v => v.Producer.OpCode); // Ldarg_0 should not be in the list! CollectionAssert.AreEqual(new[] { OpCodes.Ldc_I4_S }, opCodes); }
public void TestThatConsumedValuesAreEmptyWhenQueryInstructionIsNotPartOfMethod() { var method = GetType().FindMethod("DirectConstantUseByStaticMethod()"); var method2 = GetType().FindMethod("DirectConstantUseByVirtualMethod()"); var tracker = new MethodValueTracker(method); var values = tracker.GetConsumedValues(tracker.ValueGraphs[0], LastCall(method2)); Assert.AreEqual(0, values.Count()); }
public void TestThatConsumedValueCountCorrespondsToActualConsumption() { var method = GetType().FindMethod("DirectConstantUseByStaticMethod()"); var tracker = new MethodValueTracker(method); var consumedValues = tracker.GetConsumedValues(tracker.ValueGraphs[0], LastCall(method)); Assert.AreEqual(1, consumedValues.Count()); }