PageRenderTime 78ms CodeModel.GetById 18ms app.highlight 54ms RepoModel.GetById 1ms app.codeStats 0ms

/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}