Skip to content

Commit

Permalink
Merge pull request #573 from jdbrock/jb/feature/propertychanged
Browse files Browse the repository at this point in the history
Add automatic support for INotifyPropertyChanged via weaver.
  • Loading branch information
kristiandupont committed Jun 9, 2016
2 parents 435f6a0 + 680eb54 commit 450bef8
Show file tree
Hide file tree
Showing 6 changed files with 512 additions and 68 deletions.
339 changes: 292 additions & 47 deletions RealmWeaver/ModuleWeaver.cs

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions Tests/WeaverTests/AssemblyToProcess/TestObjects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

using System;
using Realms;
using System.ComponentModel;

namespace AssemblyToProcess
{
Expand Down Expand Up @@ -45,6 +46,32 @@ public class AllTypesObject : RealmObject
public bool? NullableBooleanProperty { get; set; }
}

public class AllTypesObjectPropertyChanged : RealmObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

public char CharProperty { get; set; }
public byte ByteProperty { get; set; }
public short Int16Property { get; set; }
public int Int32Property { get; set; }
public long Int64Property { get; set; }
public float SingleProperty { get; set; }
public double DoubleProperty { get; set; }
public bool BooleanProperty { get; set; }

public string StringProperty { get; set; }
public DateTimeOffset DateTimeOffsetProperty { get; set; }

public char? NullableCharProperty { get; set; }
public byte? NullableByteProperty { get; set; }
public short? NullableInt16Property { get; set; }
public int? NullableInt32Property { get; set; }
public long? NullableInt64Property { get; set; }
public float? NullableSingleProperty { get; set; }
public double? NullableDoubleProperty { get; set; }
public bool? NullableBooleanProperty { get; set; }
}

