Skip to content

Commit

Permalink
TS#38029 Introduce AssemblyExtractor to fix EmbeddudUploadTarget Hand…
Browse files Browse the repository at this point in the history
…ling
  • Loading branch information
Raphael-N committed Jul 15, 2024
1 parent e71d373 commit 91c8a68
Show file tree
Hide file tree
Showing 19 changed files with 301 additions and 245 deletions.
14 changes: 7 additions & 7 deletions UploadDaemon/Configuration/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -263,12 +263,12 @@ public Config(ConfigParser.YamlConfig config)
/// <summary>
/// Creates the configuration that should be applied to the given profiled process.
/// </summary>
public ConfigForProcess CreateConfigForProcess(string profiledProcessPath, TraceFile traceFile = null)
public ConfigForProcess CreateConfigForProcess(string profiledProcessPath, Dictionary<uint, (string name, string path)> assemblies = null)
{
ConfigForProcess config = new ConfigForProcess(profiledProcessPath);
foreach (ConfigParser.ProcessSection section in Sections)
{
if (SectionApplies(section, profiledProcessPath, traceFile))
if (SectionApplies(section, profiledProcessPath, assemblies))
{
config.ApplySection(section.Uploader);
}
Expand Down Expand Up @@ -320,12 +320,12 @@ public static Config Read(string yaml)
/// <summary>
/// Returns true if the given section applies to the given profiled process.
/// </summary>
private static bool SectionApplies(ConfigParser.ProcessSection section, string profiledProcessPath, TraceFile traceFile = null)
private static bool SectionApplies(ConfigParser.ProcessSection section, string profiledProcessPath, Dictionary<uint, (string name, string path)> assemblies = null)
{
bool?[] checks = new[] {
MatchesExecutableName(section, profiledProcessPath),
MatchesExecutablePathRegex(section, profiledProcessPath),
MatchesLoadedAssemblyPathRegex(section, traceFile),
MatchesLoadedAssemblyPathRegex(section, assemblies),
};

// The section applies if at least one of the check criteria is set (!= null) and all of these are true.
Expand Down Expand Up @@ -362,15 +362,15 @@ private static bool SectionApplies(ConfigParser.ProcessSection section, string p
/// <summary>
/// If loaded assembly path regex is set, at least one of the loaded assembly's path must match
/// </summary>
private static bool? MatchesLoadedAssemblyPathRegex(ConfigParser.ProcessSection section, TraceFile traceFile = null)
private static bool? MatchesLoadedAssemblyPathRegex(ConfigParser.ProcessSection section, Dictionary<uint, (string name, string path)> assemblies = null)
{
if (section.LoadedAssemblyPathRegex == null || traceFile == null)
if (section.LoadedAssemblyPathRegex == null || assemblies == null)
{
return null;
}

Regex regex = new Regex($"^{section.LoadedAssemblyPathRegex}$");
return traceFile.assemblies.Any(assembly => regex.IsMatch(assembly.Value.Item2));
return assemblies.Any(assembly => regex.IsMatch(assembly.Value.Item2));
}

/// <summary>
Expand Down
2 changes: 0 additions & 2 deletions UploadDaemon/Report/ICoverageReport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ public interface ICoverageReport
/// </summary>
string FileExtension { get; }

List<(string project, RevisionOrTimestamp revisionOrTimestamp)> EmbeddedUploadTargets { get; }

/// <summary>
/// Returns a new report wth the union of the coverage data of both reports.
/// </summary>
Expand Down
7 changes: 2 additions & 5 deletions UploadDaemon/Report/Simple/SimpleCoverageReport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,9 @@ public class SimpleCoverageReport : ICoverageReport

private readonly IDictionary<string, FileCoverage> lineCoverageByFile;

public List<(string project, RevisionFileUtils.RevisionOrTimestamp revisionOrTimestamp)> EmbeddedUploadTargets { get; }

public SimpleCoverageReport(IDictionary<string, FileCoverage> lineCoverageByFile, List<(string project, RevisionFileUtils.RevisionOrTimestamp revisionOrTimestamp)> embeddedUploadTargets)
public SimpleCoverageReport(IDictionary<string, FileCoverage> lineCoverageByFile)
{
this.lineCoverageByFile = lineCoverageByFile;
EmbeddedUploadTargets = embeddedUploadTargets;
}

/// <inheritDoc/>
Expand Down Expand Up @@ -60,7 +57,7 @@ public ICoverageReport Union(ICoverageReport otherReport)

return new SimpleCoverageReport(new[] { lineCoverageByFile, other.lineCoverageByFile }.SelectMany(dict => dict)
.ToLookup(pair => pair.Key, pair => pair.Value)
.ToDictionary(group => group.Key, group => group.Aggregate((fc1, fc2) => new FileCoverage(fc1.CoveredLineRanges.Union(fc2.CoveredLineRanges)))), EmbeddedUploadTargets);
.ToDictionary(group => group.Key, group => group.Aggregate((fc1, fc2) => new FileCoverage(fc1.CoveredLineRanges.Union(fc2.CoveredLineRanges)))));
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion UploadDaemon/Report/Testwise/Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public SimpleCoverageReport ToSimpleCoverageReport()
{
lineCoverageByPath = CoverageByPath.First().Files.ToDictionary(file => file.FileName, file => (FileCoverage)file);
}
return new SimpleCoverageReport(lineCoverageByPath, new List<(string project, SymbolAnalysis.RevisionFileUtils.RevisionOrTimestamp revisionOrTimestamp)>());
return new SimpleCoverageReport(lineCoverageByPath);
}
}
}
8 changes: 3 additions & 5 deletions UploadDaemon/Report/Testwise/TestwiseCoverageReport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,13 @@ public class TestwiseCoverageReport : ICoverageReport
[JsonProperty("tests")]
public Test[] Tests { get; }

