Skip to content

Commit 10803ec

Browse files
authored
Refactor artifacts size tests (dotnet#42447)
1 parent a40e21b commit 10803ec

File tree

9 files changed

+66
-5053
lines changed

9 files changed

+66
-5053
lines changed

src/SourceBuild/content/eng/pipelines/source-build-sdk-diff-tests.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ jobs:
4646
targetRid: ${{ variables.centOSStreamX64Rid }}
4747
architecture: x64
4848
dotnetDotnetRunId: ${{ parameters.dotnetDotnetRunId }}
49-
includeArtifactsSize: true
5049
publishTestResultsPr: true
5150

5251
- template: templates/jobs/sdk-diff-tests.yml

src/SourceBuild/content/eng/pipelines/templates/jobs/sdk-diff-tests.yml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@ parameters:
1111
- name: dotnetDotnetRunId
1212
type: string
1313

14-
- name: includeArtifactsSize
15-
type: boolean
16-
default: false
17-
1814
- name: publishTestResultsPr
1915
type: boolean
2016
default: false
@@ -131,7 +127,6 @@ jobs:
131127
/p:SdkTarballPath=$(SdkTarballPath)
132128
/p:SourceBuiltArtifactsPath=$(SourceBuiltArtifactsPath)
133129
/p:SmokeTestsWarnOnSdkContentDiffs=false
134-
/p:SmokeTestsIncludeArtifactsSizeTests=${{ parameters.includeArtifactsSize }}
135130
/p:TargetRid=${{ parameters.targetRid }}
136131
/p:PortableRid=$(Platform)-${{ parameters.architecture }}
137132
displayName: Run Tests

src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/ArtifactsSizeTests.cs

Lines changed: 35 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -17,72 +17,55 @@
1717

1818
namespace Microsoft.DotNet.SourceBuild.SmokeTests;
1919

20-
[Trait("Category", "SdkContent")]
2120
public class ArtifactsSizeTests : SdkTests
2221
{
23-
private const int SizeThresholdPercentage = 25;
24-
private static readonly string BaselineFilePath = BaselineHelper.GetBaselineFilePath($"{Config.TargetRid}.txt", nameof(ArtifactsSizeTests));
25-
private readonly Dictionary<string, long> Baseline = new();
26-
private Dictionary<string, int> FilePathCountMap = new();
27-
private StringBuilder Differences = new();
22+
private const string SdkType = "sdk";
23+
private readonly StringBuilder _differences = new();
24+
private readonly List<string> _newExclusions = new List<string>();
25+
private readonly Dictionary<string, int> _filePathCountMap = new();
26+
private readonly ExclusionsHelper _exclusionsHelper = new ExclusionsHelper("ZeroSizeExclusions.txt", nameof(ArtifactsSizeTests));
27+
public static bool IncludeArtifactsSizeTests => !string.IsNullOrWhiteSpace(Config.SdkTarballPath);
2828

29-
public ArtifactsSizeTests(ITestOutputHelper outputHelper) : base(outputHelper)
30-
{
31-
if (File.Exists(BaselineFilePath))
32-
{
33-
string[] baselineFileContent = File.ReadAllLines(BaselineFilePath);
34-
foreach (string entry in baselineFileContent)
35-
{
36-
string[] splitEntry = entry.Split(':', StringSplitOptions.TrimEntries);
37-
Baseline[splitEntry[0]] = long.Parse(splitEntry[1]);
38-
}
39-
}
40-
else
41-
{
42-
Assert.Fail($"Baseline file `{BaselineFilePath}' does not exist. Please create the baseline file then rerun the test.");
43-
}
44-
}
29+
public ArtifactsSizeTests(ITestOutputHelper outputHelper) : base(outputHelper) {}
4530

46-
[ConditionalFact(typeof(Config), nameof(Config.IncludeArtifactsSizeTests))]
47-
public void CompareArtifactsToBaseline()
31+
[ConditionalFact(typeof(ArtifactsSizeTests), nameof(IncludeArtifactsSizeTests))]
32+
public void CheckZeroSizeArtifacts()
4833
{
49-
Assert.False(string.IsNullOrWhiteSpace(Config.SourceBuiltArtifactsPath));
50-
Assert.False(string.IsNullOrWhiteSpace(Config.SdkTarballPath));
34+
ProcessTarball(Config.SdkTarballPath!, SdkType);
5135

52-
var tarEntries = ProcessSdkAndArtifactsTarballs();
53-
ScanForDifferences(tarEntries);
54-
UpdateBaselineFile();
36+
_exclusionsHelper.GenerateNewBaselineFile(updatedFileTag: null, _newExclusions);
5537

56-
// Must wait to report differences until after the baseline file is updated else a failure
57-
// will cause the baseline file to not be updated.
38+
// Wait to report differences until after the baseline file is updated.
39+
// Else a failure will cause the baseline file to not be updated.
5840
ReportDifferences();
5941
}
6042

61-
private Dictionary<string, long> ProcessSdkAndArtifactsTarballs()
43+
private void ProcessTarball(string tarballPath, string type)
6244
{
6345
string tempTarballDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
6446
Directory.CreateDirectory(tempTarballDir);
6547

66-
Utilities.ExtractTarball(Config.SdkTarballPath!, tempTarballDir, OutputHelper);
67-
Utilities.ExtractTarball(Config.SourceBuiltArtifactsPath!, tempTarballDir, OutputHelper);
48+
Utilities.ExtractTarball(tarballPath, tempTarballDir, OutputHelper);
6849

69-
Dictionary<string, long> tarEntries = Directory.EnumerateFiles(tempTarballDir, "*", SearchOption.AllDirectories)
70-
.Where(filePath => !filePath.Contains("SourceBuildReferencePackages"))
71-
.Select(filePath =>
72-
{
73-
string relativePath = filePath.Substring(tempTarballDir.Length + 1);
74-
return (ProcessFilePath(relativePath), new FileInfo(filePath).Length);
75-
})
76-
.ToDictionary(tuple => tuple.Item1, tuple => tuple.Item2);
50+
var newZeroSizedFiles = Directory
51+
.EnumerateFiles(tempTarballDir, "*", SearchOption.AllDirectories)
52+
.Where(filePath => new FileInfo(filePath).Length == 0)
53+
.Select(filePath => ProcessFilePath(tempTarballDir, filePath))
54+
.Where(processedPath => !_exclusionsHelper.IsFileExcluded(processedPath, type));
7755

78-
Directory.Delete(tempTarballDir, true);
56+
foreach (string file in newZeroSizedFiles)
57+
{
58+
_newExclusions.Add($"{file}|{type}");
59+
TrackDifference($"{file} is 0 bytes.");
60+
}
7961

80-
return tarEntries;
62+
Directory.Delete(tempTarballDir, true);
8163
}
8264

83-
private string ProcessFilePath(string originalPath)
65+
private string ProcessFilePath(string relativeTo, string originalPath)
8466
{
85-
string result = BaselineHelper.RemoveRids(originalPath);
67+
string relativePath = Path.GetRelativePath(relativeTo, originalPath);
68+
string result = BaselineHelper.RemoveRids(relativePath);
8669
result = BaselineHelper.RemoveVersions(result);
8770

8871
return AddDifferenciatingSuffix(result);
@@ -111,8 +94,8 @@ private string AddDifferenciatingSuffix(string filePath)
11194

11295
if (matchIndex != -1)
11396
{
114-
int count = FilePathCountMap.TryGetValue(filePath, out count) ? count : 0;
115-
FilePathCountMap[filePath] = count + 1;
97+
int count = _filePathCountMap.TryGetValue(filePath, out count) ? count : 0;
98+
_filePathCountMap[filePath] = count + 1;
11699

117100
if (count > 0)
118101
{
@@ -123,94 +106,14 @@ private string AddDifferenciatingSuffix(string filePath)
123106
return filePath;
124107
}
125108

126-
private void ScanForDifferences(Dictionary<string, long> tarEntries)
127-
{
128-
foreach (var entry in tarEntries)
129-
{
130-
if (!Baseline.TryGetValue(entry.Key, out long baselineBytes))
131-
{
132-
TrackDifference($"{entry.Key} does not exist in baseline. It is {entry.Value} bytes. Adding it to the baseline file.");
133-
Baseline.Add(entry.Key, entry.Value);
134-
}
135-
else
136-
{
137-
CompareFileSizes(entry.Key, entry.Value, baselineBytes);
138-
}
139-
}
140-
141-
foreach (var removedFile in Baseline.Keys.Except(tarEntries.Keys))
142-
{
143-
TrackDifference($"`{removedFile}` is no longer being produced. It was {Baseline[removedFile]} bytes.");
144-
Baseline.Remove(removedFile);
145-
}
146-
}
147-
148-
private void CompareFileSizes(string filePath, long fileSize, long baselineSize)
149-
{
150-
// Only update the baseline with breaking differences. Non-breaking differences are file size changes
151-
// less than the threshold percentage. This makes it easier to review the breaking changes and prevents
152-
// inadvertently allowing small percentage changes to be accepted that can add up to a significant
153-
// difference over time.
154-
string breakingDifference = string.Empty;
155-
156-
if (fileSize == 0 && baselineSize != 0)
157-
{
158-
breakingDifference = $"'{filePath}' is now 0 bytes. It was {baselineSize} bytes.";
159-
}
160-
else if (fileSize != 0 && baselineSize == 0)
161-
{
162-
breakingDifference = $"'{filePath}' is no longer 0 bytes. It is now {fileSize} bytes.";
163-
}
164-
else if (baselineSize != 0 && (((fileSize - baselineSize) / (double)baselineSize) * 100) >= SizeThresholdPercentage)
165-
{
166-
breakingDifference =
167-
$"'{filePath}' increased in size by more than {SizeThresholdPercentage}%. It was originally {baselineSize} bytes and is now {fileSize} bytes.";
168-
}
169-
else if (baselineSize != 0 && (((baselineSize - fileSize) / (double)baselineSize) * 100) >= SizeThresholdPercentage)
170-
{
171-
breakingDifference =
172-
$"'{filePath}' decreased in size by more than {SizeThresholdPercentage}%. It was originally {baselineSize} bytes and is now {fileSize} bytes.";
173-
}
174-
175-
if (!string.IsNullOrEmpty(breakingDifference))
176-
{
177-
TrackDifference(breakingDifference);
178-
Baseline[filePath] = fileSize;
179-
}
180-
}
181-
182-
private void TrackDifference(string difference) => Differences.AppendLine(difference);
109+
private void TrackDifference(string difference) => _differences.AppendLine(difference);
183110

184111
private void ReportDifferences()
185112
{
186-
if (Differences.Length > 0)
187-
{
188-
if (Config.WarnOnSdkContentDiffs)
189-
{
190-
OutputHelper.LogWarningMessage(Differences.ToString());
191-
}
192-
else
193-
{
194-
OutputHelper.WriteLine(Differences.ToString());
195-
Assert.Fail("Differences were found in the artifacts sizes.");
196-
}
197-
}
198-
}
199-
200-
private void UpdateBaselineFile()
201-
{
202-
try
203-
{
204-
string actualFilePath = Path.Combine(Config.LogsDirectory, $"Updated{Config.TargetRid}.txt");
205-
File.WriteAllLines(
206-
actualFilePath,
207-
Baseline
208-
.OrderBy(kvp => kvp.Key)
209-
.Select(kvp => $"{kvp.Key}: {kvp.Value}"));
210-
}
211-
catch (IOException ex)
113+
if (_differences.Length > 0)
212114
{
213-
throw new InvalidOperationException($"An error occurred while copying the baselines file: {BaselineFilePath}", ex);
115+
OutputHelper.LogWarningMessage(_differences.ToString());
116+
Assert.Fail("Differences were found in the artifacts sizes.");
214117
}
215118
}
216119
}

src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/Config.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ internal static class Config
2121

2222
public static string? CustomPackagesPath => (string)AppContext.GetData(ConfigSwitchPrefix + nameof(CustomPackagesPath))!;
2323
public static bool ExcludeOmniSharpTests => bool.TryParse((string)AppContext.GetData(ConfigSwitchPrefix + nameof(ExcludeOmniSharpTests))!, out bool excludeOmniSharpTests) && excludeOmniSharpTests;
24-
public static bool IncludeArtifactsSizeTests => bool.TryParse((string)AppContext.GetData(ConfigSwitchPrefix + nameof(IncludeArtifactsSizeTests))!, out bool includeArtifactsSizeTests) && includeArtifactsSizeTests;
2524
public static string? LicenseScanPath => (string)AppContext.GetData(ConfigSwitchPrefix + nameof(LicenseScanPath))!;
2625
public static string? MsftSdkTarballPath => (string)AppContext.GetData(ConfigSwitchPrefix + nameof(MsftSdkTarballPath))!;
2726
public static string? PoisonReportPath => (string)AppContext.GetData(ConfigSwitchPrefix + nameof(PoisonReportPath))!;

src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/ExclusionsHelper.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,12 @@ internal bool IsFileExcluded(string filePath, string suffix = NullSuffix)
5858
(suffix != NullSuffix && CheckAndRemoveIfExcluded(filePath, NullSuffix));
5959
}
6060

61-
internal void GenerateNewBaselineFile(string? updatedFileTag = null)
61+
/// <summary>
62+
/// Generates a new baseline file with the exclusions that were used during the test run.
63+
/// <param name="updatedFileTag">Optional tag to append to the updated file name.</param>
64+
/// <param name="additionalLines">Optional additional lines to append to the updated file.</param>
65+
/// </summary>
66+
internal void GenerateNewBaselineFile(string? updatedFileTag = null, List<string>? additionalLines = null)
6267
{
6368
string exclusionsFilePath = BaselineHelper.GetBaselineFilePath(_exclusionsFileName, _baselineSubDir);
6469

@@ -68,6 +73,11 @@ internal void GenerateNewBaselineFile(string? updatedFileTag = null)
6873
.Select(line => UpdateExclusionsLine(line))
6974
.Where(line => line is not null);
7075

76+
if (additionalLines is not null)
77+
{
78+
newLines = newLines.Concat(additionalLines);
79+
}
80+
7181
string updatedFileName = updatedFileTag is null
7282
? $"Updated{_exclusionsFileName}"
7383
: $"Updated{Path.GetFileNameWithoutExtension(_exclusionsFileName)}.{updatedFileTag}{Path.GetExtension(_exclusionsFileName)}";

src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/Microsoft.DotNet.SourceBuild.SmokeTests.csproj

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,6 @@
6868
<RuntimeHostConfigurationOption Include="$(MSBuildProjectName).ExcludeOmniSharpTests">
6969
<Value>$(SmokeTestsExcludeOmniSharpTests)</Value>
7070
</RuntimeHostConfigurationOption>
71-
<RuntimeHostConfigurationOption Include="$(MSBuildProjectName).IncludeArtifactsSizeTests">
72-
<Value>$(SmokeTestsIncludeArtifactsSizeTests)</Value>
73-
</RuntimeHostConfigurationOption>
7471
<RuntimeHostConfigurationOption Include="$(MSBuildProjectName).LicenseScanPath">
7572
<Value>$(SmokeTestsLicenseScanPath)</Value>
7673
</RuntimeHostConfigurationOption>

src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ Optional msbuild properties:
1111
- MsftSdkTarballPath
1212
- SmokeTestsCustomSourceBuiltPackagesPath
1313
- SmokeTestsExcludeOmniSharpTests
14-
- SmokeTestsIncludeArtifactsSizeTests
1514
- SmokeTestsLicenseScanPath
1615
- SmokeTestsPrereqsPath
1716
- SmokeTestsWarnOnLicenseScanDiffs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Contains the list of files to be excluded from artifact size tests.
2+
#
3+
# Format
4+
# Exclude the path entirely:
5+
# <path> [# comment]
6+
# Exclude a path from a specific artifact:
7+
# <path>|{sdk} [# comment]
8+
# sdk = source-built SDK
9+
#
10+
# '*' in exclusions match zero or more characters.
11+
# '*' will match files and directory names but it will not match separator characters.
12+
# '/' will be evaluated as '/**' if it is the last character.
13+
#
14+
# Examples
15+
# 'folder/*' matches all files and directories in 'folder/'. It will not match 'folder/abc/def'
16+
# 'folder/' is equivalent to 'folder/**. It matches 'folder/', 'folder/abc', and 'folder/abc/def/'
17+
18+
metadata/workloads/x.y.z/userlocal|sdk
19+
packs/runtime.banana-rid.Microsoft.DotNet.ILCompiler/x.y.z/sdk/nonportable.txt|sdk
20+
sdk/x.y.z/Microsoft/Microsoft.NET.Build.Extensions/net471/_._|sdk

0 commit comments

Comments
 (0)