diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index 44d56a3fb..d24fc5d42 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -28,6 +28,10 @@ jobs:
- name: Build Coyote projects
run: ./Scripts/build.ps1
shell: pwsh
+ - name: Validate Coyote Rewriting
+ if: ${{ matrix.platform == 'windows-latest' }}
+ run: ./Tests/compare-rewriting-diff-logs.ps1
+ shell: pwsh
- name: Run Coyote Tests
run: ./Scripts/run-tests.ps1
shell: pwsh
diff --git a/.gitignore b/.gitignore
index d3fed231f..c28b72e61 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
+*.rsuser
*.suo
*.user
*.userosscache
@@ -12,25 +13,27 @@
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
+# Mono auto generated files
+mono_crash.*
+
# Build results
-[Bb]uild/
-[Cc]odegen/
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
bld/
[Bb]in/
-[Bb]inaries/
[Oo]bj/
[Ll]og/
+[Ll]ogs/
-# Visual Studio cache/options directory
+# Visual Studio 2015/2017 cache/options directory
.vs/
-**/.vscode/**
-
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
@@ -41,9 +44,10 @@ Generated\ Files/
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
-# NUNIT
+# NUnit
*.VisualState.xml
TestResult.xml
+nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
@@ -52,13 +56,14 @@ dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
-benchmark_*/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
-**/Properties/launchSettings.json
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
@@ -66,7 +71,7 @@ StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
-*_i.h
+*_h.h
*.ilk
*.meta
*.obj
@@ -83,7 +88,9 @@ StyleCopReport.xml
*.tlh
*.tmp
*.tmp_proj
+*_wpftmp.csproj
*.log
+*.tlog
*.vspscc
*.vssscc
.builds
@@ -125,9 +132,6 @@ _ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
-# JustCode is a .NET coding add-in
-.JustCode
-
# TeamCity is a build add-in
_TeamCity*
@@ -138,6 +142,11 @@ _TeamCity*
.axoCover/*
!.axoCover/settings.json
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
# Visual Studio code coverage results
*.coverage
*.coveragexml
@@ -183,8 +192,10 @@ publish/
# in these scripts will be unencrypted
PublishScripts/
-# NuGet
+# NuGet Packages
*.nupkg
+# NuGet Symbol Packages
+*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
@@ -194,7 +205,9 @@ PublishScripts/
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
-nuget.exe
+
+# Nuget personal access tokens and Credentials
+# nuget.config
# Microsoft Azure Build Output
csx/
@@ -210,12 +223,14 @@ BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
+*.appxbundle
+*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
-!*.[Cc]ache/
+!?*.[Cc]ache/
# Others
ClientBin/
@@ -259,6 +274,9 @@ ServiceFabricBackup/
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
@@ -294,12 +312,8 @@ paket-files/
# FAKE - F# Make
.fake/
-# JetBrains Rider
-.idea/
-*.sln.iml
-
-# CodeRush
-.cr/
+# CodeRush personal settings
+.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
@@ -336,12 +350,47 @@ ASALocalRun/
# MFractors (Xamarin productivity tool) working folder
.mfractor/
-# mkdocs
-site
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+.idea/
+*.sln.iml
# Website
+site
docs/assets/images/Thumbs.db
docs/_data/sidebar-temp.yml
docs/_learn/ref/toc.yml
.ipynb_checkpoints/
-*-checkpoint.ipynb
\ No newline at end of file
+*-checkpoint.ipynb
diff --git a/Scripts/build.ps1 b/Scripts/build.ps1
index 3f72a1a85..1f5cebc7f 100644
--- a/Scripts/build.ps1
+++ b/Scripts/build.ps1
@@ -4,7 +4,8 @@
param(
[ValidateSet("Debug", "Release")]
[string]$configuration = "Release",
- [bool]$local = $true
+ [bool]$local = $true,
+ [switch]$latest
)
$ScriptDir = $PSScriptRoot
@@ -40,12 +41,12 @@ $solution = Join-Path -Path $ScriptDir -ChildPath "\.." -AdditionalChildPath "Co
$command = "build -c $configuration $solution /p:Platform=""Any CPU"""
if ($local) {
- if ($version_net4) {
+ if ($version_net4 -and -not $latest) {
# Build .NET Framework 4.x as well as the new version.
$command = $command + " /p:BUILD_NET462=yes"
}
- if ($null -ne $version_netcore31 -and $version_netcore31 -ne $sdk_version) {
+ if ($null -ne $version_netcore31 -and $version_netcore31 -ne $sdk_version -and -not $latest) {
# Build .NET Core 3.1 as well as the new version.
$command = $command + " /p:BUILD_NETCORE31=yes"
}
diff --git a/Source/Test/Rewriting/CachedNameProvider.cs b/Source/Test/Rewriting/CachedNameProvider.cs
index 468bfd4a2..8c966aba5 100644
--- a/Source/Test/Rewriting/CachedNameProvider.cs
+++ b/Source/Test/Rewriting/CachedNameProvider.cs
@@ -52,7 +52,7 @@ internal static class CachedNameProvider
internal static string GenericHashSetFullName { get; } = typeof(SystemGenericCollections.HashSet<>).FullName;
internal static string ConcurrentBagFullName { get; } = typeof(SystemConcurrentCollections.ConcurrentBag<>).FullName;
- internal static string ConcurrentDictonaryFullName { get; } = typeof(SystemConcurrentCollections.ConcurrentDictionary<,>).FullName;
+ internal static string ConcurrentDictionaryFullName { get; } = typeof(SystemConcurrentCollections.ConcurrentDictionary<,>).FullName;
internal static string ConcurrentQueueFullName { get; } = typeof(SystemConcurrentCollections.ConcurrentQueue<>).FullName;
internal static string ConcurrentStackFullName { get; } = typeof(SystemConcurrentCollections.ConcurrentStack<>).FullName;
}
diff --git a/Source/Test/Rewriting/Passes/AssemblyDiffingPass.cs b/Source/Test/Rewriting/Passes/AssemblyDiffingPass.cs
new file mode 100644
index 000000000..b651df857
--- /dev/null
+++ b/Source/Test/Rewriting/Passes/AssemblyDiffingPass.cs
@@ -0,0 +1,532 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Microsoft.Coyote.IO;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+
+namespace Microsoft.Coyote.Rewriting
+{
+ ///
+ /// A pass that diffs the IL contents of assemblies and returns them as JSON.
+ ///
+ internal class AssemblyDiffingPass : Pass
+ {
+ ///
+ /// Map from assemblies to IL contents.
+ ///
+ private Dictionary ContentMap;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal AssemblyDiffingPass(IEnumerable visitedAssemblies, ILogger logger)
+ : base(visitedAssemblies, logger)
+ {
+ this.ContentMap = new Dictionary();
+ }
+
+ ///
+ protected internal override void VisitAssembly(AssemblyDefinition assembly)
+ {
+ if (!this.ContentMap.ContainsKey(assembly.FullName))
+ {
+ var contents = new AssemblyContents()
+ {
+ FullName = assembly.FullName,
+ Modules = new List()
+ };
+
+ this.ContentMap.Add(assembly.FullName, contents);
+ }
+
+ base.VisitAssembly(assembly);
+ }
+
+ ///
+ protected internal override void VisitModule(ModuleDefinition module)
+ {
+ if (this.ContentMap.TryGetValue(this.Assembly.FullName, out AssemblyContents contents))
+ {
+ contents.Modules.Add(new ModuleContents()
+ {
+ Name = module.Name,
+ Types = new List()
+ });
+ }
+
+ base.VisitModule(module);
+ }
+
+ ///
+ protected internal override void VisitType(TypeDefinition type)
+ {
+ if (this.ContentMap.TryGetValue(this.Assembly.FullName, out AssemblyContents contents))
+ {
+ contents.Modules.FirstOrDefault(m => m.Name == this.Module.Name)?.Types.Add(
+ new TypeContents()
+ {
+ FullName = type.FullName,
+ Methods = new List()
+ });
+ }
+
+ base.VisitType(type);
+ }
+
+ ///
+ protected internal override void VisitField(FieldDefinition field)
+ {
+ if (this.ContentMap.TryGetValue(this.Assembly.FullName, out AssemblyContents contents))
+ {
+ contents.Modules.FirstOrDefault(m => m.Name == this.Module.Name)?.Types
+ .FirstOrDefault(t => t.FullName == this.TypeDef.FullName)?
+ .AddField(field);
+ }
+
+ base.VisitField(field);
+ }
+
+ ///
+ protected internal override void VisitMethod(MethodDefinition method)
+ {
+ if (this.ContentMap.TryGetValue(this.Assembly.FullName, out AssemblyContents contents))
+ {
+ contents.Modules.FirstOrDefault(m => m.Name == this.Module.Name)?.Types
+ .FirstOrDefault(t => t.FullName == this.TypeDef.FullName)?.Methods
+ .Add(new MethodContents()
+ {
+ Index = this.TypeDef.Methods.IndexOf(method),
+ Name = method.Name,
+ FullName = method.FullName,
+ Instructions = new List()
+ });
+ }
+
+ base.VisitMethod(method);
+ }
+
+ ///
+ protected override void VisitVariable(VariableDefinition variable)
+ {
+ if (this.ContentMap.TryGetValue(this.Assembly.FullName, out AssemblyContents contents))
+ {
+ contents.Modules.FirstOrDefault(m => m.Name == this.Module.Name)?.Types
+ .FirstOrDefault(t => t.FullName == this.TypeDef.FullName)?.Methods
+ .FirstOrDefault(m => m.FullName == this.Method.FullName)?
+ .AddVariable(variable);
+ }
+
+ base.VisitVariable(variable);
+ }
+
+ ///
+ protected override Instruction VisitInstruction(Instruction instruction)
+ {
+ if (this.ContentMap.TryGetValue(this.Assembly.FullName, out AssemblyContents contents))
+ {
+ contents.Modules.FirstOrDefault(m => m.Name == this.Module.Name)?.Types
+ .FirstOrDefault(t => t.FullName == this.TypeDef.FullName)?.Methods
+ .FirstOrDefault(m => m.FullName == this.Method.FullName)?.Instructions
+ .Add(instruction.ToString());
+ }
+
+ return base.VisitInstruction(instruction);
+ }
+
+ ///
+ /// Returns the diff between the IL contents of this pass against the specified pass as JSON.
+ ///
+ internal string GetDiffJson(AssemblyInfo assembly, AssemblyDiffingPass pass)
+ {
+ if (!this.ContentMap.TryGetValue(assembly.FullName, out AssemblyContents thisContents) ||
+ !pass.ContentMap.TryGetValue(assembly.FullName, out AssemblyContents otherContents))
+ {
+ this.Logger.WriteLine(LogSeverity.Error, "Unable to diff IL code that belongs to different assemblies.");
+ return string.Empty;
+ }
+
+ // Diff contents of the two assemblies.
+ var diffedContents = thisContents.Diff(otherContents);
+ return this.GetJson(diffedContents);
+ }
+
+ ///
+ /// Returns the IL contents of the specified assembly as JSON.
+ ///
+ internal string GetJson(AssemblyInfo assembly) =>
+ this.ContentMap.TryGetValue(assembly.Definition.FullName, out AssemblyContents contents) ?
+ this.GetJson(contents) : string.Empty;
+
+ ///
+ /// Returns the IL contents of the specified assembly as JSON.
+ ///
+ private string GetJson(AssemblyContents contents)
+ {
+ try
+ {
+ contents.Resolve();
+ return JsonSerializer.Serialize(contents, new JsonSerializerOptions()
+ {
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
+ WriteIndented = true
+ });
+ }
+ catch (Exception ex)
+ {
+ this.Logger.WriteLine(LogSeverity.Error, $"Unable to serialize IL to JSON. {ex.Message}");
+ }
+
+ return string.Empty;
+ }
+
+ ///
+ /// The status of diffing two IL contents.
+ ///
+ private enum DiffStatus
+ {
+ ///
+ /// Contents are identical.
+ ///
+ None,
+
+ ///
+ /// Contents have been added.
+ ///
+ Added,
+
+ ///
+ /// Contents have been removed.
+ ///
+ Removed
+ }
+
+ private class AssemblyContents
+ {
+ public string FullName { get; set; }
+ public List Modules { get; set; }
+
+ ///
+ /// Returns the diff between the IL contents of this assembly against the specified assembly.
+ ///
+ internal AssemblyContents Diff(AssemblyContents other)
+ {
+ var diffedContents = new AssemblyContents()
+ {
+ FullName = this.FullName,
+ Modules = new List()
+ };
+
+ var diffedModules = this.Modules.Union(other.Modules);
+ foreach (var module in diffedModules)
+ {
+ var thisModule = this.Modules.FirstOrDefault(m => m.Equals(module));
+ var otherModule = other.Modules.FirstOrDefault(m => m.Equals(module));
+ if (thisModule is null)
+ {
+ diffedContents.Modules.Add(module.Clone(DiffStatus.Added));
+ }
+ else if (otherModule is null)
+ {
+ diffedContents.Modules.Add(module.Clone(DiffStatus.Removed));
+ }
+ else
+ {
+ diffedContents.Modules.Add(thisModule.Diff(otherModule));
+ }
+ }
+
+ return diffedContents;
+ }
+
+ internal void Resolve()
+ {
+ this.Modules.ForEach(m => m.Resolve());
+ this.Modules.RemoveAll(t => t.Types is null);
+ this.Modules = this.Modules.Count is 0 ? null : this.Modules;
+ }
+
+ public override bool Equals(object obj) => obj is AssemblyContents other && this.FullName == other.FullName;
+ public override int GetHashCode() => this.FullName.GetHashCode();
+ }
+
+ private class ModuleContents
+ {
+ public string Name { get; set; }
+ public List Types { get; set; }
+
+ [JsonIgnore]
+ internal DiffStatus DiffStatus { get; private set; } = DiffStatus.None;
+
+ ///
+ /// Returns the diff between the IL contents of this module against the specified module.
+ ///
+ internal ModuleContents Diff(ModuleContents other)
+ {
+ var diffedContents = new ModuleContents()
+ {
+ Name = this.Name,
+ Types = new List()
+ };
+
+ var diffedTypes = this.Types.Union(other.Types);
+ foreach (var type in diffedTypes)
+ {
+ var thisType = this.Types.FirstOrDefault(t => t.Equals(type));
+ var otherType = other.Types.FirstOrDefault(t => t.Equals(type));
+ if (thisType is null)
+ {
+ diffedContents.Types.Add(type.Clone(DiffStatus.Added));
+ }
+ else if (otherType is null)
+ {
+ diffedContents.Types.Add(type.Clone(DiffStatus.Removed));
+ }
+ else
+ {
+ diffedContents.Types.Add(thisType.Diff(otherType));
+ }
+ }
+
+ return diffedContents;
+ }
+
+ internal ModuleContents Clone(DiffStatus status)
+ {
+ string prefix = status is DiffStatus.Added ? "[+] " : status is DiffStatus.Removed ? "[-] " : string.Empty;
+ return new ModuleContents()
+ {
+ Name = prefix + this.Name,
+ Types = this.Types.Select(t => t.Clone(DiffStatus.None)).ToList(),
+ DiffStatus = status
+ };
+ }
+
+ internal void Resolve()
+ {
+ this.Types.ForEach(t => t.Resolve());
+ this.Types.RemoveAll(t => t.Fields is null && t.Methods is null);
+ this.Types = this.Types.Count is 0 ? null : this.Types;
+ }
+
+ public override bool Equals(object obj) => obj is ModuleContents other && this.Name == other.Name;
+ public override int GetHashCode() => this.Name.GetHashCode();
+ }
+
+ private class TypeContents
+ {
+ public string FullName { get; set; }
+ public IEnumerable Fields { get; set; }
+ public List Methods { get; set; }
+
+ private List<(string, string)> FieldContents = new List<(string, string)>();
+
+ [JsonIgnore]
+ internal DiffStatus DiffStatus { get; private set; } = DiffStatus.None;
+
+ internal void AddField(FieldDefinition field)
+ {
+ this.FieldContents.Add((field.Name, field.ToString()));
+ }
+
+ ///
+ /// Returns the diff between the IL contents of this type against the specified type.
+ ///
+ internal TypeContents Diff(TypeContents other)
+ {
+ var diffedContents = new TypeContents()
+ {
+ FullName = this.FullName,
+ Methods = new List()
+ };
+
+ var diffedFields = this.FieldContents.Union(other.FieldContents);
+ foreach (var field in diffedFields)
+ {
+ var (thisField, thisType) = this.FieldContents.FirstOrDefault(f => f == field);
+ var (otherField, otherType) = other.FieldContents.FirstOrDefault(f => f == field);
+ if (thisField is null)
+ {
+ diffedContents.FieldContents.Add((field.Item1, "[+] " + field.Item2));
+ }
+ else if (otherField is null)
+ {
+ diffedContents.FieldContents.Add((field.Item1, "[-] " + field.Item2));
+ }
+ }
+
+ var diffedMethods = this.Methods.Union(other.Methods);
+ foreach (var method in diffedMethods)
+ {
+ var thisMethod = this.Methods.FirstOrDefault(m => m.Equals(method));
+ var otherMethod = other.Methods.FirstOrDefault(m => m.Equals(method));
+ if (thisMethod is null)
+ {
+ diffedContents.Methods.Add(method.Clone(DiffStatus.Added));
+ }
+ else if (otherMethod is null)
+ {
+ diffedContents.Methods.Add(method.Clone(DiffStatus.Removed));
+ }
+ else
+ {
+ diffedContents.Methods.Add(thisMethod.Diff(otherMethod));
+ }
+ }
+
+ return diffedContents;
+ }
+
+ internal TypeContents Clone(DiffStatus status)
+ {
+ string prefix = status is DiffStatus.Added ? "[+] " : status is DiffStatus.Removed ? "[-] " : string.Empty;
+ return new TypeContents()
+ {
+ FullName = prefix + this.FullName,
+ FieldContents = this.FieldContents.ToList(),
+ Methods = this.Methods.Select(m => m.Clone(DiffStatus.None)).ToList(),
+ DiffStatus = status
+ };
+ }
+
+ internal void Resolve()
+ {
+ this.FieldContents = this.FieldContents.Count is 0 ? null : this.FieldContents;
+ if (this.FieldContents != null)
+ {
+ this.Fields = this.FieldContents.OrderBy(kvp => kvp.Item1).Select(kvp => kvp.Item2);
+ }
+
+ this.Methods.ForEach(m => m.Resolve());
+ this.Methods.RemoveAll(m => m.Variables is null && m.Instructions is null);
+ this.Methods = this.Methods.Count is 0 ? null : this.Methods;
+ }
+
+ public override bool Equals(object obj) => obj is TypeContents other && this.FullName == other.FullName;
+ public override int GetHashCode() => this.FullName.GetHashCode();
+ }
+
+ private class MethodContents
+ {
+ [JsonIgnore]
+ internal int Index { get; set; }
+
+ public string Name { get; set; }
+ public string FullName { get; set; }
+ public IEnumerable Variables { get; set; }
+ public List Instructions { get; set; }
+
+ private List<(int, string)> VariableContents = new List<(int, string)>();
+
+ [JsonIgnore]
+ internal DiffStatus DiffStatus { get; private set; } = DiffStatus.None;
+
+ internal void AddVariable(VariableDefinition variable)
+ {
+ this.VariableContents.Add((variable.Index, $"[{variable.Index}] {variable.VariableType.FullName}"));
+ }
+
+ ///
+ /// Returns the diff between the IL contents of this method against the specified method.
+ ///
+ internal MethodContents Diff(MethodContents other)
+ {
+ // TODO: show diff in method signature.
+ var diffedContents = new MethodContents()
+ {
+ Index = this.Index,
+ Name = this.Name,
+ FullName = other.FullName,
+ Instructions = new List()
+ };
+
+ var diffedVariables = this.VariableContents.Union(other.VariableContents);
+ foreach (var variable in diffedVariables)
+ {
+ var (thisVariable, thisType) = this.VariableContents.FirstOrDefault(v => v == variable);
+ var (otherVariable, otherType) = other.VariableContents.FirstOrDefault(v => v == variable);
+ if (thisType is null)
+ {
+ diffedContents.VariableContents.Add((variable.Item1, "[+] " + variable.Item2));
+ }
+ else if (otherType is null)
+ {
+ diffedContents.VariableContents.Add((variable.Item1, "[-] " + variable.Item2));
+ }
+ }
+
+ var diffedInstructions = this.Instructions.Union(other.Instructions);
+ foreach (var instruction in diffedInstructions)
+ {
+ var thisInstruction = this.Instructions.FirstOrDefault(i => i == instruction);
+ var otherInstruction = other.Instructions.FirstOrDefault(i => i == instruction);
+ if (thisInstruction is null)
+ {
+ diffedContents.Instructions.Add("[+] " + instruction);
+ }
+ else if (otherInstruction is null)
+ {
+ diffedContents.Instructions.Add("[-] " + instruction);
+ }
+ }
+
+ diffedContents.Instructions.Sort((x, y) =>
+ {
+ // Find offset of each instruction and convert from hex to int.
+ // The offset in IL is in the form 'IL_004a'.
+ int xOffset = Convert.ToInt32(x.Substring(7, 4), 16);
+ int yOffset = Convert.ToInt32(y.Substring(7, 4), 16);
+ return xOffset == yOffset ? x[1].CompareTo(y[1]) * -1 : xOffset.CompareTo(yOffset);
+ });
+
+ return diffedContents;
+ }
+
+ internal MethodContents Clone(DiffStatus status)
+ {
+ string prefix = status is DiffStatus.Added ? "[+] " : status is DiffStatus.Removed ? "[-] " : string.Empty;
+ return new MethodContents()
+ {
+ Index = this.Index,
+ Name = this.Name,
+ FullName = prefix + this.FullName,
+ VariableContents = this.VariableContents.ToList(),
+ Instructions = this.Instructions.ToList(),
+ DiffStatus = status
+ };
+ }
+
+ internal void Resolve()
+ {
+ this.VariableContents = this.VariableContents.Count is 0 ? null : this.VariableContents;
+ if (this.VariableContents != null)
+ {
+ this.Variables = this.VariableContents.OrderBy(kvp => kvp.Item1).Select(kvp => kvp.Item2);
+ }
+
+ this.Instructions = this.Instructions.Count is 0 ? null : this.Instructions;
+ }
+
+ public override bool Equals(object obj) => obj is MethodContents other &&
+ this.Index == other.Index && this.Name == other.Name;
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hash = 19;
+ hash = (hash * 31) + this.Index.GetHashCode();
+ hash = (hash * 31) + this.Name.GetHashCode();
+ return hash;
+ }
+ }
+ }
+ }
+}
diff --git a/Source/Test/Rewriting/Passes/LoggingPass.cs b/Source/Test/Rewriting/Passes/LoggingPass.cs
deleted file mode 100644
index 422ae7c8e..000000000
--- a/Source/Test/Rewriting/Passes/LoggingPass.cs
+++ /dev/null
@@ -1,189 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.Json;
-using Microsoft.Coyote.IO;
-using Mono.Cecil;
-using Mono.Cecil.Cil;
-
-namespace Microsoft.Coyote.Rewriting
-{
- ///
- /// A pass that logs all assembly contents into text.
- ///
- internal class LoggingPass : Pass
- {
- ///
- /// Map from assemblies to IL contents.
- ///
- private Dictionary ContentMap;
-
- ///
- /// Initializes a new instance of the class.
- ///
- internal LoggingPass(IEnumerable visitedAssemblies, ILogger logger)
- : base(visitedAssemblies, logger)
- {
- this.ContentMap = new Dictionary();
- }
-
- ///
- internal override void VisitAssembly(AssemblyDefinition assembly)
- {
- if (!this.ContentMap.ContainsKey(assembly.FullName))
- {
- var contents = new AssemblyContents()
- {
- AssemblyName = assembly.FullName,
- Modules = new List()
- };
-
- this.ContentMap.Add(assembly.FullName, contents);
- }
-
- base.VisitAssembly(assembly);
- }
-
- ///
- internal override void VisitModule(ModuleDefinition module)
- {
- if (this.ContentMap.TryGetValue(this.Assembly.FullName, out AssemblyContents contents))
- {
- contents.Modules.Add(new ModuleContents()
- {
- FileName = module.FileName,
- Types = new List()
- });
- }
-
- base.VisitModule(module);
- }
-
- ///
- internal override void VisitType(TypeDefinition type)
- {
- if (this.ContentMap.TryGetValue(this.Assembly.FullName, out AssemblyContents contents))
- {
- contents.Modules.FirstOrDefault(m => m.FileName == this.Module.FileName)?.Types.Add(
- new TypeContents()
- {
- Type = type.FullName,
- Fields = new List(),
- Methods = new List()
- });
- }
-
- base.VisitType(type);
- }
-
- ///
- internal override void VisitField(FieldDefinition field)
- {
- if (this.ContentMap.TryGetValue(this.Assembly.FullName, out AssemblyContents contents))
- {
- contents.Modules.FirstOrDefault(m => m.FileName == this.Module.FileName)?.Types
- .FirstOrDefault(t => t.Type == this.TypeDef.FullName)?.Fields.Add(field.FullName);
- }
-
- base.VisitField(field);
- }
-
- ///
- internal override void VisitMethod(MethodDefinition method)
- {
- if (this.ContentMap.TryGetValue(this.Assembly.FullName, out AssemblyContents contents))
- {
- contents.Modules.FirstOrDefault(m => m.FileName == this.Module.FileName)?.Types
- .FirstOrDefault(t => t.Type == this.TypeDef.FullName)?.Methods
- .Add(new MethodContents()
- {
- Method = method.FullName,
- Variables = new List(),
- Instructions = new List()
- });
- }
-
- base.VisitMethod(method);
- }
-
- ///
- protected override void VisitVariable(VariableDefinition variable)
- {
- if (this.ContentMap.TryGetValue(this.Assembly.FullName, out AssemblyContents contents))
- {
- contents.Modules.FirstOrDefault(m => m.FileName == this.Module.FileName)?.Types
- .FirstOrDefault(t => t.Type == this.TypeDef.FullName)?.Methods
- .FirstOrDefault(m => m.Method == this.Method.FullName)?.Variables
- .Add(variable.ToString());
- }
-
- base.VisitVariable(variable);
- }
-
- ///
- protected override Instruction VisitInstruction(Instruction instruction)
- {
- if (this.ContentMap.TryGetValue(this.Assembly.FullName, out AssemblyContents contents))
- {
- contents.Modules.FirstOrDefault(m => m.FileName == this.Module.FileName)?.Types
- .FirstOrDefault(t => t.Type == this.TypeDef.FullName)?.Methods
- .FirstOrDefault(m => m.Method == this.Method.FullName)?.Instructions
- .Add(instruction.ToString());
- }
-
- return base.VisitInstruction(instruction);
- }
-
- ///
- /// Returns the contents of the specified assembly as JSON.
- ///
- internal string GetJSON(AssemblyInfo assembly)
- {
- try
- {
- if (this.ContentMap.TryGetValue(assembly.Definition.FullName, out AssemblyContents contents))
- {
- return JsonSerializer.Serialize(contents, new JsonSerializerOptions()
- {
- WriteIndented = true
- });
- }
- }
- catch (Exception ex)
- {
- this.Logger.WriteLine(LogSeverity.Error, $"Unable to serialize IL to JSON. {ex.Message}");
- }
-
- return string.Empty;
- }
-
- private class AssemblyContents
- {
- public string AssemblyName { get; set; }
- public IList Modules { get; set; }
- }
-
- private class ModuleContents
- {
- public string FileName { get; set; }
- public IList Types { get; set; }
- }
-
- private class TypeContents
- {
- public string Type { get; set; }
- public IList Fields { get; set; }
- public IList Methods { get; set; }
- }
-
- private class MethodContents
- {
- public string Method { get; set; }
- public IList Variables { get; set; }
- public IList Instructions { get; set; }
- }
- }
-}
diff --git a/Source/Test/Rewriting/Passes/Pass.cs b/Source/Test/Rewriting/Passes/Pass.cs
index 9680a036b..767ff1afb 100644
--- a/Source/Test/Rewriting/Passes/Pass.cs
+++ b/Source/Test/Rewriting/Passes/Pass.cs
@@ -69,7 +69,7 @@ protected Pass(IEnumerable visitedAssemblies, ILogger logger)
/// Visits the specified .
///
/// The assembly definition to visit.
- internal virtual void VisitAssembly(AssemblyDefinition assembly)
+ protected internal virtual void VisitAssembly(AssemblyDefinition assembly)
{
this.Assembly = assembly;
this.Module = null;
@@ -83,7 +83,7 @@ internal virtual void VisitAssembly(AssemblyDefinition assembly)
/// visited .
///
/// The module definition to visit.
- internal virtual void VisitModule(ModuleDefinition module)
+ protected internal virtual void VisitModule(ModuleDefinition module)
{
this.Module = module;
this.TypeDef = null;
@@ -96,7 +96,7 @@ internal virtual void VisitModule(ModuleDefinition module)
/// visited .
///
/// The type definition to visit.
- internal virtual void VisitType(TypeDefinition type)
+ protected internal virtual void VisitType(TypeDefinition type)
{
this.TypeDef = type;
this.Method = null;
@@ -108,7 +108,7 @@ internal virtual void VisitType(TypeDefinition type)
/// visited .
///
/// The field definition to visit.
- internal virtual void VisitField(FieldDefinition field)
+ protected internal virtual void VisitField(FieldDefinition field)
{
}
@@ -117,7 +117,7 @@ internal virtual void VisitField(FieldDefinition field)
/// visited .
///
/// The method definition to visit.
- internal virtual void VisitMethod(MethodDefinition method)
+ protected internal virtual void VisitMethod(MethodDefinition method)
{
this.Method = method;
@@ -165,7 +165,7 @@ protected virtual Instruction VisitInstruction(Instruction instruction)
///
/// Completes the visit over the current assembly.
///
- internal virtual void CompleteVisit()
+ protected internal virtual void CompleteVisit()
{
}
diff --git a/Source/Test/Rewriting/Passes/Rewriting/ConcurrentCollectionRewritingPass.cs b/Source/Test/Rewriting/Passes/Rewriting/ConcurrentCollectionRewritingPass.cs
index 07d8567eb..bcaa8fec6 100644
--- a/Source/Test/Rewriting/Passes/Rewriting/ConcurrentCollectionRewritingPass.cs
+++ b/Source/Test/Rewriting/Passes/Rewriting/ConcurrentCollectionRewritingPass.cs
@@ -75,7 +75,7 @@ protected override TypeReference RewriteDeclaringTypeReference(MethodReference m
{
type = this.Module.ImportReference(typeof(ControlledConcurrentBag));
}
- else if (fullName == CachedNameProvider.ConcurrentDictonaryFullName)
+ else if (fullName == CachedNameProvider.ConcurrentDictionaryFullName)
{
type = this.Module.ImportReference(typeof(ControlledConcurrentDictionary));
}
diff --git a/Source/Test/Rewriting/Passes/Rewriting/ExceptionFilterRewritingPass.cs b/Source/Test/Rewriting/Passes/Rewriting/ExceptionFilterRewritingPass.cs
index 6da8d597f..bf0a1967a 100644
--- a/Source/Test/Rewriting/Passes/Rewriting/ExceptionFilterRewritingPass.cs
+++ b/Source/Test/Rewriting/Passes/Rewriting/ExceptionFilterRewritingPass.cs
@@ -31,7 +31,7 @@ internal ExceptionFilterRewritingPass(IEnumerable visitedAssemblie
}
///
- internal override void VisitType(TypeDefinition type)
+ protected internal override void VisitType(TypeDefinition type)
{
this.IsAsyncStateMachineType = type.Interfaces.Any(
i => i.InterfaceType.FullName == typeof(SystemCompiler.IAsyncStateMachine).FullName);
@@ -39,7 +39,7 @@ internal override void VisitType(TypeDefinition type)
}
///
- internal override void VisitMethod(MethodDefinition method)
+ protected internal override void VisitMethod(MethodDefinition method)
{
base.VisitMethod(method);
diff --git a/Source/Test/Rewriting/Passes/Rewriting/MSTestRewritingPass.cs b/Source/Test/Rewriting/Passes/Rewriting/MSTestRewritingPass.cs
index cd84c40cf..064f51934 100644
--- a/Source/Test/Rewriting/Passes/Rewriting/MSTestRewritingPass.cs
+++ b/Source/Test/Rewriting/Passes/Rewriting/MSTestRewritingPass.cs
@@ -29,7 +29,7 @@ internal MSTestRewritingPass(Configuration configuration, IEnumerable
- internal override void VisitMethod(MethodDefinition method)
+ protected internal override void VisitMethod(MethodDefinition method)
{
if (method.IsAbstract)
{
diff --git a/Source/Test/Rewriting/Passes/Rewriting/MonitorRewritingPass.cs b/Source/Test/Rewriting/Passes/Rewriting/MonitorRewritingPass.cs
index 1539b4ce3..4de48fb3f 100644
--- a/Source/Test/Rewriting/Passes/Rewriting/MonitorRewritingPass.cs
+++ b/Source/Test/Rewriting/Passes/Rewriting/MonitorRewritingPass.cs
@@ -33,7 +33,7 @@ internal MonitorRewritingPass(IEnumerable visitedAssemblies, ILogg
}
///
- internal override void VisitModule(ModuleDefinition module)
+ protected internal override void VisitModule(ModuleDefinition module)
{
this.ControlledMonitorType = null;
base.VisitModule(module);
diff --git a/Source/Test/Rewriting/Passes/Rewriting/TaskRewritingPass.cs b/Source/Test/Rewriting/Passes/Rewriting/TaskRewritingPass.cs
index ea9836dc8..16d9481c8 100644
--- a/Source/Test/Rewriting/Passes/Rewriting/TaskRewritingPass.cs
+++ b/Source/Test/Rewriting/Passes/Rewriting/TaskRewritingPass.cs
@@ -40,7 +40,7 @@ internal TaskRewritingPass(IEnumerable visitedAssemblies, ILogger
}
///
- internal override void VisitField(FieldDefinition field)
+ protected internal override void VisitField(FieldDefinition field)
{
if (this.TryRewriteCompilerType(field.FieldType, out TypeReference newFieldType))
{
@@ -51,7 +51,7 @@ internal override void VisitField(FieldDefinition field)
}
///
- internal override void VisitMethod(MethodDefinition method)
+ protected internal override void VisitMethod(MethodDefinition method)
{
base.VisitMethod(method);
diff --git a/Source/Test/Rewriting/Passes/Rewriting/ThreadingRewritingPass.cs b/Source/Test/Rewriting/Passes/Rewriting/ThreadingRewritingPass.cs
index 2af0537ae..77b516d9e 100644
--- a/Source/Test/Rewriting/Passes/Rewriting/ThreadingRewritingPass.cs
+++ b/Source/Test/Rewriting/Passes/Rewriting/ThreadingRewritingPass.cs
@@ -25,7 +25,7 @@ internal ThreadingRewritingPass(IEnumerable visitedAssemblies, ILo
}
///
- internal override void VisitModule(ModuleDefinition module)
+ protected internal override void VisitModule(ModuleDefinition module)
{
this.ControlledThreadType = null;
base.VisitModule(module);
diff --git a/Source/Test/Rewriting/RewritingEngine.cs b/Source/Test/Rewriting/RewritingEngine.cs
index ebea3dd56..5b2e0e4c8 100644
--- a/Source/Test/Rewriting/RewritingEngine.cs
+++ b/Source/Test/Rewriting/RewritingEngine.cs
@@ -158,11 +158,11 @@ private void InitializePasses(IEnumerable assemblies)
this.Passes.AddLast(new InterAssemblyInvocationRewritingPass(assemblies, this.Logger));
this.Passes.AddLast(new UncontrolledInvocationRewritingPass(assemblies, this.Logger));
- if (this.Options.IsLoggingContentsAsJson)
+ if (this.Options.IsLoggingAssemblyContents || this.Options.IsDiffingAssemblyContents)
{
- // Logging the contents of an assembly must happen before and after any other pass.
- this.Passes.AddFirst(new LoggingPass(assemblies, this.Logger));
- this.Passes.AddLast(new LoggingPass(assemblies, this.Logger));
+ // Parsing the contents of an assembly must happen before and after any other pass.
+ this.Passes.AddFirst(new AssemblyDiffingPass(assemblies, this.Logger));
+ this.Passes.AddLast(new AssemblyDiffingPass(assemblies, this.Logger));
}
}
@@ -190,14 +190,21 @@ private void RewriteAssembly(AssemblyInfo assembly, string outputPath)
assembly.ApplyRewritingSignatureAttribute(GetAssemblyRewriterVersion());
// Write the binary in the output path with portable symbols enabled.
- this.Logger.WriteLine($"..... Writing the modified '{assembly.Name}' assembly to " +
- $"{(this.Options.IsReplacingAssemblies() ? assembly.FilePath : outputPath)}");
+ string resolvedOutputPath = this.Options.IsReplacingAssemblies() ? assembly.FilePath : outputPath;
+ this.Logger.WriteLine($"..... Writing the modified '{assembly.Name}' assembly to {resolvedOutputPath}");
assembly.Write(outputPath);
- if (this.Options.IsLoggingContentsAsJson)
+ if (this.Options.IsLoggingAssemblyContents)
{
- // Log the original and rewritten contents as JSON.
- this.LogContentsToJson(assembly, outputPath);
+ // Write the IL before and after rewriting to a JSON file.
+ this.WriteILToJson(assembly, false, resolvedOutputPath);
+ this.WriteILToJson(assembly, true, resolvedOutputPath);
+ }
+
+ if (this.Options.IsDiffingAssemblyContents)
+ {
+ // Write the IL diff before and after rewriting to a JSON file.
+ this.WriteILDiffToJson(assembly, resolvedOutputPath);
}
}
finally
@@ -218,6 +225,45 @@ private void RewriteAssembly(AssemblyInfo assembly, string outputPath)
}
}
+ ///
+ /// Writes the original or rewritten IL to a JSON file in the specified output path.
+ ///
+ internal void WriteILToJson(AssemblyInfo assembly, bool isRewritten, string outputPath)
+ {
+ var diffingPass = (isRewritten ? this.Passes.Last : this.Passes.First).Value as AssemblyDiffingPass;
+ if (diffingPass != null)
+ {
+ string json = diffingPass.GetJson(assembly);
+ if (!string.IsNullOrEmpty(json))
+ {
+ string jsonFile = Path.ChangeExtension(outputPath, $".{(isRewritten ? "rw" : "il")}.json");
+ this.Logger.WriteLine($"..... Writing the {(isRewritten ? "rewritten" : "original")} IL " +
+ $"of '{assembly.Name}' as JSON to {jsonFile}");
+ File.WriteAllText(jsonFile, json);
+ }
+ }
+ }
+
+ ///
+ /// Writes the IL diff to a JSON file in the specified output path.
+ ///
+ internal void WriteILDiffToJson(AssemblyInfo assembly, string outputPath)
+ {
+ var originalDiffingPass = this.Passes.First.Value as AssemblyDiffingPass;
+ var rewrittenDiffingPass = this.Passes.Last.Value as AssemblyDiffingPass;
+ if (originalDiffingPass != null && rewrittenDiffingPass != null)
+ {
+ // Compute the diff between the original and rewritten IL and dump it to JSON.
+ string diffJson = originalDiffingPass.GetDiffJson(assembly, rewrittenDiffingPass);
+ if (!string.IsNullOrEmpty(diffJson))
+ {
+ string jsonFile = Path.ChangeExtension(outputPath, ".diff.json");
+ this.Logger.WriteLine($"..... Writing the IL diff of '{assembly.Name}' as JSON to {jsonFile}");
+ File.WriteAllText(jsonFile, diffJson);
+ }
+ }
+ }
+
///
/// Checks if the specified assembly has been already rewritten with the current version.
///
@@ -232,29 +278,6 @@ public static bool IsAssemblyRewritten(Assembly assembly) =>
///
private static Version GetAssemblyRewriterVersion() => Assembly.GetExecutingAssembly().GetName().Version;
- ///
- /// Writes the contents before and after rewriting in JSON format to the specified output path.
- ///
- internal void LogContentsToJson(AssemblyInfo assembly, string outputPath)
- {
- outputPath = this.Options.IsReplacingAssemblies() ? assembly.FilePath : outputPath;
- if (this.Passes.First.Value is LoggingPass originalLoggingPass)
- {
- string json = originalLoggingPass.GetJSON(assembly);
- string jsonFile = Path.ChangeExtension(outputPath, ".il.json");
- this.Logger.WriteLine($"..... Writing the original IL of '{assembly.Name}' as JSON to {jsonFile}");
- File.WriteAllText(jsonFile, json);
- }
-
- if (this.Passes.Last.Value is LoggingPass rewrittenLoggingPass)
- {
- string json = rewrittenLoggingPass.GetJSON(assembly);
- string jsonFile = Path.ChangeExtension(outputPath, ".rw.json");
- this.Logger.WriteLine($"..... Writing the rewritten IL of '{assembly.Name}' as JSON to {jsonFile}");
- File.WriteAllText(jsonFile, json);
- }
- }
-
///
/// Creates the output directory, if it does not already exists, and copies all necessary files.
///
diff --git a/Source/Test/Rewriting/RewritingOptions.cs b/Source/Test/Rewriting/RewritingOptions.cs
index 53c68586e..146b42b4c 100644
--- a/Source/Test/Rewriting/RewritingOptions.cs
+++ b/Source/Test/Rewriting/RewritingOptions.cs
@@ -81,28 +81,22 @@ internal class RewritingOptions
///
/// True if rewriting of unit test methods is enabled, else false.
///
- ///
- /// If unit test rewriting is enabled, Coyote will instrument the binary to run unit test
- /// methods in the scope of the Coyote testing engine. Note that this rewriting does not
- /// change the semantics of the original test. For example, if the test is sequential it
- /// will remain sequential, limiting the concurrency coverage that Coyote can achieve.
- ///
internal bool IsRewritingUnitTests { get; set; }
///
/// True if rewriting threads as controlled tasks.
///
- ///
- /// Normally Thread is not supported by Coyote, but this experimental feature wraps the
- /// thread in a Task so that Coyote knows about it which avoids uncontrolled concurrency
- /// errors in some cases.
- ///
internal bool IsRewritingThreads { get; set; }
///
- /// True if the rewriter should log the IL contents before and after rewriting in JSON.
+ /// True if the rewriter should log the IL before and after rewriting.
+ ///
+ internal bool IsLoggingAssemblyContents { get; set; }
+
+ ///
+ /// True if the rewriter should diff the IL before and after rewriting.
///
- internal bool IsLoggingContentsAsJson { get; set; }
+ internal bool IsDiffingAssemblyContents { get; set; }
///
/// The .NET platform version that Coyote was compiled for.
@@ -147,7 +141,8 @@ internal static RewritingOptions Create() =>
IsRewritingDependencies = false,
IsRewritingUnitTests = false,
IsRewritingThreads = false,
- IsLoggingContentsAsJson = false,
+ IsLoggingAssemblyContents = false,
+ IsDiffingAssemblyContents = false,
};
///
diff --git a/Tests/Tests.Actors.BugFinding/Tests.Actors.BugFinding.csproj b/Tests/Tests.Actors.BugFinding/Tests.Actors.BugFinding.csproj
index 4b7e24b7d..6d7c94378 100644
--- a/Tests/Tests.Actors.BugFinding/Tests.Actors.BugFinding.csproj
+++ b/Tests/Tests.Actors.BugFinding/Tests.Actors.BugFinding.csproj
@@ -30,9 +30,9 @@
-
+
-
+
\ No newline at end of file
diff --git a/Tests/Tests.BugFinding/Tests.BugFinding.csproj b/Tests/Tests.BugFinding/Tests.BugFinding.csproj
index fd9ee8ba6..988865e4e 100644
--- a/Tests/Tests.BugFinding/Tests.BugFinding.csproj
+++ b/Tests/Tests.BugFinding/Tests.BugFinding.csproj
@@ -26,9 +26,9 @@
-
+
-
+
\ No newline at end of file
diff --git a/Tests/Tests.Rewriting/Tests.Rewriting.csproj b/Tests/Tests.Rewriting/Tests.Rewriting.csproj
index eff823771..e5a6c3025 100644
--- a/Tests/Tests.Rewriting/Tests.Rewriting.csproj
+++ b/Tests/Tests.Rewriting/Tests.Rewriting.csproj
@@ -33,9 +33,9 @@
-
+
-
+
\ No newline at end of file
diff --git a/Tests/Tests.Standalone/Tests.Standalone.csproj b/Tests/Tests.Standalone/Tests.Standalone.csproj
index a5a5713b7..55f1971da 100644
--- a/Tests/Tests.Standalone/Tests.Standalone.csproj
+++ b/Tests/Tests.Standalone/Tests.Standalone.csproj
@@ -25,9 +25,9 @@
-
+
-
+
\ No newline at end of file
diff --git a/Tests/compare-rewriting-diff-logs.ps1 b/Tests/compare-rewriting-diff-logs.ps1
new file mode 100644
index 000000000..be8f9f0c4
--- /dev/null
+++ b/Tests/compare-rewriting-diff-logs.ps1
@@ -0,0 +1,42 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+Import-Module $PSScriptRoot/../Scripts/powershell/common.psm1 -Force
+
+$framework = "net5.0"
+$targets = [ordered]@{
+ "rewriting" = "Tests.Rewriting"
+ "testing" = "Tests.BugFinding"
+ "actors" = "Tests.Actors"
+ "actors-testing" = "Tests.Actors.BugFinding"
+ "standalone" = "Tests.Standalone"
+}
+
+$expected_hashes = [ordered]@{
+ "rewriting" = "611BDAE4DE1DE025116805076A3BC33151BB1ABF1E369D6BEA819345C7BE8818"
+ "testing" = "471916B83D10B7665D775817ECBCFCAE129B4859BE147DF414976420920C67C7"
+ "actors" = "8A9EAED0963801134DC58273FAF30550482842860FBA52187942601F73FF4110"
+ "actors-testing" = "88865B28250700738C4E9A0A7463E8447272C031A4D256C6026D26C92C98240A"
+ "standalone" = "ABF26E1DC4CB7F3A65B4508229AF2DDDF896F3E351F3F4A928C86758F55742E0"
+}
+
+Write-Comment -prefix "." -text "Comparing the test rewriting diff logs" -color "yellow"
+
+# Compare all IL diff logs.
+foreach ($kvp in $targets.GetEnumerator()) {
+ $project = $($kvp.Value)
+ if ($project -eq $targets["actors"]) {
+ $project = $targets["actors-testing"]
+ }
+
+ $new = "$PSScriptRoot/$project/bin/$framework/Microsoft.Coyote.$($kvp.Value).diff.json"
+ $new_hash = $(Get-FileHash $new).Hash
+ Write-Comment -prefix "..." -text "Computed IL diff hash '$new_hash' for '$($kvp.Value)' project" -color "white"
+ $expected_hash = $expected_hashes[$($kvp.Key)]
+ if ($new_hash -ne $expected_hash) {
+ Write-Error "The '$($kvp.Value)' project's IL diff hash '$new_hash' is not the expected '$expected_hash'."
+ exit 1
+ }
+}
+
+Write-Comment -prefix "." -text "Done" -color "green"
diff --git a/Tools/Coyote/Utilities/CommandLineOptions.cs b/Tools/Coyote/Utilities/CommandLineOptions.cs
index 14d178cf3..62553947f 100644
--- a/Tools/Coyote/Utilities/CommandLineOptions.cs
+++ b/Tools/Coyote/Utilities/CommandLineOptions.cs
@@ -70,7 +70,8 @@ internal CommandLineOptions()
rewritingGroup.AddArgument("rewrite-dependencies", null, "Rewrite all dependent assemblies that are found in the same location as the given path", typeof(bool));
rewritingGroup.AddArgument("rewrite-unit-tests", null, "Rewrite unit tests to run in the scope of the Coyote testing engine", typeof(bool));
rewritingGroup.AddArgument("rewrite-threads", null, "Rewrite low-level threading APIs (experimental)", typeof(bool));
- rewritingGroup.AddArgument("dump-il", null, "Dumps the IL contents before and after rewriting as JSON", typeof(bool));
+ rewritingGroup.AddArgument("dump-il", null, "Dumps the original and rewritten IL in JSON", typeof(bool));
+ rewritingGroup.AddArgument("dump-il-diff", null, "Dumps the IL diff in JSON", typeof(bool));
var coverageGroup = this.Parser.GetOrCreateGroup("coverageGroup", "Code and activity coverage options");
coverageGroup.DependsOn = new CommandLineArgumentDependency() { Name = "command", Value = "test" };
@@ -418,7 +419,10 @@ private static void UpdateConfigurationWithParsedArgument(Configuration configur
rewritingOptions.IsRewritingThreads = true;
break;
case "dump-il":
- rewritingOptions.IsLoggingContentsAsJson = true;
+ rewritingOptions.IsLoggingAssemblyContents = true;
+ break;
+ case "dump-il-diff":
+ rewritingOptions.IsDiffingAssemblyContents = true;
break;
case "timeout-delay":
configuration.TimeoutDelay = (uint)option.Value;