Commit 56ce0bb9 56ce0bb9c82cf4cf9b5bf8fae4b3ac7defb93a15 by Christian Gerdes

First version of SetTestParameters as WebTest Plugin. Not testet AT ALL!

1 parent 35418d67
...@@ -21,9 +21,520 @@ using System.ComponentModel; ...@@ -21,9 +21,520 @@ using System.ComponentModel;
21 using System.Text.RegularExpressions; 21 using System.Text.RegularExpressions;
22 using System.Security.Cryptography.X509Certificates; 22 using System.Security.Cryptography.X509Certificates;
23 using System.Diagnostics; 23 using System.Diagnostics;
24 using System.Collections.Specialized;
24 25
25 namespace LIL_VSTT_Plugins 26 namespace LIL_VSTT_Plugins
26 { 27 {
28 public class SetWebTestParameter : WebTestPlugin
29 {
30 // Summary:
31 // Initializes the load test plug-in.
32 //
33 // Parameters:
34 // loadTest:
35 // The load test to be executed.
36
37 private string myConnectionString = "";
38 private string myLogFileString = "";
39 private string myParameterName = "";
40 private string myTestNames = "";
41 private string myScenarioNames = "";
42 private string myAgentNames = "";
43 private string myColNames = "";
44 private string myDebugLogFile = "";
45 private bool myUseRandom = true;
46 private bool myUseUnique = false;
47 private bool myUseUniqueFiles = false;
48 private bool myUseUniqueIteration = false;
49 private bool myUseUniqueTestIteration = false;
50 private bool myLogToFile = false;
51 private bool myLogAppendID = false;
52 private bool myLogAppendName = false;
53 private bool mySeqLoop = false;
54 private bool myHasColName = false;
55 private bool myUseAutoSplit = false;
56 private bool myIgnoreBlanks = true;
57 private bool myDebug = false;
58
59 private int iterationCounter = 0;
60 private static bool isParamsLoaded = false;
61 private static int globalIterationCounter = 0;
62 private static readonly Object globalIterationCounterLock = new Object();
63
64 private static readonly StringCollection myParams = new StringCollection();
65 private static readonly Queue<string> myUsedQueue = new Queue<string>();
66 private static readonly Queue<string> myUnUsedQueue = new Queue<string>();
67 private Random random = new Random();
68
69 #region guiparams
70
71 [Category("Context")]
72 [DisplayName("Parameter Namn")]
73 [Description("Ange namnet på parametern som vi ska lägga till i TestContext, om det är flera använd CSV format med kommatecken som separator.")]
74 [DefaultValue("UserName")]
75 public string Parameter_Name
76 {
77 get { return myParameterName; }
78 set { myParameterName = value; }
79 }
80
81 [Category("XDebug")]
82 [DisplayName("Debug Mode")]
83 [Description("Set True in order to enable Debug Mode. Each agent will log debug messages to the given Debug Log File.")]
84 [DefaultValue(false)]
85 public bool DebugMode
86 {
87 get { return myDebug; }
88 set { myDebug = value; }
89 }
90
91 [Category("XDebug")]
92 [DisplayName("Debug Log File")]
93 [Description("Log file path to be used for debug logging, if enabled (True)")]
94 [DefaultValue("C:\\Temp\\SetTestParameterDebug.log")]
95 public string DebugLogFile
96 {
97 get { return myDebugLogFile; }
98 set { myDebugLogFile = value; }
99 }
100
101 [Category("CSV Testdata")]
102 [DisplayName("Filens sökväg")]
103 [Description("Ange filens namn om den finns som Deployment Item i dina testsettings, eller fullständig sökväg om den inte deployas. Du kan ange en nätverksmappad disk eller UNC sökväg. Vid lokal sökväg behöver filen finnas på den agent där pluginet körs, vilket är alla agenter om du inte anger undantag.")]
104 [DefaultValue("C:\\Userdata.csv")]
105 public string Connection_String
106 {
107 get { return myConnectionString; }
108 set { myConnectionString = value; }
109 }
110
111 [Category("CSV Testdata")]
112 [DisplayName("Filen har kolumner med namn")]
113 [Description("Ange om csv filen har rubriker i form av kolumnnamn på första raden. Om du sätter True kommer även kolumnens namn att användas som parameternamn istället.")]
114 [DefaultValue(false)]
115 public bool Has_col_name
116 {
117 get { return myHasColName; }
118 set { myHasColName = value; }
119 }
120
121 [Category("CSV Testdata")]
122 [DisplayName("Autosplit per agent")]
123 [Description("Ange True om du vill att filen automatiskt ska splittas mellan alla aktiva agenter i testet. Obligatoriskt för att kunna ha unikt testdata över hela ditt loadtest då agenterna inte pratar med varandra under körningen.")]
124 [DefaultValue(false)]
125 public bool Autosplit
126 {
127 get { return myUseAutoSplit; }
128 set { myUseAutoSplit = value; }
129 }
130
131 [Category("CSV Testdata")]
132 [DisplayName("Ignorera blankskott")]
133 [Description("Ange False om du inte vill att rader med blankskott ignoreras (tomma/blanka rader eller samtliga kolumner tomma/blanka).")]
134 [DefaultValue(true)]
135 public bool IgnoreBlanks
136 {
137 get { return myIgnoreBlanks; }
138 set { myIgnoreBlanks = value; }
139 }
140
141 [Category("Loggning")]
142 [DisplayName("Loggfilens namn")]
143 [Description("Ange den fullständiga sökvägen till logg filen. Om filen finns kommer den inte skrivas över utan läggas till i slutet.")]
144 [DefaultValue("C:\\Temp\\Fungerande.log")]
145 public string LogFilePathString
146 {
147 get { return myLogFileString; }
148 set { myLogFileString = value; }
149 }
150
151 [Category("Loggning")]
152 [DisplayName("Lägg till ID")]
153 [Description("Ange True om du vill att Agent ID samt VU ID läggs till automatiskt i slutet på filnamnet.")]
154 [DefaultValue(false)]
155 public bool LogFileAppendID
156 {
157 get { return myLogAppendID; }
158 set { myLogAppendID = value; }
159 }
160
161 [Category("Loggning")]
162 [DisplayName("Lägg till Namn")]
163 [Description("Ange True om du vill att Scenario Name samt Test Name läggs till automatiskt i slutet på filnamnet.")]
164 [DefaultValue(false)]
165 public bool LogFileAppendName
166 {
167 get { return myLogAppendName; }
168 set { myLogAppendName = value; }
169 }
170
171 [Category("Radmappning")]
172 [DisplayName("1: Test Iteration Number")]
173 [Description("Varje iteration av ett och samma test på samma Agent, får en ny rad från din fil. Testets iterationsnummer mappas till raderna i din testdatafil. Börjar på 1 på varje Agent. Autosplit fördelar rader mellan agenter.")]
174 [DefaultValue(false)]
175 public bool Use_UniqueTestIteration
176 {
177 get { return myUseUniqueTestIteration; }
178 set { myUseUniqueTestIteration = value; }
179 }
180
181 [Category("Radmappning")]
182 [DisplayName("2: Total Iteration Number")]
183 [Description("Varje iteration av ett test på samma Agent, oavsett test, får en ny rad från din fil. Agentens Globala iterationsnummer mappas till raderna i din testdatafil. Börjar på 1 på varje Agent. Autosplit fördelar rader mellan agenter.")]
184 [DefaultValue(false)]
185 public bool Use_UniqueIteration
186 {
187 get { return myUseUniqueIteration; }
188 set { myUseUniqueIteration = value; }
189 }
190
191 [Category("Radmappning")]
192 [DisplayName("3: FIFO Kö med Pop/Enqueue")]
193 [Description("Varje rad läses in i en kö på agenten. När en VU vill köra ett test får den raden överst i kön. När en VU är klar med iterationen av ett test läggs raden tillbaka sist i kön. Detta säkerställer att du inte behöver fler rader i testdata filen än antalet samtidiga/parallella VU som kör dina tester, även om du har olika testdatafiler för olika tester. Varje agent börjar på rad 1 i filen om du inte använder Autosplit.")]
194 [DefaultValue(false)]
195 public bool Use_UniqueFiles
196 {
197 get { return myUseUniqueFiles; }
198 set { myUseUniqueFiles = value; }
199 }
200
201 [Category("Radmappning")]
202 [DisplayName("4: Virtual User ID Number")]
203 [Description("Varje ny VU får en egen rad från din fil, och återanvänder denna rad om den kör fler tester/iterationer. Agentens Virtual User ID Number mappas till raderna i din testdatafil. En VU varierar vilka tester den kör i din mix. En ny VU (enligt procent nya VU i run settings) får ett nytt nummer och därmed en ny rad i din fil. Första VU får nummer 1 på varje Agent. Autosplit fördelar rader mellan agenter.")]
204 [DefaultValue(false)]
205 public bool Use_Unique
206 {
207 get { return myUseUnique; }
208 set { myUseUnique = value; }
209 }
210
211 [Category("Radmappning")]
212 [DisplayName("5: Slumpmässigt")]
213 [Description("Slumpmässigt val av rader i filen. Ingen kontroll eller viss ordning och flera VU kan slumpa fram samma rad.")]
214 [DefaultValue(false)]
215 public bool Use_Random
216 {
217 get { return myUseRandom; }
218 set { myUseRandom = value; }
219 }
220
221 [Category("Radmappning")]
222 [DisplayName("6: Virtual User Iteration Number")]
223 [Description("Varje VU väljer rad baserat på antalet tidigare tester/iterationer den gjort. Varje ny VU börjar på rad 1. Om procent nya VU är 100 används endast rad 1 i alla tester. Observera att en VU som inte är ny kommer att byta mellan olika tester under din körning, om du har flera tester/skript i din mix. Om du använder undantag och flera instanser av detta plugin, kommer vissa rader att hoppas över.")]
224 [DefaultValue(true)]
225 public bool Use_Seq
226 {
227 get; set; // Fake. Actually enabled by setting all other options above to false.
228 }
229
230 [Category("CSV Testdata")]
231 [DisplayName("Loopa testdata")]
232 [Description("Ange true om du vill börja om från början av testdatafilen när alla används en gång. Gäller alla unik typer utom Push/Pull. Med False på detta val kommer sista raden ges till alla om datat tar slut, eller OutOfTestDataException slängas och loadtestet stoppas om det är aktiverat.")]
233 [DefaultValue(false)]
234 public bool Use_Loop
235 {
236 get { return mySeqLoop; }
237 set { mySeqLoop = value; }
238 }
239
240 [Category("CSV Testdata")]
241 [DisplayName("Avbryt med OutOfTestDataException")]
242 [Description("Ange true om du vill att ditt loadtest ska stoppas om testdata tar slut (och Sekventiell Loop är satt till false).")]
243 [DefaultValue(false)]
244 public bool ThrowException
245 {
246 get; set;
247 }
248
249 [Category("Loggning")]
250 [DisplayName("Logga fungerande till fil?")]
251 [Description("Ange True om du vill att poster vars tester slutar i Pass ska loggas till fil (c:\\fungerande.log). Om filen redan finns läggs de till i slutet.")]
252 [DefaultValue(false)]
253 public bool Log_To_File
254 {
255 get { return myLogToFile; }
256 set { myLogToFile = value; }
257 }
258
259 [Category("Undantag")]
260 [DisplayName("Endast dessa Tester")]
261 [Description("Denna instans av pluginet körs endast på Tester i test mixen där namnet eller del av namnet för testet finns i denna lista. Lämna blankt för att köra i alla tester.")]
262 [DefaultValue("")]
263 public string Test_Names
264 {
265 get { return myTestNames; }
266 set { myTestNames = value; }
267 }
268
269 [Category("Undantag")]
270 [DisplayName("Endast dessa Scenarios")]
271 [Description("Denna instans av pluginet körs endast på Scenarion där namnet eller del av namnet för scenariot finns i denna lista. Lämna blankt för att köra i alla scenarion.")]
272 [DefaultValue("")]
273 public string Scenario_Names
274 {
275 get { return myScenarioNames; }
276 set { myScenarioNames = value; }
277 }
278
279 [Category("Undantag")]
280 [DisplayName("Endast dessa Agenter")]
281 [Description("Denna instans av pluginet körs endast på Agenter där namnet eller del av namnet för agenten finns i denna lista. Lämna blankt för att köra på alla agenter.")]
282 [DefaultValue("")]
283 public string Agent_Names
284 {
285 get { return myAgentNames; }
286 set { myAgentNames = value; }
287 }
288
289 #endregion
290
291 public override void PreWebTest(object sender, PreWebTestEventArgs e)
292 {
293 base.PreWebTest(sender, e);
294
295 // Only run on specific agents if specified
296 if (myAgentNames.Length > 0 && !myAgentNames.ToLower().Contains(e.WebTest.Context.AgentName.ToLower())) return;
297
298 // Update the global iteration Counter
299 lock (globalIterationCounterLock) globalIterationCounter++;
300
301 // Update the local (this users or instance) iteration counter
302 iterationCounter++;
303
304 // Read the values into the param array if not already done (checks isParamsLoaded)
305 this.initUserArray(myConnectionString, e.WebTest.Context.AgentCount, e.WebTest.Context.AgentId);
306
307 // If we do have params in the array, select one as specified by the properties
308 if (myParams.Count > 0 || myUnUsedQueue.Count > 0)
309 {
310 if (myUseUniqueTestIteration)
311 loadTestStartingUniqueTestIteration(e.WebTest);
312 else if (myUseUniqueIteration)
313 loadTestStartingUniqueIteration(e.WebTest);
314 else if (myUseUniqueFiles)
315 loadTestStartingUniqueFiles(e.WebTest);
316 else if (myUseUnique)
317 loadTestStartingUnique(e.WebTest);
318 else if (myUseRandom)
319 loadTestStartingRandom(e.WebTest);
320 else
321 loadTestStartingSeq(e.WebTest);
322 }
323 }
324
325 public override void PostWebTest(object sender, PostWebTestEventArgs e)
326 {
327 base.PostWebTest(sender, e);
328
329 if(myUseUniqueFiles)
330 loadTestFinishedUniqueFiles(e.WebTest);
331
332 if (myLogToFile)
333 loadTestEndLogger(e.WebTest);
334 }
335
336 void loadTestEndLogger(WebTest e)
337 {
338 // Log the user to logfile if the test is passed
339 if (e.Outcome == Outcome.Pass)
340 {
341 string fileName = myLogFileString;
342 if (myLogAppendID) fileName = fileName + "." + e.Context.AgentName + ".Vu" + e.Context.WebTestUserId;
343 if (myLogAppendName) fileName = fileName + "." + e.Name;
344 string[] allNames;
345 if (myHasColName) allNames = myColNames.Split(','); else allNames = myParameterName.Split(',');
346 string row = "";
347 foreach (string name in allNames)
348 {
349 if (e.Context.Keys.Contains(name))
350 {
351 if (row.Length == 0)
352 row += e.Context[name];
353 else
354 row += "," + e.Context[name];
355 }
356 }
357 File.AppendAllText(fileName + ".csv", row + "\r\n");
358 }
359 }
360
361 void loadTestStartingRandom(WebTest e)
362 {
363 setParameters(this.getRandomUser(), e);
364 }
365
366 void loadTestStartingSeq(WebTest e)
367 {
368 setParameters(this.getSeqUser(iterationCounter), e);
369 }
370
371 void loadTestStartingUniqueFiles(WebTest e)
372 {
373 string strParams = "OutOfData";
374 // Go single threaded
375 lock (myUnUsedQueue)
376 {
377 if (myUnUsedQueue.Count > 0)
378 {
379 strParams = myUnUsedQueue.Dequeue();
380 e.Context["QueueVal"] = strParams;
381 }
382 else
383 {
384 // Out of testdata
385 e.Context["QueueVal"] = null;
386 stopAndThrow();
387 }
388 }
389 setParameters(strParams, e);
390 if (myDebug) lock (myDebugLogFile) { File.AppendAllText(myDebugLogFile, DateTime.Now.ToLocalTime() + " File: " + myConnectionString + " Test: " + e.Name + " VU: " + e.Context.WebTestUserId + " Value: \"" + strParams + "\" PULL\r\n"); }
391 }
392
393 void loadTestFinishedUniqueFiles(WebTest e)
394 {
395 String queueVal = (String)e.Context["QueueVal"];
396 if (queueVal != null)
397 lock (myUnUsedQueue)
398 {
399 myUnUsedQueue.Enqueue(queueVal);
400 }
401 if (myDebug) lock (myDebugLogFile) { File.AppendAllText(myDebugLogFile, DateTime.Now.ToLocalTime() + " File: " + myConnectionString + " Test: " + e.Name + " VU: " + e.Context.WebTestUserId + " Value: \"" + queueVal + "\" PUSH\r\n"); }
402 }
403
404 void loadTestStartingUnique(WebTest e)
405 {
406 setParameters(this.getSeqUser(e.Context.WebTestUserId), e);
407 }
408
409 void loadTestStartingUniqueIteration(WebTest e)
410 {
411 setParameters(this.getSeqUser(globalIterationCounter), e);
412 }
413
414 void loadTestStartingUniqueTestIteration(WebTest e)
415 {
416 int testIteration = e.Context.WebTestIteration;
417 setParameters(this.getSeqUser(testIteration - 1), e);
418 }
419
420 void setParameters(string user, WebTest e)
421 {
422 // Add context parameters to the starting test
423 int numParams = 1;
424 if (myHasColName == true && myColNames.Contains(',')) numParams = countColumns(myColNames);
425 if (myHasColName == false && myParameterName.Contains(',')) numParams = countColumns(myParameterName);
426
427 string[] allParams = user.Split(',');
428 string[] allNames;
429 if (myHasColName) allNames = myColNames.Split(','); else allNames = myParameterName.Split(',');
430 for (int i = 0; i < numParams; i++)
431 {
432 e.Context[allNames[i]] = allParams[i];
433 }
434 }
435
436 int countColumns(string input)
437 {
438 int count = 1;
439 for (int i = 0; i < input.Length; i++)
440 {
441 if (input[i] == ',') count++;
442 }
443 return count;
444 }
445
446 string getRandomUser()
447 {
448 int randomIndex = random.Next(myParams.Count - 1);
449 return myParams[randomIndex];
450 }
451
452 string getSeqUser(int seqIndex)
453 {
454 if (seqIndex < myParams.Count)
455 return myParams[seqIndex];
456 else
457 {
458 if (mySeqLoop)
459 return myParams[seqIndex % myParams.Count];
460 else
461 {
462 // Handle out of testdata here
463 if (ThrowException)
464 {
465 stopAndThrow();
466 return "OutOfData";
467 }
468 else return myParams[myParams.Count - 1];
469 }
470 }
471 }
472
473 void stopAndThrow()
474 {
475 throw new Exception("Out of Test Data");
476 }
477
478 bool initUserArray(string path, int agentCount, int agentId)
479 {
480 // Check if someone has loaded the params array
481 if (isParamsLoaded == false)
482 {
483 // Try to lock the array
484 lock (myParams)
485 {
486 // Only read the file if we have a path and we still have not loaded a file now that we have the lock
487 if (!String.IsNullOrEmpty(path) && !isParamsLoaded)
488 {
489 StreamReader re = new StreamReader(path, System.Text.Encoding.Default);
490 string input = null;
491 int lineNum = 0;
492 int dataNum = 0;
493 char[] trim = { ' ', '\x00', '\t', '\x20' };
494 while ((input = re.ReadLine()) != null)
495 {
496 // Ignore blank lines and empty lines (just whitespace) or lines with only blank/empty/whitespace columns
497 if (myIgnoreBlanks && String.IsNullOrWhiteSpace(input.Replace(',', ' '))) continue;
498
499 lineNum++;
500 if (lineNum == 1 && myHasColName == true)
501 {
502 // First line is column names
503 myColNames = input.TrimEnd(trim);
504 }
505 else
506 {
507 if (myUseAutoSplit)
508 {
509 int ifAgentId = 0;
510 if (dataNum >= agentCount) ifAgentId = (dataNum % agentCount) + 1;
511 else ifAgentId = dataNum + 1;
512 if (ifAgentId == agentId)
513 {
514 if (myUseUniqueFiles) myUnUsedQueue.Enqueue(input.TrimEnd(trim));
515 else myParams.Add(input.TrimEnd(trim));
516 }
517 dataNum++;
518 }
519 else
520 {
521 if (myUseUniqueFiles) myUnUsedQueue.Enqueue(input.TrimEnd(trim));
522 else myParams.Add(input.TrimEnd(trim));
523 }
524 }
525 }
526 re.Close();
527 // Let the world know we have read the file before we release the lock
528 isParamsLoaded = true;
529 return true;
530 }
531 }
532 }
533 // If we get here, we did not return in any of the loading parts above. Return false to indicate we did not load the file.
534 return false;
535 }
536 }
537
27 /// <summary> 538 /// <summary>
28 /// Datasource Unique Once 539 /// Datasource Unique Once
29 /// </summary> 540 /// </summary>
......