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;
using System.Text.RegularExpressions;
using System.Security.Cryptography.X509Certificates;
using System.Diagnostics;
using System.Collections.Specialized;
namespace LIL_VSTT_Plugins
{
public class SetWebTestParameter : WebTestPlugin
{
// Summary:
// Initializes the load test plug-in.
//
// Parameters:
// loadTest:
// The load test to be executed.
private string myConnectionString = "";
private string myLogFileString = "";
private string myParameterName = "";
private string myTestNames = "";
private string myScenarioNames = "";
private string myAgentNames = "";
private string myColNames = "";
private string myDebugLogFile = "";
private bool myUseRandom = true;
private bool myUseUnique = false;
private bool myUseUniqueFiles = false;
private bool myUseUniqueIteration = false;
private bool myUseUniqueTestIteration = false;
private bool myLogToFile = false;
private bool myLogAppendID = false;
private bool myLogAppendName = false;
private bool mySeqLoop = false;
private bool myHasColName = false;
private bool myUseAutoSplit = false;
private bool myIgnoreBlanks = true;
private bool myDebug = false;
private int iterationCounter = 0;
private static bool isParamsLoaded = false;
private static int globalIterationCounter = 0;
private static readonly Object globalIterationCounterLock = new Object();
private static readonly StringCollection myParams = new StringCollection();
private static readonly Queue<string> myUsedQueue = new Queue<string>();
private static readonly Queue<string> myUnUsedQueue = new Queue<string>();
private Random random = new Random();
#region guiparams
[Category("Context")]
[DisplayName("Parameter Namn")]
[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.")]
[DefaultValue("UserName")]
public string Parameter_Name
{
get { return myParameterName; }
set { myParameterName = value; }
}
[Category("XDebug")]
[DisplayName("Debug Mode")]
[Description("Set True in order to enable Debug Mode. Each agent will log debug messages to the given Debug Log File.")]
[DefaultValue(false)]
public bool DebugMode
{
get { return myDebug; }
set { myDebug = value; }
}
[Category("XDebug")]
[DisplayName("Debug Log File")]
[Description("Log file path to be used for debug logging, if enabled (True)")]
[DefaultValue("C:\\Temp\\SetTestParameterDebug.log")]
public string DebugLogFile
{
get { return myDebugLogFile; }
set { myDebugLogFile = value; }
}
[Category("CSV Testdata")]
[DisplayName("Filens sökväg")]
[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.")]
[DefaultValue("C:\\Userdata.csv")]
public string Connection_String
{
get { return myConnectionString; }
set { myConnectionString = value; }
}
[Category("CSV Testdata")]
[DisplayName("Filen har kolumner med namn")]
[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.")]
[DefaultValue(false)]
public bool Has_col_name
{
get { return myHasColName; }
set { myHasColName = value; }
}
[Category("CSV Testdata")]
[DisplayName("Autosplit per agent")]
[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.")]
[DefaultValue(false)]
public bool Autosplit
{
get { return myUseAutoSplit; }
set { myUseAutoSplit = value; }
}
[Category("CSV Testdata")]
[DisplayName("Ignorera blankskott")]
[Description("Ange False om du inte vill att rader med blankskott ignoreras (tomma/blanka rader eller samtliga kolumner tomma/blanka).")]
[DefaultValue(true)]
public bool IgnoreBlanks
{
get { return myIgnoreBlanks; }
set { myIgnoreBlanks = value; }
}
[Category("Loggning")]
[DisplayName("Loggfilens namn")]
[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.")]
[DefaultValue("C:\\Temp\\Fungerande.log")]
public string LogFilePathString
{
get { return myLogFileString; }
set { myLogFileString = value; }
}
[Category("Loggning")]
[DisplayName("Lägg till ID")]
[Description("Ange True om du vill att Agent ID samt VU ID läggs till automatiskt i slutet på filnamnet.")]
[DefaultValue(false)]
public bool LogFileAppendID
{
get { return myLogAppendID; }
set { myLogAppendID = value; }
}
[Category("Loggning")]
[DisplayName("Lägg till Namn")]
[Description("Ange True om du vill att Scenario Name samt Test Name läggs till automatiskt i slutet på filnamnet.")]
[DefaultValue(false)]
public bool LogFileAppendName
{
get { return myLogAppendName; }
set { myLogAppendName = value; }
}
[Category("Radmappning")]
[DisplayName("1: Test Iteration Number")]
[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.")]
[DefaultValue(false)]
public bool Use_UniqueTestIteration
{
get { return myUseUniqueTestIteration; }
set { myUseUniqueTestIteration = value; }
}
[Category("Radmappning")]
[DisplayName("2: Total Iteration Number")]
[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.")]
[DefaultValue(false)]
public bool Use_UniqueIteration
{
get { return myUseUniqueIteration; }
set { myUseUniqueIteration = value; }
}
[Category("Radmappning")]
[DisplayName("3: FIFO Kö med Pop/Enqueue")]
[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.")]
[DefaultValue(false)]
public bool Use_UniqueFiles
{
get { return myUseUniqueFiles; }
set { myUseUniqueFiles = value; }
}
[Category("Radmappning")]
[DisplayName("4: Virtual User ID Number")]
[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.")]
[DefaultValue(false)]
public bool Use_Unique
{
get { return myUseUnique; }
set { myUseUnique = value; }
}
[Category("Radmappning")]
[DisplayName("5: Slumpmässigt")]
[Description("Slumpmässigt val av rader i filen. Ingen kontroll eller viss ordning och flera VU kan slumpa fram samma rad.")]
[DefaultValue(false)]
public bool Use_Random
{
get { return myUseRandom; }
set { myUseRandom = value; }
}
[Category("Radmappning")]
[DisplayName("6: Virtual User Iteration Number")]
[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.")]
[DefaultValue(true)]
public bool Use_Seq
{
get; set; // Fake. Actually enabled by setting all other options above to false.
}
[Category("CSV Testdata")]
[DisplayName("Loopa testdata")]
[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.")]
[DefaultValue(false)]
public bool Use_Loop
{
get { return mySeqLoop; }
set { mySeqLoop = value; }
}
[Category("CSV Testdata")]
[DisplayName("Avbryt med OutOfTestDataException")]
[Description("Ange true om du vill att ditt loadtest ska stoppas om testdata tar slut (och Sekventiell Loop är satt till false).")]
[DefaultValue(false)]
public bool ThrowException
{
get; set;
}
[Category("Loggning")]
[DisplayName("Logga fungerande till fil?")]
[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.")]
[DefaultValue(false)]
public bool Log_To_File
{
get { return myLogToFile; }
set { myLogToFile = value; }
}
[Category("Undantag")]
[DisplayName("Endast dessa Tester")]
[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.")]
[DefaultValue("")]
public string Test_Names
{
get { return myTestNames; }
set { myTestNames = value; }
}
[Category("Undantag")]
[DisplayName("Endast dessa Scenarios")]
[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.")]
[DefaultValue("")]
public string Scenario_Names
{
get { return myScenarioNames; }
set { myScenarioNames = value; }
}
[Category("Undantag")]
[DisplayName("Endast dessa Agenter")]
[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.")]
[DefaultValue("")]
public string Agent_Names
{
get { return myAgentNames; }
set { myAgentNames = value; }
}
#endregion
public override void PreWebTest(object sender, PreWebTestEventArgs e)
{
base.PreWebTest(sender, e);
// Only run on specific agents if specified
if (myAgentNames.Length > 0 && !myAgentNames.ToLower().Contains(e.WebTest.Context.AgentName.ToLower())) return;
// Update the global iteration Counter
lock (globalIterationCounterLock) globalIterationCounter++;
// Update the local (this users or instance) iteration counter
iterationCounter++;
// Read the values into the param array if not already done (checks isParamsLoaded)
this.initUserArray(myConnectionString, e.WebTest.Context.AgentCount, e.WebTest.Context.AgentId);
// If we do have params in the array, select one as specified by the properties
if (myParams.Count > 0 || myUnUsedQueue.Count > 0)
{
if (myUseUniqueTestIteration)
loadTestStartingUniqueTestIteration(e.WebTest);
else if (myUseUniqueIteration)
loadTestStartingUniqueIteration(e.WebTest);
else if (myUseUniqueFiles)
loadTestStartingUniqueFiles(e.WebTest);
else if (myUseUnique)
loadTestStartingUnique(e.WebTest);
else if (myUseRandom)
loadTestStartingRandom(e.WebTest);
else
loadTestStartingSeq(e.WebTest);
}
}
public override void PostWebTest(object sender, PostWebTestEventArgs e)
{
base.PostWebTest(sender, e);
if(myUseUniqueFiles)
loadTestFinishedUniqueFiles(e.WebTest);
if (myLogToFile)
loadTestEndLogger(e.WebTest);
}
void loadTestEndLogger(WebTest e)
{
// Log the user to logfile if the test is passed
if (e.Outcome == Outcome.Pass)
{
string fileName = myLogFileString;
if (myLogAppendID) fileName = fileName + "." + e.Context.AgentName + ".Vu" + e.Context.WebTestUserId;
if (myLogAppendName) fileName = fileName + "." + e.Name;
string[] allNames;
if (myHasColName) allNames = myColNames.Split(','); else allNames = myParameterName.Split(',');
string row = "";
foreach (string name in allNames)
{
if (e.Context.Keys.Contains(name))
{
if (row.Length == 0)
row += e.Context[name];
else
row += "," + e.Context[name];
}
}
File.AppendAllText(fileName + ".csv", row + "\r\n");
}
}
void loadTestStartingRandom(WebTest e)
{
setParameters(this.getRandomUser(), e);
}
void loadTestStartingSeq(WebTest e)
{
setParameters(this.getSeqUser(iterationCounter), e);
}
void loadTestStartingUniqueFiles(WebTest e)
{
string strParams = "OutOfData";
// Go single threaded
lock (myUnUsedQueue)
{
if (myUnUsedQueue.Count > 0)
{
strParams = myUnUsedQueue.Dequeue();
e.Context["QueueVal"] = strParams;
}
else
{
// Out of testdata
e.Context["QueueVal"] = null;
stopAndThrow();
}
}
setParameters(strParams, e);
if (myDebug) lock (myDebugLogFile) { File.AppendAllText(myDebugLogFile, DateTime.Now.ToLocalTime() + " File: " + myConnectionString + " Test: " + e.Name + " VU: " + e.Context.WebTestUserId + " Value: \"" + strParams + "\" PULL\r\n"); }
}
void loadTestFinishedUniqueFiles(WebTest e)
{
String queueVal = (String)e.Context["QueueVal"];
if (queueVal != null)
lock (myUnUsedQueue)
{
myUnUsedQueue.Enqueue(queueVal);
}
if (myDebug) lock (myDebugLogFile) { File.AppendAllText(myDebugLogFile, DateTime.Now.ToLocalTime() + " File: " + myConnectionString + " Test: " + e.Name + " VU: " + e.Context.WebTestUserId + " Value: \"" + queueVal + "\" PUSH\r\n"); }
}
void loadTestStartingUnique(WebTest e)
{
setParameters(this.getSeqUser(e.Context.WebTestUserId), e);
}
void loadTestStartingUniqueIteration(WebTest e)
{
setParameters(this.getSeqUser(globalIterationCounter), e);
}
void loadTestStartingUniqueTestIteration(WebTest e)
{
int testIteration = e.Context.WebTestIteration;
setParameters(this.getSeqUser(testIteration - 1), e);
}
void setParameters(string user, WebTest e)
{
// Add context parameters to the starting test
int numParams = 1;
if (myHasColName == true && myColNames.Contains(',')) numParams = countColumns(myColNames);
if (myHasColName == false && myParameterName.Contains(',')) numParams = countColumns(myParameterName);
string[] allParams = user.Split(',');
string[] allNames;
if (myHasColName) allNames = myColNames.Split(','); else allNames = myParameterName.Split(',');
for (int i = 0; i < numParams; i++)
{
e.Context[allNames[i]] = allParams[i];
}
}
int countColumns(string input)
{
int count = 1;
for (int i = 0; i < input.Length; i++)
{
if (input[i] == ',') count++;
}
return count;
}
string getRandomUser()
{
int randomIndex = random.Next(myParams.Count - 1);
return myParams[randomIndex];
}
string getSeqUser(int seqIndex)
{
if (seqIndex < myParams.Count)
return myParams[seqIndex];
else
{
if (mySeqLoop)
return myParams[seqIndex % myParams.Count];
else
{
// Handle out of testdata here
if (ThrowException)
{
stopAndThrow();
return "OutOfData";
}
else return myParams[myParams.Count - 1];
}
}
}
void stopAndThrow()
{
throw new Exception("Out of Test Data");
}
bool initUserArray(string path, int agentCount, int agentId)
{
// Check if someone has loaded the params array
if (isParamsLoaded == false)
{
// Try to lock the array
lock (myParams)
{
// Only read the file if we have a path and we still have not loaded a file now that we have the lock
if (!String.IsNullOrEmpty(path) && !isParamsLoaded)
{
StreamReader re = new StreamReader(path, System.Text.Encoding.Default);
string input = null;
int lineNum = 0;
int dataNum = 0;
char[] trim = { ' ', '\x00', '\t', '\x20' };
while ((input = re.ReadLine()) != null)
{
// Ignore blank lines and empty lines (just whitespace) or lines with only blank/empty/whitespace columns
if (myIgnoreBlanks && String.IsNullOrWhiteSpace(input.Replace(',', ' '))) continue;
lineNum++;
if (lineNum == 1 && myHasColName == true)
{
// First line is column names
myColNames = input.TrimEnd(trim);
}
else
{
if (myUseAutoSplit)
{
int ifAgentId = 0;
if (dataNum >= agentCount) ifAgentId = (dataNum % agentCount) + 1;
else ifAgentId = dataNum + 1;
if (ifAgentId == agentId)
{
if (myUseUniqueFiles) myUnUsedQueue.Enqueue(input.TrimEnd(trim));
else myParams.Add(input.TrimEnd(trim));
}
dataNum++;
}
else
{
if (myUseUniqueFiles) myUnUsedQueue.Enqueue(input.TrimEnd(trim));
else myParams.Add(input.TrimEnd(trim));
}
}
}
re.Close();
// Let the world know we have read the file before we release the lock
isParamsLoaded = true;
return true;
}
}
}
// 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.
return false;
}
}
/// <summary>
/// Datasource Unique Once
/// </summary>
......