#region License // Copyright (c) 2007 James Newton-King // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. #endregion using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq.JsonPath; using System; #if DNXCORE50 using Xunit; using Test = Xunit.FactAttribute; using Assert = Newtonsoft.Json.Tests.XUnitAssert; #else using NUnit.Framework; #endif namespace Newtonsoft.Json.Tests.Issues { /// /// Here's where we test the sandbox for needles and broken glass /// [TestFixture] public class Issue1837 { [Test] public void AllStrictEqualityTests() { // this is a bit cargo-culty; making absolutely sure no false positives caused by instance equivalence var lhs = new TestData(); var rhs = new TestData(); // For all tests, if Type(x) is different from Type(y), return false. // given x === y, if Type(x) is Null, return true var target = lhs.Null; AssertAll(StrictEquality, target, rhs.Null); AssertNone(StrictEquality, target, rhs.ErrybodyButNull); // given x === y, if x is the same Number value as y, return true. target = lhs.One; AssertAll(StrictEquality, target, rhs.One, rhs.OneDotZero); Assert.IsFalse(BooleanQueryExpression.EqualsWithStrictMatch(target, rhs.Two)); target = lhs.Scientific; Assert.IsTrue(BooleanQueryExpression.EqualsWithStrictMatch(target, rhs.Scientific)); // given x === y, if Type(x) is String, then return true if x and y are exactly the same sequence of characters (same length and same characters in corresponding positions); otherwise, return false. target = lhs.DerpString; AssertNone(StrictEquality, target, rhs.HerpString); AssertAll(StrictEquality, target, rhs.DerpString); // given x === y, if Type(x) is Boolean, return true if x and y are both true or both false; otherwise, return false. target = lhs.True; AssertAll(StrictEquality, target, rhs.True); AssertNone(StrictEquality, target, new[] { rhs.False }, rhs.Nopes, rhs.Numbers, rhs.Strings, rhs.Dates); target = lhs.False; AssertAll(StrictEquality, target, rhs.False); AssertNone(StrictEquality, target, new[] { rhs.True }, rhs.Nopes, rhs.Numbers, rhs.Strings, rhs.Dates); //Dates target = lhs.DateYearMonth; AssertAll(StrictEquality, target, rhs.DateYearMonth); AssertNone(StrictEquality, target, rhs.DateYear); target = lhs.DateYear; AssertNone(StrictEquality, target, rhs.DateYearMonth); AssertAll(StrictEquality, target, rhs.DateYear); target = lhs.DateISO; Assert.IsTrue(BooleanQueryExpression.EqualsWithStrictMatch(target, rhs.DateISO)); Assert.IsFalse(BooleanQueryExpression.EqualsWithStrictMatch(target, rhs.OtherISODate)); } #region helpers // used by asserters to perform the comparison public delegate bool Comparator(JValue lhs, JValue rhs); // there was going to be an abstractEquality, but check the exception for it's implementation for why that's skipped for now private readonly Comparator StrictEquality = (lhs, rhs) => BooleanQueryExpression.EqualsWithStrictMatch(lhs, rhs); // a bunch of convenience methods for the test belwo // these make sure the comparator returns false for all do not wants private void AssertNone(Comparator comparator, JValue token, params JValue[][] doNotWant) { foreach (var group in doNotWant) { AssertNone(comparator, token, group); } } private void AssertNone(Comparator comparator, JValue token, params JValue[] doNotWant) { foreach (var item in doNotWant) { Assert.IsTrue(!comparator(token, item)); } } // these make sure the comparator returns true for all do not wants private void AssertAll(Comparator comparator, JValue token, params JValue[][] want) { foreach (var group in want) { AssertAll(comparator, token, group); } } private void AssertAll(Comparator comparator, JValue token, params JValue[] want) { foreach (var item in want) { Assert.IsTrue(comparator(token, item)); } } #endregion } /// /// Holds (practically) all the different possible javascript types and variants of possible values gathered from the algorithm and (imperfect) observation /// public class TestData { public readonly JValue Null; //JSON.stringify({"undef": undefined}) returns {} //public readonly JToken Undefined; public readonly JValue[] Nopes; public readonly JValue One; public readonly JValue OneDotZero; public readonly JValue Two; public readonly JValue Scientific; // stringify returns these as 0 //public readonly JToken NegativeZero; //public readonly JToken PositiveZero; // JSON.stringify({"lol": NaN}) returns "{"lol":null}" //public readonly JToken NaN; public readonly JValue[] Numbers; public readonly JValue HerpString; public readonly JValue DerpString; public readonly JValue[] Strings; public readonly JValue True; public readonly JValue False; public readonly JValue[] Boolies; // JSON.stringify({"lol": new Date("2018-09-02") - new Date("2018-09-01")}) returns "{"lol":86400000}", and so is indistinguishable from a number //public readonly JToken Timespan1; //public readonly JToken Timespan2; //public readonly JToken[] Timespans; public readonly JValue DateYearMonth; public readonly JValue DateYear; // stringify only ever uses the ISO 8601 zulu date format, so let's just bother with that one. public readonly JValue DateISO; public readonly JValue OtherISODate; public readonly JValue[] Dates; public readonly JValue[][] Errybody; public readonly JValue[][] ErrybodyButNull; public TestData() { var shebang = JObject.Parse("{\"null\":null,\"NaN\":null,\"true\":true,\"false\":false,\"two\":2,\"int\":1,\"float\":1.0,\"scifloat\":-1.3e+70,\"herp\":\"herp\",\"derp\":\"derp\",\"timespan\":86400000,\"dateYearMonth\":\"2018-09-01T00: 00:00.000Z\",\"dateYear\":\"2018-01-01T00: 00:00.000Z\",\"dateJSONAndISOZulu\":\"2018-09-20T20:38:59.463Z\", \"otherDate\": \"2018-09-20T20:41:14.821Z\"}"); Null = (JValue)shebang["null"]; One = (JValue)shebang["int"]; OneDotZero = (JValue)shebang["float"]; Two = (JValue)shebang["two"]; Scientific = (JValue)shebang["scifloat"]; True = (JValue)shebang["true"]; False = (JValue)shebang["false"]; HerpString = (JValue)shebang["herp"]; DerpString = (JValue)shebang["derp"]; DateYearMonth = (JValue)shebang["dateYearMonth"]; DateYear = (JValue)shebang["dateYear"]; DateISO = (JValue)shebang["dateJSONAndISOZulu"]; OtherISODate = (JValue)shebang["otherDate"]; Dates = new[] { DateYearMonth, DateYear, DateISO, OtherISODate }; Boolies = new[] { True, False }; Strings = new[] { HerpString, DerpString }; Numbers = new[] { One, OneDotZero, Two, Scientific }; Nopes = new[] { Null }; Errybody = new[] { Nopes, Numbers, Strings, Boolies, Dates }; ErrybodyButNull = new[] { Numbers, Strings, Boolies, Dates }; } } }