Skip to content

Commit

Permalink
Merge pull request #57 from MeindertN/V1.2.1
Browse files Browse the repository at this point in the history
V1.3.0
  • Loading branch information
MeindertN authored Sep 13, 2024
2 parents 10aa50b + 7c4ef3d commit d53bc3e
Show file tree
Hide file tree
Showing 39 changed files with 1,399 additions and 262 deletions.
93 changes: 77 additions & 16 deletions RoboClerk.AnnotatedUnitTests/AnnotatedUnitTestsPlugin.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using RoboClerk.Configuration;
using DocumentFormat.OpenXml.Wordprocessing;
using RoboClerk.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
Expand All @@ -15,6 +16,8 @@ public class AnnotatedUnitTestsPlugin : SourceCodeAnalysisPluginBase
private string parameterStartDelimiter = string.Empty;
private string parameterEndDelimiter = string.Empty;
private string parameterSeparator = string.Empty;
private string functionNameStartSeq = string.Empty;
private string functionNameEndSeq = string.Empty;

private Dictionary<string, UTInformation> information = new Dictionary<string, UTInformation>();

Expand Down Expand Up @@ -52,16 +55,26 @@ public override void Initialize(IConfiguration configuration)
base.Initialize(configuration);
var config = GetConfigurationTable(configuration.PluginConfigDir, $"{name}.toml");

decorationMarker = configuration.CommandLineOptionOrDefault("DecorationMarker", GetStringForKey(config, "DecorationMarker", true));
parameterStartDelimiter = configuration.CommandLineOptionOrDefault("ParameterStartDelimiter", GetStringForKey(config, "ParameterStartDelimiter", true));
parameterEndDelimiter = configuration.CommandLineOptionOrDefault("ParameterEndDelimiter", GetStringForKey(config, "ParameterEndDelimiter", true));
parameterSeparator = configuration.CommandLineOptionOrDefault("ParameterSeparator", GetStringForKey(config, "ParameterSeparator", true));
decorationMarker = configuration.CommandLineOptionOrDefault("DecorationMarker", GetObjectForKey<string>(config, "DecorationMarker", true));
parameterStartDelimiter = configuration.CommandLineOptionOrDefault("ParameterStartDelimiter", GetObjectForKey<string>(config, "ParameterStartDelimiter", true));
parameterEndDelimiter = configuration.CommandLineOptionOrDefault("ParameterEndDelimiter", GetObjectForKey<string>(config, "ParameterEndDelimiter", true));
parameterSeparator = configuration.CommandLineOptionOrDefault("ParameterSeparator", GetObjectForKey<string>(config, "ParameterSeparator", true));

PopulateUTInfo("Purpose", config);
PopulateUTInfo("PostCondition", config);
PopulateUTInfo("Identifier", config);
PopulateUTInfo("TraceID", config);