Check warning on line 22 in UploadDaemon/Report/Testwise/TestwiseCoverageReport.cs

View check run for this annotation

cqse.teamscale.io / teamscale-findings

UploadDaemon/Report/Testwise/TestwiseCoverageReport.cs#L22

Interface comment missing https://cqse.teamscale.io/findings/details/teamscale-net-profiler?t=tia%3AHEAD&id=DB30096B69B0D9D79DB035E7139ED56E

public List<(string project, RevisionOrTimestamp revisionOrTimestamp)> EmbeddedUploadTargets { get; }

public TestwiseCoverageReport(Test[] tests, List<(string project, RevisionOrTimestamp revisionOrTimestamp)> embeddedUploadTargets) : this(false, tests, embeddedUploadTargets) { }
public TestwiseCoverageReport(Test[] tests) : this(false, tests) { }

Check warning on line 25 in UploadDaemon/Report/Testwise/TestwiseCoverageReport.cs

View check run for this annotation

cqse.teamscale.io / teamscale-findings

UploadDaemon/Report/Testwise/TestwiseCoverageReport.cs#L25

Empty block: constructor https://cqse.teamscale.io/findings/details/teamscale-net-profiler?t=tia%3AHEAD&id=69D464F1DB811BF729E567D154A29FEB

public TestwiseCoverageReport(bool partial, Test[] tests, List<(string project, RevisionOrTimestamp revisionOrTimestamp)> embeddedUploadTargets)
public TestwiseCoverageReport(bool partial, Test[] tests)
{
Partial = partial;
Tests = tests;
EmbeddedUploadTargets = embeddedUploadTargets;
}

/// <inheritDoc/>
Expand Down Expand Up @@ -63,7 +61,7 @@ public ICoverageReport Union(ICoverageReport coverageReport)
}
}

return new TestwiseCoverageReport(this.Partial || other.Partial, mergedCoverage.Values.ToArray(), this.EmbeddedUploadTargets);
return new TestwiseCoverageReport(this.Partial || other.Partial, mergedCoverage.Values.ToArray());
}

/// <summary>
Expand Down
130 changes: 130 additions & 0 deletions UploadDaemon/Scanning/AssemblyExtractor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Text.RegularExpressions;
using static UploadDaemon.SymbolAnalysis.RevisionFileUtils;

