PageRenderTime 46ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/src/NUnit/core/Builders/PairwiseStrategy.cs

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