if(config.ContainsKey("FunctionName"))
{
var tomlTable = (TomlTable)config["FunctionName"];
functionNameStartSeq = tomlTable["StartString"].ToString();
functionNameEndSeq = tomlTable["EndString"].ToString();
}
else
{
throw new Exception($"Table \"FunctionName\" missing from configuration file: \"{name}.toml\".");
}
}
catch (Exception e)
{
Expand All @@ -78,20 +91,28 @@ private int ParameterEnd(string input)
int closers = 0;
bool insideStringLiteral = false;
bool ignoreStringDelim = false;

bool insideStringBlock = false;
for (int i = 0; i < input.Length; i++)
{
var temp = input.Substring(i);
if (temp.StartsWith("\"") && !ignoreStringDelim)
if (temp.StartsWith("\"\"\"") && !ignoreStringDelim)
{
if (insideStringBlock && temp.Length >= 4 && temp[3] == '"')
continue;
insideStringBlock = !insideStringBlock;
i += 2;
continue;
}
if (temp.StartsWith("\"") && !ignoreStringDelim && !insideStringBlock)
{
insideStringLiteral = !insideStringLiteral;
}
ignoreStringDelim = temp.StartsWith("\\\"");
if (!insideStringLiteral && temp.StartsWith(parameterStartDelimiter))
if ( (!insideStringLiteral && !insideStringBlock) && temp.StartsWith(parameterStartDelimiter))
{
openers++;
}
if (!insideStringLiteral && temp.StartsWith(parameterEndDelimiter))
if ( (!insideStringLiteral && !insideStringBlock) && temp.StartsWith(parameterEndDelimiter))
{
closers++;
}
Expand All @@ -110,17 +131,32 @@ private Dictionary<string, string> ParseParameterString(string pms, int startLin
//to be used in practice
StringBuilder pmsSb = new StringBuilder(pms);
bool insideString = false;
bool insideTextBlock = false;
for (int i = 0; i < pms.Length; i++)
{
if (pms[i] == '"')
{
insideString = !insideString;
if (i + 2 < pms.Length && pms[i + 1] == '"' && pms[i + 2] == '"')
{
int index = i + 2;
while (index < pms.Length && pms[index] == '"')
{
index++;
}
insideTextBlock = !insideTextBlock;
i = index;
continue;
}
else if(!insideTextBlock)
{
insideString = !insideString;
}
}
if (pms[i] == '=' && insideString)
if (pms[i] == '=' && (insideString || insideTextBlock))
{
pmsSb[i] = '\a';
}
if (pms[i] == ',' && insideString)
if (pms[i] == ',' && (insideString || insideTextBlock))
{
pmsSb[i] = '\f';
}
Expand Down Expand Up @@ -177,6 +213,7 @@ private void FindAndProcessAnnotations(string[] lines, string filename)
paramEndIndex = ParameterEnd(foundAnnotation.ToString());
if (paramEndIndex >= 0)
{
i = j;
break;
}
else
Expand All @@ -195,24 +232,48 @@ private void FindAndProcessAnnotations(string[] lines, string filename)
throw new Exception($"Required parameter {info.Key} missing from unit test anotation starting on {startLine} of \"{filename}\".");
}
}
AddUnitTest(filename, startLine, foundParameters);
// extract the function name
int startI = i;
string functionName = string.Empty;
for (int j = i ; j < lines.Length && j-startI<3 ; j++)
{
int startIndex = lines[j].IndexOf(functionNameStartSeq);
if( startIndex>=0 )
{
int endIndex = lines[j].IndexOf(functionNameEndSeq);
if( endIndex>=0 )
{
functionName = lines[j].Substring(startIndex+functionNameStartSeq.Length, endIndex-(startIndex+functionNameStartSeq.Length));
functionName = functionName.Trim();
i = j;
break;
}
}
}
AddUnitTest(filename, startLine, foundParameters, functionName);
}
}
}