namespace UploadDaemon.Scanning
{
public class AssemblyExtractor
{

Check warning on line 13 in UploadDaemon/Scanning/AssemblyExtractor.cs

View check run for this annotation

cqse.teamscale.io / teamscale-findings

UploadDaemon/Scanning/AssemblyExtractor.cs#L12-L13

[New] Interface comment missing https://cqse.teamscale.io/findings/details/teamscale-net-profiler?t=tia%3AHEAD&id=49EC229484C7630E36A75846ACAC11C6
private static readonly Logger logger = LogManager.GetCurrentClassLogger();

/// <summary>
/// The name of the Resource .resx file that holed information about embedded upload targets.
/// </summary>
private const String TeamscaleResourceName = "Teamscale";
private static readonly Regex AssemblyLineRegex = new Regex(@"^Assembly=(?<name>[^:]+):(?<id>\d+).*?(?: Path:(?<path>.*))?$");

public readonly Dictionary<uint, (string name, string path)> Assemblies = new Dictionary<uint, (string name, string path)>();

Check warning on line 22 in UploadDaemon/Scanning/AssemblyExtractor.cs

View check run for this annotation

cqse.teamscale.io / teamscale-findings

UploadDaemon/Scanning/AssemblyExtractor.cs#L22

[New] Interface comment missing https://cqse.teamscale.io/findings/details/teamscale-net-profiler?t=tia%3AHEAD&id=7166B5BF3437FE66F16FB19CA7A39FD3
public readonly List<(string project, RevisionOrTimestamp revisionOrTimestamp)> EmbeddedUploadTargets = new List<(string project, RevisionOrTimestamp revisionOrTimestamp)>();

Check warning on line 23 in UploadDaemon/Scanning/AssemblyExtractor.cs

View check run for this annotation

cqse.teamscale.io / teamscale-findings

UploadDaemon/Scanning/AssemblyExtractor.cs#L23

[New] Interface comment missing https://cqse.teamscale.io/findings/details/teamscale-net-profiler?t=tia%3AHEAD&id=753C2F402F49386B5A57F03A65976E0F

public void ExtractAssemblies(string[] lines)
{

Check warning on line 26 in UploadDaemon/Scanning/AssemblyExtractor.cs

View check run for this annotation

cqse.teamscale.io / teamscale-findings

UploadDaemon/Scanning/AssemblyExtractor.cs#L25-L26

[New] Interface comment missing https://cqse.teamscale.io/findings/details/teamscale-net-profiler?t=tia%3AHEAD&id=B719F51B2770270A98D7452EEC0EEEA0
foreach (string line in lines)
{
string[] keyValuePair = line.Split(new[] { '=' }, 2);
if (keyValuePair.Length < 2)
{
continue;
}

if (keyValuePair[0] == "Assembly")
{
Match assemblyMatch = AssemblyLineRegex.Match(line);
Assemblies[Convert.ToUInt32(assemblyMatch.Groups["id"].Value)] = (assemblyMatch.Groups["name"].Value, assemblyMatch.Groups["path"].Value);
}
}

SearchForEmbeddedUploadTargets(Assemblies, EmbeddedUploadTargets);
}

/// <summary>
/// Checks the loaded assemblies for resources that contain information about target revision or teamscale projects.
/// </summary>
private void SearchForEmbeddedUploadTargets(Dictionary<uint, (string, string)> assemblyTokens, List<(string project, RevisionOrTimestamp revisionOrTimestamp)> uploadTargets)
{
foreach (KeyValuePair<uint, (string, string)> entry in assemblyTokens)
{
Assembly assembly = LoadAssemblyFromPath(entry.Value.Item2);
if (assembly == null || assembly.DefinedTypes == null)
{
continue;
}
TypeInfo teamscaleResourceType = assembly.DefinedTypes.FirstOrDefault(x => x.Name == TeamscaleResourceName) ?? null;
if (teamscaleResourceType == null)
{
continue;
}
logger.Info("Found embedded Teamscale resource in {assembly} that can be used to identify upload targets.", assembly);
ResourceManager teamscaleResourceManager = new ResourceManager(teamscaleResourceType.FullName, assembly);
string embeddedTeamscaleProject = teamscaleResourceManager.GetString("Project");
string embeddedRevision = teamscaleResourceManager.GetString("Revision");
string embeddedTimestamp = teamscaleResourceManager.GetString("Timestamp");
AddUploadTarget(embeddedRevision, embeddedTimestamp, embeddedTeamscaleProject, uploadTargets, assembly.FullName);
}
}

/// <summary>
/// Adds a revision or timestamp and optionally a project to the list of upload targets. This method checks if both, revision and timestamp, are declared, or neither.
/// </summary>
/// <param name="revision"></param>
/// <param name="timestamp"></param>
/// <param name="project"></param>
/// <param name="uploadTargets"></param>
/// <param name="origin"></param>
public static void AddUploadTarget(string revision, string timestamp, string project, List<(string project, RevisionOrTimestamp revisionOrTimestamp)> uploadTargets, string origin)
{
Logger logger = LogManager.GetCurrentClassLogger();

if (revision == null && timestamp == null)
{
logger.Error("Not all required fields in {origin}. Please specify either 'Revision' or 'Timestamp'", origin);
return;
}
if (revision != null && timestamp != null)
{
logger.Error("'Revision' and 'Timestamp' are both set in {origin}. Please set only one, not both.", origin);
return;
}
if (revision != null)
{
uploadTargets.Add((project, new RevisionOrTimestamp(revision, true)));
}
else
{
uploadTargets.Add((project, new RevisionOrTimestamp(timestamp, false)));
}
}

private Assembly LoadAssemblyFromPath(string path)
{
if (String.IsNullOrEmpty(path))
{
return null;
}
Assembly assembly;
try
{
assembly = Assembly.LoadFrom(path);
// Check that defined types can actually be loaded
if (assembly == null)
{
return null;
}
IEnumerable<TypeInfo> ignored = assembly.DefinedTypes;
}
catch (Exception e)
{
logger.Debug("Could not load {assembly}. Skipping upload resource discovery. {e}", path, e);
return null;
}
return assembly;
}


}
}
Loading

0 comments on commit 91c8a68

Please sign in to comment.