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)); } } } }
private static int CountAssertsThatDontOccurInAllInstructionPaths(TestCase testCase, IEnumerable<IList<Instruction>> paths) { //TODO: Copied from LocalExpectationRule, need a better abstraction for this! var calledAssertingMethods = testCase.GetCalledAssertingMethods(); var calledAssertingMethodsWithInstruction = testCase.TestMethod.CalledMethods().Where(calledAssertingMethods.Contains); return calledAssertingMethodsWithInstruction.Count(cmi => paths.Any(path => !path.Contains(cmi.Instruction))); }
public IEnumerable<Violation> Apply(TestCase testCase) { // It seems as if unhandled return values are popped off the stack // immediately via an explicit "pop" instruction. // We exclude asserting methods to avoid getting a violation for Assert.Throws (or Assert.Catch), // which returns the exception. var calledMethods = testCase.TestMethod.CalledMethods(); var asserting = testCase.GetCalledAssertingMethods(); var callingNonVoidInstructions = calledMethods .Where(cm => !asserting.Contains(cm)) .Select(cm => cm.Instruction); var unhandled = callingNonVoidInstructions.Where(ins => ins.Next.OpCode == OpCodes.Pop).ToList(); if (unhandled.Count == 1 && testCase.Framework.HasExpectedException(testCase.TestMethod)) yield break; // last unhandled value is ok! foreach (var instr in unhandled) { yield return new Violation(this, testCase, instr, CreateViolationMessage(instr)); } }
public IEnumerable<Violation> Apply(TestCase testCase) { if (!testCase.TestMethod.DeclaringType.Module.HasSymbols) yield break; //TODO: decide what to do here! var assertingMethods = testCase.GetCalledAssertingMethods(); // Note: The Mono compiler appears to emit multiple sequence points with the same start line, // i.e. there are multiple instructions with sequence points that refer to the same line. // Therefore, let's store line numbers and associate line numbers with asserting calls. var sequencePointsStartLines = new SortedSet<int>(); var assertingSequencePointsStartLines = new SortedSet<int>(); var tm = testCase.TestMethod; foreach (var ins in tm.Body.Instructions) { var sp = ins.SequencePoint; if (sp != null && IsSignificantSequencePoint(ins, sp)) { sequencePointsStartLines.Add(sp.StartLine); } if (sequencePointsStartLines.Count > 0 && IsAssertCall(ins, assertingMethods)) { // As sequence point, use the last one added, which isn't necessarily sp, // since the asserting instruction may lack sequence point. var lastSpLineNumber = sequencePointsStartLines.Last(); assertingSequencePointsStartLines.Add(lastSpLineNumber); } } if (assertingSequencePointsStartLines.Count == 0) yield break; // this rule doesn't apply // If the X asserting sps are the X last ones, then it's ok! if (sequencePointsStartLines.EndsWith(assertingSequencePointsStartLines)) yield break; yield return new Violation(this, testCase); }
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)); } } } }
protected override int GenerateValue(TestCase tc, Features f) { return tc.GetCalledAssertingMethods().Count; }