-
Notifications
You must be signed in to change notification settings - Fork 93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix #1673 (property bag testing); Fix #1687 (NuGet packaging warning) #1688
Conversation
<!-- | ||
Don't complain about SemVer 2.0.0-compatible version strings. | ||
See https://github.com/NuGet/Home/issues/4687#issuecomment-393302779. | ||
--> | ||
<NoWarn>NU5105</NoWarn> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<None Include="..\..\triskelion.png" Pack="true" PackagePath="\"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None [](start = 5, length = 4)
This fixes the warning NU5048
("PackageIconUrl is deprecated") that we get as we build each package. #ByDesign
"dateArray": [ | ||
"2019-09-26T15:52Z", | ||
"2019-09-26T15:54Z" | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how Formatting.Indented
renders the arrays and sub-objects within properties
. I don't know why. #ByDesign
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, it's because the PropertyBagConverter
has taken over the serialization at this point and it doesn't retain the indentation.
In reply to: 328869713 [](ancestors = 328869713)
Also: - Fix crash when deserializing a null string-valued property.
... by simplifying the way SetProperty deals with null values.
if (!Properties[propertyName].IsString) | ||
{ | ||
throw new InvalidOperationException(SdkResources.CallGenericGetProperty); | ||
} | ||
|
||
string value = Properties[propertyName].SerializedValue; | ||
|
||
// Remove the quotes around the serialized value ("x" => x) -- unless it's null. | ||
return value.Equals(NullValue, StringComparison.Ordinal) | ||
? null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
null [](start = 18, length = 4)
This null check is no longer needed because we simplified the way that SetProperty
deals with null
values (SetProperty("aProp", null)
). Rather than creating a SerializedPropertyInfo
object with SerializedValue
set to null
and placing that object into Properties["aProp"]
, we simply set Properties["aProp"]
to null
.
This is consistent with what the PropertyBagConverter
does when it encounters a null
valued property. Before this change, a null
valued property read from the log file had a different internal representation from a property you created programatically with SetProperty("aProp", null)
.
This is one of those explanatory comments that doesn't have a natural place in the source code. I'm explaining why some code no longer appears. But, come to think of it, I can add a comment on the null
path in SetProperty
, so I'll do that. #ByDesign
{ | ||
Properties.Remove(propertyName); | ||
} | ||
Properties?.Remove(propertyName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the design is settled here, but I wonder if we should've thrown in this case. Callers could try to clean up properties on the wrong objects in the SARIF hierarchy and not realize nothing is actually happening underneath. #WontFix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a good point. Changing this behavior would of course be breaking (not that that's ever stopped us before ;-)).
In reply to: 329663092 [](ancestors = 329663092)
} | ||
}; | ||
|
||
var settings = new JsonSerializerSettings { Formatting = Formatting.Indented }; | ||
string originalLogText = JsonConvert.SerializeObject(originalLog, settings); | ||
|
||
SarifLog deserializedLog = JsonConvert.DeserializeObject<SarifLog>(originalLogText); | ||
SarifLog deserializedLog = SarifUtilities.DeserializeObject<SarifLog>(originalLogText); | ||
run = deserializedLog.Runs[0]; | ||
|
||
int integerProperty = run.GetProperty<int>(intPropertyName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a ValueComparer you can use to verify the entire Run object seems to match the Deserialized one? I think it's good to have the per-property checks for clarity, but an overall compare might be good redundant verification. #Pending
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, each generated class (Run
) has an associated generated equality comparer class (RunEqualityComparer
) with a static Instance
property. The generated class has a static ValueComparer
field that is set to that instance, and a ValueEquals
method that invokes Equals
on the instance:
public partial class Run : PropertyBagHolder, ISarifNode
{
public static IEqualityComparer<Run> ValueComparer => RunEqualityComparer.Instance;
public bool ValueEquals(Run other) => ValueComparer.Equals(this, other);
But the equality comparison fails because of a code generation bug! When comparing the individual SerializedPropertyInfo
objects in the property bag, the generated comparison code does an object equality comparison instead of invoking SerializedPropertyInfo.ValueComparer.Equals
.
I filed #1689, "Generated code for property bag comparison is incorrect".
I will not fix that bug in this PR.
In reply to: 329663713 [](ancestors = 329663713)
intProperty.Should().Be(42); | ||
|
||
long longProperty = holder.GetProperty<long>("long"); | ||
longProperty.Should().Be(5000000000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment affirming that this example is over int.MaxValue? #ByDesign
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DateTime[] dateTimeArray = holder.GetProperty<DateTime[]>("dateTimeArray"); | ||
dateTimeArray.Length.Should().Be(2); | ||
dateTimeArray[0].Should().Be(ParseUtcDateTime("2019-09-26T15:52:02Z")); | ||
dateTimeArray[1].Should().Be(ParseUtcDateTime("2019-09-26T15:54Z")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How much precision do we emit for DateTimes? Does UtcNow roundtrip properly? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does UtcNow
round-trip properly? Yes.
How much precision do we emit for DateTime
s?
- For
DateTime
s in "first-class" properties, we emit three decimal digits. Example:log.Runs[0].Invocations[0].StartTimeUtc = DateTime.UtcNow;
=>"startTimeUtc": "2019-09-30T17:10:20.185Z"
. - For
DateTime
s in property bag properties, we emit seven decimal digits. Example:log.Runs[0].SetProperty("now", DateTime.UtcNow)
=>"properties": { "now": "2019-09-30T17:10:20.1851531Z" }
.
The reason for the difference is subtle. First-class DateTime
-valued properties are decorated with [JsonConvert(typeof(DateTimeConverter)]
, and DateTimeConverter
uses SarifUtilities.SarifDateTimeFormatMillisecondsPrecision
. But PropertyBagHolder.SetProperty
calls JsonConvert.SerializeObject
without a contract resolver, so the value is serialized with Newtonsoft.Json's default DateTime
serialization, which (apparently) uses more precision.
SetProperty
doesn't set a contract resolver because it doesn't know whether it's working with a v1 or a v2 log, so it doesn't know whether to use the v1 or v2 contract resolver. However, I can add code to use the DateTimeConverter
for DateTime
-valued properties. Then everything will serialize with millisecond precision.
In reply to: 329664303 [](ancestors = 329664303)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh. Doing that breaks the unit test PropertyBagHolder_SetProperty_SetsDateTimeProperty
, which has the comment:
DateTime instances should not be converted to UTC when persisted to a property bag. The reason is that we can't be sure that the user has an actual UTC time in hand, or even the ability to convert the property date time to UTC.
Nightmare. I need to think more about this. After lunch.
In reply to: 329717572 [](ancestors = 329717572,329664303)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I've thought about it. I can live with DateTime
-valued property bag properties that are created with SetProperty
having more digits than first-class DateTime
-valued properties. It's not great but IMO we're at the point of diminishing returns on this.
In reply to: 329725369 [](ancestors = 329725369,329717572,329664303)
I added a comprehensive end-to-end test of round-tripping property bag properties. I also added a
DateTime
test to thePropertyBagConverterTests
functional tests.The problem that @ScottLouvau ran into with the
DateTime
test is related to my fix for #1577. I don't mean that my fix caused his problem; I mean that to avoid the problem, we need to do the same thing I did to fix #1577. For an explanation, see this comment in SarifUtilities.cs.In short, when Newtonsoft.Json deserializes a string-valued property that looks to it like a
DateTime
, its default behavior is to convert it to aDateTime
object. That behavior breaks our property round-tripping. To avoid it, we need to callJsonConvert.DeserializeObject
with aJsonSerializerSettings
object that specifiesDateParseHandling.None
. That tells Newtonsoft.Json to leave string-valued properties alone and let us handle theDateTime
-iness. In #1577, I introducedSarifUtilities.DeserializeObject
to do exactly that.@michaelcfanning and I have discussed this and we know it's not the end of the story. It's too easy to forget (or not to know about) the need to call the
SarifUtilities
method, which is exactly what happened in that existing unit test. But as far as verifying that our handling of property bag properties is good, I believe we now have sufficient test coverage to be confident of that.Also:
null
values.