private void AddUnitTest(string fileName, int lineNumber, Dictionary<string, string> parameterValues)
private void AddUnitTest(string fileName, int lineNumber, Dictionary<string, string> parameterValues, string functionName)
{
var unitTest = new UnitTestItem();
unitTest.UnitTestFunctionName = functionName;
bool identified = false;
string shortFileName = Path.GetFileName(fileName);
unitTest.UnitTestFileName = shortFileName;

foreach (var info in information)
{
if (parameterValues.ContainsKey(info.Key))
{
var value = parameterValues[info.Key];
//all strings are assumed to start and end with a string delimiter for all supported languages
value = value.Substring(1, value.Length - 2).Replace("\\\"","\"");
//all strings are assumed to start and end with a string delimiter for all supported languages,
//note that for some languages the string delimiter can be """
if (value.StartsWith("\"\"\""))
value = value.Substring(3, value.Length - 6).Replace("\\\"", "\"");
else
value = value.Substring(1, value.Length - 2).Replace("\\\"", "\"");
switch (info.Key)
{
case "Purpose": unitTest.UnitTestPurpose = value; break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,18 @@ ParameterSeparator = ","
Keyword = "TraceID"
Optional = true

# RoboClerk assumes that after the annotation, the test function name is specified.
# In order for RoboClerk to be able to extract the function name, you need to specify
# the starting and ending elements of the function name. RoboClerk will match the first
# instance with the appropriate startstring and endstring within three lines after
# the annotation end.
# It is assumed that whatever is between the StartString and EndString is the unit
# test function name. Note that the StartString, the function name
# and the EndString are all on a single line. Otherwise, no function name will be
# detected.

[FunctionName]
StartString = "public void "
EndString = "("


16 changes: 8 additions & 8 deletions RoboClerk.AzureDevOps/AzureDevOpsSLMSPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public override void RefreshItems()
List<string> retrievedIDs = new List<string>();

logger.Info("Retrieving and processing system level requirements.");
foreach (var workitem in GetWorkItems(prsName))
foreach (var workitem in GetWorkItems(PrsConfig.Name))
{
if (IgnoreItem(workitem)) continue;
retrievedIDs.Add(workitem.Id.ToString());
Expand All @@ -76,7 +76,7 @@ public override void RefreshItems()
}

logger.Info("Retrieving and processing software level requirements.");
foreach (var workitem in GetWorkItems(srsName))
foreach (var workitem in GetWorkItems(SrsConfig.Name))
{
if (IgnoreItem(workitem)) continue;
retrievedIDs.Add(workitem.Id.ToString());
Expand All @@ -85,7 +85,7 @@ public override void RefreshItems()
}

logger.Info("Retrieving and processing documentation requirements.");
foreach (var workitem in GetWorkItems(docName))
foreach (var workitem in GetWorkItems(DocConfig.Name))
{
if (IgnoreItem(workitem)) continue;
retrievedIDs.Add(workitem.Id.ToString());
Expand All @@ -94,7 +94,7 @@ public override void RefreshItems()
}

logger.Info("Retrieving and processing docContent requirements.");
foreach (var workitem in GetWorkItems(cntName))
foreach (var workitem in GetWorkItems(CntConfig.Name))
{
if (IgnoreItem(workitem)) continue;
retrievedIDs.Add(workitem.Id.ToString());
Expand All @@ -103,7 +103,7 @@ public override void RefreshItems()
}

logger.Info("Retrieving and SOUP items.");
foreach (var workitem in GetWorkItems(soupName))
foreach (var workitem in GetWorkItems(SoupConfig.Name))
{
if (IgnoreItem(workitem)) continue;
retrievedIDs.Add(workitem.Id.ToString());
Expand All @@ -112,7 +112,7 @@ public override void RefreshItems()
}

logger.Info("Retrieving test cases.");
foreach (var workitem in GetWorkItems(tcName))
foreach (var workitem in GetWorkItems(TcConfig.Name))
{
if (IgnoreItem(workitem)) continue;
retrievedIDs.Add(workitem.Id.ToString());
Expand All @@ -122,7 +122,7 @@ public override void RefreshItems()

logger.Info("Retrieving and processing bugs.");

foreach (var workitem in GetWorkItems(bugName))
foreach (var workitem in GetWorkItems(BugConfig.Name))
{
if (IgnoreItem(workitem)) continue;
retrievedIDs.Add(workitem.Id.ToString());
Expand All @@ -133,7 +133,7 @@ public override void RefreshItems()
logger.Info("Retrieving and processing risks.");
//Note that to gather all information about the risk item, this code relies on the
//system level requirements having been retrieved already.
foreach (var workitem in GetWorkItems(riskName))
foreach (var workitem in GetWorkItems(RiskConfig.Name))
{
if (IgnoreItem(workitem)) continue;
retrievedIDs.Add(workitem.Id.ToString());
Expand Down
76 changes: 64 additions & 12 deletions RoboClerk.AzureDevOps/Configuration/AzureDevOpsSLMSPlugin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,70 @@ AccessToken = ""
OrganizationName = ""
ProjectName = "RoboClerk"

# The following allows you to indicate the work item names that map
# to various entities in the RoboClerk software
SystemRequirement = "Epic"
SoftwareRequirement = "User Story"
DocumentationRequirement = "Documentation"
DocContent = "DocContent"
SoftwareSystemTest = "Test Case"
Risk = "Risk"
Anomaly = "Bug"
SOUP = "SOUP"

# certain items in azure devops should be ignored because they are no longer
# relevant. Indicate which item statuses should be ignored.

Ignore = [ "Removed" ]
Ignore = [ "Removed" ]

# The following allows you to indicate the redmine trackers that map
# to various entities in the RoboClerk software. Set the name to the
# redmine ticket type. You can also indicate if the items are subject
# are subject to the inclusion and exclusion filters defined further
# on in this file.

[SystemRequirement]
name = "Epic"
filter = true

[SoftwareRequirement]
name = "User Story"
filter = true

[DocumentationRequirement]
name = "Documentation"
filter = true

[DocContent]
name = "DocContent"
filter = false

[SoftwareSystemTest]
name = "Test Case"
filter = false

[Risk]
name = "Risk"
filter = true

[Anomaly]
name = "Bug"
filter = false

[SOUP]
name = "SOUP"
filter = true

# To support the use case of documenting different versions of the software
# for example an RUO vs IVD version with different features or a US vs an EU
# version of the software, the RoboClerk Redmine Plugin supports providing
# fields here that will cause it to either include or exclude items and all
# attached items as well. As an example, by providing the value
# [ExcludedItemFilter]
# ReleaseType = ["IVD"]
# Roboclerk will look for a field named "ReleaseType" in the item tickets
# and if the field value is in the list (e.g. "IVD") it will ignore that ticket
# and all attached items.
# Another use of this feature is to include only those
# tickets associated with a particular release.
# [IncludedItemFilter]
# MileStone = ["1.0.0","1.0.1"]
# this will ensure that only items that have a field named MileStone with the values
# 1.0.0 or 1.0.1 will be included
# Note that we only include those items types that have the filter property set
# to true (see above)

[ExcludedItemFilter]
ReleaseType = [ "IVD" ]

[IncludedItemFilter]
MileStone = ["1.0.0","1.0.1"]
9 changes: 5 additions & 4 deletions RoboClerk.OpenAI/OpenAIPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO.Abstractions;
using RoboClerk.AISystem;

namespace RoboClerk.OpenAI
{
Expand All @@ -23,16 +24,16 @@ public override void Initialize(IConfiguration configuration)
{
base.Initialize(configuration);
var config = GetConfigurationTable(configuration.PluginConfigDir, $"{name}.toml");
bool useAzureOpenAI = configuration.CommandLineOptionOrDefault("UseAzureOpenAI", GetStringForKey(config, "UseAzureOpenAI", true)).ToUpper() == "TRUE";
bool useAzureOpenAI = configuration.CommandLineOptionOrDefault("UseAzureOpenAI", GetObjectForKey<string>(config, "UseAzureOpenAI", true)).ToUpper() == "TRUE";
if (useAzureOpenAI)
{
string azureOpenAIUri = configuration.CommandLineOptionOrDefault("AzureOpenAIUri", GetStringForKey(config, "AzureOpenAIUri", true));
string azureOpenAIResourceKey = configuration.CommandLineOptionOrDefault("AzureOpenAIResourceKey", GetStringForKey(config, "AzureOpenAIResourceKey", true));
string azureOpenAIUri = configuration.CommandLineOptionOrDefault("AzureOpenAIUri", GetObjectForKey<string>(config, "AzureOpenAIUri", true));
string azureOpenAIResourceKey = configuration.CommandLineOptionOrDefault("AzureOpenAIResourceKey", GetObjectForKey<string>(config, "AzureOpenAIResourceKey", true));
openAIClient = new OpenAIClient(new Uri(azureOpenAIUri),new Azure.AzureKeyCredential(azureOpenAIResourceKey));
}
else
{
string openAIKey = configuration.CommandLineOptionOrDefault("OpenAIKey", GetStringForKey(config, "OpenAIKey", true));
string openAIKey = configuration.CommandLineOptionOrDefault("OpenAIKey", GetObjectForKey<string>(config, "OpenAIKey", true));
openAIClient = new OpenAIClient(openAIKey);
}

Expand Down
1 change: 1 addition & 0 deletions RoboClerk.OpenAI/OpenAIPromptTemplate.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using RoboClerk.AISystem;
using Tomlet;

namespace RoboClerk
Expand Down
Loading

0 comments on commit d53bc3e

Please sign in to comment.