public class ObjectIdCharObject : RealmObject
{
[ObjectId] public char CharProperty { get; set; }
Expand Down
13 changes: 12 additions & 1 deletion Tests/WeaverTests/RealmWeaver.Tests/RealmWeaver.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
Expand Down Expand Up @@ -53,6 +55,9 @@
<HintPath>..\..\..\packages\NUnit.3.2.1\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="PropertyChanged.Fody">
<HintPath>..\..\..\packages\PropertyChanged.Fody.1.51.0\PropertyChanged.Fody.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
Expand All @@ -61,6 +66,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="WeaverOptions.cs" />
<Compile Include="WeaverTests.cs" />
<Compile Include="Verifier.cs" />
</ItemGroup>
Expand All @@ -80,6 +86,11 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="packages.config">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
</Project>
15 changes: 15 additions & 0 deletions Tests/WeaverTests/RealmWeaver.Tests/WeaverOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Tests
{
public enum WeaverOptions
{
RealmOnlyNoPropertyChanged,
RealmBeforePropertyChanged,
RealmAfterPropertyChanged
}
}
184 changes: 164 additions & 20 deletions Tests/WeaverTests/RealmWeaver.Tests/WeaverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Diagnostics;
Expand All @@ -25,10 +25,14 @@
using Mono.Cecil;
using NUnit.Framework;
using Realms;
using System.ComponentModel;
using Mono.Cecil.Cil;

namespace Tests
{
[TestFixture]
[TestFixture(WeaverOptions.RealmOnlyNoPropertyChanged)]
[TestFixture(WeaverOptions.RealmAfterPropertyChanged)]
[TestFixture(WeaverOptions.RealmBeforePropertyChanged)]
public class WeaverTests
{
#region helpers
Expand Down Expand Up @@ -58,35 +62,115 @@ public static void SetPropertyValue(object o, string propName, object propertyVa
o.GetType().GetProperty(propName).SetValue(o, propertyValue);
}

private void TryCleanupOldAssemblies(string dir)
{
try
{
// Tidy up old processed assemblies.
foreach (var filePath in Directory.EnumerateFiles(dir, "*.processed.dll"))
File.Delete(filePath);
}
catch { }
}

private string CopyAssemblyToNextUniquePath(string originalPath, string overrideSourcePath = null)
{
int stage = 1;
string newPath;

// Calculate the next unique assembly path.
do
{
newPath = originalPath.Replace(".dll", $".stage{stage++}.processed.dll");
}
while (File.Exists(newPath));

// Copy the source assembly to the calculated path.
File.Copy(overrideSourcePath ?? originalPath, newPath);

// Return the copied assembly path.
return newPath;
}

private void WeaveRealm(string targetAssemblyPath)
{
var moduleDefinition = ModuleDefinition.ReadModule(targetAssemblyPath);
(moduleDefinition.AssemblyResolver as DefaultAssemblyResolver).AddSearchDirectory(GetAssemblyDir());

var weaverPath = Path.Combine(GetAssemblyDir(), "RealmWeaver.Fody.dll");

dynamic realmWeavingTask = System.Activator.CreateInstanceFrom(weaverPath, "ModuleWeaver").Unwrap();

realmWeavingTask.ModuleDefinition = moduleDefinition;
realmWeavingTask.LogErrorPoint = new Action<string, SequencePoint>((s, point) => _errors.Add(s));
realmWeavingTask.LogWarningPoint = new Action<string, SequencePoint>((s, point) => _warnings.Add(s));

realmWeavingTask.Execute();
moduleDefinition.Write(targetAssemblyPath);
}

private void WeavePropertyChanged(string targetAssemblyPath)
{
var moduleDefinition = ModuleDefinition.ReadModule(targetAssemblyPath);
(moduleDefinition.AssemblyResolver as DefaultAssemblyResolver).AddSearchDirectory(GetAssemblyDir());

var weaverPath = Path.Combine(GetAssemblyDir(), "PropertyChanged.Fody.dll");

dynamic realmWeavingTask = System.Activator.CreateInstanceFrom(weaverPath, "ModuleWeaver").Unwrap();

realmWeavingTask.ModuleDefinition = moduleDefinition;
// NB. PropertyChanged.Fody doesn't expose an error logging property - it throws exceptions instead.
realmWeavingTask.LogWarning = new Action<string>(s => _warnings.Add(s));

realmWeavingTask.Execute();
moduleDefinition.Write(targetAssemblyPath);
}

#endregion

private Assembly _assembly;
private string _newAssemblyPath;
private string _assemblyPath;
private string _sourceAssemblyPath;
private string _targetAssemblyPath;

private readonly List<string> _warnings = new List<string>();
private readonly List<string> _errors = new List<string>();

[TestFixtureSetUp]
private readonly WeaverOptions _weaverOptions;

public WeaverTests(WeaverOptions weaverOptions)
{
_weaverOptions = weaverOptions;
}

[OneTimeSetUp]
public void FixtureSetup()
{
_assemblyPath = typeof(AssemblyToProcess.AllTypesObject).Assembly.Location;
_newAssemblyPath = _assemblyPath.Replace(".dll", ".processed.dll");
File.Copy(_assemblyPath, _newAssemblyPath, true);
var assemblyDir = GetAssemblyDir();
TryCleanupOldAssemblies(assemblyDir);

var moduleDefinition = ModuleDefinition.ReadModule(_newAssemblyPath);
(moduleDefinition.AssemblyResolver as DefaultAssemblyResolver).AddSearchDirectory(Path.GetDirectoryName(_assemblyPath));
var weavingTask = new ModuleWeaver
{
ModuleDefinition = moduleDefinition,
LogErrorPoint = (s, point) => _errors.Add(s),
LogWarningPoint = (s, point) => _warnings.Add(s)
};
_sourceAssemblyPath = GetSourceAssemblyPath();
_targetAssemblyPath = CopyAssemblyToNextUniquePath(_sourceAssemblyPath);

weavingTask.Execute();
moduleDefinition.Write(_newAssemblyPath);
switch (_weaverOptions)
{
case WeaverOptions.RealmOnlyNoPropertyChanged:
WeaveRealm(_targetAssemblyPath);
break;

case WeaverOptions.RealmAfterPropertyChanged:
WeavePropertyChanged(_targetAssemblyPath);
_targetAssemblyPath = CopyAssemblyToNextUniquePath(_sourceAssemblyPath, _targetAssemblyPath);
WeaveRealm(_targetAssemblyPath);
break;

case WeaverOptions.RealmBeforePropertyChanged:
WeaveRealm(_targetAssemblyPath);
_targetAssemblyPath = CopyAssemblyToNextUniquePath(_sourceAssemblyPath, _targetAssemblyPath);
WeavePropertyChanged(_targetAssemblyPath);
break;
}

_assembly = Assembly.LoadFile(_newAssemblyPath);
_assembly = Assembly.LoadFile(_targetAssemblyPath);

// Try accessing assembly to ensure that the assembly is still valid.
try
Expand All @@ -102,6 +186,23 @@ public void FixtureSetup()
}
}

[OneTimeTearDown]
public void Dispose()
{
var assemblyDir = GetAssemblyDir();
TryCleanupOldAssemblies(assemblyDir);
}

private string GetSourceAssemblyPath()
{
return typeof(AssemblyToProcess.AllTypesObject).Assembly.Location;
}

private string GetAssemblyDir()
{
return Path.GetDirectoryName(GetSourceAssemblyPath());
}

[TestCase("CharProperty", '0')]
[TestCase("ByteProperty", (byte)100)]
[TestCase("Int16Property", (short)100)]
Expand Down Expand Up @@ -235,6 +336,49 @@ public void SetValueManagedShouldUpdateDatabase(string typeName, object property
Assert.That(GetAutoPropertyBackingFieldValue(o, propertyName), Is.EqualTo(defaultPropertyValue));
}

[TestCase("Char", '0', char.MinValue)]
[TestCase("Byte", (byte)100, (byte)0)]
[TestCase("Int16", (short)100, (short)0)]
[TestCase("Int32", 100, 0)]
[TestCase("Int64", 100L, 0L)]
[TestCase("Single", 123.123f, 0.0f)]
[TestCase("Double", 123.123, 0.0)]
[TestCase("Boolean", true, false)]
[TestCase("String", "str", null)]
[TestCase("NullableChar", '0', null)]
[TestCase("NullableByte", (byte)100, null)]
[TestCase("NullableInt16", (short)100, null)]
[TestCase("NullableInt32", 100, null)]
[TestCase("NullableInt64", 100L, null)]
[TestCase("NullableSingle", 123.123f, null)]
[TestCase("NullableDouble", 123.123, null)]
[TestCase("NullableBoolean", true, null)]
public void SetValueManagedShouldRaisePropertyChanged(string typeName, object propertyValue, object defaultPropertyValue)
{
// Arrange
var propertyName = typeName + "Property";
var o = (dynamic)Activator.CreateInstance(_assembly.GetType("AssemblyToProcess.AllTypesObjectPropertyChanged"));
o.IsManaged = true;

var eventRaised = false;
o.PropertyChanged += new PropertyChangedEventHandler((s, e) =>
{
if (e.PropertyName == propertyName)
eventRaised = true;
});

// Act
SetPropertyValue(o, propertyName, propertyValue);

// Assert
Assert.That(o.LogList, Is.EqualTo(new List<string>
{
"IsManaged",
"RealmObject.Set" + typeName + "Value(propertyName = \"" + propertyName + "\", value = " + propertyValue + ")"
}));
Assert.That(GetAutoPropertyBackingFieldValue(o, propertyName), Is.EqualTo(defaultPropertyValue));
Assert.That(eventRaised, Is.True);
}

[TestCase("Char", '0', char.MinValue)]
[TestCase("Byte", (byte)100, (byte)0)]
Expand Down Expand Up @@ -392,7 +536,7 @@ public void MatchErrorsAndWarnings()
[Test]
public void PeVerify()
{
Verifier.Verify(_assemblyPath,_newAssemblyPath);
Verifier.Verify(_sourceAssemblyPath,_targetAssemblyPath);
}
#endif
}
Expand Down
2 changes: 2 additions & 0 deletions Tests/WeaverTests/RealmWeaver.Tests/packages.config
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="FodyCecil" version="1.29.4" targetFramework="net45" developmentDependency="true" userInstalled="true" />
<package id="Fody" version="1.29.2" targetFramework="net45" developmentDependency="true" />
<package id="NUnit" version="3.2.1" targetFramework="net45" />
<package id="PropertyChanged.Fody" version="1.51.0" targetFramework="net45" developmentDependency="true" />
</packages>

0 comments on commit 450bef8

Please sign in to comment.