/src/NUnit/core/Builders/PairwiseStrategy.cs
C# | 695 lines | 548 code | 135 blank | 12 comment | 71 complexity | ec2ff4ab794f098d2a420e3dd72640d6 MD5 | raw file
1// **************************************************************** 2// Copyright 2008, Charlie Poole 3// This is free software licensed under the NUnit license. You may 4// obtain a copy of the license at http://nunit.org. 5// **************************************************************** 6 7using System; 8using System.Collections; 9#if NET_2_0 10using System.Collections.Generic; 11#endif 12using System.Reflection; 13using System.Text; 14using NUnit.Core.Extensibility; 15 16namespace NUnit.Core.Builders 17{ 18 public class PairwiseStrategy : CombiningStrategy 19 { 20 internal class FleaRand 21 { 22 private const int FleaRandSize = 256; 23 24 private uint b; 25 private uint c; 26 private uint d; 27 private uint z; 28 29 private uint[] m = new uint[FleaRandSize]; 30 private uint[] r = new uint[FleaRandSize]; 31 32 private uint q; 33 34 public FleaRand(uint seed) 35 { 36 this.b = seed; 37 this.c = seed; 38 this.d = seed; 39 this.z = seed; 40 41 for (int i = 0; i < this.m.Length; i++) 42 { 43 this.m[i] = seed; 44 } 45 46 for (int i = 0; i < 10; i++) 47 { 48 this.Batch(); 49 } 50 51 this.q = 0; 52 } 53 54 public uint Next() 55 { 56 if (this.q == 0) 57 { 58 this.Batch(); 59 this.q = (uint)this.r.Length - 1; 60 } 61 else 62 { 63 this.q--; 64 } 65 66 return this.r[this.q]; 67 } 68 69 private void Batch() 70 { 71 uint a; 72 uint b = this.b; 73 uint c = this.c + (++this.z); 74 uint d = this.d; 75 76 for (int i = 0; i < this.r.Length; i++) 77 { 78 a = this.m[b % this.m.Length]; 79 this.m[b % this.m.Length] = d; 80 d = (c << 19) + (c >> 13) + b; 81 c = b ^ this.m[i]; 82 b = a + d; 83 this.r[i] = c; 84 } 85 86 this.b = b; 87 this.c = c; 88 this.d = d; 89 } 90 } 91 92 internal class FeatureInfo 93 { 94 public const string Names = "abcdefghijklmnopqrstuvwxyz"; 95 96 public readonly int Dimension; 97 public readonly int Feature; 98 99 public FeatureInfo(int dimension, int feature) 100 { 101 this.Dimension = dimension; 102 this.Feature = feature; 103 } 104 105#if DEBUG 106 public override string ToString() 107 { 108 return (this.Dimension + 1).ToString() + FeatureInfo.Names[this.Feature]; 109 } 110#endif 111 } 112 113 internal class Tuple 114 { 115 private readonly ArrayList features = new ArrayList(); 116 117 public int Count 118 { 119 get 120 { 121 return this.features.Count; 122 } 123 } 124 125 public FeatureInfo this[int index] 126 { 127 get 128 { 129 return (FeatureInfo)this.features[index]; 130 } 131 } 132 133 public void Add(FeatureInfo feature) 134 { 135 this.features.Add(feature); 136 } 137 138#if DEBUG 139 public override string ToString() 140 { 141 StringBuilder sb = new StringBuilder(); 142 143 sb.Append('('); 144 145 for (int i = 0; i < this.features.Count; i++) 146 { 147 if (i > 0) 148 { 149 sb.Append(' '); 150 } 151 152 sb.Append(this.features[i].ToString()); 153 } 154 155 sb.Append(')'); 156 157 return sb.ToString(); 158 } 159#endif 160 } 161 162 internal class TupleCollection 163 { 164 private readonly ArrayList tuples = new ArrayList(); 165 166 public int Count 167 { 168 get 169 { 170 return this.tuples.Count; 171 } 172 } 173 174 public Tuple this[int index] 175 { 176 get 177 { 178 return (Tuple)this.tuples[index]; 179 } 180 } 181 182 public void Add(Tuple tuple) 183 { 184 this.tuples.Add(tuple); 185 } 186 187 public void RemoveAt(int index) 188 { 189 this.tuples.RemoveAt(index); 190 } 191 } 192 193 internal class TestCase 194 { 195 public readonly int[] Features; 196 197 public TestCase(int numberOfDimensions) 198 { 199 this.Features = new int[numberOfDimensions]; 200 } 201 202 public bool IsTupleCovered(Tuple tuple) 203 { 204 for (int i = 0; i < tuple.Count; i++) 205 { 206 if (this.Features[tuple[i].Dimension] != tuple[i].Feature) 207 { 208 return false; 209 } 210 } 211 212 return true; 213 } 214 215#if DEBUG 216 public override string ToString() 217 { 218 StringBuilder sb = new StringBuilder(); 219 220 for (int i = 0; i < this.Features.Length; i++) 221 { 222 if (i > 0) 223 { 224 sb.Append(' '); 225 } 226 227 sb.Append(i + 1); 228 sb.Append(FeatureInfo.Names[this.Features[i]]); 229 } 230 231 return sb.ToString(); 232 } 233#endif 234 } 235 236 internal class TestCaseCollection : IEnumerable 237 { 238 private readonly ArrayList testCases = new ArrayList(); 239 240 public void Add(TestCase testCase) 241 { 242 this.testCases.Add(testCase); 243 } 244 245 public IEnumerator GetEnumerator() 246 { 247 return this.testCases.GetEnumerator(); 248 } 249 250 public bool IsTupleCovered(Tuple tuple) 251 { 252 foreach (TestCase testCase in this.testCases) 253 { 254 if (testCase.IsTupleCovered(tuple)) 255 { 256 return true; 257 } 258 } 259 260 return false; 261 } 262 } 263 264 internal class PairwiseTestCaseGenerator 265 { 266 private const int MaxTupleLength = 2; 267 268 private readonly FleaRand random = new FleaRand(0); 269 270 private readonly int[] dimensions; 271 272 private readonly TupleCollection[][] uncoveredTuples; 273 274 private readonly int[][] currentTupleLength; 275 276 private readonly TestCaseCollection testCases = new TestCaseCollection(); 277 278 public PairwiseTestCaseGenerator(int[] dimensions) 279 { 280 this.dimensions = dimensions; 281 282 this.uncoveredTuples = new TupleCollection[this.dimensions.Length][]; 283 284 for (int d = 0; d < this.uncoveredTuples.Length; d++) 285 { 286 this.uncoveredTuples[d] = new TupleCollection[this.dimensions[d]]; 287 288 for (int f = 0; f < this.dimensions[d]; f++) 289 { 290 this.uncoveredTuples[d][f] = new TupleCollection(); 291 } 292 } 293 294 this.currentTupleLength = new int[this.dimensions.Length][]; 295 296 for (int d = 0; d < this.dimensions.Length; d++) 297 { 298 this.currentTupleLength[d] = new int[this.dimensions[d]]; 299 } 300 } 301 302 public IEnumerable GetTestCases() 303 { 304 this.CreateTestCases(); 305 306 this.SelfTest(); 307 308 return this.testCases; 309 } 310 311 private void CreateTestCases() 312 { 313 while (true) 314 { 315 this.ExtendTupleSet(); 316 317 Tuple tuple = this.FindTupleToCover(); 318 319 if (tuple == null) 320 { 321 return; 322 } 323 324 TestCase testCase = this.FindGoodTestCase(tuple); 325 326 this.RemoveTuplesCoveredBy(testCase); 327 328 this.testCases.Add(testCase); 329 } 330 } 331 332 private void ExtendTupleSet() 333 { 334 for (int d = 0; d < this.dimensions.Length; d++) 335 { 336 for (int f = 0; f < this.dimensions[d]; f++) 337 { 338 this.ExtendTupleSet(d, f); 339 } 340 } 341 } 342 343 private void ExtendTupleSet(int dimension, int feature) 344 { 345 // If tuples for [dimension][feature] already exists, it's no needs to add more tuples. 346 if (this.uncoveredTuples[dimension][feature].Count > 0) 347 { 348 return; 349 } 350 351 // If maximum tuple length for [dimension][feature] is reached, it's no needs to add more tuples. 352 if (this.currentTupleLength[dimension][feature] == MaxTupleLength) 353 { 354 return; 355 } 356 357 this.currentTupleLength[dimension][feature]++; 358 359 int tupleLength = this.currentTupleLength[dimension][feature]; 360 361 if (tupleLength == 1) 362 { 363 Tuple tuple = new Tuple(); 364 365 tuple.Add(new FeatureInfo(dimension, feature)); 366 367 if (this.testCases.IsTupleCovered(tuple)) 368 { 369 return; 370 } 371 372 this.uncoveredTuples[dimension][feature].Add(tuple); 373 } 374 else 375 { 376 for (int d = 0; d < this.dimensions.Length; d++) 377 { 378 for (int f = 0; f < this.dimensions[d]; f++) 379 { 380 Tuple tuple = new Tuple(); 381 tuple.Add(new FeatureInfo(d, f)); 382 383 if (tuple[0].Dimension == dimension) 384 { 385 continue; 386 } 387 388 tuple.Add(new FeatureInfo(dimension, feature)); 389 390 if (this.testCases.IsTupleCovered(tuple)) 391 { 392 continue; 393 } 394 395 this.uncoveredTuples[dimension][feature].Add(tuple); 396 } 397 } 398 } 399 } 400 401 private Tuple FindTupleToCover() 402 { 403 int tupleLength = MaxTupleLength; 404 int tupleCount = 0; 405 Tuple tuple = null; 406 407 for (int d = 0; d < this.dimensions.Length; d++) 408 { 409 for (int f = 0; f < this.dimensions[d]; f++) 410 { 411 if (this.currentTupleLength[d][f] < tupleLength) 412 { 413 tupleLength = this.currentTupleLength[d][f]; 414 tupleCount = this.uncoveredTuples[d][f].Count; 415 tuple = this.uncoveredTuples[d][f][0]; 416 } 417 else 418 { 419 if (this.currentTupleLength[d][f] == tupleLength && this.uncoveredTuples[d][f].Count > tupleCount) 420 { 421 tupleCount = this.uncoveredTuples[d][f].Count; 422 tuple = this.uncoveredTuples[d][f][0]; 423 } 424 } 425 } 426 } 427 428 return tuple; 429 } 430 431 private TestCase FindGoodTestCase(Tuple tuple) 432 { 433 TestCase bestTest = null; 434 int bestCoverage = -1; 435 436 for (int i = 0; i < 5; i++) 437 { 438 TestCase test = new TestCase(this.dimensions.Length); 439 440 int coverage = this.CreateTestCase(tuple, test); 441 442 if (coverage > bestCoverage) 443 { 444 bestTest = test; 445 bestCoverage = coverage; 446 } 447 } 448 449 return bestTest; 450 } 451 452 private int CreateTestCase(Tuple tuple, TestCase test) 453 { 454 // Create a random test case... 455 for (int i = 0; i < test.Features.Length; i++) 456 { 457 test.Features[i] = (int)(this.random.Next() % this.dimensions[i]); 458 } 459 460 // ...and inject the tuple into it! 461 for (int i = 0; i < tuple.Count; i++) 462 { 463 test.Features[tuple[i].Dimension] = tuple[i].Feature; 464 } 465 466 return this.MaximizeCoverage(test, tuple); 467 } 468 469 private int MaximizeCoverage(TestCase test, Tuple tuple) 470 { 471 int[] dimensionOrder = this.GetMutableDimensions(tuple); 472 473 while (true) 474 { 475 bool progress = false; 476 int totalCoverage = 1; 477 478 // Scramble dimensions. 479 for (int i = dimensionOrder.Length; i > 1; i--) 480 { 481 int j = (int)(this.random.Next() % i); 482 int t = dimensionOrder[i - 1]; 483 dimensionOrder[i - 1] = dimensionOrder[j]; 484 dimensionOrder[j] = t; 485 } 486 487 // For each dimension that can be modified... 488 for (int i = 0; i < dimensionOrder.Length; i++) 489 { 490 int d = dimensionOrder[i]; 491 492 ArrayList bestFeatures = new ArrayList(); 493 494 int bestCoverage = this.CountTuplesCovered(test, d, test.Features[d]); 495 496 int bestTupleLength = this.currentTupleLength[d][test.Features[d]]; 497 498 // For each feature that can be modified, check if it can extend coverage. 499 for (int f = 0; f < this.dimensions[d]; f++) 500 { 501 test.Features[d] = f; 502 503 int coverage = this.CountTuplesCovered(test, d, f); 504 505 if (this.currentTupleLength[d][f] < bestTupleLength) 506 { 507 progress = true; 508 bestTupleLength = this.currentTupleLength[d][f]; 509 bestCoverage = coverage; 510 bestFeatures.Clear(); 511 bestFeatures.Add(f); 512 } 513 else 514 { 515 if (this.currentTupleLength[d][f] == bestTupleLength && coverage >= bestCoverage) 516 { 517 if (coverage > bestCoverage) 518 { 519 progress = true; 520 bestCoverage = coverage; 521 bestFeatures.Clear(); 522 } 523 524 bestFeatures.Add(f); 525 } 526 } 527 } 528 529 if (bestFeatures.Count == 1) 530 { 531 test.Features[d] = (int)bestFeatures[0]; 532 } 533 else 534 { 535 test.Features[d] = (int)bestFeatures[(int)(this.random.Next() % bestFeatures.Count)]; 536 } 537 538 totalCoverage += bestCoverage; 539 } 540 541 if (!progress) 542 { 543 return totalCoverage; 544 } 545 } 546 } 547 548 private int[] GetMutableDimensions(Tuple tuple) 549 { 550 bool[] immutableDimensions = new bool[this.dimensions.Length]; 551 552 for (int i = 0; i < tuple.Count; i++) 553 { 554 immutableDimensions[tuple[i].Dimension] = true; 555 } 556 557 ArrayList mutableDimensions = new ArrayList(); 558 559 for (int i = 0; i < this.dimensions.Length; i++) 560 { 561 if (!immutableDimensions[i]) 562 { 563 mutableDimensions.Add(i); 564 } 565 } 566 567 return (int[])mutableDimensions.ToArray(typeof(int)); 568 } 569 570 private int CountTuplesCovered(TestCase test, int dimension, int feature) 571 { 572 int tuplesCovered = 0; 573 574 TupleCollection tuples = this.uncoveredTuples[dimension][feature]; 575 576 for (int i = 0; i < tuples.Count; i++) 577 { 578 if (test.IsTupleCovered(tuples[i])) 579 { 580 tuplesCovered++; 581 } 582 } 583 584 return tuplesCovered; 585 } 586 587 private void RemoveTuplesCoveredBy(TestCase testCase) 588 { 589 for (int d = 0; d < this.uncoveredTuples.Length; d++) 590 { 591 for (int f = 0; f < this.uncoveredTuples[d].Length; f++) 592 { 593 TupleCollection tuples = this.uncoveredTuples[d][f]; 594 595 for (int i = tuples.Count - 1; i >= 0; i--) 596 { 597 if (testCase.IsTupleCovered(tuples[i])) 598 { 599 tuples.RemoveAt(i); 600 } 601 } 602 } 603 } 604 } 605 606 private void SelfTest() 607 { 608 for (int d1 = 0; d1 < this.dimensions.Length - 1; d1++) 609 { 610 for (int d2 = d1 + 1; d2 < this.dimensions.Length; d2++) 611 { 612 for (int f1 = 0; f1 < this.dimensions[d1]; f1++) 613 { 614 for (int f2 = 0; f2 < this.dimensions[d2]; f2++) 615 { 616 Tuple tuple = new Tuple(); 617 tuple.Add(new FeatureInfo(d1, f1)); 618 tuple.Add(new FeatureInfo(d2, f2)); 619 620 if (!this.testCases.IsTupleCovered(tuple)) 621 { 622 throw new ApplicationException("PairwiseStrategy self-test failed : Not all pairs are covered!"); 623 } 624 } 625 } 626 } 627 } 628 } 629 } 630 631 public PairwiseStrategy(IEnumerable[] sources) : base(sources) { } 632 633 public override IEnumerable GetTestCases() 634 { 635 ArrayList[] valueSet = CreateValueSet(); 636 int[] dimensions = CreateDimensions(valueSet); 637 638 IEnumerable pairwiseTestCases = new PairwiseTestCaseGenerator(dimensions).GetTestCases(); 639 640#if NET_2_0 641 List<ParameterSet> testCases = new List<ParameterSet>(); 642#else 643 ArrayList testCases = new ArrayList(); 644#endif 645 646 foreach (TestCase pairwiseTestCase in pairwiseTestCases) 647 { 648 object[] testData = new object[pairwiseTestCase.Features.Length]; 649 650 for (int i = 0; i < pairwiseTestCase.Features.Length; i++) 651 { 652 testData[i] = valueSet[i][pairwiseTestCase.Features[i]]; 653 } 654 655 ParameterSet testCase = new ParameterSet(); 656 testCase.Arguments = testData; 657 658 testCases.Add(testCase); 659 } 660 661 return testCases; 662 } 663 664 private ArrayList[] CreateValueSet() 665 { 666 ArrayList[] valueSet = new ArrayList[Sources.Length]; 667 668 for (int i = 0; i < valueSet.Length; i++) 669 { 670 ArrayList values = new ArrayList(); 671 672 foreach (object value in Sources[i]) 673 { 674 values.Add(value); 675 } 676 677 valueSet[i] = values; 678 } 679 680 return valueSet; 681 } 682 683 private int[] CreateDimensions(ArrayList[] valueSet) 684 { 685 int[] dimensions = new int[valueSet.Length]; 686 687 for (int i = 0; i < valueSet.Length; i++) 688 { 689 dimensions[i] = valueSet[i].Count; 690 } 691 692 return dimensions; 693 } 694 } 695}